@digital-alchemy/hass 24.9.4 → 24.9.5
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/README.md +1 -1
- package/dist/helpers/notify.helper.d.ts +2 -2
- package/package.json +16 -14
- package/scripts/mock-assistant.sh +5 -0
- package/scripts/run-e2e.sh +7 -0
- package/scripts/test.sh +2 -0
- package/src/dynamic.ts +4254 -0
- package/src/extensions/area.extension.ts +118 -0
- package/src/extensions/backup.extension.ts +63 -0
- package/src/extensions/call-proxy.extension.ts +113 -0
- package/src/extensions/config.extension.ts +119 -0
- package/src/extensions/conversation.extension.ts +46 -0
- package/src/extensions/device.extension.ts +56 -0
- package/src/extensions/entity.extension.ts +344 -0
- package/src/extensions/events.extension.ts +25 -0
- package/src/extensions/fetch-api.extension.ts +269 -0
- package/src/extensions/floor.extension.ts +76 -0
- package/src/extensions/id-by.extension.ts +157 -0
- package/src/extensions/index.ts +16 -0
- package/src/extensions/internal.extension.ts +145 -0
- package/src/extensions/label.extension.ts +83 -0
- package/src/extensions/reference.extension.ts +330 -0
- package/src/extensions/registry.extension.ts +44 -0
- package/src/extensions/websocket-api.extension.ts +554 -0
- package/src/extensions/zone.extension.ts +69 -0
- package/src/hass.module.ts +217 -0
- package/src/helpers/backup.helper.ts +11 -0
- package/src/helpers/constants.helper.ts +30 -0
- package/src/helpers/device.helper.ts +25 -0
- package/src/helpers/entity-state.helper.ts +171 -0
- package/src/helpers/features.helper.ts +580 -0
- package/src/helpers/fetch/calendar.ts +54 -0
- package/src/helpers/fetch/configuration.ts +75 -0
- package/src/helpers/fetch/index.ts +5 -0
- package/src/helpers/fetch/server-log.ts +28 -0
- package/src/helpers/fetch/service-list.ts +64 -0
- package/src/helpers/fetch/weather-forecasts.ts +86 -0
- package/src/helpers/fetch.helper.ts +328 -0
- package/src/helpers/id-by.helper.ts +53 -0
- package/src/helpers/index.ts +13 -0
- package/src/helpers/interfaces.helper.ts +340 -0
- package/src/helpers/manifest.helper.ts +0 -0
- package/src/helpers/notify.helper.ts +302 -0
- package/src/helpers/registry.ts +281 -0
- package/src/helpers/utility.helper.ts +147 -0
- package/src/helpers/websocket.helper.ts +117 -0
- package/src/index.ts +5 -0
- package/src/mock_assistant/extensions/area.extension.ts +62 -0
- package/src/mock_assistant/extensions/config.extension.ts +33 -0
- package/src/mock_assistant/extensions/device.extension.ts +44 -0
- package/src/mock_assistant/extensions/entity-registry.extension.ts +41 -0
- package/src/mock_assistant/extensions/entity.extension.ts +114 -0
- package/src/mock_assistant/extensions/events.extension.ts +37 -0
- package/src/mock_assistant/extensions/fetch.extension.ts +3 -0
- package/src/mock_assistant/extensions/fixtures.extension.ts +79 -0
- package/src/mock_assistant/extensions/floor.extension.ts +64 -0
- package/src/mock_assistant/extensions/index.ts +12 -0
- package/src/mock_assistant/extensions/label.extension.ts +64 -0
- package/src/mock_assistant/extensions/services.extension.ts +25 -0
- package/src/mock_assistant/extensions/websocket-api.extension.ts +84 -0
- package/src/mock_assistant/extensions/zone.extension.ts +65 -0
- package/src/mock_assistant/helpers/fixtures.ts +22 -0
- package/src/mock_assistant/helpers/index.ts +1 -0
- package/src/mock_assistant/index.ts +3 -0
- package/src/mock_assistant/main.ts +46 -0
- package/src/mock_assistant/mock-assistant.module.ts +90 -0
- package/src/quickboot.module.ts +23 -0
- package/src/testing/area.spec.ts +189 -0
- package/src/testing/backup.spec.ts +157 -0
- package/src/testing/config.spec.ts +188 -0
- package/src/testing/device.spec.ts +89 -0
- package/src/testing/entity.spec.ts +171 -0
- package/src/testing/events.spec.ts +78 -0
- package/src/testing/fetch-api.spec.ts +410 -0
- package/src/testing/fixtures.spec.ts +158 -0
- package/src/testing/floor.spec.ts +186 -0
- package/src/testing/id-by.spec.ts +140 -0
- package/src/testing/label.spec.ts +186 -0
- package/src/testing/ref-by.spec.ts +300 -0
- package/src/testing/websocket.spec.ts +63 -0
- package/src/testing/workflow.spec.ts +195 -0
- package/src/testing/zone.spec.ts +109 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { CreateLibrary, createModule, SINGLE } from "@digital-alchemy/core";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { cwd } from "process";
|
|
4
|
+
|
|
5
|
+
import { LIB_HASS } from "..";
|
|
6
|
+
import {
|
|
7
|
+
MockAreaExtension,
|
|
8
|
+
MockConfig,
|
|
9
|
+
MockDeviceExtension,
|
|
10
|
+
MockEntityExtension,
|
|
11
|
+
MockEntityRegistryExtension,
|
|
12
|
+
MockEvents,
|
|
13
|
+
MockFixtures,
|
|
14
|
+
MockFloorExtension,
|
|
15
|
+
MockLabelExtension,
|
|
16
|
+
MockServices,
|
|
17
|
+
MockWebsocketAPI,
|
|
18
|
+
MockZoneExtension,
|
|
19
|
+
} from "./extensions";
|
|
20
|
+
|
|
21
|
+
export const LIB_MOCK_ASSISTANT = CreateLibrary({
|
|
22
|
+
configuration: {
|
|
23
|
+
EMIT_SLEEP: {
|
|
24
|
+
default: SINGLE,
|
|
25
|
+
description: "Time in ms to wait after emitting entity update events",
|
|
26
|
+
type: "number",
|
|
27
|
+
},
|
|
28
|
+
FIXTURES_FILE: {
|
|
29
|
+
default: join(cwd(), "fixtures.json"),
|
|
30
|
+
description: [],
|
|
31
|
+
type: "string",
|
|
32
|
+
},
|
|
33
|
+
PASS_AUTH: {
|
|
34
|
+
default: true,
|
|
35
|
+
description: "Auto pass for auth challenges",
|
|
36
|
+
type: "boolean",
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
depends: [LIB_HASS],
|
|
40
|
+
name: "mock_assistant",
|
|
41
|
+
priorityInit: [
|
|
42
|
+
"socket",
|
|
43
|
+
"floor",
|
|
44
|
+
"device",
|
|
45
|
+
"area",
|
|
46
|
+
"label",
|
|
47
|
+
"config",
|
|
48
|
+
"entity",
|
|
49
|
+
"entity_registry",
|
|
50
|
+
"services",
|
|
51
|
+
"fixtures",
|
|
52
|
+
],
|
|
53
|
+
services: {
|
|
54
|
+
area: MockAreaExtension,
|
|
55
|
+
config: MockConfig,
|
|
56
|
+
device: MockDeviceExtension,
|
|
57
|
+
entity: MockEntityExtension,
|
|
58
|
+
entity_registry: MockEntityRegistryExtension,
|
|
59
|
+
events: MockEvents,
|
|
60
|
+
fixtures: MockFixtures,
|
|
61
|
+
floor: MockFloorExtension,
|
|
62
|
+
label: MockLabelExtension,
|
|
63
|
+
services: MockServices,
|
|
64
|
+
socket: MockWebsocketAPI,
|
|
65
|
+
zone: MockZoneExtension,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
declare module "@digital-alchemy/core" {
|
|
70
|
+
export interface LoadedModules {
|
|
71
|
+
mock_assistant: typeof LIB_MOCK_ASSISTANT;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @internal
|
|
77
|
+
*
|
|
78
|
+
* Make your own
|
|
79
|
+
*/
|
|
80
|
+
export const createTestRunner = () =>
|
|
81
|
+
createModule
|
|
82
|
+
.fromLibrary(LIB_HASS)
|
|
83
|
+
.extend()
|
|
84
|
+
.toTest()
|
|
85
|
+
.configure({
|
|
86
|
+
boilerplate: { IS_TEST: true },
|
|
87
|
+
})
|
|
88
|
+
.appendLibrary(LIB_MOCK_ASSISTANT);
|
|
89
|
+
|
|
90
|
+
export const hassTestRunner = createTestRunner();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CreateApplication, TServiceParams } from "@digital-alchemy/core";
|
|
2
|
+
|
|
3
|
+
import { LIB_HASS } from ".";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Use from the node command line to probe apis
|
|
7
|
+
* Not for normal application usage
|
|
8
|
+
*/
|
|
9
|
+
export async function QuickBoot(name: string) {
|
|
10
|
+
let out: TServiceParams;
|
|
11
|
+
const app = CreateApplication({
|
|
12
|
+
libraries: [LIB_HASS],
|
|
13
|
+
// @ts-expect-error fake app, name used for loading credentials
|
|
14
|
+
name,
|
|
15
|
+
services: {
|
|
16
|
+
Loader(params: TServiceParams) {
|
|
17
|
+
out = params;
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
await app.bootstrap();
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { sleep } from "@digital-alchemy/core";
|
|
2
|
+
|
|
3
|
+
import { TAreaId } from "../dynamic";
|
|
4
|
+
import { AREA_REGISTRY_UPDATED, AreaDetails } from "../helpers";
|
|
5
|
+
import { hassTestRunner, INTERNAL_MESSAGE } from "../mock_assistant";
|
|
6
|
+
|
|
7
|
+
describe("Area", () => {
|
|
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
|
+
jest.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 = jest.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();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("API", () => {
|
|
41
|
+
describe("Formatting", () => {
|
|
42
|
+
it("should call list properly", async () => {
|
|
43
|
+
expect.assertions(1);
|
|
44
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
45
|
+
const spy = jest.spyOn(hass.socket, "sendMessage").mockImplementation(async () => []);
|
|
46
|
+
lifecycle.onReady(async () => {
|
|
47
|
+
await hass.area.list();
|
|
48
|
+
expect(spy).toHaveBeenCalledWith(
|
|
49
|
+
expect.objectContaining({ type: "config/area_registry/list" }),
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should call update properly", async () => {
|
|
56
|
+
expect.assertions(1);
|
|
57
|
+
await hassTestRunner.run(({ lifecycle, hass, event }) => {
|
|
58
|
+
const spy = jest
|
|
59
|
+
.spyOn(hass.socket, "sendMessage")
|
|
60
|
+
.mockImplementation(async () => undefined);
|
|
61
|
+
lifecycle.onReady(async () => {
|
|
62
|
+
setImmediate(() => event.emit(AREA_REGISTRY_UPDATED));
|
|
63
|
+
await hass.area.update(EXAMPLE_AREA);
|
|
64
|
+
|
|
65
|
+
expect(spy).toHaveBeenCalledWith({
|
|
66
|
+
type: "config/area_registry/update",
|
|
67
|
+
...EXAMPLE_AREA,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("should debounce updates properly", async () => {
|
|
74
|
+
expect.assertions(1);
|
|
75
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
76
|
+
jest.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
|
|
77
|
+
let counter = 0;
|
|
78
|
+
hass.events.onAreaRegistryUpdate(() => counter++);
|
|
79
|
+
lifecycle.onReady(async () => {
|
|
80
|
+
setImmediate(async () => {
|
|
81
|
+
hass.socket.socketEvents.emit("area_registry_updated");
|
|
82
|
+
await sleep(5);
|
|
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);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("should call delete properly", async () => {
|
|
96
|
+
expect.assertions(1);
|
|
97
|
+
|
|
98
|
+
await hassTestRunner.run(({ lifecycle, hass, event }) => {
|
|
99
|
+
const spy = jest
|
|
100
|
+
.spyOn(hass.socket, "sendMessage")
|
|
101
|
+
.mockImplementation(async () => undefined);
|
|
102
|
+
lifecycle.onReady(async () => {
|
|
103
|
+
setImmediate(() => event.emit(AREA_REGISTRY_UPDATED));
|
|
104
|
+
await hass.area.delete(EXAMPLE_AREA.area_id);
|
|
105
|
+
|
|
106
|
+
expect(spy).toHaveBeenCalledWith({
|
|
107
|
+
area_id: "empty_area",
|
|
108
|
+
type: "config/area_registry/delete",
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should call create properly", async () => {
|
|
115
|
+
expect.assertions(1);
|
|
116
|
+
await hassTestRunner.run(({ lifecycle, hass, event }) => {
|
|
117
|
+
const spy = jest
|
|
118
|
+
.spyOn(hass.socket, "sendMessage")
|
|
119
|
+
.mockImplementation(async () => undefined);
|
|
120
|
+
lifecycle.onReady(async () => {
|
|
121
|
+
setImmediate(() => event.emit(AREA_REGISTRY_UPDATED));
|
|
122
|
+
await hass.area.create(EXAMPLE_AREA);
|
|
123
|
+
|
|
124
|
+
expect(spy).toHaveBeenCalledWith({
|
|
125
|
+
type: "config/area_registry/create",
|
|
126
|
+
...EXAMPLE_AREA,
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("Order of operations", () => {
|
|
134
|
+
it("should wait for an update before returning when updating", async () => {
|
|
135
|
+
expect.assertions(1);
|
|
136
|
+
await hassTestRunner.run(({ lifecycle, hass, event }) => {
|
|
137
|
+
jest.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
|
|
138
|
+
lifecycle.onReady(async () => {
|
|
139
|
+
const response = hass.area.update(EXAMPLE_AREA);
|
|
140
|
+
let order = "";
|
|
141
|
+
setTimeout(() => {
|
|
142
|
+
order += "a";
|
|
143
|
+
event.emit(AREA_REGISTRY_UPDATED);
|
|
144
|
+
}, 5);
|
|
145
|
+
await response;
|
|
146
|
+
order += "b";
|
|
147
|
+
expect(order).toEqual("ab");
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should wait for an update before returning when deleting", async () => {
|
|
153
|
+
expect.assertions(1);
|
|
154
|
+
await hassTestRunner.run(({ lifecycle, hass, event }) => {
|
|
155
|
+
jest.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
|
|
156
|
+
lifecycle.onReady(async () => {
|
|
157
|
+
const response = hass.area.delete("example_area" as TAreaId);
|
|
158
|
+
let order = "";
|
|
159
|
+
setTimeout(() => {
|
|
160
|
+
order += "a";
|
|
161
|
+
event.emit(AREA_REGISTRY_UPDATED);
|
|
162
|
+
}, 5);
|
|
163
|
+
await response;
|
|
164
|
+
order += "b";
|
|
165
|
+
expect(order).toEqual("ab");
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("should wait for an update before returning when creating", async () => {
|
|
171
|
+
expect.assertions(1);
|
|
172
|
+
await hassTestRunner.run(({ lifecycle, hass, event }) => {
|
|
173
|
+
jest.spyOn(hass.socket, "sendMessage").mockImplementation(async () => undefined);
|
|
174
|
+
lifecycle.onReady(async () => {
|
|
175
|
+
const response = hass.area.create(EXAMPLE_AREA);
|
|
176
|
+
let order = "";
|
|
177
|
+
setTimeout(() => {
|
|
178
|
+
order += "a";
|
|
179
|
+
event.emit(AREA_REGISTRY_UPDATED);
|
|
180
|
+
}, 5);
|
|
181
|
+
await response;
|
|
182
|
+
order += "b";
|
|
183
|
+
expect(order).toEqual("ab");
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { SINGLE } from "@digital-alchemy/core";
|
|
2
|
+
|
|
3
|
+
import { BackupResponse } from "../helpers";
|
|
4
|
+
import { hassTestRunner } from "../mock_assistant";
|
|
5
|
+
|
|
6
|
+
describe("Backup", () => {
|
|
7
|
+
afterEach(async () => {
|
|
8
|
+
await hassTestRunner.teardown();
|
|
9
|
+
jest.restoreAllMocks();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should format removes properly", async () => {
|
|
13
|
+
expect.assertions(1);
|
|
14
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
15
|
+
lifecycle.onReady(async () => {
|
|
16
|
+
const spy = jest.spyOn(hass.socket, "sendMessage");
|
|
17
|
+
await hass.backup.remove("test");
|
|
18
|
+
expect(spy).toHaveBeenCalledWith(
|
|
19
|
+
expect.objectContaining({
|
|
20
|
+
slug: "test",
|
|
21
|
+
type: "backup/remove",
|
|
22
|
+
}),
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("should format list requests properly", async () => {
|
|
29
|
+
expect.assertions(1);
|
|
30
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
31
|
+
lifecycle.onReady(async () => {
|
|
32
|
+
const spy = jest.spyOn(hass.socket, "sendMessage");
|
|
33
|
+
await hass.backup.list();
|
|
34
|
+
expect(spy).toHaveBeenCalledWith(
|
|
35
|
+
expect.objectContaining({
|
|
36
|
+
type: "backup/info",
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should first attempt to sign with downloads", async () => {
|
|
44
|
+
expect.assertions(1);
|
|
45
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
46
|
+
lifecycle.onReady(async () => {
|
|
47
|
+
const spy = jest
|
|
48
|
+
.spyOn(hass.socket, "sendMessage")
|
|
49
|
+
.mockImplementation(async () => undefined);
|
|
50
|
+
await hass.backup.download("test", "/foo/bar");
|
|
51
|
+
expect(spy).toHaveBeenCalledWith(
|
|
52
|
+
expect.objectContaining({
|
|
53
|
+
path: `/api/backup/download/test`,
|
|
54
|
+
type: "auth/sign_path",
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("should use the sign path to download", async () => {
|
|
62
|
+
expect.assertions(1);
|
|
63
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
64
|
+
lifecycle.onReady(async () => {
|
|
65
|
+
const path = "/test/thing";
|
|
66
|
+
const destination = "/foo/bar";
|
|
67
|
+
jest.spyOn(hass.socket, "sendMessage").mockImplementation(async () => ({ path }));
|
|
68
|
+
const spy = jest.spyOn(hass.fetch, "download").mockImplementation(async () => undefined);
|
|
69
|
+
await hass.backup.download("test", destination);
|
|
70
|
+
expect(spy).toHaveBeenCalledWith(destination, expect.objectContaining({ url: path }));
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should use the sign path to download", async () => {
|
|
76
|
+
expect.assertions(1);
|
|
77
|
+
await hassTestRunner.run(({ lifecycle, hass }) => {
|
|
78
|
+
lifecycle.onReady(async () => {
|
|
79
|
+
const path = "/test/thing";
|
|
80
|
+
const destination = "/foo/bar";
|
|
81
|
+
jest.spyOn(hass.socket, "sendMessage").mockImplementation(async () => ({ path }));
|
|
82
|
+
const spy = jest.spyOn(hass.fetch, "download").mockImplementation(async () => undefined);
|
|
83
|
+
await hass.backup.download("test", destination);
|
|
84
|
+
expect(spy).toHaveBeenCalledWith(destination, expect.objectContaining({ url: path }));
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should not start a new backup if one is already in progress", async () => {
|
|
90
|
+
expect.assertions(1);
|
|
91
|
+
await hassTestRunner.configure({ hass: { RETRY_INTERVAL: 0 } }).run(({ lifecycle, hass }) => {
|
|
92
|
+
lifecycle.onReady(async () => {
|
|
93
|
+
const spy = jest.spyOn(hass.socket, "sendMessage");
|
|
94
|
+
const responses = [
|
|
95
|
+
{ backing_up: true, backups: [] },
|
|
96
|
+
{ backing_up: false, backups: [] },
|
|
97
|
+
] as BackupResponse[];
|
|
98
|
+
|
|
99
|
+
jest.spyOn(hass.backup, "list").mockImplementation(async () => responses.shift());
|
|
100
|
+
|
|
101
|
+
await hass.backup.generate();
|
|
102
|
+
expect(spy).not.toHaveBeenCalledWith({ type: "backup/generate" });
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should start a new backup if one is not already in progress", async () => {
|
|
108
|
+
expect.assertions(1);
|
|
109
|
+
await hassTestRunner.configure({ hass: { RETRY_INTERVAL: 0 } }).run(({ lifecycle, hass }) => {
|
|
110
|
+
lifecycle.onReady(async () => {
|
|
111
|
+
const spy = jest.spyOn(hass.socket, "sendMessage");
|
|
112
|
+
const responses = [
|
|
113
|
+
{ backing_up: false, backups: [] },
|
|
114
|
+
{ backing_up: true, backups: [] },
|
|
115
|
+
{ backing_up: false, backups: [] },
|
|
116
|
+
] as BackupResponse[];
|
|
117
|
+
|
|
118
|
+
jest.spyOn(hass.backup, "list").mockImplementation(async () => responses.shift());
|
|
119
|
+
|
|
120
|
+
await hass.backup.generate();
|
|
121
|
+
expect(spy).toHaveBeenCalledWith(expect.objectContaining({ type: "backup/generate" }));
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should confirm a new backup is in progress before monitoring", async () => {
|
|
127
|
+
expect.assertions(1);
|
|
128
|
+
await hassTestRunner.configure({ hass: { RETRY_INTERVAL: 0 } }).run(({ lifecycle, hass }) => {
|
|
129
|
+
lifecycle.onReady(async () => {
|
|
130
|
+
const responses = [
|
|
131
|
+
// not backing up
|
|
132
|
+
{ backing_up: false, backups: [] },
|
|
133
|
+
// waiting...
|
|
134
|
+
{ backing_up: false, backups: [] },
|
|
135
|
+
// waiting...
|
|
136
|
+
{ backing_up: false, backups: [] },
|
|
137
|
+
// waiting...
|
|
138
|
+
{ backing_up: false, backups: [] },
|
|
139
|
+
// true breaks out of loop confirm loop
|
|
140
|
+
{ backing_up: true, backups: [] },
|
|
141
|
+
// false breaks out of actively backing up loop
|
|
142
|
+
{ backing_up: false, backups: [] },
|
|
143
|
+
// should never be sent
|
|
144
|
+
{ backing_up: false, backups: [] },
|
|
145
|
+
] as BackupResponse[];
|
|
146
|
+
const length = responses.length;
|
|
147
|
+
|
|
148
|
+
const spy = jest
|
|
149
|
+
.spyOn(hass.backup, "list")
|
|
150
|
+
.mockImplementation(async () => responses.shift());
|
|
151
|
+
|
|
152
|
+
await hass.backup.generate();
|
|
153
|
+
expect(spy).toHaveBeenCalledTimes(length - SINGLE);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { env } from "process";
|
|
2
|
+
|
|
3
|
+
import { hassTestRunner } from "../mock_assistant";
|
|
4
|
+
|
|
5
|
+
const TOKEN = "DEFAULTS";
|
|
6
|
+
|
|
7
|
+
describe("Config", () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
delete env.HASSIO_TOKEN;
|
|
10
|
+
delete env.SUPERVISOR_TOKEN;
|
|
11
|
+
delete env.HASS_SERVER;
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(async () => {
|
|
15
|
+
await hassTestRunner.teardown();
|
|
16
|
+
jest.restoreAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("Auto", () => {
|
|
20
|
+
// # Should do nothing if variables do not exist
|
|
21
|
+
it("should do nothing if variables do not exist", async () => {
|
|
22
|
+
expect.assertions(2);
|
|
23
|
+
const BASE_URL = "http://localhost:9123";
|
|
24
|
+
await hassTestRunner.configure({ hass: { BASE_URL, TOKEN } }).run(({ lifecycle, config }) => {
|
|
25
|
+
lifecycle.onPostConfig(() => {
|
|
26
|
+
expect(config.hass.BASE_URL).toBe(BASE_URL);
|
|
27
|
+
expect(config.hass.TOKEN).toBe(TOKEN);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// # Should set BASE_URL & TOKEN if provided env
|
|
33
|
+
it("should set BASE_URL & TOKEN if provided env", async () => {
|
|
34
|
+
expect.assertions(2);
|
|
35
|
+
env.HASSIO_TOKEN = "FOO";
|
|
36
|
+
await hassTestRunner
|
|
37
|
+
.configure({
|
|
38
|
+
hass: { BASE_URL: "http://localhost:9123", TOKEN: TOKEN },
|
|
39
|
+
})
|
|
40
|
+
.run(({ lifecycle, config }) => {
|
|
41
|
+
lifecycle.onPostConfig(() => {
|
|
42
|
+
expect(config.hass.BASE_URL).toBe("http://supervisor/core");
|
|
43
|
+
expect(config.hass.TOKEN).toBe("FOO");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// # Should use HASSIO_TOKEN over SUPERVISOR_TOKEN
|
|
49
|
+
it("should use HASSIO_TOKEN over SUPERVISOR_TOKEN", async () => {
|
|
50
|
+
expect.assertions(1);
|
|
51
|
+
env.HASSIO_TOKEN = "FOO";
|
|
52
|
+
env.SUPERVISOR_TOKEN = "BAR";
|
|
53
|
+
|
|
54
|
+
await hassTestRunner.run(({ lifecycle, config }) => {
|
|
55
|
+
lifecycle.onPostConfig(() => {
|
|
56
|
+
expect(config.hass.TOKEN).toBe("FOO");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// # Should allow SUPERVISOR_TOKEN
|
|
62
|
+
it("should allow SUPERVISOR_TOKEN", async () => {
|
|
63
|
+
expect.assertions(1);
|
|
64
|
+
env.SUPERVISOR_TOKEN = "BAR";
|
|
65
|
+
await hassTestRunner.run(({ lifecycle, config }) => {
|
|
66
|
+
lifecycle.onPostConfig(() => {
|
|
67
|
+
expect(config.hass.TOKEN).toBe("BAR");
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// # Should allow HASS_SERVER
|
|
73
|
+
it("should allow HASS_SERVER", async () => {
|
|
74
|
+
expect.assertions(2);
|
|
75
|
+
env.HASSIO_TOKEN = "FOO";
|
|
76
|
+
env.HASS_SERVER = "http://test/url";
|
|
77
|
+
|
|
78
|
+
await hassTestRunner.run(({ lifecycle, config }) => {
|
|
79
|
+
lifecycle.onPostConfig(() => {
|
|
80
|
+
expect(config.hass.TOKEN).toBe("FOO");
|
|
81
|
+
expect(config.hass.BASE_URL).toBe("http://test/url");
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("Validate Config", () => {
|
|
88
|
+
// # Should not exit if not set
|
|
89
|
+
it("should not exit if not set", async () => {
|
|
90
|
+
expect.assertions(1);
|
|
91
|
+
const exitSpy = jest
|
|
92
|
+
.spyOn(process, "exit")
|
|
93
|
+
// @ts-expect-error testing
|
|
94
|
+
.mockImplementation(() => {});
|
|
95
|
+
|
|
96
|
+
await hassTestRunner
|
|
97
|
+
.configure({
|
|
98
|
+
hass: { TOKEN: "TEST" },
|
|
99
|
+
})
|
|
100
|
+
.run(() => {});
|
|
101
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// # Should info log on success
|
|
105
|
+
it("should info log on success", async () => {
|
|
106
|
+
const exitSpy = jest
|
|
107
|
+
.spyOn(process, "exit")
|
|
108
|
+
// @ts-expect-error testing
|
|
109
|
+
.mockImplementation(() => {});
|
|
110
|
+
let spy: jest.SpyInstance;
|
|
111
|
+
|
|
112
|
+
await hassTestRunner
|
|
113
|
+
.configure({
|
|
114
|
+
hass: { VALIDATE_CONFIGURATION: true },
|
|
115
|
+
})
|
|
116
|
+
.run(({ internal, hass }) => {
|
|
117
|
+
const logger = internal.boilerplate.logger.getBaseLogger();
|
|
118
|
+
spy = jest.spyOn(logger, "info").mockImplementation(() => {});
|
|
119
|
+
jest
|
|
120
|
+
.spyOn(hass.fetch, "checkCredentials")
|
|
121
|
+
.mockImplementation(async () => ({ message: "ok" }));
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
expect(exitSpy).toHaveBeenCalledWith(1);
|
|
125
|
+
expect(spy).toHaveBeenCalledWith("hass:configure", { name: "onPostConfig" }, "ok");
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// # Should error log on bad token
|
|
129
|
+
it("should error log on bad token", async () => {
|
|
130
|
+
let spy: jest.SpyInstance;
|
|
131
|
+
const exitSpy = jest
|
|
132
|
+
.spyOn(process, "exit")
|
|
133
|
+
// @ts-expect-error testing
|
|
134
|
+
.mockImplementation(() => {});
|
|
135
|
+
|
|
136
|
+
await hassTestRunner
|
|
137
|
+
.configure({ hass: { TOKEN: "TEST", VALIDATE_CONFIGURATION: true } })
|
|
138
|
+
.run(({ internal, hass }) => {
|
|
139
|
+
const logger = internal.boilerplate.logger.getBaseLogger();
|
|
140
|
+
spy = jest.spyOn(logger, "error").mockImplementation(() => {});
|
|
141
|
+
jest
|
|
142
|
+
.spyOn(hass.fetch, "checkCredentials")
|
|
143
|
+
// anything that isn't the success works
|
|
144
|
+
.mockImplementation(async () => ({ message: "big_bad_error" }));
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
148
|
+
expect(spy).toHaveBeenCalledWith(
|
|
149
|
+
"hass:configure",
|
|
150
|
+
{ name: "onPostConfig" },
|
|
151
|
+
String({ message: "big_bad_error" }),
|
|
152
|
+
);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// # Should error log on bad url
|
|
156
|
+
it("should error log on bad url", async () => {
|
|
157
|
+
const error = new Error("BOOM");
|
|
158
|
+
let spy: jest.SpyInstance;
|
|
159
|
+
const exitSpy = jest
|
|
160
|
+
.spyOn(process, "exit")
|
|
161
|
+
// @ts-expect-error testing
|
|
162
|
+
.mockImplementation(() => {});
|
|
163
|
+
jest.spyOn(console, "log").mockImplementation(() => {});
|
|
164
|
+
|
|
165
|
+
jest.spyOn(console, "error").mockImplementation(() => {});
|
|
166
|
+
|
|
167
|
+
await hassTestRunner
|
|
168
|
+
.configure({ hass: { TOKEN: "TEST", VALIDATE_CONFIGURATION: true } })
|
|
169
|
+
.run(({ internal, hass }) => {
|
|
170
|
+
const logger = internal.boilerplate.logger.getBaseLogger();
|
|
171
|
+
spy = jest.spyOn(logger, "error").mockImplementation(() => {});
|
|
172
|
+
jest
|
|
173
|
+
.spyOn(hass.fetch, "checkCredentials")
|
|
174
|
+
// anything that isn't the success works
|
|
175
|
+
.mockImplementation(async () => {
|
|
176
|
+
throw error;
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
expect(exitSpy).toHaveBeenCalledWith(0);
|
|
181
|
+
expect(spy).toHaveBeenCalledWith(
|
|
182
|
+
"hass:configure",
|
|
183
|
+
{ error, name: "onPostConfig" },
|
|
184
|
+
"failed to send request",
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|