@coffeerunhobby/mcp-ha-connect 2.2.1 → 2.3.1
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 +11 -2
- 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 +6 -0
- package/dist/omadaClient/index.js +15 -0
- package/dist/omadaClient/index.js.map +1 -1
- package/dist/omadaClient/macGroups.d.ts +58 -0
- package/dist/omadaClient/macGroups.js +124 -0
- package/dist/omadaClient/macGroups.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/index.js +6 -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 +8 -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,149 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AutomationOperations tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
6
|
-
import { AutomationOperations } from '../../src/haClient/automations.js';
|
|
7
|
-
import type { StateOperations } from '../../src/haClient/states.js';
|
|
8
|
-
import type { Entity } from '../../src/types/index.js';
|
|
9
|
-
|
|
10
|
-
describe('AutomationOperations', () => {
|
|
11
|
-
let mockStateOps: StateOperations;
|
|
12
|
-
let automationOps: AutomationOperations;
|
|
13
|
-
|
|
14
|
-
const mockAutomationEntities: Entity[] = [
|
|
15
|
-
{
|
|
16
|
-
entity_id: 'automation.morning_lights',
|
|
17
|
-
state: 'on',
|
|
18
|
-
attributes: {
|
|
19
|
-
friendly_name: 'Morning Lights',
|
|
20
|
-
last_triggered: '2026-01-08T06:00:00.000Z',
|
|
21
|
-
description: 'Turn on lights at sunrise',
|
|
22
|
-
mode: 'single',
|
|
23
|
-
},
|
|
24
|
-
last_changed: '2026-01-08T06:00:00.000Z',
|
|
25
|
-
last_updated: '2026-01-08T06:00:00.000Z',
|
|
26
|
-
context: { id: 'ctx-1', parent_id: null, user_id: null },
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
entity_id: 'automation.night_mode',
|
|
30
|
-
state: 'off',
|
|
31
|
-
attributes: {
|
|
32
|
-
friendly_name: 'Night Mode',
|
|
33
|
-
last_triggered: null,
|
|
34
|
-
mode: 'restart',
|
|
35
|
-
},
|
|
36
|
-
last_changed: '2026-01-07T22:00:00.000Z',
|
|
37
|
-
last_updated: '2026-01-08T10:00:00.000Z',
|
|
38
|
-
context: { id: 'ctx-2', parent_id: null, user_id: null },
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
entity_id: 'automation.motion_alert',
|
|
42
|
-
state: 'on',
|
|
43
|
-
attributes: {
|
|
44
|
-
friendly_name: 'Motion Alert',
|
|
45
|
-
last_triggered: '2026-01-08T09:30:00.000Z',
|
|
46
|
-
mode: 'queued',
|
|
47
|
-
},
|
|
48
|
-
last_changed: '2026-01-08T09:30:00.000Z',
|
|
49
|
-
last_updated: '2026-01-08T09:30:00.000Z',
|
|
50
|
-
context: { id: 'ctx-3', parent_id: null, user_id: null },
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
entity_id: 'light.living_room', // Non-automation entity
|
|
54
|
-
state: 'on',
|
|
55
|
-
attributes: { friendly_name: 'Living Room Light' },
|
|
56
|
-
last_changed: '2026-01-08T10:00:00.000Z',
|
|
57
|
-
last_updated: '2026-01-08T10:00:00.000Z',
|
|
58
|
-
context: { id: 'ctx-4', parent_id: null, user_id: null },
|
|
59
|
-
},
|
|
60
|
-
];
|
|
61
|
-
|
|
62
|
-
beforeEach(() => {
|
|
63
|
-
mockStateOps = {
|
|
64
|
-
getStates: vi.fn().mockResolvedValue(mockAutomationEntities),
|
|
65
|
-
} as unknown as StateOperations;
|
|
66
|
-
|
|
67
|
-
automationOps = new AutomationOperations(mockStateOps);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
describe('getAutomations', () => {
|
|
71
|
-
it('should return only automation entities', async () => {
|
|
72
|
-
const automations = await automationOps.getAutomations();
|
|
73
|
-
|
|
74
|
-
expect(automations).toHaveLength(3);
|
|
75
|
-
expect(automations.every(a => a.id.startsWith('automation.'))).toBe(true);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('should map entity to Automation type', async () => {
|
|
79
|
-
const automations = await automationOps.getAutomations();
|
|
80
|
-
|
|
81
|
-
const morning = automations.find(a => a.id === 'automation.morning_lights');
|
|
82
|
-
expect(morning).toEqual({
|
|
83
|
-
id: 'automation.morning_lights',
|
|
84
|
-
alias: 'Morning Lights',
|
|
85
|
-
state: 'on',
|
|
86
|
-
last_triggered: '2026-01-08T06:00:00.000Z',
|
|
87
|
-
description: 'Turn on lights at sunrise',
|
|
88
|
-
mode: 'single',
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('should handle automations without friendly_name', async () => {
|
|
93
|
-
(mockStateOps.getStates as ReturnType<typeof vi.fn>).mockResolvedValue([
|
|
94
|
-
{
|
|
95
|
-
entity_id: 'automation.unnamed',
|
|
96
|
-
state: 'on',
|
|
97
|
-
attributes: {},
|
|
98
|
-
last_changed: '2026-01-08T10:00:00.000Z',
|
|
99
|
-
last_updated: '2026-01-08T10:00:00.000Z',
|
|
100
|
-
context: { id: 'ctx-1', parent_id: null, user_id: null },
|
|
101
|
-
},
|
|
102
|
-
]);
|
|
103
|
-
|
|
104
|
-
const automations = await automationOps.getAutomations();
|
|
105
|
-
|
|
106
|
-
expect(automations[0].alias).toBe('unnamed');
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it('should handle null last_triggered', async () => {
|
|
110
|
-
const automations = await automationOps.getAutomations();
|
|
111
|
-
|
|
112
|
-
const nightMode = automations.find(a => a.id === 'automation.night_mode');
|
|
113
|
-
expect(nightMode?.last_triggered).toBeNull();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('should return empty array when no automations exist', async () => {
|
|
117
|
-
(mockStateOps.getStates as ReturnType<typeof vi.fn>).mockResolvedValue([
|
|
118
|
-
{
|
|
119
|
-
entity_id: 'light.test',
|
|
120
|
-
state: 'on',
|
|
121
|
-
attributes: {},
|
|
122
|
-
last_changed: '2026-01-08T10:00:00.000Z',
|
|
123
|
-
last_updated: '2026-01-08T10:00:00.000Z',
|
|
124
|
-
context: { id: 'ctx-1', parent_id: null, user_id: null },
|
|
125
|
-
},
|
|
126
|
-
]);
|
|
127
|
-
|
|
128
|
-
const automations = await automationOps.getAutomations();
|
|
129
|
-
|
|
130
|
-
expect(automations).toEqual([]);
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
describe('AutomationOperations.createAutomation path encoding', () => {
|
|
136
|
-
it('encodes the automation id in the URL path but keeps it unchanged in the body', async () => {
|
|
137
|
-
const post = vi.fn().mockResolvedValue({ result: 'ok' });
|
|
138
|
-
const serviceOps = { callService: vi.fn().mockResolvedValue([]) };
|
|
139
|
-
const ops = new AutomationOperations(
|
|
140
|
-
{ getState: vi.fn() } as unknown as StateOperations,
|
|
141
|
-
serviceOps as never,
|
|
142
|
-
{ post, get: vi.fn() } as never
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
await ops.createAutomation({ id: 'room/a?b', alias: 'Test', trigger: [], action: [] } as never);
|
|
146
|
-
|
|
147
|
-
expect(post).toHaveBeenCalledWith('/config/automation/config/room%2Fa%3Fb', expect.objectContaining({ id: 'room/a?b' }));
|
|
148
|
-
});
|
|
149
|
-
});
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ConfigOperations tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
6
|
-
import { ConfigOperations } from '../../src/haClient/config.js';
|
|
7
|
-
import type { RequestHandler } from '../../src/haClient/request.js';
|
|
8
|
-
import type { HaVersion } from '../../src/types/index.js';
|
|
9
|
-
|
|
10
|
-
describe('ConfigOperations', () => {
|
|
11
|
-
let mockRequest: RequestHandler;
|
|
12
|
-
let configOps: ConfigOperations;
|
|
13
|
-
|
|
14
|
-
// Full config response from HA (includes sensitive fields)
|
|
15
|
-
const mockFullConfig = {
|
|
16
|
-
version: '2026.1.0',
|
|
17
|
-
config_dir: '/config',
|
|
18
|
-
location_name: 'Home',
|
|
19
|
-
time_zone: 'Europe/London',
|
|
20
|
-
unit_system: {
|
|
21
|
-
length: 'km',
|
|
22
|
-
mass: 'kg',
|
|
23
|
-
temperature: '°C',
|
|
24
|
-
volume: 'L',
|
|
25
|
-
},
|
|
26
|
-
components: ['homeassistant', 'frontend', 'light', 'switch', 'sensor', 'automation'],
|
|
27
|
-
state: 'RUNNING',
|
|
28
|
-
allowlist_external_dirs: ['/media'],
|
|
29
|
-
allowlist_external_urls: [],
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// Sanitized version (only safe, public info)
|
|
33
|
-
const expectedSanitizedVersion: HaVersion = {
|
|
34
|
-
version: '2026.1.0',
|
|
35
|
-
location_name: 'Home',
|
|
36
|
-
time_zone: 'Europe/London',
|
|
37
|
-
unit_system: {
|
|
38
|
-
length: 'km',
|
|
39
|
-
mass: 'kg',
|
|
40
|
-
temperature: '°C',
|
|
41
|
-
volume: 'L',
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
beforeEach(() => {
|
|
46
|
-
mockRequest = {
|
|
47
|
-
get: vi.fn(),
|
|
48
|
-
} as unknown as RequestHandler;
|
|
49
|
-
|
|
50
|
-
configOps = new ConfigOperations(mockRequest);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe('getConfig', () => {
|
|
54
|
-
it('should fetch configuration', async () => {
|
|
55
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue(mockFullConfig);
|
|
56
|
-
|
|
57
|
-
const config = await configOps.getConfig();
|
|
58
|
-
|
|
59
|
-
expect(config).toEqual(mockFullConfig);
|
|
60
|
-
expect(mockRequest.get).toHaveBeenCalledWith('/config');
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
describe('checkApi', () => {
|
|
65
|
-
it('should return API health status', async () => {
|
|
66
|
-
const mockApiResponse = { message: 'API running.' };
|
|
67
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue(mockApiResponse);
|
|
68
|
-
|
|
69
|
-
const result = await configOps.checkApi();
|
|
70
|
-
|
|
71
|
-
expect(result).toEqual(mockApiResponse);
|
|
72
|
-
expect(mockRequest.get).toHaveBeenCalledWith('/');
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
describe('getVersion', () => {
|
|
77
|
-
it('should return sanitized version information', async () => {
|
|
78
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue(mockFullConfig);
|
|
79
|
-
|
|
80
|
-
const version = await configOps.getVersion();
|
|
81
|
-
|
|
82
|
-
expect(version).toEqual(expectedSanitizedVersion);
|
|
83
|
-
expect(version.version).toBe('2026.1.0');
|
|
84
|
-
expect(mockRequest.get).toHaveBeenCalledWith('/config');
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('should filter out sensitive config fields', async () => {
|
|
88
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue(mockFullConfig);
|
|
89
|
-
|
|
90
|
-
const version = await configOps.getVersion();
|
|
91
|
-
|
|
92
|
-
// Should include safe fields
|
|
93
|
-
expect(version.location_name).toBe('Home');
|
|
94
|
-
expect(version.time_zone).toBe('Europe/London');
|
|
95
|
-
|
|
96
|
-
// Should NOT include sensitive fields
|
|
97
|
-
expect((version as Record<string, unknown>).config_dir).toBeUndefined();
|
|
98
|
-
expect((version as Record<string, unknown>).state).toBeUndefined();
|
|
99
|
-
expect((version as Record<string, unknown>).components).toBeUndefined();
|
|
100
|
-
expect((version as Record<string, unknown>).allowlist_external_dirs).toBeUndefined();
|
|
101
|
-
expect((version as Record<string, unknown>).allowlist_external_urls).toBeUndefined();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('should include unit_system', async () => {
|
|
105
|
-
(mockRequest.get as ReturnType<typeof vi.fn>).mockResolvedValue(mockFullConfig);
|
|
106
|
-
|
|
107
|
-
const version = await configOps.getVersion();
|
|
108
|
-
|
|
109
|
-
expect(version.unit_system).toEqual({
|
|
110
|
-
length: 'km',
|
|
111
|
-
mass: 'kg',
|
|
112
|
-
temperature: '°C',
|
|
113
|
-
volume: 'L',
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
});
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* EntityOperations tests
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
6
|
-
import { EntityOperations } from '../../src/haClient/entities.js';
|
|
7
|
-
import type { StateOperations } from '../../src/haClient/states.js';
|
|
8
|
-
import type { Entity } from '../../src/types/index.js';
|
|
9
|
-
|
|
10
|
-
describe('EntityOperations', () => {
|
|
11
|
-
let mockStateOps: StateOperations;
|
|
12
|
-
let entityOps: EntityOperations;
|
|
13
|
-
|
|
14
|
-
const mockEntities: Entity[] = [
|
|
15
|
-
{
|
|
16
|
-
entity_id: 'light.living_room',
|
|
17
|
-
state: 'on',
|
|
18
|
-
attributes: { friendly_name: 'Living Room Light', brightness: 255 },
|
|
19
|
-
last_changed: '2026-01-08T10:00:00.000Z',
|
|
20
|
-
last_updated: '2026-01-08T10:00:00.000Z',
|
|
21
|
-
context: { id: 'ctx-1', parent_id: null, user_id: null },
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
entity_id: 'light.bedroom',
|
|
25
|
-
state: 'off',
|
|
26
|
-
attributes: { friendly_name: 'Bedroom Light' },
|
|
27
|
-
last_changed: '2026-01-08T09:00:00.000Z',
|
|
28
|
-
last_updated: '2026-01-08T10:00:00.000Z',
|
|
29
|
-
context: { id: 'ctx-2', parent_id: null, user_id: null },
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
entity_id: 'sensor.temperature',
|
|
33
|
-
state: '22.5',
|
|
34
|
-
attributes: { friendly_name: 'Temperature', unit_of_measurement: '°C' },
|
|
35
|
-
last_changed: '2026-01-08T10:00:00.000Z',
|
|
36
|
-
last_updated: '2026-01-08T10:00:00.000Z',
|
|
37
|
-
context: { id: 'ctx-3', parent_id: null, user_id: null },
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
entity_id: 'switch.kitchen',
|
|
41
|
-
state: 'on',
|
|
42
|
-
attributes: { friendly_name: 'Kitchen Switch' },
|
|
43
|
-
last_changed: '2026-01-08T08:00:00.000Z',
|
|
44
|
-
last_updated: '2026-01-08T10:00:00.000Z',
|
|
45
|
-
context: { id: 'ctx-4', parent_id: null, user_id: null },
|
|
46
|
-
},
|
|
47
|
-
];
|
|
48
|
-
|
|
49
|
-
beforeEach(() => {
|
|
50
|
-
mockStateOps = {
|
|
51
|
-
getStates: vi.fn().mockResolvedValue(mockEntities),
|
|
52
|
-
} as unknown as StateOperations;
|
|
53
|
-
|
|
54
|
-
entityOps = new EntityOperations(mockStateOps);
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
describe('getEntitiesByDomain', () => {
|
|
58
|
-
it('should filter entities by domain', async () => {
|
|
59
|
-
const lights = await entityOps.getEntitiesByDomain('light');
|
|
60
|
-
|
|
61
|
-
expect(lights).toHaveLength(2);
|
|
62
|
-
expect(lights.map(e => e.entity_id)).toContain('light.living_room');
|
|
63
|
-
expect(lights.map(e => e.entity_id)).toContain('light.bedroom');
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should return empty array for non-existent domain', async () => {
|
|
67
|
-
const climate = await entityOps.getEntitiesByDomain('climate');
|
|
68
|
-
|
|
69
|
-
expect(climate).toEqual([]);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('should return single entity domain', async () => {
|
|
73
|
-
const switches = await entityOps.getEntitiesByDomain('switch');
|
|
74
|
-
|
|
75
|
-
expect(switches).toHaveLength(1);
|
|
76
|
-
expect(switches[0].entity_id).toBe('switch.kitchen');
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
describe('searchEntities', () => {
|
|
81
|
-
it('should find entities by friendly name', async () => {
|
|
82
|
-
const results = await entityOps.searchEntities('living');
|
|
83
|
-
|
|
84
|
-
expect(results).toHaveLength(1);
|
|
85
|
-
expect(results[0].entity_id).toBe('light.living_room');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('should find entities by entity_id', async () => {
|
|
89
|
-
const results = await entityOps.searchEntities('kitchen');
|
|
90
|
-
|
|
91
|
-
expect(results).toHaveLength(1);
|
|
92
|
-
expect(results[0].entity_id).toBe('switch.kitchen');
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('should be case insensitive', async () => {
|
|
96
|
-
const results = await entityOps.searchEntities('BEDROOM');
|
|
97
|
-
|
|
98
|
-
expect(results).toHaveLength(1);
|
|
99
|
-
expect(results[0].entity_id).toBe('light.bedroom');
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('should return multiple matches', async () => {
|
|
103
|
-
const results = await entityOps.searchEntities('light');
|
|
104
|
-
|
|
105
|
-
expect(results).toHaveLength(2);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('should return empty array for no matches', async () => {
|
|
109
|
-
const results = await entityOps.searchEntities('nonexistent');
|
|
110
|
-
|
|
111
|
-
expect(results).toEqual([]);
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
describe('getDomainSummary', () => {
|
|
116
|
-
it('should return domain summary with counts', async () => {
|
|
117
|
-
const summary = await entityOps.getDomainSummary('light');
|
|
118
|
-
|
|
119
|
-
expect(summary.domain).toBe('light');
|
|
120
|
-
expect(summary.count).toBe(2);
|
|
121
|
-
expect(summary.states).toEqual({ on: 1, off: 1 });
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should include entity list with state and name', async () => {
|
|
125
|
-
const summary = await entityOps.getDomainSummary('light');
|
|
126
|
-
|
|
127
|
-
expect(summary.entities).toHaveLength(2);
|
|
128
|
-
expect(summary.entities).toContainEqual({
|
|
129
|
-
entity_id: 'light.living_room',
|
|
130
|
-
state: 'on',
|
|
131
|
-
friendly_name: 'Living Room Light',
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('should return empty summary for non-existent domain', async () => {
|
|
136
|
-
const summary = await entityOps.getDomainSummary('climate');
|
|
137
|
-
|
|
138
|
-
expect(summary.domain).toBe('climate');
|
|
139
|
-
expect(summary.count).toBe(0);
|
|
140
|
-
expect(summary.states).toEqual({});
|
|
141
|
-
expect(summary.entities).toEqual([]);
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
describe('listEntities', () => {
|
|
146
|
-
it('should list all entities without filters', async () => {
|
|
147
|
-
const entities = await entityOps.listEntities();
|
|
148
|
-
|
|
149
|
-
expect(entities).toEqual(mockEntities);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('should filter by domain', async () => {
|
|
153
|
-
const entities = await entityOps.listEntities({ domain: 'light' });
|
|
154
|
-
|
|
155
|
-
expect(entities).toHaveLength(2);
|
|
156
|
-
expect(entities.every(e => e.entity_id.startsWith('light.'))).toBe(true);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it('should filter by state', async () => {
|
|
160
|
-
const entities = await entityOps.listEntities({ state: 'on' });
|
|
161
|
-
|
|
162
|
-
expect(entities).toHaveLength(2);
|
|
163
|
-
expect(entities.every(e => e.state === 'on')).toBe(true);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it('should filter by search query', async () => {
|
|
167
|
-
const entities = await entityOps.listEntities({ search: 'room' });
|
|
168
|
-
|
|
169
|
-
expect(entities).toHaveLength(2); // living_room and bedroom
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it('should apply limit', async () => {
|
|
173
|
-
const entities = await entityOps.listEntities({ limit: 2 });
|
|
174
|
-
|
|
175
|
-
expect(entities).toHaveLength(2);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('should combine multiple filters', async () => {
|
|
179
|
-
const entities = await entityOps.listEntities({
|
|
180
|
-
domain: 'light',
|
|
181
|
-
state: 'on',
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
expect(entities).toHaveLength(1);
|
|
185
|
-
expect(entities[0].entity_id).toBe('light.living_room');
|
|
186
|
-
});
|
|
187
|
-
});
|
|
188
|
-
});
|