Skip to content

Commit 3d14fe2

Browse files
authored
Merge pull request #2719 from ai16z-demirix/tests/client-eliza-home
client-eliza-home: test config and test coverage
2 parents 8ea70e3 + 85b7542 commit 3d14fe2

File tree

5 files changed

+498
-2
lines changed

5 files changed

+498
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import type { IAgentRuntime } from '@elizaos/core';
3+
4+
// Mock global fetch
5+
const mockFetch = vi.fn();
6+
global.fetch = mockFetch;
7+
8+
// Create a mock class that matches the SmartThingsApi interface
9+
class MockSmartThingsApi {
10+
private baseUrl = 'https://api.smartthings.com/v1';
11+
private token: string;
12+
13+
constructor(runtime: IAgentRuntime) {
14+
this.token = runtime.getSetting("SMARTTHINGS_TOKEN");
15+
if (!this.token) {
16+
throw new Error("SmartThings token is required");
17+
}
18+
}
19+
20+
private async request(endpoint: string, options: RequestInit = {}) {
21+
const url = `${this.baseUrl}${endpoint}`;
22+
const response = await fetch(url, {
23+
...options,
24+
headers: {
25+
'Authorization': `Bearer ${this.token}`,
26+
'Content-Type': 'application/json',
27+
...options.headers,
28+
},
29+
});
30+
31+
if (!response.ok) {
32+
throw new Error(`SmartThings API error: ${response.statusText}`);
33+
}
34+
35+
return response.json();
36+
}
37+
38+
devices = {
39+
list: () => this.request('/devices'),
40+
get: (deviceId: string) => this.request(`/devices/${deviceId}`),
41+
getStatus: (deviceId: string) => this.request(`/devices/${deviceId}/status`),
42+
executeCommand: (deviceId: string, command: any) =>
43+
this.request(`/devices/${deviceId}/commands`, {
44+
method: 'POST',
45+
body: JSON.stringify({
46+
commands: [command]
47+
})
48+
}),
49+
executeCommands: (deviceId: string, commands: any[]) =>
50+
this.request(`/devices/${deviceId}/commands`, {
51+
method: 'POST',
52+
body: JSON.stringify({ commands })
53+
}),
54+
getComponents: (deviceId: string) =>
55+
this.request(`/devices/${deviceId}/components`),
56+
getCapabilities: (deviceId: string) =>
57+
this.request(`/devices/${deviceId}/capabilities`)
58+
};
59+
60+
scenes = {
61+
list: () => this.request('/scenes'),
62+
execute: (sceneId: string) =>
63+
this.request(`/scenes/${sceneId}/execute`, {
64+
method: 'POST'
65+
})
66+
};
67+
68+
rooms = {
69+
list: () => this.request('/rooms'),
70+
get: (roomId: string) => this.request(`/rooms/${roomId}`)
71+
};
72+
}
73+
74+
describe('SmartThingsApi', () => {
75+
let api: MockSmartThingsApi;
76+
let mockRuntime: IAgentRuntime;
77+
78+
beforeEach(() => {
79+
vi.clearAllMocks();
80+
mockRuntime = {
81+
getSetting: vi.fn().mockReturnValue('mock-token'),
82+
} as unknown as IAgentRuntime;
83+
api = new MockSmartThingsApi(mockRuntime);
84+
});
85+
86+
it('should throw error if token is not provided', () => {
87+
const runtimeWithoutToken = {
88+
getSetting: vi.fn().mockReturnValue(null),
89+
} as unknown as IAgentRuntime;
90+
91+
expect(() => new MockSmartThingsApi(runtimeWithoutToken))
92+
.toThrow('SmartThings token is required');
93+
});
94+
95+
describe('devices', () => {
96+
beforeEach(() => {
97+
mockFetch.mockResolvedValue({
98+
ok: true,
99+
json: () => Promise.resolve({ data: 'success' }),
100+
});
101+
});
102+
103+
it('should list devices', async () => {
104+
await api.devices.list();
105+
106+
expect(mockFetch).toHaveBeenCalledWith(
107+
'https://api.smartthings.com/v1/devices',
108+
expect.objectContaining({
109+
headers: expect.objectContaining({
110+
'Authorization': 'Bearer mock-token',
111+
'Content-Type': 'application/json',
112+
}),
113+
})
114+
);
115+
});
116+
117+
it('should get device details', async () => {
118+
const deviceId = 'device123';
119+
await api.devices.get(deviceId);
120+
121+
expect(mockFetch).toHaveBeenCalledWith(
122+
`https://api.smartthings.com/v1/devices/${deviceId}`,
123+
expect.objectContaining({
124+
headers: expect.objectContaining({
125+
'Authorization': 'Bearer mock-token',
126+
}),
127+
})
128+
);
129+
});
130+
131+
it('should execute device command', async () => {
132+
const deviceId = 'device123';
133+
const command = { capability: 'switch', command: 'on' };
134+
135+
await api.devices.executeCommand(deviceId, command);
136+
137+
expect(mockFetch).toHaveBeenCalledWith(
138+
`https://api.smartthings.com/v1/devices/${deviceId}/commands`,
139+
expect.objectContaining({
140+
method: 'POST',
141+
body: JSON.stringify({ commands: [command] }),
142+
headers: expect.objectContaining({
143+
'Authorization': 'Bearer mock-token',
144+
'Content-Type': 'application/json',
145+
}),
146+
})
147+
);
148+
});
149+
150+
it('should handle API errors', async () => {
151+
mockFetch.mockResolvedValue({
152+
ok: false,
153+
statusText: 'Not Found',
154+
});
155+
156+
await expect(api.devices.list())
157+
.rejects
158+
.toThrow('SmartThings API error: Not Found');
159+
});
160+
});
161+
162+
describe('scenes', () => {
163+
beforeEach(() => {
164+
mockFetch.mockResolvedValue({
165+
ok: true,
166+
json: () => Promise.resolve({ data: 'success' }),
167+
});
168+
});
169+
170+
it('should list scenes', async () => {
171+
await api.scenes.list();
172+
173+
expect(mockFetch).toHaveBeenCalledWith(
174+
'https://api.smartthings.com/v1/scenes',
175+
expect.objectContaining({
176+
headers: expect.objectContaining({
177+
'Authorization': 'Bearer mock-token',
178+
}),
179+
})
180+
);
181+
});
182+
183+
it('should execute scene', async () => {
184+
const sceneId = 'scene123';
185+
await api.scenes.execute(sceneId);
186+
187+
expect(mockFetch).toHaveBeenCalledWith(
188+
`https://api.smartthings.com/v1/scenes/${sceneId}/execute`,
189+
expect.objectContaining({
190+
method: 'POST',
191+
headers: expect.objectContaining({
192+
'Authorization': 'Bearer mock-token',
193+
}),
194+
})
195+
);
196+
});
197+
});
198+
199+
describe('rooms', () => {
200+
beforeEach(() => {
201+
mockFetch.mockResolvedValue({
202+
ok: true,
203+
json: () => Promise.resolve({ data: 'success' }),
204+
});
205+
});
206+
207+
it('should list rooms', async () => {
208+
await api.rooms.list();
209+
210+
expect(mockFetch).toHaveBeenCalledWith(
211+
'https://api.smartthings.com/v1/rooms',
212+
expect.objectContaining({
213+
headers: expect.objectContaining({
214+
'Authorization': 'Bearer mock-token',
215+
}),
216+
})
217+
);
218+
});
219+
220+
it('should get room details', async () => {
221+
const roomId = 'room123';
222+
await api.rooms.get(roomId);
223+
224+
expect(mockFetch).toHaveBeenCalledWith(
225+
`https://api.smartthings.com/v1/rooms/${roomId}`,
226+
expect.objectContaining({
227+
headers: expect.objectContaining({
228+
'Authorization': 'Bearer mock-token',
229+
}),
230+
})
231+
);
232+
});
233+
});
234+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { describe, it, expect, vi, beforeEach } from 'vitest';
2+
import { SmartHomeManager } from '../src/smart_home';
3+
import { SmartThingsApi } from '../src/services/smart_things_api';
4+
import { CommandParser } from '../src/utils/command_parser';
5+
import type { IAgentRuntime } from '@elizaos/core';
6+
7+
// Define mock interface that extends IAgentRuntime
8+
interface MockAgentRuntime extends IAgentRuntime {
9+
llm: {
10+
shouldRespond: ReturnType<typeof vi.fn>;
11+
complete: ReturnType<typeof vi.fn>;
12+
};
13+
}
14+
15+
// Mock dependencies
16+
vi.mock('../src/services/smart_things_api', () => ({
17+
SmartThingsApi: vi.fn().mockImplementation(() => ({
18+
devices: {
19+
list: vi.fn().mockResolvedValue([]),
20+
executeCommand: vi.fn().mockResolvedValue({ status: 'success' })
21+
}
22+
}))
23+
}));
24+
vi.mock('../src/utils/command_parser');
25+
vi.mock('@elizaos/core', () => ({
26+
elizaLogger: {
27+
error: vi.fn(),
28+
},
29+
}));
30+
31+
describe('SmartHomeManager', () => {
32+
let smartHomeManager: SmartHomeManager;
33+
let mockRuntime: MockAgentRuntime;
34+
35+
beforeEach(() => {
36+
// Reset all mocks
37+
vi.clearAllMocks();
38+
39+
// Create mock runtime with proper typing
40+
mockRuntime = {
41+
llm: {
42+
shouldRespond: vi.fn(),
43+
complete: vi.fn(),
44+
},
45+
getSetting: vi.fn().mockReturnValue('mock-token'),
46+
// Add required IAgentRuntime properties
47+
agentId: 'test-agent-id',
48+
serverUrl: 'http://test-server',
49+
databaseAdapter: {
50+
init: vi.fn(),
51+
close: vi.fn(),
52+
// Add other required database methods as needed
53+
},
54+
token: 'test-token',
55+
modelProvider: 'test-provider',
56+
} as MockAgentRuntime;
57+
58+
smartHomeManager = new SmartHomeManager(mockRuntime);
59+
});
60+
61+
describe('handleCommand', () => {
62+
it('should return null when shouldRespond returns IGNORE', async () => {
63+
// Arrange
64+
vi.mocked(mockRuntime.llm.shouldRespond).mockResolvedValue('IGNORE');
65+
66+
// Act
67+
const result = await smartHomeManager.handleCommand('turn on lights', 'user123');
68+
69+
// Assert
70+
expect(result).toBeNull();
71+
expect(mockRuntime.llm.shouldRespond).toHaveBeenCalledWith(
72+
expect.any(String),
73+
'turn on lights'
74+
);
75+
});
76+
77+
it('should execute command and return response when shouldRespond returns RESPOND', async () => {
78+
// Arrange
79+
const mockResponse = 'Command executed successfully';
80+
vi.mocked(mockRuntime.llm.shouldRespond).mockResolvedValue('RESPOND');
81+
vi.mocked(mockRuntime.llm.complete).mockResolvedValue(mockResponse);
82+
vi.mocked(CommandParser.parseCommand).mockReturnValue({
83+
command: 'turn_on',
84+
args: { device: 'lights' }
85+
});
86+
vi.mocked(CommandParser.mapToDeviceCommand).mockReturnValue({
87+
deviceId: 'device123',
88+
capability: 'switch',
89+
command: 'on'
90+
});
91+
92+
// Act
93+
const result = await smartHomeManager.handleCommand('turn on lights', 'user123');
94+
95+
// Assert
96+
expect(result).toEqual({
97+
success: true,
98+
message: mockResponse,
99+
data: { status: 'success' }
100+
});
101+
expect(mockRuntime.llm.shouldRespond).toHaveBeenCalled();
102+
expect(mockRuntime.llm.complete).toHaveBeenCalled();
103+
expect(CommandParser.parseCommand).toHaveBeenCalledWith('turn on lights');
104+
expect(CommandParser.mapToDeviceCommand).toHaveBeenCalled();
105+
});
106+
107+
it('should handle errors gracefully', async () => {
108+
// Arrange
109+
const mockError = new Error('Test error');
110+
vi.mocked(mockRuntime.llm.shouldRespond).mockRejectedValue(mockError);
111+
112+
// Act & Assert
113+
await expect(smartHomeManager.handleCommand('turn on lights', 'user123'))
114+
.rejects
115+
.toThrow(mockError);
116+
});
117+
});
118+
});

0 commit comments

Comments
 (0)