@coffeerunhobby/mcp-ha-connect 2.2.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +1 -1
- package/README.md +24 -2
- package/dist/omadaClient/accessControl.d.ts +290 -0
- package/dist/omadaClient/accessControl.js +509 -0
- package/dist/omadaClient/accessControl.js.map +1 -0
- package/dist/omadaClient/client.d.ts +6 -1
- package/dist/omadaClient/client.js +12 -1
- package/dist/omadaClient/client.js.map +1 -1
- package/dist/omadaClient/diagnose.d.ts +68 -0
- package/dist/omadaClient/diagnose.js +164 -0
- package/dist/omadaClient/diagnose.js.map +1 -0
- package/dist/omadaClient/index.d.ts +9 -0
- package/dist/omadaClient/index.js +19 -0
- package/dist/omadaClient/index.js.map +1 -1
- package/dist/omadaClient/macGroups.d.ts +58 -0
- package/dist/omadaClient/macGroups.js +110 -0
- package/dist/omadaClient/macGroups.js.map +1 -0
- package/dist/omadaClient/serialize.d.ts +13 -0
- package/dist/omadaClient/serialize.js +29 -0
- package/dist/omadaClient/serialize.js.map +1 -0
- package/dist/omadaClient/timeRange.d.ts +41 -0
- package/dist/omadaClient/timeRange.js +109 -0
- package/dist/omadaClient/timeRange.js.map +1 -0
- package/dist/tools/infra/actions.d.ts +1 -1
- package/dist/tools/infra/actions.js +1 -1
- package/dist/tools/omada/accessControl.d.ts +151 -0
- package/dist/tools/omada/accessControl.js +158 -0
- package/dist/tools/omada/accessControl.js.map +1 -0
- package/dist/tools/omada/index.js +9 -0
- package/dist/tools/omada/index.js.map +1 -1
- package/dist/tools/omada/macGroups.d.ts +19 -0
- package/dist/tools/omada/macGroups.js +77 -0
- package/dist/tools/omada/macGroups.js.map +1 -0
- package/dist/tools/omada/namespace.js +16 -0
- package/dist/tools/omada/namespace.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -1
- package/.codex/review-rules.md +0 -16
- package/.dockerignore +0 -8
- package/.gitattributes +0 -30
- package/.github/workflows/ci.yml +0 -193
- package/AGENTS.md +0 -389
- package/Dockerfile +0 -50
- package/docker-compose.yml +0 -23
- package/docs/CHANGELOG.md +0 -341
- package/docs/COVERAGE.md +0 -219
- package/docs/QUICK_START.md +0 -240
- package/eslint.config.js +0 -33
- package/n8n/ai-agent-safety-monitor.json +0 -355
- package/openapi.json +0 -1259
- package/scripts/check-version-sync.mjs +0 -69
- package/scripts/generate-jwt.ts +0 -121
- package/src/config.ts +0 -440
- package/src/haClient/automations.ts +0 -261
- package/src/haClient/calendars.ts +0 -94
- package/src/haClient/config.ts +0 -52
- package/src/haClient/devices.ts +0 -653
- package/src/haClient/entities.ts +0 -128
- package/src/haClient/events.ts +0 -689
- package/src/haClient/history.ts +0 -73
- package/src/haClient/index.ts +0 -336
- package/src/haClient/request.ts +0 -142
- package/src/haClient/services.ts +0 -45
- package/src/haClient/states.ts +0 -60
- package/src/haClient/updates.ts +0 -58
- package/src/haClient/wsCommand.ts +0 -44
- package/src/index.ts +0 -164
- package/src/localAI/index.ts +0 -88
- package/src/localAI/prompts.ts +0 -50
- package/src/localAI/providers/base.ts +0 -120
- package/src/localAI/providers/ollama.ts +0 -78
- package/src/localAI/providers/openai.ts +0 -86
- package/src/localAI/types.ts +0 -67
- package/src/omadaClient/auth.ts +0 -178
- package/src/omadaClient/client.ts +0 -337
- package/src/omadaClient/device.ts +0 -118
- package/src/omadaClient/index.ts +0 -310
- package/src/omadaClient/network.ts +0 -284
- package/src/omadaClient/request.ts +0 -365
- package/src/omadaClient/security.ts +0 -51
- package/src/omadaClient/site.ts +0 -344
- package/src/ownerQuestions/service.ts +0 -536
- package/src/permissions/index.ts +0 -143
- package/src/resources/index.ts +0 -213
- package/src/server/auth.ts +0 -175
- package/src/server/chatFace.ts +0 -789
- package/src/server/chatSlice.ts +0 -57
- package/src/server/common.ts +0 -170
- package/src/server/eventSubscription.ts +0 -343
- package/src/server/http.ts +0 -626
- package/src/server/instructions.ts +0 -97
- package/src/server/rateLimiter.ts +0 -227
- package/src/server/stdio.ts +0 -41
- package/src/server/stream.ts +0 -205
- package/src/tools/ai/analyzeSensors.ts +0 -33
- package/src/tools/ai/index.ts +0 -20
- package/src/tools/common.ts +0 -442
- package/src/tools/homeassistant/askOwner.ts +0 -133
- package/src/tools/homeassistant/automationControls.ts +0 -66
- package/src/tools/homeassistant/calendar.ts +0 -97
- package/src/tools/homeassistant/callService.ts +0 -25
- package/src/tools/homeassistant/controlClimate.ts +0 -68
- package/src/tools/homeassistant/controlCover.ts +0 -48
- package/src/tools/homeassistant/controlFan.ts +0 -44
- package/src/tools/homeassistant/controlLight.ts +0 -38
- package/src/tools/homeassistant/controlMediaPlayer.ts +0 -50
- package/src/tools/homeassistant/createAutomation.ts +0 -35
- package/src/tools/homeassistant/deleteAutomation.ts +0 -24
- package/src/tools/homeassistant/entityAction.ts +0 -35
- package/src/tools/homeassistant/entityGuard.ts +0 -28
- package/src/tools/homeassistant/getAllSensors.ts +0 -46
- package/src/tools/homeassistant/getAutomationTrace.ts +0 -30
- package/src/tools/homeassistant/getDomainSummary.ts +0 -29
- package/src/tools/homeassistant/getEntitiesByDomain.ts +0 -56
- package/src/tools/homeassistant/getHistory.ts +0 -24
- package/src/tools/homeassistant/getState.ts +0 -24
- package/src/tools/homeassistant/getStates.ts +0 -46
- package/src/tools/homeassistant/getVersion.ts +0 -21
- package/src/tools/homeassistant/index.ts +0 -121
- package/src/tools/homeassistant/listAutomations.ts +0 -35
- package/src/tools/homeassistant/listEntities.ts +0 -44
- package/src/tools/homeassistant/listPersons.ts +0 -46
- package/src/tools/homeassistant/sceneAndScript.ts +0 -53
- package/src/tools/homeassistant/searchEntities.ts +0 -58
- package/src/tools/homeassistant/sendNotification.ts +0 -179
- package/src/tools/homeassistant/systemTools.ts +0 -52
- package/src/tools/homeassistant/triggerAutomation.ts +0 -24
- package/src/tools/index.ts +0 -78
- package/src/tools/infra/actions.ts +0 -95
- package/src/tools/infra/index.ts +0 -138
- package/src/tools/omada/blockClient.ts +0 -28
- package/src/tools/omada/cyclePoePort.ts +0 -32
- package/src/tools/omada/deleteClient.ts +0 -27
- package/src/tools/omada/disableClientRateLimit.ts +0 -23
- package/src/tools/omada/getClient.ts +0 -14
- package/src/tools/omada/getDevice.ts +0 -14
- package/src/tools/omada/getFirewallSetting.ts +0 -14
- package/src/tools/omada/getInternetInfo.ts +0 -14
- package/src/tools/omada/getLanNetworkList.ts +0 -14
- package/src/tools/omada/getLanProfileList.ts +0 -14
- package/src/tools/omada/getPortForwardingStatus.ts +0 -26
- package/src/tools/omada/getRateLimitProfiles.ts +0 -14
- package/src/tools/omada/getSiteNtpStatus.ts +0 -14
- package/src/tools/omada/getSsidDetail.ts +0 -22
- package/src/tools/omada/getSsidList.ts +0 -21
- package/src/tools/omada/getSwitchStackDetail.ts +0 -14
- package/src/tools/omada/getThreatList.ts +0 -42
- package/src/tools/omada/getWlanGroupList.ts +0 -14
- package/src/tools/omada/graph.ts +0 -187
- package/src/tools/omada/index.ts +0 -153
- package/src/tools/omada/listBlockedClients.ts +0 -17
- package/src/tools/omada/listClients.ts +0 -14
- package/src/tools/omada/listClientsActivity.ts +0 -25
- package/src/tools/omada/listClientsPastConnections.ts +0 -44
- package/src/tools/omada/listDevices.ts +0 -14
- package/src/tools/omada/listDevicesStats.ts +0 -41
- package/src/tools/omada/listMostActiveClients.ts +0 -14
- package/src/tools/omada/listSites.ts +0 -18
- package/src/tools/omada/namespace.ts +0 -1164
- package/src/tools/omada/searchDevices.ts +0 -20
- package/src/tools/omada/setClientRateLimit.ts +0 -26
- package/src/tools/omada/setClientRateLimitProfile.ts +0 -25
- package/src/tools/omada/setSiteNtpServers.ts +0 -36
- package/src/tools/omada/setSsidEnabled.ts +0 -38
- package/src/tools/omada/unblockClient.ts +0 -26
- package/src/tools/types.ts +0 -11
- package/src/tools/utils/pagination-schema.ts +0 -27
- package/src/types/ai/index.ts +0 -11
- package/src/types/ai/types.ts +0 -67
- package/src/types/homeassistant/automations.ts +0 -43
- package/src/types/homeassistant/calendars.ts +0 -26
- package/src/types/homeassistant/config.ts +0 -14
- package/src/types/homeassistant/entities.ts +0 -14
- package/src/types/homeassistant/errors.ts +0 -33
- package/src/types/homeassistant/haConfig.ts +0 -7
- package/src/types/homeassistant/history.ts +0 -13
- package/src/types/homeassistant/index.ts +0 -46
- package/src/types/homeassistant/request.ts +0 -6
- package/src/types/homeassistant/serviceCall.ts +0 -18
- package/src/types/homeassistant/states.ts +0 -58
- package/src/types/homeassistant/updates.ts +0 -28
- package/src/types/index.ts +0 -12
- package/src/types/omada/activeClientInfo.ts +0 -17
- package/src/types/omada/clientActivity.ts +0 -32
- package/src/types/omada/clientBlockStatus.ts +0 -11
- package/src/types/omada/clientPastConnection.ts +0 -82
- package/src/types/omada/index.ts +0 -14
- package/src/types/omada/omadaApiResponse.ts +0 -5
- package/src/types/omada/omadaClientInfo.ts +0 -11
- package/src/types/omada/omadaDeviceInfo.ts +0 -11
- package/src/types/omada/omadaDeviceStats.ts +0 -31
- package/src/types/omada/omadaSiteSummary.ts +0 -5
- package/src/types/omada/oswStackDetail.ts +0 -11
- package/src/types/omada/paginatedResult.ts +0 -6
- package/src/types/omada/rateLimitProfile.ts +0 -58
- package/src/types/omada/threatInfo.ts +0 -38
- package/src/types/omada/tokenResult.ts +0 -6
- package/src/types.ts +0 -2
- package/src/utils/config-validations.ts +0 -68
- package/src/utils/jwt.ts +0 -112
- package/src/utils/logger.ts +0 -121
- package/src/utils/plainText.ts +0 -48
- package/src/utils/redact.ts +0 -59
- package/src/utils/sanitizeError.ts +0 -18
- package/src/utils/tlsDispatcher.ts +0 -69
- package/src/version.ts +0 -5
- package/tests/bdd/automation-creation.spec.ts +0 -214
- package/tests/bdd/automation-management.spec.ts +0 -267
- package/tests/bdd/calendar.spec.ts +0 -190
- package/tests/bdd/configuration.spec.ts +0 -283
- package/tests/bdd/device-control.spec.ts +0 -344
- package/tests/bdd/entity-listing.spec.ts +0 -208
- package/tests/bdd/entity-search.spec.ts +0 -183
- package/tests/bdd/history-retrieval.spec.ts +0 -195
- package/tests/bdd/notifications.spec.ts +0 -238
- package/tests/bdd/service-calls.spec.ts +0 -144
- package/tests/bdd/state-retrieval.spec.ts +0 -204
- package/tests/bench/hotpaths.bench.ts +0 -68
- package/tests/config.test.ts +0 -355
- package/tests/features/automation-creation.feature +0 -36
- package/tests/features/automation-management.feature +0 -54
- package/tests/features/calendar.feature +0 -37
- package/tests/features/configuration.feature +0 -70
- package/tests/features/device-control.feature +0 -100
- package/tests/features/entity-listing.feature +0 -51
- package/tests/features/entity-search.feature +0 -44
- package/tests/features/history-retrieval.feature +0 -38
- package/tests/features/notifications.feature +0 -37
- package/tests/features/service-calls.feature +0 -35
- package/tests/features/state-retrieval.feature +0 -37
- package/tests/fixtures/openapi-default-slice.json +0 -290
- package/tests/haClient/automationTrace.test.ts +0 -59
- package/tests/haClient/automations.test.ts +0 -149
- package/tests/haClient/config.test.ts +0 -117
- package/tests/haClient/entities.test.ts +0 -188
- package/tests/haClient/events.test.ts +0 -455
- package/tests/haClient/eventsTls.test.ts +0 -52
- package/tests/haClient/history.test.ts +0 -228
- package/tests/haClient/index.test.ts +0 -243
- package/tests/haClient/request.test.ts +0 -217
- package/tests/haClient/services.test.ts +0 -120
- package/tests/haClient/states.test.ts +0 -119
- package/tests/haClient/updates.test.ts +0 -193
- package/tests/index.test.ts +0 -160
- package/tests/integration/homeassistant.test.ts +0 -144
- package/tests/integration/omada.test.ts +0 -167
- package/tests/integration/server.test.ts +0 -151
- package/tests/localAI/index.test.ts +0 -177
- package/tests/localAI/ollama.test.ts +0 -187
- package/tests/localAI/openai.test.ts +0 -257
- package/tests/omadaClient/actions.test.ts +0 -161
- package/tests/omadaClient/clientBlocks.test.ts +0 -170
- package/tests/omadaClient/ntp.test.ts +0 -381
- package/tests/omadaClient/site.test.ts +0 -37
- package/tests/ownerQuestions/service.test.ts +0 -499
- package/tests/ownerQuestions/tools.test.ts +0 -186
- package/tests/permissions/index.test.ts +0 -217
- package/tests/resources/index.test.ts +0 -288
- package/tests/scripts/generateJwt.test.ts +0 -44
- package/tests/security/accessControl.test.ts +0 -223
- package/tests/security/authentication.test.ts +0 -117
- package/tests/security/configuration.test.ts +0 -170
- package/tests/security/cryptoTransport.test.ts +0 -172
- package/tests/security/dependencies.test.ts +0 -152
- package/tests/security/injection.test.ts +0 -117
- package/tests/security/insecureDesign.test.ts +0 -121
- package/tests/security/logging.test.ts +0 -131
- package/tests/security/resourceConsumption.test.ts +0 -142
- package/tests/server/auth.test.ts +0 -249
- package/tests/server/chatFace.test.ts +0 -320
- package/tests/server/common.test.ts +0 -84
- package/tests/server/http.test.ts +0 -561
- package/tests/server/instructions.test.ts +0 -152
- package/tests/server/memoizeJsonSchema.test.ts +0 -71
- package/tests/server/stdio.test.ts +0 -99
- package/tests/server/stream.test.ts +0 -285
- package/tests/server/toolVisibility.http.test.ts +0 -111
- package/tests/server/toolVisibility.test.ts +0 -129
- package/tests/tools/ai/analyzeSensors.test.ts +0 -227
- package/tests/tools/common.test.ts +0 -123
- package/tests/tools/homeassistant/handlers.test.ts +0 -872
- package/tests/tools/infra/invokeAction.test.ts +0 -280
- package/tests/tools/omada/graph.test.ts +0 -385
- package/tests/tools/omada/handlers.test.ts +0 -897
- package/tests/tools/omada/namespace.test.ts +0 -483
- package/tests/tools/registry.test.ts +0 -168
- package/tests/utils/config-validations.test.ts +0 -352
- package/tests/utils/jwt.test.ts +0 -115
- package/tests/utils/logger.test.ts +0 -327
- package/tests/utils/redact.test.ts +0 -105
- package/tsconfig.json +0 -24
- package/vitest.config.ts +0 -38
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HistoryOperations tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
6
|
-
import { HistoryOperations } from '../../src/haClient/history.js';
|
|
7
|
-
import type { RequestHandler } from '../../src/haClient/request.js';
|
|
8
|
-
import type { Entity, LogbookEntry } from '../../src/types/index.js';
|
|
9
|
-
|
|
10
|
-
describe('HistoryOperations', () => {
|
|
11
|
-
let mockRequest: RequestHandler;
|
|
12
|
-
let historyOps: HistoryOperations;
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
mockRequest = {
|
|
16
|
-
get: vi.fn(),
|
|
17
|
-
} as unknown as RequestHandler;
|
|
18
|
-
|
|
19
|
-
historyOps = new HistoryOperations(mockRequest);
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe('getHistory', () => {
|
|
23
|
-
it('should fetch history for entity', async () => {
|
|
24
|
-
const mockHistory: Entity[][] = [
|
|
25
|
-
[
|
|
26
|
-
{
|
|
27
|
-
entity_id: 'sensor.temperature',
|
|
28
|
-
state: '20.0',
|
|
29
|
-
attributes: { unit_of_measurement: '°C' },
|
|
30
|
-
last_changed: '2026-01-08T08:00:00.000Z',
|
|
31
|
-
last_updated: '2026-01-08T08:00:00.000Z',
|
|
32
|
-
context: { id: 'ctx-1', parent_id: null, user_id: null },
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
entity_id: 'sensor.temperature',
|
|
36
|
-
state: '21.5',
|
|
37
|
-
attributes: { unit_of_measurement: '°C' },
|
|
38
|
-
last_changed: '2026-01-08T09:00:00.000Z',
|
|
39
|
-
last_updated: '2026-01-08T09:00:00.000Z',
|
|
40
|
-
context: { id: 'ctx-2', parent_id: null, user_id: null },
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
entity_id: 'sensor.temperature',
|
|
44
|
-
state: '22.5',
|
|
45
|
-
attributes: { unit_of_measurement: '°C' },
|
|
46
|
-
last_changed: '2026-01-08T10:00:00.000Z',
|
|
47
|
-
last_updated: '2026-01-08T10:00:00.000Z',
|
|
48
|
-
context: { id: 'ctx-3', parent_id: null, user_id: null },
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
|
-
];
|
|
52
|
-
|
|
53
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue(mockHistory);
|
|
54
|
-
|
|
55
|
-
const history = await historyOps.getHistory('sensor.temperature', 24);
|
|
56
|
-
|
|
57
|
-
expect(history).toEqual(mockHistory);
|
|
58
|
-
expect(mockRequest.get).toHaveBeenCalledWith(
|
|
59
|
-
expect.stringContaining('/history/period/'),
|
|
60
|
-
expect.objectContaining({
|
|
61
|
-
filter_entity_id: 'sensor.temperature',
|
|
62
|
-
end_time: expect.any(String),
|
|
63
|
-
})
|
|
64
|
-
);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it('should use default 24 hours', async () => {
|
|
68
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue([[]]);
|
|
69
|
-
|
|
70
|
-
await historyOps.getHistory('sensor.test');
|
|
71
|
-
|
|
72
|
-
expect(mockRequest.get).toHaveBeenCalled();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it('should handle custom hours parameter', async () => {
|
|
76
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue([[]]);
|
|
77
|
-
|
|
78
|
-
await historyOps.getHistory('sensor.test', 48);
|
|
79
|
-
|
|
80
|
-
expect(mockRequest.get).toHaveBeenCalled();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should return empty history', async () => {
|
|
84
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue([[]]);
|
|
85
|
-
|
|
86
|
-
const history = await historyOps.getHistory('sensor.nonexistent');
|
|
87
|
-
|
|
88
|
-
expect(history).toEqual([[]]);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
describe('getSystemLog', () => {
|
|
93
|
-
it('should fetch logbook entries', async () => {
|
|
94
|
-
const mockEntries: LogbookEntry[] = [
|
|
95
|
-
{
|
|
96
|
-
when: '2026-01-08T10:00:00.000Z',
|
|
97
|
-
name: 'Living Room Light',
|
|
98
|
-
entity_id: 'light.living_room',
|
|
99
|
-
state: 'on',
|
|
100
|
-
domain: 'light',
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
when: '2026-01-08T09:30:00.000Z',
|
|
104
|
-
name: 'Motion detected',
|
|
105
|
-
entity_id: 'binary_sensor.motion',
|
|
106
|
-
state: 'on',
|
|
107
|
-
domain: 'binary_sensor',
|
|
108
|
-
},
|
|
109
|
-
];
|
|
110
|
-
|
|
111
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue(mockEntries);
|
|
112
|
-
|
|
113
|
-
const entries = await historyOps.getSystemLog();
|
|
114
|
-
|
|
115
|
-
expect(entries).toEqual(mockEntries);
|
|
116
|
-
expect(mockRequest.get).toHaveBeenCalledWith(
|
|
117
|
-
expect.stringContaining('/logbook/'),
|
|
118
|
-
expect.objectContaining({
|
|
119
|
-
end_time: expect.any(String),
|
|
120
|
-
})
|
|
121
|
-
);
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should filter by entity_id', async () => {
|
|
125
|
-
const mockEntries: LogbookEntry[] = [
|
|
126
|
-
{
|
|
127
|
-
when: '2026-01-08T10:00:00.000Z',
|
|
128
|
-
name: 'Living Room Light',
|
|
129
|
-
entity_id: 'light.living_room',
|
|
130
|
-
state: 'on',
|
|
131
|
-
},
|
|
132
|
-
];
|
|
133
|
-
|
|
134
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue(mockEntries);
|
|
135
|
-
|
|
136
|
-
await historyOps.getSystemLog({ entity_id: 'light.living_room' });
|
|
137
|
-
|
|
138
|
-
expect(mockRequest.get).toHaveBeenCalledWith(
|
|
139
|
-
expect.any(String),
|
|
140
|
-
expect.objectContaining({
|
|
141
|
-
entity: 'light.living_room',
|
|
142
|
-
})
|
|
143
|
-
);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('should use custom hours parameter', async () => {
|
|
147
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
148
|
-
|
|
149
|
-
await historyOps.getSystemLog({ hours: 48 });
|
|
150
|
-
|
|
151
|
-
expect(mockRequest.get).toHaveBeenCalled();
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
it('should use default 24 hours', async () => {
|
|
155
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
156
|
-
|
|
157
|
-
await historyOps.getSystemLog();
|
|
158
|
-
|
|
159
|
-
expect(mockRequest.get).toHaveBeenCalled();
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('should return empty array when no logs', async () => {
|
|
163
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
164
|
-
|
|
165
|
-
const entries = await historyOps.getSystemLog();
|
|
166
|
-
|
|
167
|
-
expect(entries).toEqual([]);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('should apply limit to results', async () => {
|
|
171
|
-
// Create 150 mock entries
|
|
172
|
-
const mockEntries: LogbookEntry[] = Array.from({ length: 150 }, (_, i) => ({
|
|
173
|
-
when: `2026-01-08T${String(i % 24).padStart(2, '0')}:00:00.000Z`,
|
|
174
|
-
name: `Entity ${i}`,
|
|
175
|
-
entity_id: `sensor.test_${i}`,
|
|
176
|
-
state: 'on',
|
|
177
|
-
domain: 'sensor',
|
|
178
|
-
}));
|
|
179
|
-
|
|
180
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue(mockEntries);
|
|
181
|
-
|
|
182
|
-
// Default limit is 100
|
|
183
|
-
const entries = await historyOps.getSystemLog();
|
|
184
|
-
|
|
185
|
-
expect(entries).toHaveLength(100);
|
|
186
|
-
// Should return the LAST 100 entries (most recent)
|
|
187
|
-
expect(entries[0].entity_id).toBe('sensor.test_50');
|
|
188
|
-
expect(entries[99].entity_id).toBe('sensor.test_149');
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it('should respect custom limit', async () => {
|
|
192
|
-
const mockEntries: LogbookEntry[] = Array.from({ length: 50 }, (_, i) => ({
|
|
193
|
-
when: `2026-01-08T${String(i % 24).padStart(2, '0')}:00:00.000Z`,
|
|
194
|
-
name: `Entity ${i}`,
|
|
195
|
-
entity_id: `sensor.test_${i}`,
|
|
196
|
-
state: 'on',
|
|
197
|
-
domain: 'sensor',
|
|
198
|
-
}));
|
|
199
|
-
|
|
200
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue(mockEntries);
|
|
201
|
-
|
|
202
|
-
const entries = await historyOps.getSystemLog({ limit: 10 });
|
|
203
|
-
|
|
204
|
-
expect(entries).toHaveLength(10);
|
|
205
|
-
// Should return the LAST 10 entries
|
|
206
|
-
expect(entries[0].entity_id).toBe('sensor.test_40');
|
|
207
|
-
expect(entries[9].entity_id).toBe('sensor.test_49');
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it('should cap hours at 168 (1 week)', async () => {
|
|
211
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue([]);
|
|
212
|
-
|
|
213
|
-
await historyOps.getSystemLog({ hours: 500 });
|
|
214
|
-
|
|
215
|
-
// Verify that the start time is capped at 168 hours ago
|
|
216
|
-
const call = (mockRequest.get as ReturnType<typeof vi.fn>).mock.calls[0];
|
|
217
|
-
const path = call[0] as string;
|
|
218
|
-
const startTimeStr = path.replace('/logbook/', '');
|
|
219
|
-
const startTime = new Date(startTimeStr);
|
|
220
|
-
const now = new Date();
|
|
221
|
-
const hoursDiff = (now.getTime() - startTime.getTime()) / (1000 * 60 * 60);
|
|
222
|
-
|
|
223
|
-
// Should be approximately 168 hours, not 500
|
|
224
|
-
expect(hoursDiff).toBeGreaterThan(160);
|
|
225
|
-
expect(hoursDiff).toBeLessThan(175);
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
});
|
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HaClient aggregator tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
-
|
|
7
|
-
// Create mock instances
|
|
8
|
-
const statesInstance = {
|
|
9
|
-
getStates: vi.fn().mockResolvedValue([{ entity_id: 'light.test' }]),
|
|
10
|
-
getState: vi.fn().mockResolvedValue({ entity_id: 'light.test' }),
|
|
11
|
-
getAllSensors: vi.fn().mockResolvedValue([{ entity_id: 'sensor.test' }]),
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
const servicesInstance = {
|
|
15
|
-
callService: vi.fn().mockResolvedValue({ context: { id: 'ctx-1' } }),
|
|
16
|
-
restartServer: vi.fn().mockResolvedValue(undefined),
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const entitiesInstance = {
|
|
20
|
-
getEntitiesByDomain: vi.fn().mockResolvedValue([{ entity_id: 'light.test' }]),
|
|
21
|
-
searchEntities: vi.fn().mockResolvedValue([{ entity_id: 'light.test' }]),
|
|
22
|
-
getDomainSummary: vi.fn().mockResolvedValue({ domain: 'light', count: 1 }),
|
|
23
|
-
listEntities: vi.fn().mockResolvedValue([{ entity_id: 'light.test' }]),
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
const automationsInstance = {
|
|
27
|
-
getAutomations: vi.fn().mockResolvedValue([{ id: 'automation.test' }]),
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const historyInstance = {
|
|
31
|
-
getHistory: vi.fn().mockResolvedValue([[{ entity_id: 'sensor.test' }]]),
|
|
32
|
-
getSystemLog: vi.fn().mockResolvedValue([{ when: '2026-01-08T10:00:00Z' }]),
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const updatesInstance = {
|
|
36
|
-
getAvailableUpdates: vi.fn().mockResolvedValue({ updates: [], entities: [] }),
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const configInstance = {
|
|
40
|
-
getConfig: vi.fn().mockResolvedValue({ version: '2026.1.0' }),
|
|
41
|
-
checkApi: vi.fn().mockResolvedValue({ message: 'API running.' }),
|
|
42
|
-
getVersion: vi.fn().mockResolvedValue({ version: '2026.1.0' }),
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const requestInstance = {
|
|
46
|
-
request: vi.fn().mockResolvedValue({}),
|
|
47
|
-
get: vi.fn().mockResolvedValue({}),
|
|
48
|
-
post: vi.fn().mockResolvedValue({}),
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// Mock classes for vitest 4.x compatibility
|
|
52
|
-
const StateOperationsMock = vi.fn().mockImplementation(() => statesInstance);
|
|
53
|
-
const ServiceOperationsMock = vi.fn().mockImplementation(() => servicesInstance);
|
|
54
|
-
const EntityOperationsMock = vi.fn().mockImplementation(() => entitiesInstance);
|
|
55
|
-
const AutomationOperationsMock = vi.fn().mockImplementation(() => automationsInstance);
|
|
56
|
-
const HistoryOperationsMock = vi.fn().mockImplementation(() => historyInstance);
|
|
57
|
-
const UpdateOperationsMock = vi.fn().mockImplementation(() => updatesInstance);
|
|
58
|
-
const ConfigOperationsMock = vi.fn().mockImplementation(() => configInstance);
|
|
59
|
-
const RequestHandlerMock = vi.fn().mockImplementation(() => requestInstance);
|
|
60
|
-
|
|
61
|
-
// Apply mocks with class-style factories
|
|
62
|
-
vi.mock('../../src/haClient/states.js', () => ({
|
|
63
|
-
StateOperations: class {
|
|
64
|
-
constructor() {
|
|
65
|
-
StateOperationsMock();
|
|
66
|
-
return statesInstance;
|
|
67
|
-
}
|
|
68
|
-
},
|
|
69
|
-
}));
|
|
70
|
-
|
|
71
|
-
vi.mock('../../src/haClient/services.js', () => ({
|
|
72
|
-
ServiceOperations: class {
|
|
73
|
-
constructor() {
|
|
74
|
-
ServiceOperationsMock();
|
|
75
|
-
return servicesInstance;
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
}));
|
|
79
|
-
|
|
80
|
-
vi.mock('../../src/haClient/entities.js', () => ({
|
|
81
|
-
EntityOperations: class {
|
|
82
|
-
constructor() {
|
|
83
|
-
EntityOperationsMock();
|
|
84
|
-
return entitiesInstance;
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
}));
|
|
88
|
-
|
|
89
|
-
vi.mock('../../src/haClient/automations.js', () => ({
|
|
90
|
-
AutomationOperations: class {
|
|
91
|
-
constructor() {
|
|
92
|
-
AutomationOperationsMock();
|
|
93
|
-
return automationsInstance;
|
|
94
|
-
}
|
|
95
|
-
},
|
|
96
|
-
}));
|
|
97
|
-
|
|
98
|
-
vi.mock('../../src/haClient/history.js', () => ({
|
|
99
|
-
HistoryOperations: class {
|
|
100
|
-
constructor() {
|
|
101
|
-
HistoryOperationsMock();
|
|
102
|
-
return historyInstance;
|
|
103
|
-
}
|
|
104
|
-
},
|
|
105
|
-
}));
|
|
106
|
-
|
|
107
|
-
vi.mock('../../src/haClient/updates.js', () => ({
|
|
108
|
-
UpdateOperations: class {
|
|
109
|
-
constructor() {
|
|
110
|
-
UpdateOperationsMock();
|
|
111
|
-
return updatesInstance;
|
|
112
|
-
}
|
|
113
|
-
},
|
|
114
|
-
}));
|
|
115
|
-
|
|
116
|
-
vi.mock('../../src/haClient/config.js', () => ({
|
|
117
|
-
ConfigOperations: class {
|
|
118
|
-
constructor() {
|
|
119
|
-
ConfigOperationsMock();
|
|
120
|
-
return configInstance;
|
|
121
|
-
}
|
|
122
|
-
},
|
|
123
|
-
}));
|
|
124
|
-
|
|
125
|
-
vi.mock('../../src/haClient/request.js', () => ({
|
|
126
|
-
RequestHandler: class {
|
|
127
|
-
constructor() {
|
|
128
|
-
RequestHandlerMock();
|
|
129
|
-
return requestInstance;
|
|
130
|
-
}
|
|
131
|
-
},
|
|
132
|
-
}));
|
|
133
|
-
|
|
134
|
-
import type { HaClientOptions } from '../../src/haClient/index.js';
|
|
135
|
-
|
|
136
|
-
const baseConfig: HaClientOptions = {
|
|
137
|
-
baseUrl: 'http://homeassistant.10.0.0.19.nip.io:8123',
|
|
138
|
-
token: 'test-token',
|
|
139
|
-
strictSsl: false,
|
|
140
|
-
timeout: 30000,
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
describe('HaClient aggregator', () => {
|
|
144
|
-
beforeEach(() => {
|
|
145
|
-
vi.clearAllMocks();
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('initializes all operation modules', async () => {
|
|
149
|
-
const { HaClient } = await import('../../src/haClient/index.js');
|
|
150
|
-
const client = new HaClient(baseConfig);
|
|
151
|
-
|
|
152
|
-
expect(RequestHandlerMock).toHaveBeenCalled();
|
|
153
|
-
expect(StateOperationsMock).toHaveBeenCalled();
|
|
154
|
-
expect(ServiceOperationsMock).toHaveBeenCalled();
|
|
155
|
-
expect(EntityOperationsMock).toHaveBeenCalled();
|
|
156
|
-
expect(AutomationOperationsMock).toHaveBeenCalled();
|
|
157
|
-
expect(HistoryOperationsMock).toHaveBeenCalled();
|
|
158
|
-
expect(UpdateOperationsMock).toHaveBeenCalled();
|
|
159
|
-
expect(ConfigOperationsMock).toHaveBeenCalled();
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('proxies state operations', async () => {
|
|
163
|
-
const { HaClient } = await import('../../src/haClient/index.js');
|
|
164
|
-
const client = new HaClient(baseConfig);
|
|
165
|
-
|
|
166
|
-
await expect(client.getStates()).resolves.toEqual([{ entity_id: 'light.test' }]);
|
|
167
|
-
await expect(client.getState('light.test')).resolves.toEqual({ entity_id: 'light.test' });
|
|
168
|
-
await expect(client.getAllSensors()).resolves.toEqual([{ entity_id: 'sensor.test' }]);
|
|
169
|
-
|
|
170
|
-
expect(statesInstance.getStates).toHaveBeenCalled();
|
|
171
|
-
expect(statesInstance.getState).toHaveBeenCalledWith('light.test');
|
|
172
|
-
expect(statesInstance.getAllSensors).toHaveBeenCalled();
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
it('proxies service operations', async () => {
|
|
176
|
-
const { HaClient } = await import('../../src/haClient/index.js');
|
|
177
|
-
const client = new HaClient(baseConfig);
|
|
178
|
-
|
|
179
|
-
const serviceData = { domain: 'light', service: 'turn_on' };
|
|
180
|
-
await expect(client.callService(serviceData)).resolves.toEqual({ context: { id: 'ctx-1' } });
|
|
181
|
-
await expect(client.restartServer()).resolves.toBeUndefined();
|
|
182
|
-
|
|
183
|
-
expect(servicesInstance.callService).toHaveBeenCalledWith(serviceData);
|
|
184
|
-
expect(servicesInstance.restartServer).toHaveBeenCalled();
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it('proxies entity operations', async () => {
|
|
188
|
-
const { HaClient } = await import('../../src/haClient/index.js');
|
|
189
|
-
const client = new HaClient(baseConfig);
|
|
190
|
-
|
|
191
|
-
await expect(client.getEntitiesByDomain('light')).resolves.toEqual([{ entity_id: 'light.test' }]);
|
|
192
|
-
await expect(client.searchEntities('test')).resolves.toEqual([{ entity_id: 'light.test' }]);
|
|
193
|
-
await expect(client.getDomainSummary('light')).resolves.toEqual({ domain: 'light', count: 1 });
|
|
194
|
-
await expect(client.listEntities({ domain: 'light' })).resolves.toEqual([{ entity_id: 'light.test' }]);
|
|
195
|
-
|
|
196
|
-
expect(entitiesInstance.getEntitiesByDomain).toHaveBeenCalledWith('light');
|
|
197
|
-
expect(entitiesInstance.searchEntities).toHaveBeenCalledWith('test');
|
|
198
|
-
expect(entitiesInstance.getDomainSummary).toHaveBeenCalledWith('light');
|
|
199
|
-
expect(entitiesInstance.listEntities).toHaveBeenCalledWith({ domain: 'light' });
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it('proxies automation operations', async () => {
|
|
203
|
-
const { HaClient } = await import('../../src/haClient/index.js');
|
|
204
|
-
const client = new HaClient(baseConfig);
|
|
205
|
-
|
|
206
|
-
await expect(client.getAutomations()).resolves.toEqual([{ id: 'automation.test' }]);
|
|
207
|
-
|
|
208
|
-
expect(automationsInstance.getAutomations).toHaveBeenCalled();
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
it('proxies history operations', async () => {
|
|
212
|
-
const { HaClient } = await import('../../src/haClient/index.js');
|
|
213
|
-
const client = new HaClient(baseConfig);
|
|
214
|
-
|
|
215
|
-
await expect(client.getHistory('sensor.test', 24)).resolves.toEqual([[{ entity_id: 'sensor.test' }]]);
|
|
216
|
-
await expect(client.getSystemLog({ hours: 24 })).resolves.toEqual([{ when: '2026-01-08T10:00:00Z' }]);
|
|
217
|
-
|
|
218
|
-
expect(historyInstance.getHistory).toHaveBeenCalledWith('sensor.test', 24);
|
|
219
|
-
expect(historyInstance.getSystemLog).toHaveBeenCalledWith({ hours: 24 });
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
it('proxies update operations', async () => {
|
|
223
|
-
const { HaClient } = await import('../../src/haClient/index.js');
|
|
224
|
-
const client = new HaClient(baseConfig);
|
|
225
|
-
|
|
226
|
-
await expect(client.getAvailableUpdates()).resolves.toEqual({ updates: [], entities: [] });
|
|
227
|
-
|
|
228
|
-
expect(updatesInstance.getAvailableUpdates).toHaveBeenCalled();
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
it('proxies config operations', async () => {
|
|
232
|
-
const { HaClient } = await import('../../src/haClient/index.js');
|
|
233
|
-
const client = new HaClient(baseConfig);
|
|
234
|
-
|
|
235
|
-
await expect(client.getConfig()).resolves.toEqual({ version: '2026.1.0' });
|
|
236
|
-
await expect(client.checkApi()).resolves.toEqual({ message: 'API running.' });
|
|
237
|
-
await expect(client.getVersion()).resolves.toEqual({ version: '2026.1.0' });
|
|
238
|
-
|
|
239
|
-
expect(configInstance.getConfig).toHaveBeenCalled();
|
|
240
|
-
expect(configInstance.checkApi).toHaveBeenCalled();
|
|
241
|
-
expect(configInstance.getVersion).toHaveBeenCalled();
|
|
242
|
-
});
|
|
243
|
-
});
|
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* RequestHandler tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
6
|
-
import { RequestHandler } from '../../src/haClient/request.js';
|
|
7
|
-
import { AuthenticationError, ApiError } from '../../src/types/index.js';
|
|
8
|
-
|
|
9
|
-
// Mock fetch globally
|
|
10
|
-
global.fetch = vi.fn();
|
|
11
|
-
|
|
12
|
-
describe('RequestHandler', () => {
|
|
13
|
-
let handler: RequestHandler;
|
|
14
|
-
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
handler = new RequestHandler({
|
|
17
|
-
baseUrl: 'http://homeassistant.10.0.0.19.nip.io:8123',
|
|
18
|
-
token: 'test-token-12345',
|
|
19
|
-
timeout: 30000,
|
|
20
|
-
strictSsl: false,
|
|
21
|
-
});
|
|
22
|
-
vi.clearAllMocks();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
afterEach(() => {
|
|
26
|
-
vi.clearAllMocks();
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
describe('request', () => {
|
|
30
|
-
it('should make GET request with authorization header', async () => {
|
|
31
|
-
(global.fetch as any).mockResolvedValueOnce({
|
|
32
|
-
ok: true,
|
|
33
|
-
headers: new Map([['content-type', 'application/json']]),
|
|
34
|
-
json: async () => ({ data: 'test' }),
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
await handler.request('/test');
|
|
38
|
-
|
|
39
|
-
expect(global.fetch).toHaveBeenCalledWith(
|
|
40
|
-
'http://homeassistant.10.0.0.19.nip.io:8123/api/test',
|
|
41
|
-
expect.objectContaining({
|
|
42
|
-
method: 'GET',
|
|
43
|
-
headers: expect.objectContaining({
|
|
44
|
-
'Authorization': 'Bearer test-token-12345',
|
|
45
|
-
'Content-Type': 'application/json',
|
|
46
|
-
}),
|
|
47
|
-
})
|
|
48
|
-
);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it('should make POST request with body', async () => {
|
|
52
|
-
(global.fetch as any).mockResolvedValueOnce({
|
|
53
|
-
ok: true,
|
|
54
|
-
headers: new Map([['content-type', 'application/json']]),
|
|
55
|
-
json: async () => ({ success: true }),
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
await handler.request('/test', {
|
|
59
|
-
method: 'POST',
|
|
60
|
-
body: { key: 'value' },
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
expect(global.fetch).toHaveBeenCalledWith(
|
|
64
|
-
'http://homeassistant.10.0.0.19.nip.io:8123/api/test',
|
|
65
|
-
expect.objectContaining({
|
|
66
|
-
method: 'POST',
|
|
67
|
-
body: JSON.stringify({ key: 'value' }),
|
|
68
|
-
})
|
|
69
|
-
);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should add query parameters to URL', async () => {
|
|
73
|
-
(global.fetch as any).mockResolvedValueOnce({
|
|
74
|
-
ok: true,
|
|
75
|
-
headers: new Map([['content-type', 'application/json']]),
|
|
76
|
-
json: async () => ({}),
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
await handler.request('/test', {
|
|
80
|
-
params: { filter: 'active', limit: 10 },
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
expect(global.fetch).toHaveBeenCalledWith(
|
|
84
|
-
expect.stringContaining('filter=active'),
|
|
85
|
-
expect.any(Object)
|
|
86
|
-
);
|
|
87
|
-
expect(global.fetch).toHaveBeenCalledWith(
|
|
88
|
-
expect.stringContaining('limit=10'),
|
|
89
|
-
expect.any(Object)
|
|
90
|
-
);
|
|
91
|
-
});
|
|
92
|
-
|
|
93
|
-
it('should throw AuthenticationError on 401', async () => {
|
|
94
|
-
const mockResponse = {
|
|
95
|
-
ok: false,
|
|
96
|
-
status: 401,
|
|
97
|
-
statusText: 'Unauthorized',
|
|
98
|
-
text: async () => 'Invalid token',
|
|
99
|
-
};
|
|
100
|
-
(global.fetch as any).mockResolvedValueOnce(mockResponse);
|
|
101
|
-
|
|
102
|
-
const error = await handler.request('/test').catch((e) => e);
|
|
103
|
-
expect(error).toBeInstanceOf(AuthenticationError);
|
|
104
|
-
expect(error.message).toBe('Invalid Home Assistant token');
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it('should throw ApiError on other HTTP errors', async () => {
|
|
108
|
-
(global.fetch as any).mockResolvedValueOnce({
|
|
109
|
-
ok: false,
|
|
110
|
-
status: 500,
|
|
111
|
-
statusText: 'Internal Server Error',
|
|
112
|
-
text: async () => 'Server error',
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
await expect(handler.request('/test')).rejects.toThrow(ApiError);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
it('should handle empty responses', async () => {
|
|
119
|
-
(global.fetch as any).mockResolvedValueOnce({
|
|
120
|
-
ok: true,
|
|
121
|
-
headers: new Map([['content-type', 'text/plain']]),
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
const result = await handler.request('/test');
|
|
125
|
-
|
|
126
|
-
expect(result).toEqual({});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('should handle network errors', async () => {
|
|
130
|
-
(global.fetch as any).mockRejectedValueOnce(new Error('Network timeout'));
|
|
131
|
-
|
|
132
|
-
await expect(handler.request('/test')).rejects.toThrow('Network timeout');
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
describe('get', () => {
|
|
137
|
-
it('should make GET request', async () => {
|
|
138
|
-
(global.fetch as any).mockResolvedValueOnce({
|
|
139
|
-
ok: true,
|
|
140
|
-
headers: new Map([['content-type', 'application/json']]),
|
|
141
|
-
json: async () => ({ data: 'result' }),
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
const result = await handler.get<{ data: string }>('/states');
|
|
145
|
-
|
|
146
|
-
expect(result).toEqual({ data: 'result' });
|
|
147
|
-
expect(global.fetch).toHaveBeenCalledWith(
|
|
148
|
-
expect.stringContaining('/states'),
|
|
149
|
-
expect.objectContaining({ method: 'GET' })
|
|
150
|
-
);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('should pass query parameters', async () => {
|
|
154
|
-
(global.fetch as any).mockResolvedValueOnce({
|
|
155
|
-
ok: true,
|
|
156
|
-
headers: new Map([['content-type', 'application/json']]),
|
|
157
|
-
json: async () => ({}),
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
await handler.get('/history', { entity_id: 'light.living_room' });
|
|
161
|
-
|
|
162
|
-
expect(global.fetch).toHaveBeenCalledWith(
|
|
163
|
-
expect.stringContaining('entity_id=light.living_room'),
|
|
164
|
-
expect.any(Object)
|
|
165
|
-
);
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
describe('post', () => {
|
|
170
|
-
it('should make POST request with body', async () => {
|
|
171
|
-
(global.fetch as any).mockResolvedValueOnce({
|
|
172
|
-
ok: true,
|
|
173
|
-
headers: new Map([['content-type', 'application/json']]),
|
|
174
|
-
json: async () => ({ context: { id: '123' } }),
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
const result = await handler.post('/services/light/turn_on', {
|
|
178
|
-
entity_id: 'light.living_room',
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
expect(result).toEqual({ context: { id: '123' } });
|
|
182
|
-
expect(global.fetch).toHaveBeenCalledWith(
|
|
183
|
-
expect.stringContaining('/services/light/turn_on'),
|
|
184
|
-
expect.objectContaining({
|
|
185
|
-
method: 'POST',
|
|
186
|
-
body: JSON.stringify({ entity_id: 'light.living_room' }),
|
|
187
|
-
})
|
|
188
|
-
);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
describe('SSL configuration', () => {
|
|
193
|
-
it('should create HTTPS agent when strictSsl is false', () => {
|
|
194
|
-
const handlerNoSsl = new RequestHandler({
|
|
195
|
-
baseUrl: 'https://homeassistant.10.0.0.19.nip.io:8123',
|
|
196
|
-
token: 'token',
|
|
197
|
-
timeout: 30000,
|
|
198
|
-
strictSsl: false,
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
// Handler is created successfully with strictSsl: false
|
|
202
|
-
expect(handlerNoSsl).toBeDefined();
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it('should not create HTTPS agent when strictSsl is true', () => {
|
|
206
|
-
const handlerWithSsl = new RequestHandler({
|
|
207
|
-
baseUrl: 'https://homeassistant.10.0.0.19.nip.io:8123',
|
|
208
|
-
token: 'token',
|
|
209
|
-
timeout: 30000,
|
|
210
|
-
strictSsl: true,
|
|
211
|
-
});
|
|
212
|
-
|
|
213
|
-
// Handler is created successfully with strictSsl: true
|
|
214
|
-
expect(handlerWithSsl).toBeDefined();
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
});
|