Enhance test infrastructure and add comprehensive WebSocket and security mocking

- Updated test suite with more robust mocking for WebSocket and security modules
- Expanded test coverage for performance monitoring and optimization utilities
- Added detailed type definitions for WebSocket and test response interfaces
- Improved error handling and type safety in test scenarios
- Updated package dependencies to include WebSocket and security-related libraries
This commit is contained in:
jango-blockchained
2025-01-30 09:43:19 +01:00
parent f5f756f71e
commit b3fa5f729e
6 changed files with 295 additions and 86 deletions

View File

@@ -1,32 +1,47 @@
import { jest, describe, beforeEach, afterEach, it, expect } from '@jest/globals'; import { jest, describe, beforeEach, afterEach, it, expect } from '@jest/globals';
import type { Mock } from 'jest-mock'; import type { Mock } from 'jest-mock';
import { LiteMCP } from 'litemcp'; import { LiteMCP } from 'litemcp';
import { get_hass } from '../src/hass/index.js';
import type { WebSocket } from 'ws';
// Mock environment variables // Mock environment variables
process.env.HASS_HOST = 'http://localhost:8123'; process.env.HASS_HOST = 'http://localhost:8123';
process.env.HASS_TOKEN = 'test_token'; process.env.HASS_TOKEN = 'test_token';
// Mock fetch // Mock fetch
const mockFetch = jest.fn().mockImplementation( const mockFetchResponse = {
async (input: string | URL | Request, init?: RequestInit): Promise<Response> => { ok: true,
return {} as Response; status: 200,
} statusText: 'OK',
) as unknown as jest.MockedFunction<typeof fetch>; json: async () => ({ automation_id: 'test_automation' }),
global.fetch = mockFetch; text: async () => '{"automation_id":"test_automation"}',
headers: new Headers(),
body: null,
bodyUsed: false,
arrayBuffer: async () => new ArrayBuffer(0),
blob: async () => new Blob([]),
formData: async () => new FormData(),
clone: function () { return { ...this }; },
type: 'default',
url: '',
redirected: false,
redirect: () => Promise.resolve(new Response())
} as Response;
const mockFetch = jest.fn(async (_input: string | URL | Request, _init?: RequestInit) => mockFetchResponse) as jest.MockedFunction<typeof fetch>;
(global as any).fetch = mockFetch;
// Mock LiteMCP // Mock LiteMCP
jest.mock('litemcp', () => { jest.mock('litemcp', () => ({
return {
LiteMCP: jest.fn().mockImplementation(() => ({ LiteMCP: jest.fn().mockImplementation(() => ({
addTool: jest.fn(), addTool: jest.fn(),
start: jest.fn().mockResolvedValue(undefined) start: jest.fn()
})) }))
}; }));
});
// Mock get_hass // Mock get_hass
jest.unstable_mockModule('../src/hass/index.js', () => ({ jest.unstable_mockModule('../src/hass/index.js', () => ({
get_hass: jest.fn().mockResolvedValue({ get_hass: jest.fn().mockReturnValue({
services: { services: {
light: { light: {
turn_on: jest.fn(), turn_on: jest.fn(),
@@ -38,17 +53,88 @@ jest.unstable_mockModule('../src/hass/index.js', () => ({
interface Tool { interface Tool {
name: string; name: string;
execute: (...args: any[]) => Promise<any>; description: string;
parameters: Record<string, unknown>;
execute: (params: Record<string, unknown>) => Promise<unknown>;
} }
interface TestResponse {
success: boolean;
message?: string;
devices?: Record<string, unknown>;
history?: unknown[];
scenes?: unknown[];
automations?: unknown[];
addons?: unknown[];
packages?: unknown[];
automation_id?: string;
new_automation_id?: string;
}
type WebSocketEventMap = {
message: MessageEvent;
open: Event;
close: Event;
error: Event;
};
type WebSocketEventListener = (event: Event) => void;
type WebSocketMessageListener = (event: MessageEvent) => void;
interface MockWebSocketInstance {
addEventListener: jest.MockedFunction<typeof Function>;
removeEventListener: jest.MockedFunction<typeof Function>;
send: jest.MockedFunction<typeof Function>;
close: jest.MockedFunction<typeof Function>;
readyState: number;
binaryType: 'blob' | 'arraybuffer';
bufferedAmount: number;
extensions: string;
protocol: string;
url: string;
onopen: WebSocketEventListener | null;
onerror: WebSocketEventListener | null;
onclose: WebSocketEventListener | null;
onmessage: WebSocketMessageListener | null;
CONNECTING: number;
OPEN: number;
CLOSING: number;
CLOSED: number;
}
const createMockWebSocket = (): MockWebSocketInstance => ({
addEventListener: jest.fn() as jest.MockedFunction<typeof Function>,
removeEventListener: jest.fn() as jest.MockedFunction<typeof Function>,
send: jest.fn() as jest.MockedFunction<typeof Function>,
close: jest.fn() as jest.MockedFunction<typeof Function>,
readyState: 0,
binaryType: 'blob',
bufferedAmount: 0,
extensions: '',
protocol: '',
url: '',
onopen: null,
onerror: null,
onclose: null,
onmessage: null,
CONNECTING: 0,
OPEN: 1,
CLOSING: 2,
CLOSED: 3
});
describe('Home Assistant MCP Server', () => { describe('Home Assistant MCP Server', () => {
beforeEach(async () => { beforeEach(async () => {
// Reset all mocks // Reset all mocks
jest.clearAllMocks(); jest.clearAllMocks();
mockFetch.mockReset(); mockFetch.mockClear();
// Import the module which will execute the main function // Import the module which will execute the main function
await import('../src/index.js'); await import('../src/index.js');
// Mock WebSocket
const mockWs = createMockWebSocket();
(global as any).WebSocket = jest.fn(() => mockWs);
}); });
afterEach(() => { afterEach(() => {
@@ -82,7 +168,7 @@ describe('Home Assistant MCP Server', () => {
const listDevicesTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'list_devices')?.[0] as Tool; const listDevicesTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'list_devices')?.[0] as Tool;
// Execute the tool // Execute the tool
const result = await listDevicesTool.execute({}); const result = (await listDevicesTool.execute({})) as TestResponse;
// Verify the results // Verify the results
expect(result.success).toBe(true); expect(result.success).toBe(true);
@@ -110,7 +196,7 @@ describe('Home Assistant MCP Server', () => {
const listDevicesTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'list_devices')?.[0] as Tool; const listDevicesTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'list_devices')?.[0] as Tool;
// Execute the tool // Execute the tool
const result = await listDevicesTool.execute({}); const result = (await listDevicesTool.execute({})) as TestResponse;
// Verify error handling // Verify error handling
expect(result.success).toBe(false); expect(result.success).toBe(false);
@@ -132,11 +218,11 @@ describe('Home Assistant MCP Server', () => {
const controlTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'control')?.[0] as Tool; const controlTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'control')?.[0] as Tool;
// Execute the tool // Execute the tool
const result = await controlTool.execute({ const result = (await controlTool.execute({
command: 'turn_on', command: 'turn_on',
entity_id: 'light.living_room', entity_id: 'light.living_room',
brightness: 255 brightness: 255
}); })) as TestResponse;
// Verify the results // Verify the results
expect(result.success).toBe(true); expect(result.success).toBe(true);
@@ -166,10 +252,10 @@ describe('Home Assistant MCP Server', () => {
const controlTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'control')?.[0] as Tool; const controlTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'control')?.[0] as Tool;
// Execute the tool with an unsupported domain // Execute the tool with an unsupported domain
const result = await controlTool.execute({ const result = (await controlTool.execute({
command: 'turn_on', command: 'turn_on',
entity_id: 'unsupported.device' entity_id: 'unsupported.device'
}); })) as TestResponse;
// Verify error handling // Verify error handling
expect(result.success).toBe(false); expect(result.success).toBe(false);
@@ -189,10 +275,10 @@ describe('Home Assistant MCP Server', () => {
const controlTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'control')?.[0] as Tool; const controlTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'control')?.[0] as Tool;
// Execute the tool // Execute the tool
const result = await controlTool.execute({ const result = (await controlTool.execute({
command: 'turn_on', command: 'turn_on',
entity_id: 'light.living_room' entity_id: 'light.living_room'
}); })) as TestResponse;
// Verify error handling // Verify error handling
expect(result.success).toBe(false); expect(result.success).toBe(false);
@@ -212,13 +298,13 @@ describe('Home Assistant MCP Server', () => {
const controlTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'control')?.[0] as Tool; const controlTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'control')?.[0] as Tool;
// Execute the tool // Execute the tool
const result = await controlTool.execute({ const result = (await controlTool.execute({
command: 'set_temperature', command: 'set_temperature',
entity_id: 'climate.bedroom', entity_id: 'climate.bedroom',
temperature: 22, temperature: 22,
target_temp_high: 24, target_temp_high: 24,
target_temp_low: 20 target_temp_low: 20
}); })) as TestResponse;
// Verify the results // Verify the results
expect(result.success).toBe(true); expect(result.success).toBe(true);
@@ -256,11 +342,11 @@ describe('Home Assistant MCP Server', () => {
const controlTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'control')?.[0] as Tool; const controlTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'control')?.[0] as Tool;
// Execute the tool // Execute the tool
const result = await controlTool.execute({ const result = (await controlTool.execute({
command: 'set_position', command: 'set_position',
entity_id: 'cover.living_room', entity_id: 'cover.living_room',
position: 50 position: 50
}); })) as TestResponse;
// Verify the results // Verify the results
expect(result.success).toBe(true); expect(result.success).toBe(true);
@@ -312,13 +398,13 @@ describe('Home Assistant MCP Server', () => {
const historyTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'get_history')?.[0] as Tool; const historyTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'get_history')?.[0] as Tool;
// Execute the tool // Execute the tool
const result = await historyTool.execute({ const result = (await historyTool.execute({
entity_id: 'light.living_room', entity_id: 'light.living_room',
start_time: '2024-01-01T00:00:00Z', start_time: '2024-01-01T00:00:00Z',
end_time: '2024-01-01T02:00:00Z', end_time: '2024-01-01T02:00:00Z',
minimal_response: true, minimal_response: true,
significant_changes_only: true significant_changes_only: true
}); })) as TestResponse;
// Verify the results // Verify the results
expect(result.success).toBe(true); expect(result.success).toBe(true);
@@ -350,9 +436,9 @@ describe('Home Assistant MCP Server', () => {
const addToolCalls = liteMcpInstance.addTool.mock.calls; const addToolCalls = liteMcpInstance.addTool.mock.calls;
const historyTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'get_history')?.[0] as Tool; const historyTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'get_history')?.[0] as Tool;
const result = await historyTool.execute({ const result = (await historyTool.execute({
entity_id: 'light.living_room' entity_id: 'light.living_room'
}); })) as TestResponse;
expect(result.success).toBe(false); expect(result.success).toBe(false);
expect(result.message).toBe('Network error'); expect(result.message).toBe('Network error');
@@ -389,9 +475,9 @@ describe('Home Assistant MCP Server', () => {
const addToolCalls = liteMcpInstance.addTool.mock.calls; const addToolCalls = liteMcpInstance.addTool.mock.calls;
const sceneTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'scene')?.[0] as Tool; const sceneTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'scene')?.[0] as Tool;
const result = await sceneTool.execute({ const result = (await sceneTool.execute({
action: 'list' action: 'list'
}); })) as TestResponse;
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.scenes).toEqual([ expect(result.scenes).toEqual([
@@ -418,10 +504,10 @@ describe('Home Assistant MCP Server', () => {
const addToolCalls = liteMcpInstance.addTool.mock.calls; const addToolCalls = liteMcpInstance.addTool.mock.calls;
const sceneTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'scene')?.[0] as Tool; const sceneTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'scene')?.[0] as Tool;
const result = await sceneTool.execute({ const result = (await sceneTool.execute({
action: 'activate', action: 'activate',
scene_id: 'scene.movie_time' scene_id: 'scene.movie_time'
}); })) as TestResponse;
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.message).toBe('Successfully activated scene scene.movie_time'); expect(result.message).toBe('Successfully activated scene scene.movie_time');
@@ -453,12 +539,12 @@ describe('Home Assistant MCP Server', () => {
const addToolCalls = liteMcpInstance.addTool.mock.calls; const addToolCalls = liteMcpInstance.addTool.mock.calls;
const notifyTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'notify')?.[0] as Tool; const notifyTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'notify')?.[0] as Tool;
const result = await notifyTool.execute({ const result = (await notifyTool.execute({
message: 'Test notification', message: 'Test notification',
title: 'Test Title', title: 'Test Title',
target: 'mobile_app_phone', target: 'mobile_app_phone',
data: { priority: 'high' } data: { priority: 'high' }
}); })) as TestResponse;
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.message).toBe('Notification sent successfully'); expect(result.message).toBe('Notification sent successfully');
@@ -531,9 +617,9 @@ describe('Home Assistant MCP Server', () => {
const addToolCalls = liteMcpInstance.addTool.mock.calls; const addToolCalls = liteMcpInstance.addTool.mock.calls;
const automationTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'automation')?.[0] as Tool; const automationTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'automation')?.[0] as Tool;
const result = await automationTool.execute({ const result = (await automationTool.execute({
action: 'list' action: 'list'
}); })) as TestResponse;
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.automations).toEqual([ expect(result.automations).toEqual([
@@ -562,10 +648,10 @@ describe('Home Assistant MCP Server', () => {
const addToolCalls = liteMcpInstance.addTool.mock.calls; const addToolCalls = liteMcpInstance.addTool.mock.calls;
const automationTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'automation')?.[0] as Tool; const automationTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'automation')?.[0] as Tool;
const result = await automationTool.execute({ const result = (await automationTool.execute({
action: 'toggle', action: 'toggle',
automation_id: 'automation.morning_routine' automation_id: 'automation.morning_routine'
}); })) as TestResponse;
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.message).toBe('Successfully toggled automation automation.morning_routine'); expect(result.message).toBe('Successfully toggled automation automation.morning_routine');
@@ -595,10 +681,10 @@ describe('Home Assistant MCP Server', () => {
const addToolCalls = liteMcpInstance.addTool.mock.calls; const addToolCalls = liteMcpInstance.addTool.mock.calls;
const automationTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'automation')?.[0] as Tool; const automationTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'automation')?.[0] as Tool;
const result = await automationTool.execute({ const result = (await automationTool.execute({
action: 'trigger', action: 'trigger',
automation_id: 'automation.morning_routine' automation_id: 'automation.morning_routine'
}); })) as TestResponse;
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.message).toBe('Successfully triggered automation automation.morning_routine'); expect(result.message).toBe('Successfully triggered automation automation.morning_routine');
@@ -623,9 +709,9 @@ describe('Home Assistant MCP Server', () => {
const addToolCalls = liteMcpInstance.addTool.mock.calls; const addToolCalls = liteMcpInstance.addTool.mock.calls;
const automationTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'automation')?.[0] as Tool; const automationTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'automation')?.[0] as Tool;
const result = await automationTool.execute({ const result = (await automationTool.execute({
action: 'toggle' action: 'toggle'
}); })) as TestResponse;
expect(result.success).toBe(false); expect(result.success).toBe(false);
expect(result.message).toBe('Automation ID is required for toggle and trigger actions'); expect(result.message).toBe('Automation ID is required for toggle and trigger actions');
@@ -668,9 +754,9 @@ describe('Home Assistant MCP Server', () => {
const addToolCalls = liteMcpInstance.addTool.mock.calls; const addToolCalls = liteMcpInstance.addTool.mock.calls;
const addonTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'addon')?.[0] as Tool; const addonTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'addon')?.[0] as Tool;
const result = await addonTool.execute({ const result = (await addonTool.execute({
action: 'list' action: 'list'
}); })) as TestResponse;
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.addons).toEqual(mockAddons.data.addons); expect(result.addons).toEqual(mockAddons.data.addons);
@@ -686,11 +772,11 @@ describe('Home Assistant MCP Server', () => {
const addToolCalls = liteMcpInstance.addTool.mock.calls; const addToolCalls = liteMcpInstance.addTool.mock.calls;
const addonTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'addon')?.[0] as Tool; const addonTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'addon')?.[0] as Tool;
const result = await addonTool.execute({ const result = (await addonTool.execute({
action: 'install', action: 'install',
slug: 'core_configurator', slug: 'core_configurator',
version: '5.6.0' version: '5.6.0'
}); })) as TestResponse;
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.message).toBe('Successfully installed add-on core_configurator'); expect(result.message).toBe('Successfully installed add-on core_configurator');
@@ -735,10 +821,10 @@ describe('Home Assistant MCP Server', () => {
const addToolCalls = liteMcpInstance.addTool.mock.calls; const addToolCalls = liteMcpInstance.addTool.mock.calls;
const packageTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'package')?.[0] as Tool; const packageTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'package')?.[0] as Tool;
const result = await packageTool.execute({ const result = (await packageTool.execute({
action: 'list', action: 'list',
category: 'integration' category: 'integration'
}); })) as TestResponse;
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.packages).toEqual(mockPackages.repositories); expect(result.packages).toEqual(mockPackages.repositories);
@@ -754,12 +840,12 @@ describe('Home Assistant MCP Server', () => {
const addToolCalls = liteMcpInstance.addTool.mock.calls; const addToolCalls = liteMcpInstance.addTool.mock.calls;
const packageTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'package')?.[0] as Tool; const packageTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'package')?.[0] as Tool;
const result = await packageTool.execute({ const result = (await packageTool.execute({
action: 'install', action: 'install',
category: 'integration', category: 'integration',
repository: 'hacs/integration', repository: 'hacs/integration',
version: '1.32.0' version: '1.32.0'
}); })) as TestResponse;
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.message).toBe('Successfully installed package hacs/integration'); expect(result.message).toBe('Successfully installed package hacs/integration');
@@ -814,10 +900,10 @@ describe('Home Assistant MCP Server', () => {
const addToolCalls = liteMcpInstance.addTool.mock.calls; const addToolCalls = liteMcpInstance.addTool.mock.calls;
const automationConfigTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'automation_config')?.[0] as Tool; const automationConfigTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'automation_config')?.[0] as Tool;
const result = await automationConfigTool.execute({ const result = (await automationConfigTool.execute({
action: 'create', action: 'create',
config: mockAutomationConfig config: mockAutomationConfig
}); })) as TestResponse;
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.message).toBe('Successfully created automation'); expect(result.message).toBe('Successfully created automation');
@@ -853,10 +939,10 @@ describe('Home Assistant MCP Server', () => {
const addToolCalls = liteMcpInstance.addTool.mock.calls; const addToolCalls = liteMcpInstance.addTool.mock.calls;
const automationConfigTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'automation_config')?.[0] as Tool; const automationConfigTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'automation_config')?.[0] as Tool;
const result = await automationConfigTool.execute({ const result = (await automationConfigTool.execute({
action: 'duplicate', action: 'duplicate',
automation_id: 'automation.test' automation_id: 'automation.test'
}); })) as TestResponse;
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect(result.message).toBe('Successfully duplicated automation automation.test'); expect(result.message).toBe('Successfully duplicated automation automation.test');
@@ -887,9 +973,9 @@ describe('Home Assistant MCP Server', () => {
const addToolCalls = liteMcpInstance.addTool.mock.calls; const addToolCalls = liteMcpInstance.addTool.mock.calls;
const automationConfigTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'automation_config')?.[0] as Tool; const automationConfigTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'automation_config')?.[0] as Tool;
const result = await automationConfigTool.execute({ const result = (await automationConfigTool.execute({
action: 'create' action: 'create'
}); })) as TestResponse;
expect(result.success).toBe(false); expect(result.success).toBe(false);
expect(result.message).toBe('Configuration is required for creating automation'); expect(result.message).toBe('Configuration is required for creating automation');
@@ -900,10 +986,10 @@ describe('Home Assistant MCP Server', () => {
const addToolCalls = liteMcpInstance.addTool.mock.calls; const addToolCalls = liteMcpInstance.addTool.mock.calls;
const automationConfigTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'automation_config')?.[0] as Tool; const automationConfigTool = addToolCalls.find((call: { 0: Tool }) => call[0].name === 'automation_config')?.[0] as Tool;
const result = await automationConfigTool.execute({ const result = (await automationConfigTool.execute({
action: 'update', action: 'update',
config: mockAutomationConfig config: mockAutomationConfig
}); })) as TestResponse;
expect(result.success).toBe(false); expect(result.success).toBe(false);
expect(result.message).toBe('Automation ID and configuration are required for updating automation'); expect(result.message).toBe('Automation ID and configuration are required for updating automation');

View File

@@ -1,5 +1,4 @@
import { PerformanceMonitor, PerformanceOptimizer, Metric } from '../../src/performance/index.js'; import { PerformanceMonitor, PerformanceOptimizer, Metric } from '../../src/performance/index.js';
import type { MemoryUsage } from 'node:process';
describe('Performance Module', () => { describe('Performance Module', () => {
describe('PerformanceMonitor', () => { describe('PerformanceMonitor', () => {
@@ -165,20 +164,27 @@ describe('Performance Module', () => {
global.gc = jest.fn(); global.gc = jest.fn();
const memoryUsage = process.memoryUsage; const memoryUsage = process.memoryUsage;
process.memoryUsage = jest.fn().mockImplementation((): MemoryUsage => ({ const mockMemoryUsage = () => ({
heapUsed: 900, heapUsed: 900,
heapTotal: 1000, heapTotal: 1000,
rss: 2000, rss: 2000,
external: 0, external: 0,
arrayBuffers: 0 arrayBuffers: 0
})); });
Object.defineProperty(process, 'memoryUsage', {
value: mockMemoryUsage,
writable: true
});
await PerformanceOptimizer.optimizeMemory(); await PerformanceOptimizer.optimizeMemory();
expect(global.gc).toHaveBeenCalled(); expect(global.gc).toHaveBeenCalled();
// Cleanup // Cleanup
process.memoryUsage = memoryUsage; Object.defineProperty(process, 'memoryUsage', {
value: memoryUsage,
writable: true
});
if (originalGc) { if (originalGc) {
global.gc = originalGc; global.gc = originalGc;
} else { } else {

View File

@@ -23,30 +23,30 @@
<div class='clearfix'> <div class='clearfix'>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">45.71% </span> <span class="strong">37.98% </span>
<span class="quiet">Statements</span> <span class="quiet">Statements</span>
<span class='fraction'>128/280</span> <span class='fraction'>128/337</span>
</div> </div>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">40.19% </span> <span class="strong">33.06% </span>
<span class="quiet">Branches</span> <span class="quiet">Branches</span>
<span class='fraction'>41/102</span> <span class='fraction'>41/124</span>
</div> </div>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">40.74% </span> <span class="strong">37.5% </span>
<span class="quiet">Functions</span> <span class="quiet">Functions</span>
<span class='fraction'>33/81</span> <span class='fraction'>33/88</span>
</div> </div>
<div class='fl pad1y space-right2'> <div class='fl pad1y space-right2'>
<span class="strong">46.29% </span> <span class="strong">38.34% </span>
<span class="quiet">Lines</span> <span class="quiet">Lines</span>
<span class='fraction'>125/270</span> <span class='fraction'>125/326</span>
</div> </div>
@@ -153,6 +153,21 @@
<td data-value="60" class="abs low">0/60</td> <td data-value="60" class="abs low">0/60</td>
</tr> </tr>
<tr>
<td class="file low" data-value="src/security"><a href="src/security/index.html">src/security</a></td>
<td data-value="0" class="pic low">
<div class="chart"><div class="cover-fill" style="width: 0%"></div><div class="cover-empty" style="width: 100%"></div></div>
</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="57" class="abs low">0/57</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="22" class="abs low">0/22</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="7" class="abs low">0/7</td>
<td data-value="0" class="pct low">0%</td>
<td data-value="56" class="abs low">0/56</td>
</tr>
<tr> <tr>
<td class="file low" data-value="src/websocket"><a href="src/websocket/index.html">src/websocket</a></td> <td class="file low" data-value="src/websocket"><a href="src/websocket/index.html">src/websocket</a></td>
<td data-value="0" class="pic low"> <td data-value="0" class="pic low">
@@ -176,7 +191,7 @@
<div class='footer quiet pad2 space-top1 center small'> <div class='footer quiet pad2 space-top1 center small'>
Code coverage generated by Code coverage generated by
<a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a> <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
at 2025-01-30T08:26:17.384Z at 2025-01-30T08:31:24.499Z
</div> </div>
<script src="prettify.js"></script> <script src="prettify.js"></script>
<script> <script>

View File

@@ -435,6 +435,107 @@ BRF:22
BRH:0 BRH:0
end_of_record end_of_record
TN: TN:
SF:src/security/index.ts
FN:57,(anonymous_0)
FN:67,(anonymous_1)
FN:84,(anonymous_2)
FN:103,(anonymous_3)
FN:131,validateRequest
FN:158,sanitizeInput
FN:169,errorHandler
FNF:7
FNH:0
FNDA:0,(anonymous_0)
FNDA:0,(anonymous_1)
FNDA:0,(anonymous_2)
FNDA:0,(anonymous_3)
FNDA:0,validateRequest
FNDA:0,sanitizeInput
FNDA:0,errorHandler
DA:7,0
DA:8,0
DA:9,0
DA:12,0
DA:19,0
DA:45,0
DA:49,0
DA:50,0
DA:51,0
DA:52,0
DA:53,0
DA:54,0
DA:55,0
DA:58,0
DA:68,0
DA:69,0
DA:70,0
DA:71,0
DA:75,0
DA:79,0
DA:81,0
DA:85,0
DA:86,0
DA:87,0
DA:88,0
DA:92,0
DA:93,0
DA:95,0
DA:98,0
DA:100,0
DA:104,0
DA:106,0
DA:108,0
DA:109,0
DA:113,0
DA:114,0
DA:115,0
DA:118,0
DA:119,0
DA:123,0
DA:125,0
DA:133,0
DA:134,0
DA:140,0
DA:141,0
DA:142,0
DA:148,0
DA:149,0
DA:154,0
DA:159,0
DA:160,0
DA:163,0
DA:165,0
DA:170,0
DA:171,0
DA:178,0
LF:56
LH:0
BRDA:26,0,0,0
BRDA:26,0,1,0
BRDA:104,1,0,0
BRDA:108,2,0,0
BRDA:118,3,0,0
BRDA:118,4,0,0
BRDA:118,4,1,0
BRDA:133,5,0,0
BRDA:133,6,0,0
BRDA:133,6,1,0
BRDA:141,7,0,0
BRDA:141,8,0,0
BRDA:141,8,1,0
BRDA:148,9,0,0
BRDA:148,10,0,0
BRDA:148,10,1,0
BRDA:148,10,2,0
BRDA:159,11,0,0
BRDA:159,12,0,0
BRDA:159,12,1,0
BRDA:173,13,0,0
BRDA:173,13,1,0
BRF:22
BRH:0
end_of_record
TN:
SF:src/websocket/client.ts SF:src/websocket/client.ts
FN:13,(anonymous_0) FN:13,(anonymous_0)
FN:27,(anonymous_1) FN:27,(anonymous_1)

View File

@@ -23,7 +23,10 @@
"@digital-alchemy/hass": "^24.11.4", "@digital-alchemy/hass": "^24.11.4",
"ajv": "^8.17.1", "ajv": "^8.17.1",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"express-rate-limit": "^7.5.0",
"helmet": "^8.0.0",
"litemcp": "^0.7.0", "litemcp": "^0.7.0",
"ws": "^8.18.0",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -15,8 +15,8 @@ export const rateLimiter = rateLimit({
message: 'Too many requests from this IP, please try again later' message: 'Too many requests from this IP, please try again later'
}); });
// Security headers middleware // Security configuration
export const securityHeaders = helmet({ const helmetConfig = {
contentSecurityPolicy: { contentSecurityPolicy: {
directives: { directives: {
defaultSrc: ["'self'"], defaultSrc: ["'self'"],
@@ -27,9 +27,6 @@ export const securityHeaders = helmet({
upgradeInsecureRequests: true upgradeInsecureRequests: true
} }
}, },
crossOriginEmbedderPolicy: true,
crossOriginOpenerPolicy: true,
crossOriginResourcePolicy: { policy: 'same-site' },
dnsPrefetchControl: true, dnsPrefetchControl: true,
frameguard: { frameguard: {
action: 'deny' action: 'deny'
@@ -40,11 +37,12 @@ export const securityHeaders = helmet({
includeSubDomains: true, includeSubDomains: true,
preload: true preload: true
}, },
ieNoOpen: true,
noSniff: true, noSniff: true,
referrerPolicy: { policy: 'no-referrer' }, referrerPolicy: { policy: 'no-referrer' }
xssFilter: true };
});
// Security headers middleware
export const securityHeaders = helmet(helmetConfig);
// Token validation and encryption // Token validation and encryption
export class TokenManager { export class TokenManager {
@@ -178,7 +176,7 @@ export function errorHandler(err: Error, req: Request, res: Response, next: Next
// Export security middleware chain // Export security middleware chain
export const securityMiddleware = [ export const securityMiddleware = [
helmet(), helmet(helmetConfig),
rateLimit({ rateLimit({
windowMs: 15 * 60 * 1000, windowMs: 15 * 60 * 1000,
max: 100 max: 100