@digital-alchemy/hass 25.11.17-beta.0 → 25.11.27
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/dist/helpers/fetch/service-list.d.mts +86 -5
- package/dist/helpers/id-by.d.mts +1 -1
- package/dist/helpers/interfaces.d.mts +1 -1
- package/dist/helpers/interfaces.mjs.map +1 -1
- package/dist/mock_assistant/main.mjs +3 -3
- package/dist/mock_assistant/main.mjs.map +1 -1
- package/dist/mock_assistant/mock-assistant.module.mjs +2 -2
- package/dist/mock_assistant/mock-assistant.module.mjs.map +1 -1
- package/dist/mock_assistant/services/fixtures.service.mjs +1 -1
- package/dist/mock_assistant/services/fixtures.service.mjs.map +1 -1
- package/dist/mock_assistant/services/websocket-api.service.mjs +1 -1
- package/dist/mock_assistant/services/websocket-api.service.mjs.map +1 -1
- package/dist/services/config.service.mjs +2 -1
- package/dist/services/config.service.mjs.map +1 -1
- package/dist/services/diagnostics.service.mjs +1 -1
- package/dist/services/diagnostics.service.mjs.map +1 -1
- package/dist/services/id-by.service.mjs +4 -1
- package/dist/services/id-by.service.mjs.map +1 -1
- package/dist/services/internal.service.mjs +3 -3
- package/dist/services/internal.service.mjs.map +1 -1
- package/dist/services/reference.service.mjs +3 -2
- package/dist/services/reference.service.mjs.map +1 -1
- package/dist/services/websocket-api.service.mjs +1 -1
- package/dist/services/websocket-api.service.mjs.map +1 -1
- package/dist/testing/area.spec.mjs +143 -2
- package/dist/testing/area.spec.mjs.map +1 -1
- package/dist/testing/call-proxy.spec.d.mts +1 -0
- package/dist/testing/call-proxy.spec.mjs +204 -0
- package/dist/testing/call-proxy.spec.mjs.map +1 -0
- package/dist/testing/config.spec.mjs +1 -1
- package/dist/testing/config.spec.mjs.map +1 -1
- package/dist/testing/conversation.spec.mjs +1 -1
- package/dist/testing/conversation.spec.mjs.map +1 -1
- package/dist/testing/id-by.spec.mjs +38 -0
- package/dist/testing/id-by.spec.mjs.map +1 -1
- package/dist/testing/ref-by.spec.mjs +4 -3
- package/dist/testing/ref-by.spec.mjs.map +1 -1
- package/dist/testing/websocket.spec.mjs +25 -0
- package/dist/testing/websocket.spec.mjs.map +1 -1
- package/package.json +7 -6
- package/src/helpers/fetch/service-list.mts +89 -5
- package/src/helpers/id-by.mts +1 -0
- package/src/helpers/interfaces.mts +2 -1
- package/src/mock_assistant/main.mts +4 -3
- package/src/mock_assistant/mock-assistant.module.mts +3 -2
- package/src/mock_assistant/services/fixtures.service.mts +2 -1
- package/src/mock_assistant/services/websocket-api.service.mts +2 -1
- package/src/services/config.service.mts +2 -1
- package/src/services/diagnostics.service.mts +2 -1
- package/src/services/id-by.service.mts +4 -1
- package/src/services/internal.service.mts +4 -3
- package/src/services/reference.service.mts +3 -2
- package/src/services/websocket-api.service.mts +2 -1
- package/src/testing/area.spec.mts +168 -3
- package/src/testing/call-proxy.spec.mts +241 -0
- package/src/testing/config.spec.mts +2 -1
- package/src/testing/conversation.spec.mts +1 -1
- package/src/testing/id-by.spec.mts +50 -0
- package/src/testing/ref-by.spec.mts +4 -3
- package/src/testing/websocket.spec.mts +33 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import type { HassServiceDTO } from "../helpers/index.mts";
|
|
2
|
+
import { hassTestRunner } from "../mock_assistant/index.mts";
|
|
3
|
+
|
|
4
|
+
afterEach(async () => {
|
|
5
|
+
await hassTestRunner.teardown();
|
|
6
|
+
vi.restoreAllMocks();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
describe("CallProxy", () => {
|
|
10
|
+
describe("pauseMessages", () => {
|
|
11
|
+
it("should return undefined when pauseMessages is true", async () => {
|
|
12
|
+
expect.assertions(2);
|
|
13
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
14
|
+
const spy = vi.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
|
|
15
|
+
hass.socket.pauseMessages = true;
|
|
16
|
+
|
|
17
|
+
lifecycle.onReady(async () => {
|
|
18
|
+
const result = await hass.call.switch.turn_on({
|
|
19
|
+
entity_id: "switch.porch_light",
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
expect(result).toBeUndefined();
|
|
23
|
+
const callServiceCalls = spy.mock.calls.filter(
|
|
24
|
+
call =>
|
|
25
|
+
call[0]?.type === "call_service" &&
|
|
26
|
+
call[0]?.domain === "switch" &&
|
|
27
|
+
call[0]?.service === "turn_on",
|
|
28
|
+
);
|
|
29
|
+
expect(callServiceCalls).toHaveLength(0);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should call sendMessage when pauseMessages is false", async () => {
|
|
35
|
+
expect.assertions(1);
|
|
36
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
37
|
+
const spy = vi.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
|
|
38
|
+
hass.socket.pauseMessages = false;
|
|
39
|
+
|
|
40
|
+
lifecycle.onReady(async () => {
|
|
41
|
+
await hass.call.switch.turn_on({
|
|
42
|
+
entity_id: "switch.porch_light",
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(spy).toHaveBeenCalledWith(
|
|
46
|
+
expect.objectContaining({
|
|
47
|
+
domain: "switch",
|
|
48
|
+
service: "turn_on",
|
|
49
|
+
service_data: {
|
|
50
|
+
entity_id: "switch.porch_light",
|
|
51
|
+
},
|
|
52
|
+
type: "call_service",
|
|
53
|
+
}),
|
|
54
|
+
true,
|
|
55
|
+
);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe("return_response", () => {
|
|
62
|
+
it("should include return_response in payload when service has response.optional", async () => {
|
|
63
|
+
expect.assertions(1);
|
|
64
|
+
const mockServices: HassServiceDTO[] = [
|
|
65
|
+
{
|
|
66
|
+
domain: "switch",
|
|
67
|
+
services: {
|
|
68
|
+
turn_on: {
|
|
69
|
+
description: "Test service",
|
|
70
|
+
fields: {},
|
|
71
|
+
name: "turn_on",
|
|
72
|
+
response: { optional: true },
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
await hassTestRunner.run(({ lifecycle, hass, mock_assistant }) => {
|
|
79
|
+
mock_assistant.services.loadFixtures(mockServices);
|
|
80
|
+
vi.spyOn(hass.fetch, "listServices").mockImplementation(async () => mockServices);
|
|
81
|
+
|
|
82
|
+
const spy = vi
|
|
83
|
+
.spyOn(hass.socket, "sendMessage")
|
|
84
|
+
.mockImplementation(async () => ({ response: { success: true } }));
|
|
85
|
+
|
|
86
|
+
lifecycle.onReady(async () => {
|
|
87
|
+
await hass.call.switch.turn_on({ entity_id: "switch.bedroom_lamp" });
|
|
88
|
+
|
|
89
|
+
expect(spy).toHaveBeenCalledWith(
|
|
90
|
+
expect.objectContaining({
|
|
91
|
+
domain: "switch",
|
|
92
|
+
return_response: true,
|
|
93
|
+
service: "turn_on",
|
|
94
|
+
type: "call_service",
|
|
95
|
+
}),
|
|
96
|
+
true,
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should return response when service returns a response", async () => {
|
|
103
|
+
expect.assertions(1);
|
|
104
|
+
const mockServices: HassServiceDTO[] = [
|
|
105
|
+
{
|
|
106
|
+
domain: "switch",
|
|
107
|
+
services: {
|
|
108
|
+
turn_on: {
|
|
109
|
+
description: "Test service",
|
|
110
|
+
fields: {},
|
|
111
|
+
name: "turn_on",
|
|
112
|
+
response: { optional: true },
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
await hassTestRunner.run(({ lifecycle, hass, mock_assistant }) => {
|
|
119
|
+
mock_assistant.services.loadFixtures(mockServices);
|
|
120
|
+
vi.spyOn(hass.fetch, "listServices").mockImplementation(async () => mockServices);
|
|
121
|
+
|
|
122
|
+
const mockResponse = { data: "test_response" };
|
|
123
|
+
vi.spyOn(hass.socket, "sendMessage").mockImplementation(async () => ({
|
|
124
|
+
response: mockResponse,
|
|
125
|
+
}));
|
|
126
|
+
|
|
127
|
+
lifecycle.onReady(async () => {
|
|
128
|
+
const result = await hass.call.switch.turn_on({ entity_id: "switch.bedroom_lamp" });
|
|
129
|
+
expect(result).toEqual(mockResponse);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should return undefined when service does not return a response", async () => {
|
|
135
|
+
expect.assertions(1);
|
|
136
|
+
const mockServices: HassServiceDTO[] = [
|
|
137
|
+
{
|
|
138
|
+
domain: "switch",
|
|
139
|
+
services: {
|
|
140
|
+
turn_on: {
|
|
141
|
+
description: "Test service",
|
|
142
|
+
fields: {},
|
|
143
|
+
name: "turn_on",
|
|
144
|
+
response: { optional: true },
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
await hassTestRunner.run(({ lifecycle, hass, mock_assistant }) => {
|
|
151
|
+
mock_assistant.services.loadFixtures(mockServices);
|
|
152
|
+
vi.spyOn(hass.fetch, "listServices").mockImplementation(async () => mockServices);
|
|
153
|
+
|
|
154
|
+
vi.spyOn(hass.socket, "sendMessage").mockImplementation(async () => ({}));
|
|
155
|
+
|
|
156
|
+
lifecycle.onReady(async () => {
|
|
157
|
+
const result = await hass.call.switch.turn_on({ entity_id: "switch.bedroom_lamp" });
|
|
158
|
+
expect(result).toBeUndefined();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe("early access", () => {
|
|
165
|
+
it("should call console.trace when accessed before load with LOG_LEVEL trace", async () => {
|
|
166
|
+
expect.assertions(1);
|
|
167
|
+
const traceSpy = vi.spyOn(console, "trace").mockImplementation(() => {});
|
|
168
|
+
|
|
169
|
+
await hassTestRunner.configure({ boilerplate: { LOG_LEVEL: "trace" } }).run(({ hass }) => {
|
|
170
|
+
hass.call.switch;
|
|
171
|
+
});
|
|
172
|
+
expect(traceSpy).toHaveBeenCalledWith(`hass.call`);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("should not log error when accessed before load with LOG_LEVEL not trace", async () => {
|
|
176
|
+
expect.assertions(2);
|
|
177
|
+
await hassTestRunner
|
|
178
|
+
.configure({ boilerplate: { LOG_LEVEL: "warn" } })
|
|
179
|
+
.run(({ lifecycle, hass, internal }) => {
|
|
180
|
+
const logger = internal.boilerplate.logger.getBaseLogger();
|
|
181
|
+
const errorSpy = vi.spyOn(logger, "error").mockImplementation(() => {});
|
|
182
|
+
const traceSpy = vi.spyOn(console, "trace").mockImplementation(() => {});
|
|
183
|
+
|
|
184
|
+
// Access hass.call in onPreInit, which runs before onBootstrap (where services load)
|
|
185
|
+
hass.call.switch;
|
|
186
|
+
|
|
187
|
+
lifecycle.onReady(() => {
|
|
188
|
+
expect(errorSpy).not.toHaveBeenCalled();
|
|
189
|
+
expect(traceSpy).not.toHaveBeenCalled();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe("proxy methods", () => {
|
|
196
|
+
it("should return true for has when domain exists", async () => {
|
|
197
|
+
expect.assertions(1);
|
|
198
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
199
|
+
lifecycle.onReady(() => {
|
|
200
|
+
expect("switch" in hass.call).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("should return false for has when domain does not exist", async () => {
|
|
206
|
+
expect.assertions(1);
|
|
207
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
208
|
+
lifecycle.onReady(() => {
|
|
209
|
+
expect("nonexistent_domain" in hass.call).toBe(false);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it("should return ownKeys with all domain keys", async () => {
|
|
215
|
+
expect.assertions(2);
|
|
216
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
217
|
+
lifecycle.onReady(() => {
|
|
218
|
+
const keys = Object.keys(hass.call);
|
|
219
|
+
expect(keys).toContain("switch");
|
|
220
|
+
expect(keys.length).toBeGreaterThan(0);
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("should return false when trying to set a property", async () => {
|
|
226
|
+
expect.assertions(1);
|
|
227
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
228
|
+
lifecycle.onReady(() => {
|
|
229
|
+
try {
|
|
230
|
+
// @ts-expect-error testing
|
|
231
|
+
hass.call.test_domain = {};
|
|
232
|
+
} catch {
|
|
233
|
+
// Some environments throw, others return false
|
|
234
|
+
}
|
|
235
|
+
// Verify the property was not set
|
|
236
|
+
expect("test_domain" in hass.call).toBe(false);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
});
|
|
@@ -18,7 +18,7 @@ describe("Conversation Service", () => {
|
|
|
18
18
|
},
|
|
19
19
|
];
|
|
20
20
|
|
|
21
|
-
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
21
|
+
await hassTestRunner.bootLibrariesFirst().run(({ lifecycle, hass }) => {
|
|
22
22
|
const spy = vi
|
|
23
23
|
.spyOn(hass.socket, "sendMessage")
|
|
24
24
|
.mockImplementation(async () => ({ agents: mockAgents }));
|
|
@@ -137,6 +137,56 @@ describe("enabled entities", () => {
|
|
|
137
137
|
});
|
|
138
138
|
});
|
|
139
139
|
});
|
|
140
|
+
|
|
141
|
+
describe("unique_id", () => {
|
|
142
|
+
it("find entity by unique_id without platform", async () => {
|
|
143
|
+
expect.assertions(1);
|
|
144
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
145
|
+
lifecycle.onReady(() => {
|
|
146
|
+
const entity = hass.idBy.unique_id(
|
|
147
|
+
"e1806fdc93296bbd5ab42967003cd38729ff9ba6cfeefc3e15a03ad01ac894fe",
|
|
148
|
+
);
|
|
149
|
+
expect(entity).toBe("sensor.magic");
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("find entity by unique_id with matching platform", async () => {
|
|
155
|
+
expect.assertions(1);
|
|
156
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
157
|
+
lifecycle.onReady(() => {
|
|
158
|
+
const entity = hass.idBy.unique_id(
|
|
159
|
+
"e1806fdc93296bbd5ab42967003cd38729ff9ba6cfeefc3e15a03ad01ac894fe",
|
|
160
|
+
"synapse",
|
|
161
|
+
);
|
|
162
|
+
expect(entity).toBe("sensor.magic");
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("find entity by unique_id with non-matching platform", async () => {
|
|
168
|
+
expect.assertions(1);
|
|
169
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
170
|
+
lifecycle.onReady(() => {
|
|
171
|
+
const entity = hass.idBy.unique_id(
|
|
172
|
+
"e1806fdc93296bbd5ab42967003cd38729ff9ba6cfeefc3e15a03ad01ac894fe",
|
|
173
|
+
"sun",
|
|
174
|
+
);
|
|
175
|
+
expect(entity).toBeUndefined();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("find entity by unique_id with different platform", async () => {
|
|
181
|
+
expect.assertions(1);
|
|
182
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
183
|
+
lifecycle.onReady(() => {
|
|
184
|
+
const entity = hass.idBy.unique_id("5622d76001a335e3ea893c4d60d31b3d-next_dawn", "sun");
|
|
185
|
+
expect(entity).toBe("sensor.sun_next_dawn");
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
140
190
|
});
|
|
141
191
|
|
|
142
192
|
describe("disabled entities", () => {
|
|
@@ -174,7 +174,7 @@ describe("References", () => {
|
|
|
174
174
|
describe("functionality", () => {
|
|
175
175
|
describe("operators", () => {
|
|
176
176
|
it("has", async () => {
|
|
177
|
-
expect.assertions(
|
|
177
|
+
expect.assertions(18);
|
|
178
178
|
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
179
179
|
lifecycle.onReady(() => {
|
|
180
180
|
const entity = hass.refBy.id("switch.bedroom_lamp");
|
|
@@ -184,7 +184,9 @@ describe("References", () => {
|
|
|
184
184
|
"attributes",
|
|
185
185
|
"entity_id",
|
|
186
186
|
"history",
|
|
187
|
-
"
|
|
187
|
+
"last_changed",
|
|
188
|
+
"last_reported",
|
|
189
|
+
"last_updated",
|
|
188
190
|
"nextState",
|
|
189
191
|
"once",
|
|
190
192
|
"onStateFor",
|
|
@@ -219,7 +221,6 @@ describe("References", () => {
|
|
|
219
221
|
"last_updated",
|
|
220
222
|
"context",
|
|
221
223
|
"history",
|
|
222
|
-
"last",
|
|
223
224
|
"nextState",
|
|
224
225
|
"once",
|
|
225
226
|
"onStateFor",
|
|
@@ -271,6 +271,39 @@ describe("Websocket", () => {
|
|
|
271
271
|
});
|
|
272
272
|
});
|
|
273
273
|
|
|
274
|
+
describe("Auth Message Handlers", () => {
|
|
275
|
+
it("should handle auth_invalid message and exit", async () => {
|
|
276
|
+
expect.assertions(3);
|
|
277
|
+
const exitSpy = vi
|
|
278
|
+
.spyOn(process, "exit")
|
|
279
|
+
// @ts-expect-error testing
|
|
280
|
+
.mockImplementation(() => {});
|
|
281
|
+
|
|
282
|
+
await hassTestRunner.run(({ lifecycle, hass, logger }) => {
|
|
283
|
+
const fatalSpy = vi.spyOn(logger, "fatal").mockImplementation(() => {});
|
|
284
|
+
|
|
285
|
+
lifecycle.onReady(async () => {
|
|
286
|
+
const message = {
|
|
287
|
+
id: 1,
|
|
288
|
+
type: "auth_invalid" as const,
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
await hass.socket.onMessage(message);
|
|
292
|
+
|
|
293
|
+
expect(exitSpy).toHaveBeenCalled();
|
|
294
|
+
expect(hass.socket.connectionState).toBe("invalid");
|
|
295
|
+
expect(fatalSpy).toHaveBeenCalledWith(
|
|
296
|
+
expect.objectContaining({
|
|
297
|
+
message,
|
|
298
|
+
name: expect.any(Function),
|
|
299
|
+
}),
|
|
300
|
+
"received auth invalid {connecting} => {invalid}",
|
|
301
|
+
);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
274
307
|
describe("Subscription Registry", () => {
|
|
275
308
|
it("should prevent duplicate subscriptions", async () => {
|
|
276
309
|
expect.assertions(1);
|