Skip to content

Commit

Permalink
Merge pull request #2719 from ai16z-demirix/tests/client-eliza-home
Browse files Browse the repository at this point in the history
client-eliza-home: test config and test coverage
  • Loading branch information
normand1 authored Jan 24, 2025
2 parents 8ea70e3 + 85b7542 commit 3d14fe2
Show file tree
Hide file tree
Showing 5 changed files with 498 additions and 2 deletions.
234 changes: 234 additions & 0 deletions packages/client-eliza-home/__tests__/services/smart_things_api.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import type { IAgentRuntime } from '@elizaos/core';

// Mock global fetch
const mockFetch = vi.fn();
global.fetch = mockFetch;

// Create a mock class that matches the SmartThingsApi interface
class MockSmartThingsApi {
private baseUrl = 'https://api.smartthings.com/v1';
private token: string;

constructor(runtime: IAgentRuntime) {
this.token = runtime.getSetting("SMARTTHINGS_TOKEN");
if (!this.token) {
throw new Error("SmartThings token is required");
}
}

private async request(endpoint: string, options: RequestInit = {}) {
const url = `${this.baseUrl}${endpoint}`;
const response = await fetch(url, {
...options,
headers: {
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json',
...options.headers,
},
});

if (!response.ok) {
throw new Error(`SmartThings API error: ${response.statusText}`);
}

return response.json();
}

devices = {
list: () => this.request('/devices'),
get: (deviceId: string) => this.request(`/devices/${deviceId}`),
getStatus: (deviceId: string) => this.request(`/devices/${deviceId}/status`),
executeCommand: (deviceId: string, command: any) =>
this.request(`/devices/${deviceId}/commands`, {
method: 'POST',
body: JSON.stringify({
commands: [command]
})
}),
executeCommands: (deviceId: string, commands: any[]) =>
this.request(`/devices/${deviceId}/commands`, {
method: 'POST',
body: JSON.stringify({ commands })
}),
getComponents: (deviceId: string) =>
this.request(`/devices/${deviceId}/components`),
getCapabilities: (deviceId: string) =>
this.request(`/devices/${deviceId}/capabilities`)
};

scenes = {
list: () => this.request('/scenes'),
execute: (sceneId: string) =>
this.request(`/scenes/${sceneId}/execute`, {
method: 'POST'
})
};

rooms = {
list: () => this.request('/rooms'),
get: (roomId: string) => this.request(`/rooms/${roomId}`)
};
}

describe('SmartThingsApi', () => {
let api: MockSmartThingsApi;
let mockRuntime: IAgentRuntime;

beforeEach(() => {
vi.clearAllMocks();
mockRuntime = {
getSetting: vi.fn().mockReturnValue('mock-token'),
} as unknown as IAgentRuntime;
api = new MockSmartThingsApi(mockRuntime);
});

it('should throw error if token is not provided', () => {
const runtimeWithoutToken = {
getSetting: vi.fn().mockReturnValue(null),
} as unknown as IAgentRuntime;

expect(() => new MockSmartThingsApi(runtimeWithoutToken))
.toThrow('SmartThings token is required');
});

describe('devices', () => {
beforeEach(() => {
mockFetch.mockResolvedValue({
ok: true,
json: () => Promise.resolve({ data: 'success' }),
});
});

it('should list devices', async () => {
await api.devices.list();

expect(mockFetch).toHaveBeenCalledWith(
'https://api.smartthings.com/v1/devices',
expect.objectContaining({
headers: expect.objectContaining({
'Authorization': 'Bearer mock-token',
'Content-Type': 'application/json',
}),
})
);
});

it('should get device details', async () => {
const deviceId = 'device123';
await api.devices.get(deviceId);

expect(mockFetch).toHaveBeenCalledWith(
`https://api.smartthings.com/v1/devices/${deviceId}`,
expect.objectContaining({
headers: expect.objectContaining({
'Authorization': 'Bearer mock-token',
}),
})
);
});

it('should execute device command', async () => {
const deviceId = 'device123';
const command = { capability: 'switch', command: 'on' };

await api.devices.executeCommand(deviceId, command);

expect(mockFetch).toHaveBeenCalledWith(
`https://api.smartthings.com/v1/devices/${deviceId}/commands`,
expect.objectContaining({
method: 'POST',
body: JSON.stringify({ commands: [command] }),
headers: expect.objectContaining({
'Authorization': 'Bearer mock-token',
'Content-Type': 'application/json',
}),
})
);
});

it('should handle API errors', async () => {
mockFetch.mockResolvedValue({
ok: false,
statusText: 'Not Found',
});

await expect(api.devices.list())
.rejects
.toThrow('SmartThings API error: Not Found');
});
});

describe('scenes', () => {
beforeEach(() => {
mockFetch.mockResolvedValue({
ok: true,
json: () => Promise.resolve({ data: 'success' }),
});
});

it('should list scenes', async () => {
await api.scenes.list();

expect(mockFetch).toHaveBeenCalledWith(
'https://api.smartthings.com/v1/scenes',
expect.objectContaining({
headers: expect.objectContaining({
'Authorization': 'Bearer mock-token',
}),
})
);
});

it('should execute scene', async () => {
const sceneId = 'scene123';
await api.scenes.execute(sceneId);

expect(mockFetch).toHaveBeenCalledWith(
`https://api.smartthings.com/v1/scenes/${sceneId}/execute`,
expect.objectContaining({
method: 'POST',
headers: expect.objectContaining({
'Authorization': 'Bearer mock-token',
}),
})
);
});
});

describe('rooms', () => {
beforeEach(() => {
mockFetch.mockResolvedValue({
ok: true,
json: () => Promise.resolve({ data: 'success' }),
});
});

it('should list rooms', async () => {
await api.rooms.list();

expect(mockFetch).toHaveBeenCalledWith(
'https://api.smartthings.com/v1/rooms',
expect.objectContaining({
headers: expect.objectContaining({
'Authorization': 'Bearer mock-token',
}),
})
);
});

it('should get room details', async () => {
const roomId = 'room123';
await api.rooms.get(roomId);

expect(mockFetch).toHaveBeenCalledWith(
`https://api.smartthings.com/v1/rooms/${roomId}`,
expect.objectContaining({
headers: expect.objectContaining({
'Authorization': 'Bearer mock-token',
}),
})
);
});
});
});
118 changes: 118 additions & 0 deletions packages/client-eliza-home/__tests__/smart_home.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { SmartHomeManager } from '../src/smart_home';
import { SmartThingsApi } from '../src/services/smart_things_api';
import { CommandParser } from '../src/utils/command_parser';
import type { IAgentRuntime } from '@elizaos/core';

