@digital-alchemy/hass 25.3.2 → 25.5.2
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/hass.module.d.mts +23 -1
- package/dist/hass.module.mjs +26 -1
- package/dist/hass.module.mjs.map +1 -1
- package/dist/helpers/fetch.mjs +0 -1
- package/dist/helpers/fetch.mjs.map +1 -1
- package/dist/helpers/utility.d.mts +1 -0
- package/dist/helpers/utility.mjs +4 -0
- package/dist/helpers/utility.mjs.map +1 -1
- package/dist/mock_assistant/mock-assistant.module.d.mts +22 -0
- package/dist/services/area.service.mjs +9 -1
- package/dist/services/area.service.mjs.map +1 -1
- package/dist/services/call-proxy.service.mjs +10 -2
- package/dist/services/call-proxy.service.mjs.map +1 -1
- package/dist/services/config.service.mjs +15 -13
- package/dist/services/config.service.mjs.map +1 -1
- package/dist/services/device.service.mjs +3 -1
- package/dist/services/device.service.mjs.map +1 -1
- package/dist/services/diagnostics.service.d.mts +26 -0
- package/dist/services/diagnostics.service.mjs +41 -0
- package/dist/services/diagnostics.service.mjs.map +1 -0
- package/dist/services/entity.service.mjs +12 -1
- package/dist/services/entity.service.mjs.map +1 -1
- package/dist/services/fetch-api.service.mjs +8 -2
- package/dist/services/fetch-api.service.mjs.map +1 -1
- package/dist/services/floor.service.mjs +3 -1
- package/dist/services/floor.service.mjs.map +1 -1
- package/dist/services/id-by.service.d.mts +1 -1
- package/dist/services/id-by.service.mjs +20 -13
- package/dist/services/id-by.service.mjs.map +1 -1
- package/dist/services/index.d.mts +1 -0
- package/dist/services/index.mjs +1 -0
- package/dist/services/index.mjs.map +1 -1
- package/dist/services/label.service.mjs +3 -1
- package/dist/services/label.service.mjs.map +1 -1
- package/dist/services/reference.service.mjs +15 -4
- package/dist/services/reference.service.mjs.map +1 -1
- package/dist/services/registry.service.mjs +8 -8
- package/dist/services/registry.service.mjs.map +1 -1
- package/dist/services/websocket-api.service.mjs +10 -2
- package/dist/services/websocket-api.service.mjs.map +1 -1
- package/dist/services/zone.service.mjs +3 -1
- package/dist/services/zone.service.mjs.map +1 -1
- package/dist/testing/area.spec.mjs +141 -132
- package/dist/testing/area.spec.mjs.map +1 -1
- package/dist/testing/device.spec.mjs +17 -0
- package/dist/testing/device.spec.mjs.map +1 -1
- package/dist/testing/entity.spec.mjs +167 -0
- package/dist/testing/entity.spec.mjs.map +1 -1
- package/dist/testing/fetch.spec.d.mts +1 -0
- package/dist/testing/fetch.spec.mjs +45 -0
- package/dist/testing/fetch.spec.mjs.map +1 -0
- package/dist/testing/floor.spec.mjs +17 -0
- package/dist/testing/floor.spec.mjs.map +1 -1
- package/dist/testing/id-by.spec.mjs +93 -5
- package/dist/testing/id-by.spec.mjs.map +1 -1
- package/dist/testing/label.spec.mjs +17 -0
- package/dist/testing/label.spec.mjs.map +1 -1
- package/dist/testing/ref-by.spec.mjs +1 -1
- package/dist/testing/ref-by.spec.mjs.map +1 -1
- package/dist/testing/zone.spec.mjs +24 -5
- package/dist/testing/zone.spec.mjs.map +1 -1
- package/package.json +35 -31
- package/src/hass.module.mts +29 -0
- package/src/helpers/fetch.mts +1 -1
- package/src/helpers/utility.mts +5 -0
- package/src/services/area.service.mts +9 -0
- package/src/services/call-proxy.service.mts +16 -9
- package/src/services/config.service.mts +21 -16
- package/src/services/device.service.mts +3 -0
- package/src/services/diagnostics.service.mts +45 -0
- package/src/services/entity.service.mts +12 -0
- package/src/services/fetch-api.service.mts +11 -2
- package/src/services/floor.service.mts +3 -0
- package/src/services/id-by.service.mts +25 -15
- package/src/services/index.mts +1 -0
- package/src/services/label.service.mts +3 -0
- package/src/services/reference.service.mts +15 -3
- package/src/services/registry.service.mts +8 -8
- package/src/services/websocket-api.service.mts +10 -2
- package/src/services/zone.service.mts +3 -0
- package/src/testing/area.spec.mts +153 -140
- package/src/testing/device.spec.mts +22 -0
- package/src/testing/entity.spec.mts +201 -0
- package/src/testing/fetch.spec.mts +54 -0
- package/src/testing/floor.spec.mts +22 -0
- package/src/testing/id-by.spec.mts +100 -5
- package/src/testing/label.spec.mts +22 -0
- package/src/testing/ref-by.spec.mts +1 -1
- package/src/testing/zone.spec.mts +29 -5
|
@@ -1,187 +1,200 @@
|
|
|
1
1
|
import { sleep } from "@digital-alchemy/core";
|
|
2
|
+
import { subscribe } from "diagnostics_channel";
|
|
2
3
|
|
|
3
4
|
import { AREA_REGISTRY_UPDATED, AreaDetails } from "../helpers/index.mts";
|
|
4
5
|
import { hassTestRunner, INTERNAL_MESSAGE } from "../mock_assistant/index.mts";
|
|
5
6
|
import { TAreaId } from "../user.mts";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
const EXAMPLE_AREA = {
|
|
9
|
+
area_id: "empty_area" as TAreaId,
|
|
10
|
+
floor_id: null,
|
|
11
|
+
icon: null,
|
|
12
|
+
labels: [],
|
|
13
|
+
name: "Empty Area",
|
|
14
|
+
picture: null,
|
|
15
|
+
} as AreaDetails;
|
|
16
|
+
|
|
17
|
+
afterEach(async () => {
|
|
18
|
+
await hassTestRunner.teardown();
|
|
19
|
+
vi.restoreAllMocks();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("Lifecycle", () => {
|
|
23
|
+
it("should force values to be available before ready", async () => {
|
|
24
|
+
expect.assertions(1);
|
|
25
|
+
|
|
26
|
+
const app = await hassTestRunner.run(({ mock_assistant, lifecycle, hass }) => {
|
|
27
|
+
const spy = vi.fn();
|
|
28
|
+
mock_assistant.socket.connection.on(INTERNAL_MESSAGE, spy);
|
|
29
|
+
lifecycle.onReady(async () => {
|
|
30
|
+
await hass.area.list();
|
|
31
|
+
expect(spy).toHaveBeenCalledWith(
|
|
32
|
+
expect.objectContaining({ type: "config/area_registry/list" }),
|
|
33
|
+
);
|
|
34
|
+
}, -1);
|
|
35
|
+
});
|
|
36
|
+
await app.teardown();
|
|
20
37
|
});
|
|
38
|
+
});
|
|
21
39
|
|
|
22
|
-
|
|
23
|
-
|
|
40
|
+
describe("API", () => {
|
|
41
|
+
describe("Formatting", () => {
|
|
42
|
+
it("should call list properly", async () => {
|
|
24
43
|
expect.assertions(1);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const spy = vi.fn();
|
|
28
|
-
mock_assistant.socket.connection.on(INTERNAL_MESSAGE, spy);
|
|
44
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
45
|
+
const spy = vi.spyOn(hass.socket, "sendMessage").mockImplementation(async () => []);
|
|
29
46
|
lifecycle.onReady(async () => {
|
|
30
47
|
await hass.area.list();
|
|
31
48
|
expect(spy).toHaveBeenCalledWith(
|
|
32
49
|
expect.objectContaining({ type: "config/area_registry/list" }),
|
|
33
50
|
);
|
|
34
|
-
}
|
|
51
|
+
});
|
|
35
52
|
});
|
|
36
|
-
await app.teardown();
|
|
37
53
|
});
|
|
38
|
-
});
|
|
39
54
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
it("should call update properly", async () => {
|
|
56
|
+
expect.assertions(1);
|
|
57
|
+
await hassTestRunner.run(({ lifecycle, hass, event }) => {
|
|
58
|
+
const spy = vi.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
|
|
59
|
+
lifecycle.onReady(async () => {
|
|
60
|
+
setImmediate(() => event.emit(AREA_REGISTRY_UPDATED));
|
|
61
|
+
await hass.area.update(EXAMPLE_AREA);
|
|
62
|
+
|
|
63
|
+
expect(spy).toHaveBeenCalledWith({
|
|
64
|
+
type: "config/area_registry/update",
|
|
65
|
+
...EXAMPLE_AREA,
|
|
51
66
|
});
|
|
52
67
|
});
|
|
53
68
|
});
|
|
69
|
+
});
|
|
54
70
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
it("should debounce updates properly", async () => {
|
|
72
|
+
expect.assertions(1);
|
|
73
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
74
|
+
vi.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
|
|
75
|
+
let counter = 0;
|
|
76
|
+
hass.events.onAreaRegistryUpdate(() => counter++);
|
|
77
|
+
lifecycle.onReady(async () => {
|
|
78
|
+
setImmediate(async () => {
|
|
79
|
+
hass.socket.socketEvents.emit("area_registry_updated");
|
|
80
|
+
await sleep(5);
|
|
81
|
+
hass.socket.socketEvents.emit("area_registry_updated");
|
|
82
|
+
await sleep(5);
|
|
83
|
+
hass.socket.socketEvents.emit("area_registry_updated");
|
|
84
|
+
await sleep(75);
|
|
85
|
+
hass.socket.socketEvents.emit("area_registry_updated");
|
|
69
86
|
});
|
|
87
|
+
await sleep(200);
|
|
88
|
+
expect(counter).toBe(2);
|
|
70
89
|
});
|
|
71
90
|
});
|
|
91
|
+
});
|
|
72
92
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
hass.socket.socketEvents.emit("area_registry_updated");
|
|
84
|
-
await sleep(5);
|
|
85
|
-
hass.socket.socketEvents.emit("area_registry_updated");
|
|
86
|
-
await sleep(75);
|
|
87
|
-
hass.socket.socketEvents.emit("area_registry_updated");
|
|
88
|
-
});
|
|
89
|
-
await sleep(200);
|
|
90
|
-
expect(counter).toBe(2);
|
|
93
|
+
it("should publish diagnostics on area registry update", async () => {
|
|
94
|
+
expect.assertions(1);
|
|
95
|
+
hassTestRunner.configure({ hass: { EMIT_DIAGNOSTICS: true } });
|
|
96
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
97
|
+
vi.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
|
|
98
|
+
const spy = vi.fn();
|
|
99
|
+
subscribe(hass.diagnostics.area.registry_update.name, spy);
|
|
100
|
+
lifecycle.onReady(async () => {
|
|
101
|
+
setImmediate(async () => {
|
|
102
|
+
hass.socket.socketEvents.emit("area_registry_updated");
|
|
91
103
|
});
|
|
104
|
+
await sleep(100);
|
|
105
|
+
expect(spy).toHaveBeenCalledWith(
|
|
106
|
+
expect.objectContaining({ ms: expect.any(Number) }),
|
|
107
|
+
hass.diagnostics.area.registry_update.name,
|
|
108
|
+
);
|
|
92
109
|
});
|
|
93
110
|
});
|
|
111
|
+
});
|
|
94
112
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
area_id: "empty_area",
|
|
108
|
-
type: "config/area_registry/delete",
|
|
109
|
-
});
|
|
113
|
+
it("should call delete properly", async () => {
|
|
114
|
+
expect.assertions(1);
|
|
115
|
+
|
|
116
|
+
await hassTestRunner.run(({ lifecycle, hass, event }) => {
|
|
117
|
+
const spy = vi.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
|
|
118
|
+
lifecycle.onReady(async () => {
|
|
119
|
+
setImmediate(() => event.emit(AREA_REGISTRY_UPDATED));
|
|
120
|
+
await hass.area.delete(EXAMPLE_AREA.area_id);
|
|
121
|
+
|
|
122
|
+
expect(spy).toHaveBeenCalledWith({
|
|
123
|
+
area_id: "empty_area",
|
|
124
|
+
type: "config/area_registry/delete",
|
|
110
125
|
});
|
|
111
126
|
});
|
|
112
127
|
});
|
|
128
|
+
});
|
|
113
129
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
type: "config/area_registry/create",
|
|
126
|
-
...EXAMPLE_AREA,
|
|
127
|
-
});
|
|
130
|
+
it("should call create properly", async () => {
|
|
131
|
+
expect.assertions(1);
|
|
132
|
+
await hassTestRunner.run(({ lifecycle, hass, event }) => {
|
|
133
|
+
const spy = vi.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
|
|
134
|
+
lifecycle.onReady(async () => {
|
|
135
|
+
setImmediate(() => event.emit(AREA_REGISTRY_UPDATED));
|
|
136
|
+
await hass.area.create(EXAMPLE_AREA);
|
|
137
|
+
|
|
138
|
+
expect(spy).toHaveBeenCalledWith({
|
|
139
|
+
type: "config/area_registry/create",
|
|
140
|
+
...EXAMPLE_AREA,
|
|
128
141
|
});
|
|
129
142
|
});
|
|
130
143
|
});
|
|
131
144
|
});
|
|
145
|
+
});
|
|
132
146
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
});
|
|
147
|
+
describe("Order of operations", () => {
|
|
148
|
+
it("should wait for an update before returning when updating", async () => {
|
|
149
|
+
expect.assertions(1);
|
|
150
|
+
await hassTestRunner.run(({ lifecycle, hass, event }) => {
|
|
151
|
+
vi.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
|
|
152
|
+
lifecycle.onReady(async () => {
|
|
153
|
+
const response = hass.area.update(EXAMPLE_AREA);
|
|
154
|
+
let order = "";
|
|
155
|
+
setTimeout(() => {
|
|
156
|
+
order += "a";
|
|
157
|
+
event.emit(AREA_REGISTRY_UPDATED);
|
|
158
|
+
}, 5);
|
|
159
|
+
await response;
|
|
160
|
+
order += "b";
|
|
161
|
+
expect(order).toEqual("ab");
|
|
149
162
|
});
|
|
150
163
|
});
|
|
164
|
+
});
|
|
151
165
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
});
|
|
166
|
+
it("should wait for an update before returning when deleting", async () => {
|
|
167
|
+
expect.assertions(1);
|
|
168
|
+
await hassTestRunner.run(({ lifecycle, hass, event }) => {
|
|
169
|
+
vi.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
|
|
170
|
+
lifecycle.onReady(async () => {
|
|
171
|
+
const response = hass.area.delete("example_area" as TAreaId);
|
|
172
|
+
let order = "";
|
|
173
|
+
setTimeout(() => {
|
|
174
|
+
order += "a";
|
|
175
|
+
event.emit(AREA_REGISTRY_UPDATED);
|
|
176
|
+
}, 5);
|
|
177
|
+
await response;
|
|
178
|
+
order += "b";
|
|
179
|
+
expect(order).toEqual("ab");
|
|
167
180
|
});
|
|
168
181
|
});
|
|
182
|
+
});
|
|
169
183
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
});
|
|
184
|
+
it("should wait for an update before returning when creating", async () => {
|
|
185
|
+
expect.assertions(1);
|
|
186
|
+
await hassTestRunner.run(({ lifecycle, hass, event }) => {
|
|
187
|
+
vi.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
|
|
188
|
+
lifecycle.onReady(async () => {
|
|
189
|
+
const response = hass.area.create(EXAMPLE_AREA);
|
|
190
|
+
let order = "";
|
|
191
|
+
setTimeout(() => {
|
|
192
|
+
order += "a";
|
|
193
|
+
event.emit(AREA_REGISTRY_UPDATED);
|
|
194
|
+
}, 5);
|
|
195
|
+
await response;
|
|
196
|
+
order += "b";
|
|
197
|
+
expect(order).toEqual("ab");
|
|
185
198
|
});
|
|
186
199
|
});
|
|
187
200
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { subscribe } from "node:diagnostics_channel";
|
|
2
|
+
|
|
1
3
|
import { sleep } from "@digital-alchemy/core";
|
|
2
4
|
|
|
3
5
|
import { DeviceDetails } from "../helpers/index.mts";
|
|
@@ -86,4 +88,24 @@ describe("Device", () => {
|
|
|
86
88
|
});
|
|
87
89
|
});
|
|
88
90
|
});
|
|
91
|
+
|
|
92
|
+
it("should publish diagnostics on device registry update", async () => {
|
|
93
|
+
expect.assertions(1);
|
|
94
|
+
hassTestRunner.configure({ hass: { EMIT_DIAGNOSTICS: true } });
|
|
95
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
96
|
+
vi.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
|
|
97
|
+
const spy = vi.fn();
|
|
98
|
+
subscribe(hass.diagnostics.device.registry_update.name, spy);
|
|
99
|
+
lifecycle.onReady(async () => {
|
|
100
|
+
setImmediate(async () => {
|
|
101
|
+
hass.socket.socketEvents.emit("device_registry_updated");
|
|
102
|
+
});
|
|
103
|
+
await sleep(100);
|
|
104
|
+
expect(spy).toHaveBeenCalledWith(
|
|
105
|
+
expect.objectContaining({ ms: expect.any(Number) }),
|
|
106
|
+
hass.diagnostics.device.registry_update.name,
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
|
89
111
|
});
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { subscribe } from "node:diagnostics_channel";
|
|
2
|
+
|
|
1
3
|
import { sleep } from "@digital-alchemy/core";
|
|
4
|
+
import dayjs from "dayjs";
|
|
2
5
|
|
|
3
6
|
import { ENTITY_STATE } from "../index.mts";
|
|
4
7
|
import { hassTestRunner } from "../mock_assistant/index.mts";
|
|
@@ -169,4 +172,202 @@ describe("Entity", () => {
|
|
|
169
172
|
});
|
|
170
173
|
});
|
|
171
174
|
});
|
|
175
|
+
|
|
176
|
+
describe("Diagnostics", () => {
|
|
177
|
+
it("should publish diagnostics on history lookup", async () => {
|
|
178
|
+
expect.assertions(2);
|
|
179
|
+
await hassTestRunner
|
|
180
|
+
.configure({
|
|
181
|
+
hass: { EMIT_DIAGNOSTICS: true },
|
|
182
|
+
})
|
|
183
|
+
.run(({ lifecycle, hass }) => {
|
|
184
|
+
const spy = vi.fn();
|
|
185
|
+
subscribe(hass.diagnostics.entity.history_lookup.name, spy);
|
|
186
|
+
|
|
187
|
+
// Mock the socket response
|
|
188
|
+
const sendMessageSpy = vi.spyOn(hass.socket, "sendMessage").mockResolvedValue({
|
|
189
|
+
"sensor.magic": [
|
|
190
|
+
{
|
|
191
|
+
entity_id: "sensor.magic",
|
|
192
|
+
last_changed: "2024-01-01T00:00:00Z",
|
|
193
|
+
last_updated: "2024-01-01T00:00:00Z",
|
|
194
|
+
state: "test",
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
lifecycle.onReady(async () => {
|
|
200
|
+
const now = new Date();
|
|
201
|
+
await hass.entity.history({
|
|
202
|
+
end_time: now.toISOString(),
|
|
203
|
+
entity_ids: ["sensor.magic"],
|
|
204
|
+
start_time: new Date(now.getTime() - 3600000).toISOString(), // 1 hour ago
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
expect(sendMessageSpy).toHaveBeenCalledWith(
|
|
208
|
+
expect.objectContaining({
|
|
209
|
+
entity_ids: ["sensor.magic"],
|
|
210
|
+
type: "history/history_during_period",
|
|
211
|
+
}),
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
expect(spy).toHaveBeenCalledWith(
|
|
215
|
+
expect.objectContaining({
|
|
216
|
+
ms: expect.any(Number),
|
|
217
|
+
payload: expect.objectContaining({
|
|
218
|
+
entity_ids: ["sensor.magic"],
|
|
219
|
+
}),
|
|
220
|
+
result: expect.objectContaining({
|
|
221
|
+
"sensor.magic": expect.arrayContaining([
|
|
222
|
+
expect.objectContaining({
|
|
223
|
+
entity_id: "sensor.magic",
|
|
224
|
+
state: "test",
|
|
225
|
+
}),
|
|
226
|
+
]),
|
|
227
|
+
}),
|
|
228
|
+
}),
|
|
229
|
+
hass.diagnostics.entity.history_lookup.name,
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("should publish diagnostics on refresh entities", async () => {
|
|
236
|
+
expect.assertions(1);
|
|
237
|
+
await hassTestRunner
|
|
238
|
+
.configure({
|
|
239
|
+
hass: { EMIT_DIAGNOSTICS: true },
|
|
240
|
+
})
|
|
241
|
+
.run(({ lifecycle, hass }) => {
|
|
242
|
+
const spy = vi.fn();
|
|
243
|
+
subscribe(hass.diagnostics.entity.refresh_entities.name, spy);
|
|
244
|
+
|
|
245
|
+
lifecycle.onReady(async () => {
|
|
246
|
+
await hass.entity.refresh();
|
|
247
|
+
expect(spy).toHaveBeenCalledWith(
|
|
248
|
+
expect.objectContaining({
|
|
249
|
+
emitUpdates: [],
|
|
250
|
+
}),
|
|
251
|
+
hass.diagnostics.entity.refresh_entities.name,
|
|
252
|
+
);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("should publish diagnostics on entity update", async () => {
|
|
258
|
+
expect.assertions(1);
|
|
259
|
+
await hassTestRunner
|
|
260
|
+
.configure({
|
|
261
|
+
hass: { EMIT_DIAGNOSTICS: true },
|
|
262
|
+
})
|
|
263
|
+
.run(({ lifecycle, hass, mock_assistant }) => {
|
|
264
|
+
const spy = vi.fn();
|
|
265
|
+
subscribe(hass.diagnostics.entity.entity_updated.name, spy);
|
|
266
|
+
|
|
267
|
+
lifecycle.onReady(async () => {
|
|
268
|
+
await mock_assistant.events.emitEntityUpdate("sensor.magic", {
|
|
269
|
+
state: "test",
|
|
270
|
+
});
|
|
271
|
+
expect(spy).toHaveBeenCalledWith(
|
|
272
|
+
expect.objectContaining({
|
|
273
|
+
entity_id: "sensor.magic",
|
|
274
|
+
new_state: expect.objectContaining({
|
|
275
|
+
entity_id: "sensor.magic",
|
|
276
|
+
state: "test",
|
|
277
|
+
}),
|
|
278
|
+
old_state: expect.objectContaining({
|
|
279
|
+
entity_id: "sensor.magic",
|
|
280
|
+
state: "unavailable",
|
|
281
|
+
}),
|
|
282
|
+
}),
|
|
283
|
+
hass.diagnostics.entity.entity_updated.name,
|
|
284
|
+
);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("should publish diagnostics on entity remove", async () => {
|
|
290
|
+
expect.assertions(1);
|
|
291
|
+
await hassTestRunner
|
|
292
|
+
.configure({
|
|
293
|
+
hass: { EMIT_DIAGNOSTICS: true },
|
|
294
|
+
})
|
|
295
|
+
.run(({ lifecycle, hass }) => {
|
|
296
|
+
const spy = vi.fn();
|
|
297
|
+
subscribe(hass.diagnostics.entity.entity_remove.name, spy);
|
|
298
|
+
|
|
299
|
+
lifecycle.onReady(async () => {
|
|
300
|
+
// Directly call _entityUpdateReceiver with null new_state to trigger entity remove
|
|
301
|
+
hass.entity._entityUpdateReceiver("sensor.magic", null, {
|
|
302
|
+
attributes: {
|
|
303
|
+
friendly_name: "magic",
|
|
304
|
+
icon: "mdi:satellite-uplink",
|
|
305
|
+
restored: true,
|
|
306
|
+
supported_features: 0,
|
|
307
|
+
},
|
|
308
|
+
context: {
|
|
309
|
+
id: "test",
|
|
310
|
+
parent_id: null,
|
|
311
|
+
user_id: null,
|
|
312
|
+
},
|
|
313
|
+
entity_id: "sensor.magic",
|
|
314
|
+
last_changed: dayjs("2024-01-01T00:00:00Z"),
|
|
315
|
+
last_reported: dayjs("2024-01-01T00:00:00Z"),
|
|
316
|
+
last_updated: dayjs("2024-01-01T00:00:00Z"),
|
|
317
|
+
state: "test",
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
expect(spy).toHaveBeenCalledWith(
|
|
321
|
+
expect.objectContaining({
|
|
322
|
+
entity_id: "sensor.magic",
|
|
323
|
+
}),
|
|
324
|
+
hass.diagnostics.entity.entity_remove.name,
|
|
325
|
+
);
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("should publish diagnostics on entity add", async () => {
|
|
331
|
+
expect.assertions(1);
|
|
332
|
+
await hassTestRunner
|
|
333
|
+
.configure({
|
|
334
|
+
hass: { EMIT_DIAGNOSTICS: true },
|
|
335
|
+
})
|
|
336
|
+
.run(({ lifecycle, hass }) => {
|
|
337
|
+
const spy = vi.fn();
|
|
338
|
+
subscribe(hass.diagnostics.entity.entity_add.name, spy);
|
|
339
|
+
|
|
340
|
+
lifecycle.onReady(async () => {
|
|
341
|
+
// Call _entityUpdateReceiver with null old_state to trigger entity add
|
|
342
|
+
const newState = {
|
|
343
|
+
attributes: {
|
|
344
|
+
friendly_name: "magic" as const,
|
|
345
|
+
icon: "mdi:satellite-uplink" as const,
|
|
346
|
+
restored: true as const,
|
|
347
|
+
supported_features: 0 as const,
|
|
348
|
+
},
|
|
349
|
+
context: {
|
|
350
|
+
id: "test",
|
|
351
|
+
parent_id: null as null,
|
|
352
|
+
user_id: null as null,
|
|
353
|
+
},
|
|
354
|
+
entity_id: "sensor.magic" as const,
|
|
355
|
+
last_changed: dayjs("2024-01-01T00:00:00Z"),
|
|
356
|
+
last_reported: dayjs("2024-01-01T00:00:00Z"),
|
|
357
|
+
last_updated: dayjs("2024-01-01T00:00:00Z"),
|
|
358
|
+
state: "test",
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
hass.entity._entityUpdateReceiver("sensor.magic", newState, null);
|
|
362
|
+
|
|
363
|
+
expect(spy).toHaveBeenCalledWith(
|
|
364
|
+
expect.objectContaining({
|
|
365
|
+
entity_id: "sensor.magic",
|
|
366
|
+
}),
|
|
367
|
+
hass.diagnostics.entity.entity_add.name,
|
|
368
|
+
);
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
});
|
|
172
373
|
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { subscribe } from "node:diagnostics_channel";
|
|
2
|
+
|
|
3
|
+
import { hassTestRunner } from "../mock_assistant/index.mts";
|
|
4
|
+
|
|
5
|
+
describe("Fetch", () => {
|
|
6
|
+
const BASE_URL = "http://homeassistant.local:8123";
|
|
7
|
+
|
|
8
|
+
afterEach(async () => {
|
|
9
|
+
await hassTestRunner.teardown();
|
|
10
|
+
vi.restoreAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
describe("API", () => {
|
|
14
|
+
describe("Diagnostics", () => {
|
|
15
|
+
it("should publish diagnostics on fetch operation", async () => {
|
|
16
|
+
expect.assertions(1);
|
|
17
|
+
hassTestRunner.configure({
|
|
18
|
+
hass: {
|
|
19
|
+
BASE_URL,
|
|
20
|
+
EMIT_DIAGNOSTICS: true,
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
24
|
+
const mockResponse = { data: "test response" };
|
|
25
|
+
vi.spyOn(globalThis, "fetch").mockImplementation(async () => {
|
|
26
|
+
return {
|
|
27
|
+
text: async () => JSON.stringify(mockResponse),
|
|
28
|
+
} as unknown as Response;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const spy = vi.fn();
|
|
32
|
+
subscribe(hass.diagnostics.fetch.fetch.name, spy);
|
|
33
|
+
|
|
34
|
+
lifecycle.onPostConfig(() => {
|
|
35
|
+
hass.fetch._fetcher.base_url = BASE_URL;
|
|
36
|
+
hass.fetch._fetcher.base_headers = { Authorization: "Bearer test_token" };
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
lifecycle.onReady(async () => {
|
|
40
|
+
await hass.fetch.fetch({ url: "/api/test" });
|
|
41
|
+
expect(spy).toHaveBeenCalledWith(
|
|
42
|
+
expect.objectContaining({
|
|
43
|
+
ms: expect.any(Number),
|
|
44
|
+
options: expect.objectContaining({ url: "/api/test" }),
|
|
45
|
+
out: mockResponse,
|
|
46
|
+
}),
|
|
47
|
+
hass.diagnostics.fetch.fetch.name,
|
|
48
|
+
);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { subscribe } from "node:diagnostics_channel";
|
|
2
|
+
|
|
1
3
|
import { sleep } from "@digital-alchemy/core";
|
|
2
4
|
|
|
3
5
|
import { FLOOR_REGISTRY_UPDATED, FloorDetails } from "../helpers/index.mts";
|
|
@@ -103,6 +105,26 @@ describe("Floor", () => {
|
|
|
103
105
|
});
|
|
104
106
|
});
|
|
105
107
|
});
|
|
108
|
+
|
|
109
|
+
it("should publish diagnostics on floor registry update", async () => {
|
|
110
|
+
expect.assertions(1);
|
|
111
|
+
hassTestRunner.configure({ hass: { EMIT_DIAGNOSTICS: true } });
|
|
112
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
113
|
+
vi.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
|
|
114
|
+
const spy = vi.fn();
|
|
115
|
+
subscribe(hass.diagnostics.floor.registry_update.name, spy);
|
|
116
|
+
lifecycle.onReady(async () => {
|
|
117
|
+
setImmediate(async () => {
|
|
118
|
+
hass.socket.socketEvents.emit("floor_registry_updated");
|
|
119
|
+
});
|
|
120
|
+
await sleep(100);
|
|
121
|
+
expect(spy).toHaveBeenCalledWith(
|
|
122
|
+
expect.objectContaining({ ms: expect.any(Number) }),
|
|
123
|
+
hass.diagnostics.floor.registry_update.name,
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
106
128
|
});
|
|
107
129
|
|
|
108
130
|
describe("Order of operations", () => {
|