@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.
Files changed (281) hide show
  1. package/.env.example +1 -1
  2. package/README.md +11 -2
  3. package/dist/omadaClient/client.d.ts +6 -1
  4. package/dist/omadaClient/client.js +12 -1
  5. package/dist/omadaClient/client.js.map +1 -1
  6. package/dist/omadaClient/diagnose.d.ts +68 -0
  7. package/dist/omadaClient/diagnose.js +164 -0
  8. package/dist/omadaClient/diagnose.js.map +1 -0
  9. package/dist/omadaClient/index.d.ts +6 -0
  10. package/dist/omadaClient/index.js +15 -0
  11. package/dist/omadaClient/index.js.map +1 -1
  12. package/dist/omadaClient/macGroups.d.ts +58 -0
  13. package/dist/omadaClient/macGroups.js +124 -0
  14. package/dist/omadaClient/macGroups.js.map +1 -0
  15. package/dist/tools/infra/actions.d.ts +1 -1
  16. package/dist/tools/infra/actions.js +1 -1
  17. package/dist/tools/omada/index.js +6 -0
  18. package/dist/tools/omada/index.js.map +1 -1
  19. package/dist/tools/omada/macGroups.d.ts +19 -0
  20. package/dist/tools/omada/macGroups.js +77 -0
  21. package/dist/tools/omada/macGroups.js.map +1 -0
  22. package/dist/tools/omada/namespace.js +8 -0
  23. package/dist/tools/omada/namespace.js.map +1 -1
  24. package/dist/version.d.ts +1 -1
  25. package/dist/version.js +1 -1
  26. package/package.json +5 -1
  27. package/.codex/review-rules.md +0 -16
  28. package/.dockerignore +0 -8
  29. package/.gitattributes +0 -30
  30. package/.github/workflows/ci.yml +0 -193
  31. package/AGENTS.md +0 -389
  32. package/Dockerfile +0 -50
  33. package/docker-compose.yml +0 -23
  34. package/docs/CHANGELOG.md +0 -341
  35. package/docs/COVERAGE.md +0 -219
  36. package/docs/QUICK_START.md +0 -240
  37. package/eslint.config.js +0 -33
  38. package/n8n/ai-agent-safety-monitor.json +0 -355
  39. package/openapi.json +0 -1259
  40. package/scripts/check-version-sync.mjs +0 -69
  41. package/scripts/generate-jwt.ts +0 -121
  42. package/src/config.ts +0 -440
  43. package/src/haClient/automations.ts +0 -261
  44. package/src/haClient/calendars.ts +0 -94
  45. package/src/haClient/config.ts +0 -52
  46. package/src/haClient/devices.ts +0 -653
  47. package/src/haClient/entities.ts +0 -128
  48. package/src/haClient/events.ts +0 -689
  49. package/src/haClient/history.ts +0 -73
  50. package/src/haClient/index.ts +0 -336
  51. package/src/haClient/request.ts +0 -142
  52. package/src/haClient/services.ts +0 -45
  53. package/src/haClient/states.ts +0 -60
  54. package/src/haClient/updates.ts +0 -58
  55. package/src/haClient/wsCommand.ts +0 -44
  56. package/src/index.ts +0 -164
  57. package/src/localAI/index.ts +0 -88
  58. package/src/localAI/prompts.ts +0 -50
  59. package/src/localAI/providers/base.ts +0 -120
  60. package/src/localAI/providers/ollama.ts +0 -78
  61. package/src/localAI/providers/openai.ts +0 -86
  62. package/src/localAI/types.ts +0 -67
  63. package/src/omadaClient/auth.ts +0 -178
  64. package/src/omadaClient/client.ts +0 -337
  65. package/src/omadaClient/device.ts +0 -118
  66. package/src/omadaClient/index.ts +0 -310
  67. package/src/omadaClient/network.ts +0 -284
  68. package/src/omadaClient/request.ts +0 -365
  69. package/src/omadaClient/security.ts +0 -51
  70. package/src/omadaClient/site.ts +0 -344
  71. package/src/ownerQuestions/service.ts +0 -536
  72. package/src/permissions/index.ts +0 -143
  73. package/src/resources/index.ts +0 -213
  74. package/src/server/auth.ts +0 -175
  75. package/src/server/chatFace.ts +0 -789
  76. package/src/server/chatSlice.ts +0 -57
  77. package/src/server/common.ts +0 -170
  78. package/src/server/eventSubscription.ts +0 -343
  79. package/src/server/http.ts +0 -626
  80. package/src/server/instructions.ts +0 -97
  81. package/src/server/rateLimiter.ts +0 -227
  82. package/src/server/stdio.ts +0 -41
  83. package/src/server/stream.ts +0 -205
  84. package/src/tools/ai/analyzeSensors.ts +0 -33
  85. package/src/tools/ai/index.ts +0 -20
  86. package/src/tools/common.ts +0 -442
  87. package/src/tools/homeassistant/askOwner.ts +0 -133
  88. package/src/tools/homeassistant/automationControls.ts +0 -66
  89. package/src/tools/homeassistant/calendar.ts +0 -97
  90. package/src/tools/homeassistant/callService.ts +0 -25
  91. package/src/tools/homeassistant/controlClimate.ts +0 -68
  92. package/src/tools/homeassistant/controlCover.ts +0 -48
  93. package/src/tools/homeassistant/controlFan.ts +0 -44
  94. package/src/tools/homeassistant/controlLight.ts +0 -38
  95. package/src/tools/homeassistant/controlMediaPlayer.ts +0 -50
  96. package/src/tools/homeassistant/createAutomation.ts +0 -35
  97. package/src/tools/homeassistant/deleteAutomation.ts +0 -24
  98. package/src/tools/homeassistant/entityAction.ts +0 -35
  99. package/src/tools/homeassistant/entityGuard.ts +0 -28
  100. package/src/tools/homeassistant/getAllSensors.ts +0 -46
  101. package/src/tools/homeassistant/getAutomationTrace.ts +0 -30
  102. package/src/tools/homeassistant/getDomainSummary.ts +0 -29
  103. package/src/tools/homeassistant/getEntitiesByDomain.ts +0 -56
  104. package/src/tools/homeassistant/getHistory.ts +0 -24
  105. package/src/tools/homeassistant/getState.ts +0 -24
  106. package/src/tools/homeassistant/getStates.ts +0 -46
  107. package/src/tools/homeassistant/getVersion.ts +0 -21
  108. package/src/tools/homeassistant/index.ts +0 -121
  109. package/src/tools/homeassistant/listAutomations.ts +0 -35
  110. package/src/tools/homeassistant/listEntities.ts +0 -44
  111. package/src/tools/homeassistant/listPersons.ts +0 -46
  112. package/src/tools/homeassistant/sceneAndScript.ts +0 -53
  113. package/src/tools/homeassistant/searchEntities.ts +0 -58
  114. package/src/tools/homeassistant/sendNotification.ts +0 -179
  115. package/src/tools/homeassistant/systemTools.ts +0 -52
  116. package/src/tools/homeassistant/triggerAutomation.ts +0 -24
  117. package/src/tools/index.ts +0 -78
  118. package/src/tools/infra/actions.ts +0 -95
  119. package/src/tools/infra/index.ts +0 -138
  120. package/src/tools/omada/blockClient.ts +0 -28
  121. package/src/tools/omada/cyclePoePort.ts +0 -32
  122. package/src/tools/omada/deleteClient.ts +0 -27
  123. package/src/tools/omada/disableClientRateLimit.ts +0 -23
  124. package/src/tools/omada/getClient.ts +0 -14
  125. package/src/tools/omada/getDevice.ts +0 -14
  126. package/src/tools/omada/getFirewallSetting.ts +0 -14
  127. package/src/tools/omada/getInternetInfo.ts +0 -14
  128. package/src/tools/omada/getLanNetworkList.ts +0 -14
  129. package/src/tools/omada/getLanProfileList.ts +0 -14
  130. package/src/tools/omada/getPortForwardingStatus.ts +0 -26
  131. package/src/tools/omada/getRateLimitProfiles.ts +0 -14
  132. package/src/tools/omada/getSiteNtpStatus.ts +0 -14
  133. package/src/tools/omada/getSsidDetail.ts +0 -22
  134. package/src/tools/omada/getSsidList.ts +0 -21
  135. package/src/tools/omada/getSwitchStackDetail.ts +0 -14
  136. package/src/tools/omada/getThreatList.ts +0 -42
  137. package/src/tools/omada/getWlanGroupList.ts +0 -14
  138. package/src/tools/omada/graph.ts +0 -187
  139. package/src/tools/omada/index.ts +0 -153
  140. package/src/tools/omada/listBlockedClients.ts +0 -17
  141. package/src/tools/omada/listClients.ts +0 -14
  142. package/src/tools/omada/listClientsActivity.ts +0 -25
  143. package/src/tools/omada/listClientsPastConnections.ts +0 -44
  144. package/src/tools/omada/listDevices.ts +0 -14
  145. package/src/tools/omada/listDevicesStats.ts +0 -41
  146. package/src/tools/omada/listMostActiveClients.ts +0 -14
  147. package/src/tools/omada/listSites.ts +0 -18
  148. package/src/tools/omada/namespace.ts +0 -1164
  149. package/src/tools/omada/searchDevices.ts +0 -20
  150. package/src/tools/omada/setClientRateLimit.ts +0 -26
  151. package/src/tools/omada/setClientRateLimitProfile.ts +0 -25
  152. package/src/tools/omada/setSiteNtpServers.ts +0 -36
  153. package/src/tools/omada/setSsidEnabled.ts +0 -38
  154. package/src/tools/omada/unblockClient.ts +0 -26
  155. package/src/tools/types.ts +0 -11
  156. package/src/tools/utils/pagination-schema.ts +0 -27
  157. package/src/types/ai/index.ts +0 -11
  158. package/src/types/ai/types.ts +0 -67
  159. package/src/types/homeassistant/automations.ts +0 -43
  160. package/src/types/homeassistant/calendars.ts +0 -26
  161. package/src/types/homeassistant/config.ts +0 -14
  162. package/src/types/homeassistant/entities.ts +0 -14
  163. package/src/types/homeassistant/errors.ts +0 -33
  164. package/src/types/homeassistant/haConfig.ts +0 -7
  165. package/src/types/homeassistant/history.ts +0 -13
  166. package/src/types/homeassistant/index.ts +0 -46
  167. package/src/types/homeassistant/request.ts +0 -6
  168. package/src/types/homeassistant/serviceCall.ts +0 -18
  169. package/src/types/homeassistant/states.ts +0 -58
  170. package/src/types/homeassistant/updates.ts +0 -28
  171. package/src/types/index.ts +0 -12
  172. package/src/types/omada/activeClientInfo.ts +0 -17
  173. package/src/types/omada/clientActivity.ts +0 -32
  174. package/src/types/omada/clientBlockStatus.ts +0 -11
  175. package/src/types/omada/clientPastConnection.ts +0 -82
  176. package/src/types/omada/index.ts +0 -14
  177. package/src/types/omada/omadaApiResponse.ts +0 -5
  178. package/src/types/omada/omadaClientInfo.ts +0 -11
  179. package/src/types/omada/omadaDeviceInfo.ts +0 -11
  180. package/src/types/omada/omadaDeviceStats.ts +0 -31
  181. package/src/types/omada/omadaSiteSummary.ts +0 -5
  182. package/src/types/omada/oswStackDetail.ts +0 -11
  183. package/src/types/omada/paginatedResult.ts +0 -6
  184. package/src/types/omada/rateLimitProfile.ts +0 -58
  185. package/src/types/omada/threatInfo.ts +0 -38
  186. package/src/types/omada/tokenResult.ts +0 -6
  187. package/src/types.ts +0 -2
  188. package/src/utils/config-validations.ts +0 -68
  189. package/src/utils/jwt.ts +0 -112
  190. package/src/utils/logger.ts +0 -121
  191. package/src/utils/plainText.ts +0 -48
  192. package/src/utils/redact.ts +0 -59
  193. package/src/utils/sanitizeError.ts +0 -18
  194. package/src/utils/tlsDispatcher.ts +0 -69
  195. package/src/version.ts +0 -5
  196. package/tests/bdd/automation-creation.spec.ts +0 -214
  197. package/tests/bdd/automation-management.spec.ts +0 -267
  198. package/tests/bdd/calendar.spec.ts +0 -190
  199. package/tests/bdd/configuration.spec.ts +0 -283
  200. package/tests/bdd/device-control.spec.ts +0 -344
  201. package/tests/bdd/entity-listing.spec.ts +0 -208
  202. package/tests/bdd/entity-search.spec.ts +0 -183
  203. package/tests/bdd/history-retrieval.spec.ts +0 -195
  204. package/tests/bdd/notifications.spec.ts +0 -238
  205. package/tests/bdd/service-calls.spec.ts +0 -144
  206. package/tests/bdd/state-retrieval.spec.ts +0 -204
  207. package/tests/bench/hotpaths.bench.ts +0 -68
  208. package/tests/config.test.ts +0 -355
  209. package/tests/features/automation-creation.feature +0 -36
  210. package/tests/features/automation-management.feature +0 -54
  211. package/tests/features/calendar.feature +0 -37
  212. package/tests/features/configuration.feature +0 -70
  213. package/tests/features/device-control.feature +0 -100
  214. package/tests/features/entity-listing.feature +0 -51
  215. package/tests/features/entity-search.feature +0 -44
  216. package/tests/features/history-retrieval.feature +0 -38
  217. package/tests/features/notifications.feature +0 -37
  218. package/tests/features/service-calls.feature +0 -35
  219. package/tests/features/state-retrieval.feature +0 -37
  220. package/tests/fixtures/openapi-default-slice.json +0 -290
  221. package/tests/haClient/automationTrace.test.ts +0 -59
  222. package/tests/haClient/automations.test.ts +0 -149
  223. package/tests/haClient/config.test.ts +0 -117
  224. package/tests/haClient/entities.test.ts +0 -188
  225. package/tests/haClient/events.test.ts +0 -455
  226. package/tests/haClient/eventsTls.test.ts +0 -52
  227. package/tests/haClient/history.test.ts +0 -228
  228. package/tests/haClient/index.test.ts +0 -243
  229. package/tests/haClient/request.test.ts +0 -217
  230. package/tests/haClient/services.test.ts +0 -120
  231. package/tests/haClient/states.test.ts +0 -119
  232. package/tests/haClient/updates.test.ts +0 -193
  233. package/tests/index.test.ts +0 -160
  234. package/tests/integration/homeassistant.test.ts +0 -144
  235. package/tests/integration/omada.test.ts +0 -167
  236. package/tests/integration/server.test.ts +0 -151
  237. package/tests/localAI/index.test.ts +0 -177
  238. package/tests/localAI/ollama.test.ts +0 -187
  239. package/tests/localAI/openai.test.ts +0 -257
  240. package/tests/omadaClient/actions.test.ts +0 -161
  241. package/tests/omadaClient/clientBlocks.test.ts +0 -170
  242. package/tests/omadaClient/ntp.test.ts +0 -381
  243. package/tests/omadaClient/site.test.ts +0 -37
  244. package/tests/ownerQuestions/service.test.ts +0 -499
  245. package/tests/ownerQuestions/tools.test.ts +0 -186
  246. package/tests/permissions/index.test.ts +0 -217
  247. package/tests/resources/index.test.ts +0 -288
  248. package/tests/scripts/generateJwt.test.ts +0 -44
  249. package/tests/security/accessControl.test.ts +0 -223
  250. package/tests/security/authentication.test.ts +0 -117
  251. package/tests/security/configuration.test.ts +0 -170
  252. package/tests/security/cryptoTransport.test.ts +0 -172
  253. package/tests/security/dependencies.test.ts +0 -152
  254. package/tests/security/injection.test.ts +0 -117
  255. package/tests/security/insecureDesign.test.ts +0 -121
  256. package/tests/security/logging.test.ts +0 -131
  257. package/tests/security/resourceConsumption.test.ts +0 -142
  258. package/tests/server/auth.test.ts +0 -249
  259. package/tests/server/chatFace.test.ts +0 -320
  260. package/tests/server/common.test.ts +0 -84
  261. package/tests/server/http.test.ts +0 -561
  262. package/tests/server/instructions.test.ts +0 -152
  263. package/tests/server/memoizeJsonSchema.test.ts +0 -71
  264. package/tests/server/stdio.test.ts +0 -99
  265. package/tests/server/stream.test.ts +0 -285
  266. package/tests/server/toolVisibility.http.test.ts +0 -111
  267. package/tests/server/toolVisibility.test.ts +0 -129
  268. package/tests/tools/ai/analyzeSensors.test.ts +0 -227
  269. package/tests/tools/common.test.ts +0 -123
  270. package/tests/tools/homeassistant/handlers.test.ts +0 -872
  271. package/tests/tools/infra/invokeAction.test.ts +0 -280
  272. package/tests/tools/omada/graph.test.ts +0 -385
  273. package/tests/tools/omada/handlers.test.ts +0 -897
  274. package/tests/tools/omada/namespace.test.ts +0 -483
  275. package/tests/tools/registry.test.ts +0 -168
  276. package/tests/utils/config-validations.test.ts +0 -352
  277. package/tests/utils/jwt.test.ts +0 -115
  278. package/tests/utils/logger.test.ts +0 -327
  279. package/tests/utils/redact.test.ts +0 -105
  280. package/tsconfig.json +0 -24
  281. 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
- });