// Define mock interface that extends IAgentRuntime
interface MockAgentRuntime extends IAgentRuntime {
llm: {
shouldRespond: ReturnType<typeof vi.fn>;
complete: ReturnType<typeof vi.fn>;
};
}

// Mock dependencies
vi.mock('../src/services/smart_things_api', () => ({
SmartThingsApi: vi.fn().mockImplementation(() => ({
devices: {
list: vi.fn().mockResolvedValue([]),
executeCommand: vi.fn().mockResolvedValue({ status: 'success' })
}
}))
}));
vi.mock('../src/utils/command_parser');
vi.mock('@elizaos/core', () => ({
elizaLogger: {
error: vi.fn(),
},
}));

describe('SmartHomeManager', () => {
let smartHomeManager: SmartHomeManager;
let mockRuntime: MockAgentRuntime;

beforeEach(() => {
// Reset all mocks
vi.clearAllMocks();

// Create mock runtime with proper typing
mockRuntime = {
llm: {
shouldRespond: vi.fn(),
complete: vi.fn(),
},
getSetting: vi.fn().mockReturnValue('mock-token'),
// Add required IAgentRuntime properties
agentId: 'test-agent-id',
serverUrl: 'http://test-server',
databaseAdapter: {
init: vi.fn(),
close: vi.fn(),
// Add other required database methods as needed
},
token: 'test-token',
modelProvider: 'test-provider',
} as MockAgentRuntime;

smartHomeManager = new SmartHomeManager(mockRuntime);
});

describe('handleCommand', () => {
it('should return null when shouldRespond returns IGNORE', async () => {
// Arrange
vi.mocked(mockRuntime.llm.shouldRespond).mockResolvedValue('IGNORE');

// Act
const result = await smartHomeManager.handleCommand('turn on lights', 'user123');

// Assert
expect(result).toBeNull();
expect(mockRuntime.llm.shouldRespond).toHaveBeenCalledWith(
expect.any(String),
'turn on lights'
);
});

it('should execute command and return response when shouldRespond returns RESPOND', async () => {
// Arrange
const mockResponse = 'Command executed successfully';
vi.mocked(mockRuntime.llm.shouldRespond).mockResolvedValue('RESPOND');
vi.mocked(mockRuntime.llm.complete).mockResolvedValue(mockResponse);
vi.mocked(CommandParser.parseCommand).mockReturnValue({
command: 'turn_on',
args: { device: 'lights' }
});
vi.mocked(CommandParser.mapToDeviceCommand).mockReturnValue({
deviceId: 'device123',
capability: 'switch',
command: 'on'
});

// Act
const result = await smartHomeManager.handleCommand('turn on lights', 'user123');

// Assert
expect(result).toEqual({
success: true,
message: mockResponse,
data: { status: 'success' }
});
expect(mockRuntime.llm.shouldRespond).toHaveBeenCalled();
expect(mockRuntime.llm.complete).toHaveBeenCalled();
expect(CommandParser.parseCommand).toHaveBeenCalledWith('turn on lights');
expect(CommandParser.mapToDeviceCommand).toHaveBeenCalled();
});

it('should handle errors gracefully', async () => {
// Arrange
const mockError = new Error('Test error');
vi.mocked(mockRuntime.llm.shouldRespond).mockRejectedValue(mockError);

// Act & Assert
await expect(smartHomeManager.handleCommand('turn on lights', 'user123'))
.rejects
.toThrow(mockError);
});
});
});
Loading

0 comments on commit 3d14fe2

Please sign in to comment.