refactor: update TypeScript configuration and test utilities for improved type safety

- Modify tsconfig.json to relax strict type checking for gradual migration
- Update test files to use more flexible type checking and mocking
- Add type-safe mock and test utility functions
- Improve error handling and type inference in test suites
- Export Tool interface and tools list for better testing support
This commit is contained in:
jango-blockchained
2025-02-05 09:16:21 +01:00
parent eefbf790c3
commit 4e9ebbbc2c
21 changed files with 526 additions and 532 deletions

View File

@@ -54,8 +54,8 @@ interface MockWebSocketConstructor extends jest.Mock<MockWebSocketInstance> {
}
// Mock the entire hass module
jest.mock('../../src/hass/index.js', () => ({
get_hass: jest.fn()
// // jest.mock('../../src/hass/index.js', () => ({
get_hass: mock()
}));
describe('Home Assistant API', () => {
@@ -66,11 +66,11 @@ describe('Home Assistant API', () => {
beforeEach(() => {
hass = new HassInstanceImpl('http://localhost:8123', 'test_token');
mockWs = {
send: jest.fn(),
close: jest.fn(),
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
send: mock(),
close: mock(),
addEventListener: mock(),
removeEventListener: mock(),
dispatchEvent: mock(),
onopen: null,
onclose: null,
onmessage: null,
@@ -84,7 +84,7 @@ describe('Home Assistant API', () => {
} as MockWebSocketInstance;
// Create a mock WebSocket constructor
MockWebSocket = jest.fn().mockImplementation(() => mockWs) as MockWebSocketConstructor;
MockWebSocket = mock().mockImplementation(() => mockWs) as MockWebSocketConstructor;
MockWebSocket.CONNECTING = 0;
MockWebSocket.OPEN = 1;
MockWebSocket.CLOSING = 2;
@@ -96,7 +96,7 @@ describe('Home Assistant API', () => {
});
describe('State Management', () => {
it('should fetch all states', async () => {
test('should fetch all states', async () => {
const mockStates: HomeAssistant.Entity[] = [
{
entity_id: 'light.living_room',
@@ -108,7 +108,7 @@ describe('Home Assistant API', () => {
}
];
global.fetch = jest.fn().mockResolvedValueOnce({
global.fetch = mock().mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(mockStates)
});
@@ -121,7 +121,7 @@ describe('Home Assistant API', () => {
);
});
it('should fetch single state', async () => {
test('should fetch single state', async () => {
const mockState: HomeAssistant.Entity = {
entity_id: 'light.living_room',
state: 'on',
@@ -131,7 +131,7 @@ describe('Home Assistant API', () => {
context: { id: '123', parent_id: null, user_id: null }
};
global.fetch = jest.fn().mockResolvedValueOnce({
global.fetch = mock().mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve(mockState)
});
@@ -144,16 +144,16 @@ describe('Home Assistant API', () => {
);
});
it('should handle state fetch errors', async () => {
global.fetch = jest.fn().mockRejectedValueOnce(new Error('Failed to fetch states'));
test('should handle state fetch errors', async () => {
global.fetch = mock().mockRejectedValueOnce(new Error('Failed to fetch states'));
await expect(hass.fetchStates()).rejects.toThrow('Failed to fetch states');
});
});
describe('Service Calls', () => {
it('should call service', async () => {
global.fetch = jest.fn().mockResolvedValueOnce({
test('should call service', async () => {
global.fetch = mock().mockResolvedValueOnce({
ok: true,
json: () => Promise.resolve({})
});
@@ -175,8 +175,8 @@ describe('Home Assistant API', () => {
);
});
it('should handle service call errors', async () => {
global.fetch = jest.fn().mockRejectedValueOnce(new Error('Service call failed'));
test('should handle service call errors', async () => {
global.fetch = mock().mockRejectedValueOnce(new Error('Service call failed'));
await expect(
hass.callService('invalid_domain', 'invalid_service', {})
@@ -185,8 +185,8 @@ describe('Home Assistant API', () => {
});
describe('Event Subscription', () => {
it('should subscribe to events', async () => {
const callback = jest.fn();
test('should subscribe to events', async () => {
const callback = mock();
await hass.subscribeEvents(callback, 'state_changed');
expect(MockWebSocket).toHaveBeenCalledWith(
@@ -194,8 +194,8 @@ describe('Home Assistant API', () => {
);
});
it('should handle subscription errors', async () => {
const callback = jest.fn();
test('should handle subscription errors', async () => {
const callback = mock();
MockWebSocket.mockImplementation(() => {
throw new Error('WebSocket connection failed');
});
@@ -207,14 +207,14 @@ describe('Home Assistant API', () => {
});
describe('WebSocket connection', () => {
it('should connect to WebSocket endpoint', async () => {
test('should connect to WebSocket endpoint', async () => {
await hass.subscribeEvents(() => { });
expect(MockWebSocket).toHaveBeenCalledWith(
'ws://localhost:8123/api/websocket'
);
});
it('should handle connection errors', async () => {
test('should handle connection errors', async () => {
MockWebSocket.mockImplementation(() => {
throw new Error('Connection failed');
});

View File

@@ -40,7 +40,7 @@ jest.unstable_mockModule('@digital-alchemy/core', () => ({
bootstrap: async () => mockInstance,
services: {}
})),
TServiceParams: jest.fn()
TServiceParams: mock()
}));
jest.unstable_mockModule('@digital-alchemy/hass', () => ({
@@ -78,7 +78,7 @@ describe('Home Assistant Connection', () => {
process.env = originalEnv;
});
it('should return a Home Assistant instance with services', async () => {
test('should return a Home Assistant instance with services', async () => {
const { get_hass } = await import('../../src/hass/index.js');
const hass = await get_hass();
@@ -89,7 +89,7 @@ describe('Home Assistant Connection', () => {
expect(typeof hass.services.climate.set_temperature).toBe('function');
});
it('should reuse the same instance on subsequent calls', async () => {
test('should reuse the same instance on subsequent calls', async () => {
const { get_hass } = await import('../../src/hass/index.js');
const firstInstance = await get_hass();
const secondInstance = await get_hass();

View File

@@ -44,19 +44,19 @@ const mockWebSocket: WebSocketMock = {
close: jest.fn<WebSocketCloseHandler>(),
readyState: 1,
OPEN: 1,
removeAllListeners: jest.fn()
removeAllListeners: mock()
};
jest.mock('ws', () => ({
WebSocket: jest.fn().mockImplementation(() => mockWebSocket)
// // jest.mock('ws', () => ({
WebSocket: mock().mockImplementation(() => mockWebSocket)
}));
// Mock fetch globally
const mockFetch = jest.fn() as jest.MockedFunction<typeof fetch>;
const mockFetch = mock() as jest.MockedFunction<typeof fetch>;
global.fetch = mockFetch;
// Mock get_hass
jest.mock('../../src/hass/index.js', () => {
// // jest.mock('../../src/hass/index.js', () => {
let instance: TestHassInstance | null = null;
const actual = jest.requireActual<typeof import('../../src/hass/index.js')>('../../src/hass/index.js');
return {
@@ -85,12 +85,12 @@ describe('Home Assistant Integration', () => {
jest.clearAllMocks();
});
it('should create a WebSocket client with the provided URL and token', () => {
test('should create a WebSocket client with the provided URL and token', () => {
expect(client).toBeInstanceOf(EventEmitter);
expect(jest.mocked(WebSocket)).toHaveBeenCalledWith(mockUrl);
expect(// // jest.mocked(WebSocket)).toHaveBeenCalledWith(mockUrl);
});
it('should connect and authenticate successfully', async () => {
test('should connect and authenticate successfully', async () => {
const connectPromise = client.connect();
// Get and call the open callback
@@ -114,7 +114,7 @@ describe('Home Assistant Integration', () => {
await connectPromise;
});
it('should handle authentication failure', async () => {
test('should handle authentication failure', async () => {
const connectPromise = client.connect();
// Get and call the open callback
@@ -130,7 +130,7 @@ describe('Home Assistant Integration', () => {
await expect(connectPromise).rejects.toThrow();
});
it('should handle connection errors', async () => {
test('should handle connection errors', async () => {
const connectPromise = client.connect();
// Get and call the error callback
@@ -141,7 +141,7 @@ describe('Home Assistant Integration', () => {
await expect(connectPromise).rejects.toThrow('Connection failed');
});
it('should handle message parsing errors', async () => {
test('should handle message parsing errors', async () => {
const connectPromise = client.connect();
// Get and call the open callback
@@ -198,12 +198,12 @@ describe('Home Assistant Integration', () => {
});
});
it('should create instance with correct properties', () => {
test('should create instance with correct properties', () => {
expect(instance['baseUrl']).toBe(mockBaseUrl);
expect(instance['token']).toBe(mockToken);
});
it('should fetch states', async () => {
test('should fetch states', async () => {
const states = await instance.fetchStates();
expect(states).toEqual([mockState]);
expect(mockFetch).toHaveBeenCalledWith(
@@ -216,7 +216,7 @@ describe('Home Assistant Integration', () => {
);
});
it('should fetch single state', async () => {
test('should fetch single state', async () => {
const state = await instance.fetchState('light.test');
expect(state).toEqual(mockState);
expect(mockFetch).toHaveBeenCalledWith(
@@ -229,7 +229,7 @@ describe('Home Assistant Integration', () => {
);
});
it('should call service', async () => {
test('should call service', async () => {
await instance.callService('light', 'turn_on', { entity_id: 'light.test' });
expect(mockFetch).toHaveBeenCalledWith(
`${mockBaseUrl}/api/services/light/turn_on`,
@@ -244,17 +244,17 @@ describe('Home Assistant Integration', () => {
);
});
it('should handle fetch errors', async () => {
test('should handle fetch errors', async () => {
mockFetch.mockRejectedValueOnce(new Error('Network error'));
await expect(instance.fetchStates()).rejects.toThrow('Network error');
});
it('should handle invalid JSON responses', async () => {
test('should handle invalid JSON responses', async () => {
mockFetch.mockResolvedValueOnce(new Response('invalid json'));
await expect(instance.fetchStates()).rejects.toThrow();
});
it('should handle non-200 responses', async () => {
test('should handle non-200 responses', async () => {
mockFetch.mockResolvedValueOnce(new Response('Error', { status: 500 }));
await expect(instance.fetchStates()).rejects.toThrow();
});
@@ -263,15 +263,15 @@ describe('Home Assistant Integration', () => {
let eventCallback: (event: HassEvent) => void;
beforeEach(() => {
eventCallback = jest.fn();
eventCallback = mock();
});
it('should subscribe to events', async () => {
test('should subscribe to events', async () => {
const subscriptionId = await instance.subscribeEvents(eventCallback);
expect(typeof subscriptionId).toBe('number');
});
it('should unsubscribe from events', async () => {
test('should unsubscribe from events', async () => {
const subscriptionId = await instance.subscribeEvents(eventCallback);
await instance.unsubscribeEvents(subscriptionId);
});
@@ -309,19 +309,19 @@ describe('Home Assistant Integration', () => {
process.env = originalEnv;
});
it('should create instance with default configuration', async () => {
test('should create instance with default configuration', async () => {
const instance = await get_hass() as TestHassInstance;
expect(instance._baseUrl).toBe('http://localhost:8123');
expect(instance._token).toBe('test_token');
});
it('should reuse existing instance', async () => {
test('should reuse existing instance', async () => {
const instance1 = await get_hass();
const instance2 = await get_hass();
expect(instance1).toBe(instance2);
});
it('should use custom configuration', async () => {
test('should use custom configuration', async () => {
process.env.HASS_HOST = 'https://hass.example.com';
process.env.HASS_TOKEN = 'prod_token';
const instance = await get_hass() as TestHassInstance;