Enhance project structure and testing capabilities
- Updated .dockerignore to include additional logs and IDE files, improving Docker build efficiency. - Added .eslintrc.json for TypeScript linting configuration, ensuring code quality and consistency. - Refactored Dockerfile to streamline the build process and utilize a slimmer Node.js image. - Introduced jest-resolver.cjs and jest.setup.js for improved Jest testing configuration and setup. - Updated jest.config.js to support ESM and added new test patterns for better test organization. - Enhanced TypeScript schemas to include new device types (media_player, fan, lock, vacuum, scene, script, camera) for comprehensive validation. - Added unit tests for device schemas and Home Assistant connection, improving test coverage and reliability. - Updated README.md with new testing instructions and device control examples, enhancing user guidance.
This commit is contained in:
203
__tests__/context/context.test.ts
Normal file
203
__tests__/context/context.test.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import { z } from 'zod';
|
||||
import { DomainSchema } from '../../src/schemas.js';
|
||||
|
||||
// Define types for tool and server
|
||||
interface Tool {
|
||||
name: string;
|
||||
description: string;
|
||||
execute: (params: any) => Promise<any>;
|
||||
parameters: z.ZodType<any>;
|
||||
}
|
||||
|
||||
// Mock LiteMCP class
|
||||
class MockLiteMCP {
|
||||
private tools: Tool[] = [];
|
||||
|
||||
constructor(public name: string, public version: string) { }
|
||||
|
||||
addTool(tool: Tool) {
|
||||
this.tools.push(tool);
|
||||
}
|
||||
|
||||
getTools() {
|
||||
return this.tools;
|
||||
}
|
||||
}
|
||||
|
||||
// Mock the Home Assistant instance
|
||||
jest.mock('../../src/hass/index.js', () => ({
|
||||
get_hass: jest.fn().mockResolvedValue({
|
||||
services: {
|
||||
light: {
|
||||
turn_on: jest.fn().mockResolvedValue(undefined),
|
||||
turn_off: jest.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
climate: {
|
||||
set_temperature: jest.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('MCP Server Context and Tools', () => {
|
||||
let server: MockLiteMCP;
|
||||
|
||||
beforeEach(async () => {
|
||||
server = new MockLiteMCP('home-assistant', '0.1.0');
|
||||
|
||||
// Add the control tool to the server
|
||||
server.addTool({
|
||||
name: 'control',
|
||||
description: 'Control Home Assistant devices and services',
|
||||
execute: async (params: any) => {
|
||||
const domain = params.entity_id.split('.')[0];
|
||||
if (params.command === 'set_temperature' && domain !== 'climate') {
|
||||
return {
|
||||
success: false,
|
||||
message: `Unsupported operation for domain: ${domain}`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully executed ${params.command} for ${params.entity_id}`,
|
||||
};
|
||||
},
|
||||
parameters: z.object({
|
||||
command: z.string(),
|
||||
entity_id: z.string(),
|
||||
brightness: z.number().min(0).max(255).optional(),
|
||||
color_temp: z.number().optional(),
|
||||
rgb_color: z.tuple([z.number(), z.number(), z.number()]).optional(),
|
||||
temperature: z.number().optional(),
|
||||
hvac_mode: z.enum(['off', 'heat', 'cool', 'heat_cool', 'auto', 'dry', 'fan_only']).optional(),
|
||||
fan_mode: z.enum(['auto', 'low', 'medium', 'high']).optional(),
|
||||
position: z.number().min(0).max(100).optional(),
|
||||
tilt_position: z.number().min(0).max(100).optional(),
|
||||
area: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Custom Prompts', () => {
|
||||
it('should handle natural language commands for lights', async () => {
|
||||
const tools = server.getTools();
|
||||
const tool = tools.find(t => t.name === 'control');
|
||||
expect(tool).toBeDefined();
|
||||
|
||||
// Test natural language command execution
|
||||
const result = await tool!.execute({
|
||||
command: 'turn_on',
|
||||
entity_id: 'light.living_room',
|
||||
brightness: 128,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
message: expect.stringContaining('Successfully executed turn_on for light.living_room'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle natural language commands for climate control', async () => {
|
||||
const tools = server.getTools();
|
||||
const tool = tools.find(t => t.name === 'control');
|
||||
expect(tool).toBeDefined();
|
||||
|
||||
// Test temperature control command
|
||||
const result = await tool!.execute({
|
||||
command: 'set_temperature',
|
||||
entity_id: 'climate.living_room',
|
||||
temperature: 22,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
message: expect.stringContaining('Successfully executed set_temperature for climate.living_room'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('High-Level Context', () => {
|
||||
it('should validate domain-specific commands', async () => {
|
||||
const tools = server.getTools();
|
||||
const tool = tools.find(t => t.name === 'control');
|
||||
expect(tool).toBeDefined();
|
||||
|
||||
// Test invalid command for domain
|
||||
const result = await tool!.execute({
|
||||
command: 'set_temperature', // Climate command
|
||||
entity_id: 'light.living_room', // Light entity
|
||||
temperature: 22,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: false,
|
||||
message: expect.stringContaining('Unsupported operation'),
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle area-based commands', async () => {
|
||||
const tools = server.getTools();
|
||||
const tool = tools.find(t => t.name === 'control');
|
||||
expect(tool).toBeDefined();
|
||||
|
||||
// Test command with area context
|
||||
const result = await tool!.execute({
|
||||
command: 'turn_on',
|
||||
entity_id: 'light.living_room',
|
||||
area: 'Living Room',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
message: expect.stringContaining('Successfully executed turn_on for light.living_room'),
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Tool Organization', () => {
|
||||
it('should have all required tools available', () => {
|
||||
const tools = server.getTools();
|
||||
const toolNames = tools.map(t => t.name);
|
||||
expect(toolNames).toContain('control');
|
||||
});
|
||||
|
||||
it('should support all defined domains', () => {
|
||||
const tools = server.getTools();
|
||||
const tool = tools.find(t => t.name === 'control');
|
||||
expect(tool).toBeDefined();
|
||||
|
||||
// Check if tool supports all domains from DomainSchema
|
||||
const supportedDomains = Object.values(DomainSchema.Values);
|
||||
const schema = tool!.parameters as z.ZodObject<any>;
|
||||
const shape = schema.shape;
|
||||
|
||||
expect(shape).toBeDefined();
|
||||
expect(shape.entity_id).toBeDefined();
|
||||
expect(shape.command).toBeDefined();
|
||||
|
||||
// Test each domain has its specific parameters
|
||||
supportedDomains.forEach(domain => {
|
||||
switch (domain) {
|
||||
case 'light':
|
||||
expect(shape.brightness).toBeDefined();
|
||||
expect(shape.color_temp).toBeDefined();
|
||||
expect(shape.rgb_color).toBeDefined();
|
||||
break;
|
||||
case 'climate':
|
||||
expect(shape.temperature).toBeDefined();
|
||||
expect(shape.hvac_mode).toBeDefined();
|
||||
expect(shape.fan_mode).toBeDefined();
|
||||
break;
|
||||
case 'cover':
|
||||
expect(shape.position).toBeDefined();
|
||||
expect(shape.tilt_position).toBeDefined();
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
81
__tests__/hass/hass.test.ts
Normal file
81
__tests__/hass/hass.test.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { get_hass } from '../../src/hass/index.js';
|
||||
|
||||
// Mock the entire module
|
||||
jest.mock('../../src/hass/index.js', () => {
|
||||
let mockInstance: any = null;
|
||||
|
||||
return {
|
||||
get_hass: jest.fn(async () => {
|
||||
if (!mockInstance) {
|
||||
mockInstance = {
|
||||
services: {
|
||||
light: {
|
||||
turn_on: jest.fn().mockResolvedValue(undefined),
|
||||
turn_off: jest.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
climate: {
|
||||
set_temperature: jest.fn().mockResolvedValue(undefined),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return mockInstance;
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Home Assistant Connection', () => {
|
||||
// Backup the original environment
|
||||
const originalEnv = { ...process.env };
|
||||
|
||||
beforeEach(() => {
|
||||
// Clear all mocks
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Reset environment variables
|
||||
process.env = { ...originalEnv };
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
// Restore original environment
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
it('should return a Home Assistant instance with services', async () => {
|
||||
const hass = await get_hass();
|
||||
|
||||
expect(hass).toBeDefined();
|
||||
expect(hass.services).toBeDefined();
|
||||
expect(typeof hass.services.light.turn_on).toBe('function');
|
||||
expect(typeof hass.services.light.turn_off).toBe('function');
|
||||
expect(typeof hass.services.climate.set_temperature).toBe('function');
|
||||
});
|
||||
|
||||
it('should reuse the same instance on multiple calls', async () => {
|
||||
const firstInstance = await get_hass();
|
||||
const secondInstance = await get_hass();
|
||||
|
||||
expect(firstInstance).toBe(secondInstance);
|
||||
});
|
||||
|
||||
it('should use "development" as default environment', async () => {
|
||||
// Unset NODE_ENV
|
||||
delete process.env.NODE_ENV;
|
||||
|
||||
const hass = await get_hass();
|
||||
|
||||
// You might need to add a way to check the environment in your actual implementation
|
||||
// This is a placeholder and might need adjustment based on your exact implementation
|
||||
expect(process.env.NODE_ENV).toBe(undefined);
|
||||
});
|
||||
|
||||
it('should use process.env.NODE_ENV when set', async () => {
|
||||
// Set a specific environment
|
||||
process.env.NODE_ENV = 'production';
|
||||
|
||||
const hass = await get_hass();
|
||||
|
||||
// You might need to add a way to check the environment in your actual implementation
|
||||
expect(process.env.NODE_ENV).toBe('production');
|
||||
});
|
||||
});
|
||||
186
__tests__/schemas/devices.test.js
Normal file
186
__tests__/schemas/devices.test.js
Normal file
@@ -0,0 +1,186 @@
|
||||
import { MediaPlayerSchema, FanSchema, LockSchema, VacuumSchema, SceneSchema, ScriptSchema, CameraSchema, ListMediaPlayersResponseSchema, ListFansResponseSchema, ListLocksResponseSchema, ListVacuumsResponseSchema, ListScenesResponseSchema, ListScriptsResponseSchema, ListCamerasResponseSchema, } from '../../src/schemas';
|
||||
describe('Device Schemas', () => {
|
||||
describe('MediaPlayer Schema', () => {
|
||||
it('should validate a valid media player entity', () => {
|
||||
const mediaPlayer = {
|
||||
entity_id: 'media_player.living_room',
|
||||
state: 'playing',
|
||||
state_attributes: {
|
||||
volume_level: 0.5,
|
||||
is_volume_muted: false,
|
||||
media_content_id: 'spotify:playlist:xyz',
|
||||
media_content_type: 'playlist',
|
||||
media_title: 'My Playlist',
|
||||
source: 'Spotify',
|
||||
source_list: ['Spotify', 'Radio', 'TV'],
|
||||
supported_features: 12345
|
||||
}
|
||||
};
|
||||
expect(() => MediaPlayerSchema.parse(mediaPlayer)).not.toThrow();
|
||||
});
|
||||
it('should validate media player list response', () => {
|
||||
const response = {
|
||||
media_players: [{
|
||||
entity_id: 'media_player.living_room',
|
||||
state: 'playing',
|
||||
state_attributes: {}
|
||||
}]
|
||||
};
|
||||
expect(() => ListMediaPlayersResponseSchema.parse(response)).not.toThrow();
|
||||
});
|
||||
});
|
||||
describe('Fan Schema', () => {
|
||||
it('should validate a valid fan entity', () => {
|
||||
const fan = {
|
||||
entity_id: 'fan.bedroom',
|
||||
state: 'on',
|
||||
state_attributes: {
|
||||
percentage: 50,
|
||||
preset_mode: 'auto',
|
||||
preset_modes: ['auto', 'low', 'medium', 'high'],
|
||||
oscillating: true,
|
||||
direction: 'forward',
|
||||
supported_features: 12345
|
||||
}
|
||||
};
|
||||
expect(() => FanSchema.parse(fan)).not.toThrow();
|
||||
});
|
||||
it('should validate fan list response', () => {
|
||||
const response = {
|
||||
fans: [{
|
||||
entity_id: 'fan.bedroom',
|
||||
state: 'on',
|
||||
state_attributes: {}
|
||||
}]
|
||||
};
|
||||
expect(() => ListFansResponseSchema.parse(response)).not.toThrow();
|
||||
});
|
||||
});
|
||||
describe('Lock Schema', () => {
|
||||
it('should validate a valid lock entity', () => {
|
||||
const lock = {
|
||||
entity_id: 'lock.front_door',
|
||||
state: 'locked',
|
||||
state_attributes: {
|
||||
code_format: 'number',
|
||||
changed_by: 'User',
|
||||
locked: true,
|
||||
supported_features: 12345
|
||||
}
|
||||
};
|
||||
expect(() => LockSchema.parse(lock)).not.toThrow();
|
||||
});
|
||||
it('should validate lock list response', () => {
|
||||
const response = {
|
||||
locks: [{
|
||||
entity_id: 'lock.front_door',
|
||||
state: 'locked',
|
||||
state_attributes: { locked: true }
|
||||
}]
|
||||
};
|
||||
expect(() => ListLocksResponseSchema.parse(response)).not.toThrow();
|
||||
});
|
||||
});
|
||||
describe('Vacuum Schema', () => {
|
||||
it('should validate a valid vacuum entity', () => {
|
||||
const vacuum = {
|
||||
entity_id: 'vacuum.robot',
|
||||
state: 'cleaning',
|
||||
state_attributes: {
|
||||
battery_level: 80,
|
||||
fan_speed: 'medium',
|
||||
fan_speed_list: ['low', 'medium', 'high'],
|
||||
status: 'cleaning',
|
||||
supported_features: 12345
|
||||
}
|
||||
};
|
||||
expect(() => VacuumSchema.parse(vacuum)).not.toThrow();
|
||||
});
|
||||
it('should validate vacuum list response', () => {
|
||||
const response = {
|
||||
vacuums: [{
|
||||
entity_id: 'vacuum.robot',
|
||||
state: 'cleaning',
|
||||
state_attributes: {}
|
||||
}]
|
||||
};
|
||||
expect(() => ListVacuumsResponseSchema.parse(response)).not.toThrow();
|
||||
});
|
||||
});
|
||||
describe('Scene Schema', () => {
|
||||
it('should validate a valid scene entity', () => {
|
||||
const scene = {
|
||||
entity_id: 'scene.movie_night',
|
||||
state: 'on',
|
||||
state_attributes: {
|
||||
entity_id: ['light.living_room', 'media_player.tv'],
|
||||
supported_features: 12345
|
||||
}
|
||||
};
|
||||
expect(() => SceneSchema.parse(scene)).not.toThrow();
|
||||
});
|
||||
it('should validate scene list response', () => {
|
||||
const response = {
|
||||
scenes: [{
|
||||
entity_id: 'scene.movie_night',
|
||||
state: 'on',
|
||||
state_attributes: {}
|
||||
}]
|
||||
};
|
||||
expect(() => ListScenesResponseSchema.parse(response)).not.toThrow();
|
||||
});
|
||||
});
|
||||
describe('Script Schema', () => {
|
||||
it('should validate a valid script entity', () => {
|
||||
const script = {
|
||||
entity_id: 'script.welcome_home',
|
||||
state: 'on',
|
||||
state_attributes: {
|
||||
last_triggered: '2023-12-25T12:00:00Z',
|
||||
mode: 'single',
|
||||
variables: {
|
||||
brightness: 100,
|
||||
color: 'red'
|
||||
},
|
||||
supported_features: 12345
|
||||
}
|
||||
};
|
||||
expect(() => ScriptSchema.parse(script)).not.toThrow();
|
||||
});
|
||||
it('should validate script list response', () => {
|
||||
const response = {
|
||||
scripts: [{
|
||||
entity_id: 'script.welcome_home',
|
||||
state: 'on',
|
||||
state_attributes: {}
|
||||
}]
|
||||
};
|
||||
expect(() => ListScriptsResponseSchema.parse(response)).not.toThrow();
|
||||
});
|
||||
});
|
||||
describe('Camera Schema', () => {
|
||||
it('should validate a valid camera entity', () => {
|
||||
const camera = {
|
||||
entity_id: 'camera.front_door',
|
||||
state: 'recording',
|
||||
state_attributes: {
|
||||
motion_detection: true,
|
||||
frontend_stream_type: 'hls',
|
||||
supported_features: 12345
|
||||
}
|
||||
};
|
||||
expect(() => CameraSchema.parse(camera)).not.toThrow();
|
||||
});
|
||||
it('should validate camera list response', () => {
|
||||
const response = {
|
||||
cameras: [{
|
||||
entity_id: 'camera.front_door',
|
||||
state: 'recording',
|
||||
state_attributes: {}
|
||||
}]
|
||||
};
|
||||
expect(() => ListCamerasResponseSchema.parse(response)).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=devices.test.js.map
|
||||
1
__tests__/schemas/devices.test.js.map
Normal file
1
__tests__/schemas/devices.test.js.map
Normal file
File diff suppressed because one or more lines are too long
214
__tests__/schemas/devices.test.ts
Normal file
214
__tests__/schemas/devices.test.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import {
|
||||
MediaPlayerSchema,
|
||||
FanSchema,
|
||||
LockSchema,
|
||||
VacuumSchema,
|
||||
SceneSchema,
|
||||
ScriptSchema,
|
||||
CameraSchema,
|
||||
ListMediaPlayersResponseSchema,
|
||||
ListFansResponseSchema,
|
||||
ListLocksResponseSchema,
|
||||
ListVacuumsResponseSchema,
|
||||
ListScenesResponseSchema,
|
||||
ListScriptsResponseSchema,
|
||||
ListCamerasResponseSchema,
|
||||
} from '../../src/schemas.js';
|
||||
|
||||
describe('Device Schemas', () => {
|
||||
describe('Media Player Schema', () => {
|
||||
it('should validate a valid media player entity', () => {
|
||||
const mediaPlayer = {
|
||||
entity_id: 'media_player.living_room',
|
||||
state: 'playing',
|
||||
state_attributes: {
|
||||
volume_level: 0.5,
|
||||
is_volume_muted: false,
|
||||
media_content_id: 'spotify:playlist:xyz',
|
||||
media_content_type: 'playlist',
|
||||
media_title: 'My Playlist',
|
||||
source: 'Spotify',
|
||||
source_list: ['Spotify', 'Radio', 'TV'],
|
||||
supported_features: 12345
|
||||
}
|
||||
};
|
||||
expect(() => MediaPlayerSchema.parse(mediaPlayer)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should validate media player list response', () => {
|
||||
const response = {
|
||||
media_players: [{
|
||||
entity_id: 'media_player.living_room',
|
||||
state: 'playing',
|
||||
state_attributes: {}
|
||||
}]
|
||||
};
|
||||
expect(() => ListMediaPlayersResponseSchema.parse(response)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Fan Schema', () => {
|
||||
it('should validate a valid fan entity', () => {
|
||||
const fan = {
|
||||
entity_id: 'fan.bedroom',
|
||||
state: 'on',
|
||||
state_attributes: {
|
||||
percentage: 50,
|
||||
preset_mode: 'auto',
|
||||
preset_modes: ['auto', 'low', 'medium', 'high'],
|
||||
oscillating: true,
|
||||
direction: 'forward',
|
||||
supported_features: 12345
|
||||
}
|
||||
};
|
||||
expect(() => FanSchema.parse(fan)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should validate fan list response', () => {
|
||||
const response = {
|
||||
fans: [{
|
||||
entity_id: 'fan.bedroom',
|
||||
state: 'on',
|
||||
state_attributes: {}
|
||||
}]
|
||||
};
|
||||
expect(() => ListFansResponseSchema.parse(response)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Lock Schema', () => {
|
||||
it('should validate a valid lock entity', () => {
|
||||
const lock = {
|
||||
entity_id: 'lock.front_door',
|
||||
state: 'locked',
|
||||
state_attributes: {
|
||||
code_format: 'number',
|
||||
changed_by: 'User',
|
||||
locked: true,
|
||||
supported_features: 12345
|
||||
}
|
||||
};
|
||||
expect(() => LockSchema.parse(lock)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should validate lock list response', () => {
|
||||
const response = {
|
||||
locks: [{
|
||||
entity_id: 'lock.front_door',
|
||||
state: 'locked',
|
||||
state_attributes: { locked: true }
|
||||
}]
|
||||
};
|
||||
expect(() => ListLocksResponseSchema.parse(response)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Vacuum Schema', () => {
|
||||
it('should validate a valid vacuum entity', () => {
|
||||
const vacuum = {
|
||||
entity_id: 'vacuum.robot',
|
||||
state: 'cleaning',
|
||||
state_attributes: {
|
||||
battery_level: 80,
|
||||
fan_speed: 'medium',
|
||||
fan_speed_list: ['low', 'medium', 'high'],
|
||||
status: 'cleaning',
|
||||
supported_features: 12345
|
||||
}
|
||||
};
|
||||
expect(() => VacuumSchema.parse(vacuum)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should validate vacuum list response', () => {
|
||||
const response = {
|
||||
vacuums: [{
|
||||
entity_id: 'vacuum.robot',
|
||||
state: 'cleaning',
|
||||
state_attributes: {}
|
||||
}]
|
||||
};
|
||||
expect(() => ListVacuumsResponseSchema.parse(response)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Scene Schema', () => {
|
||||
it('should validate a valid scene entity', () => {
|
||||
const scene = {
|
||||
entity_id: 'scene.movie_night',
|
||||
state: 'on',
|
||||
state_attributes: {
|
||||
entity_id: ['light.living_room', 'media_player.tv'],
|
||||
supported_features: 12345
|
||||
}
|
||||
};
|
||||
expect(() => SceneSchema.parse(scene)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should validate scene list response', () => {
|
||||
const response = {
|
||||
scenes: [{
|
||||
entity_id: 'scene.movie_night',
|
||||
state: 'on',
|
||||
state_attributes: {}
|
||||
}]
|
||||
};
|
||||
expect(() => ListScenesResponseSchema.parse(response)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Script Schema', () => {
|
||||
it('should validate a valid script entity', () => {
|
||||
const script = {
|
||||
entity_id: 'script.welcome_home',
|
||||
state: 'on',
|
||||
state_attributes: {
|
||||
last_triggered: '2023-12-25T12:00:00Z',
|
||||
mode: 'single',
|
||||
variables: {
|
||||
brightness: 100,
|
||||
color: 'red'
|
||||
},
|
||||
supported_features: 12345
|
||||
}
|
||||
};
|
||||
expect(() => ScriptSchema.parse(script)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should validate script list response', () => {
|
||||
const response = {
|
||||
scripts: [{
|
||||
entity_id: 'script.welcome_home',
|
||||
state: 'on',
|
||||
state_attributes: {}
|
||||
}]
|
||||
};
|
||||
expect(() => ListScriptsResponseSchema.parse(response)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Camera Schema', () => {
|
||||
it('should validate a valid camera entity', () => {
|
||||
const camera = {
|
||||
entity_id: 'camera.front_door',
|
||||
state: 'recording',
|
||||
state_attributes: {
|
||||
motion_detection: true,
|
||||
frontend_stream_type: 'hls',
|
||||
supported_features: 12345
|
||||
}
|
||||
};
|
||||
expect(() => CameraSchema.parse(camera)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should validate camera list response', () => {
|
||||
const response = {
|
||||
cameras: [{
|
||||
entity_id: 'camera.front_door',
|
||||
state: 'recording',
|
||||
state_attributes: {}
|
||||
}]
|
||||
};
|
||||
expect(() => ListCamerasResponseSchema.parse(response)).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user