@digital-alchemy/hass 24.9.3 → 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.
Files changed (276) hide show
  1. package/README.md +3 -0
  2. package/dist/dynamic.d.ts +1 -1
  3. package/dist/dynamic.js +7 -2
  4. package/dist/dynamic.js.map +1 -1
  5. package/dist/extensions/area.extension.d.ts +2 -12
  6. package/dist/extensions/area.extension.js +20 -26
  7. package/dist/extensions/area.extension.js.map +1 -1
  8. package/dist/extensions/backup.extension.d.ts +2 -7
  9. package/dist/extensions/backup.extension.js +5 -8
  10. package/dist/extensions/backup.extension.js.map +1 -1
  11. package/dist/extensions/call-proxy.extension.d.ts +1 -1
  12. package/dist/extensions/call-proxy.extension.js +3 -22
  13. package/dist/extensions/call-proxy.extension.js.map +1 -1
  14. package/dist/extensions/config.extension.d.ts +2 -6
  15. package/dist/extensions/config.extension.js +21 -25
  16. package/dist/extensions/config.extension.js.map +1 -1
  17. package/dist/extensions/conversation.extension.d.ts +2 -6
  18. package/dist/extensions/conversation.extension.js +5 -8
  19. package/dist/extensions/conversation.extension.js.map +1 -1
  20. package/dist/extensions/device.extension.d.ts +2 -5
  21. package/dist/extensions/device.extension.js +16 -22
  22. package/dist/extensions/device.extension.js.map +1 -1
  23. package/dist/extensions/entity.extension.d.ts +2 -61
  24. package/dist/extensions/entity.extension.js +42 -83
  25. package/dist/extensions/entity.extension.js.map +1 -1
  26. package/dist/extensions/events.extension.d.ts +3 -11
  27. package/dist/extensions/events.extension.js +8 -11
  28. package/dist/extensions/events.extension.js.map +1 -1
  29. package/dist/extensions/fetch-api.extension.d.ts +12 -4
  30. package/dist/extensions/fetch-api.extension.js +23 -35
  31. package/dist/extensions/fetch-api.extension.js.map +1 -1
  32. package/dist/extensions/floor.extension.d.ts +2 -9
  33. package/dist/extensions/floor.extension.js +17 -23
  34. package/dist/extensions/floor.extension.js.map +1 -1
  35. package/dist/extensions/id-by.extension.js +15 -20
  36. package/dist/extensions/id-by.extension.js.map +1 -1
  37. package/dist/extensions/index.d.ts +1 -0
  38. package/dist/extensions/index.js +16 -18
  39. package/dist/extensions/index.js.map +1 -1
  40. package/dist/extensions/internal.extension.d.ts +18 -0
  41. package/dist/extensions/internal.extension.js +102 -0
  42. package/dist/extensions/internal.extension.js.map +1 -0
  43. package/dist/extensions/label.extension.d.ts +2 -9
  44. package/dist/extensions/label.extension.js +17 -23
  45. package/dist/extensions/label.extension.js.map +1 -1
  46. package/dist/extensions/reference.extension.d.ts +2 -12
  47. package/dist/extensions/reference.extension.js +19 -25
  48. package/dist/extensions/reference.extension.js.map +1 -1
  49. package/dist/extensions/registry.extension.d.ts +2 -7
  50. package/dist/extensions/registry.extension.js +1 -4
  51. package/dist/extensions/registry.extension.js.map +1 -1
  52. package/dist/extensions/websocket-api.extension.d.ts +3 -78
  53. package/dist/extensions/websocket-api.extension.js +82 -165
  54. package/dist/extensions/websocket-api.extension.js.map +1 -1
  55. package/dist/extensions/zone.extension.d.ts +2 -7
  56. package/dist/extensions/zone.extension.js +15 -21
  57. package/dist/extensions/zone.extension.js.map +1 -1
  58. package/dist/hass.module.d.ts +47 -36
  59. package/dist/hass.module.js +70 -70
  60. package/dist/hass.module.js.map +1 -1
  61. package/dist/helpers/backup.helper.js +1 -2
  62. package/dist/helpers/constants.helper.js +15 -18
  63. package/dist/helpers/constants.helper.js.map +1 -1
  64. package/dist/helpers/device.helper.js +2 -5
  65. package/dist/helpers/device.helper.js.map +1 -1
  66. package/dist/helpers/entity-state.helper.d.ts +3 -8
  67. package/dist/helpers/entity-state.helper.js +1 -8
  68. package/dist/helpers/entity-state.helper.js.map +1 -1
  69. package/dist/helpers/features.helper.js +79 -85
  70. package/dist/helpers/features.helper.js.map +1 -1
  71. package/dist/helpers/fetch/calendar.js +1 -2
  72. package/dist/helpers/fetch/configuration.js +2 -5
  73. package/dist/helpers/fetch/configuration.js.map +1 -1
  74. package/dist/helpers/fetch/index.js +5 -8
  75. package/dist/helpers/fetch/index.js.map +1 -1
  76. package/dist/helpers/fetch/server-log.js +1 -2
  77. package/dist/helpers/fetch/server-log.js.map +1 -1
  78. package/dist/helpers/fetch/service-list.js +1 -2
  79. package/dist/helpers/fetch/weather-forecasts.js +1 -2
  80. package/dist/helpers/fetch/weather-forecasts.js.map +1 -1
  81. package/dist/helpers/fetch.helper.d.ts +162 -0
  82. package/dist/helpers/fetch.helper.js +161 -0
  83. package/dist/helpers/fetch.helper.js.map +1 -0
  84. package/dist/helpers/id-by.helper.js +1 -2
  85. package/dist/helpers/index.d.ts +2 -1
  86. package/dist/helpers/index.js +13 -15
  87. package/dist/helpers/index.js.map +1 -1
  88. package/dist/helpers/interfaces.helper.d.ts +228 -0
  89. package/dist/helpers/interfaces.helper.js +10 -0
  90. package/dist/helpers/interfaces.helper.js.map +1 -0
  91. package/dist/helpers/manifest.helper.d.ts +0 -1
  92. package/dist/helpers/manifest.helper.js +0 -1
  93. package/dist/helpers/notify.helper.d.ts +13 -5
  94. package/dist/helpers/notify.helper.js +1 -2
  95. package/dist/helpers/registry.js +7 -10
  96. package/dist/helpers/registry.js.map +1 -1
  97. package/dist/helpers/utility.helper.d.ts +6 -1
  98. package/dist/helpers/utility.helper.js +9 -13
  99. package/dist/helpers/utility.helper.js.map +1 -1
  100. package/dist/helpers/websocket.helper.d.ts +1 -2
  101. package/dist/helpers/websocket.helper.js +1 -2
  102. package/dist/index.js +5 -8
  103. package/dist/index.js.map +1 -1
  104. package/dist/mock_assistant/extensions/area.extension.d.ts +8 -0
  105. package/dist/mock_assistant/extensions/area.extension.js +51 -0
  106. package/dist/mock_assistant/extensions/area.extension.js.map +1 -0
  107. package/dist/mock_assistant/extensions/config.extension.d.ts +14 -0
  108. package/dist/mock_assistant/extensions/config.extension.js +29 -0
  109. package/dist/mock_assistant/extensions/config.extension.js.map +1 -0
  110. package/dist/mock_assistant/extensions/device.extension.d.ts +8 -0
  111. package/dist/mock_assistant/extensions/device.extension.js +33 -0
  112. package/dist/mock_assistant/extensions/device.extension.js.map +1 -0
  113. package/dist/mock_assistant/extensions/entity-registry.extension.d.ts +13 -0
  114. package/dist/mock_assistant/extensions/entity-registry.extension.js +28 -0
  115. package/dist/mock_assistant/extensions/entity-registry.extension.js.map +1 -0
  116. package/dist/mock_assistant/extensions/entity.extension.d.ts +30 -0
  117. package/dist/mock_assistant/extensions/entity.extension.js +77 -0
  118. package/dist/mock_assistant/extensions/entity.extension.js.map +1 -0
  119. package/dist/mock_assistant/extensions/events.extension.d.ts +1 -1
  120. package/dist/mock_assistant/extensions/events.extension.js +3 -6
  121. package/dist/mock_assistant/extensions/events.extension.js.map +1 -1
  122. package/dist/mock_assistant/extensions/fetch.extension.d.ts +1 -0
  123. package/dist/mock_assistant/extensions/fetch.extension.js +4 -0
  124. package/dist/mock_assistant/extensions/fetch.extension.js.map +1 -0
  125. package/dist/mock_assistant/extensions/fixtures.extension.d.ts +1 -1
  126. package/dist/mock_assistant/extensions/fixtures.extension.js +29 -33
  127. package/dist/mock_assistant/extensions/fixtures.extension.js.map +1 -1
  128. package/dist/mock_assistant/extensions/floor.extension.d.ts +8 -0
  129. package/dist/mock_assistant/extensions/floor.extension.js +51 -0
  130. package/dist/mock_assistant/extensions/floor.extension.js.map +1 -0
  131. package/dist/mock_assistant/extensions/index.d.ts +10 -0
  132. package/dist/mock_assistant/extensions/index.js +12 -5
  133. package/dist/mock_assistant/extensions/index.js.map +1 -1
  134. package/dist/mock_assistant/extensions/label.extension.d.ts +8 -0
  135. package/dist/mock_assistant/extensions/label.extension.js +51 -0
  136. package/dist/mock_assistant/extensions/label.extension.js.map +1 -0
  137. package/dist/mock_assistant/extensions/services.extension.d.ts +12 -0
  138. package/dist/mock_assistant/extensions/services.extension.js +20 -0
  139. package/dist/mock_assistant/extensions/services.extension.js.map +1 -0
  140. package/dist/mock_assistant/extensions/websocket-api.extension.d.ts +15 -0
  141. package/dist/mock_assistant/extensions/websocket-api.extension.js +68 -0
  142. package/dist/mock_assistant/extensions/websocket-api.extension.js.map +1 -0
  143. package/dist/mock_assistant/extensions/zone.extension.d.ts +8 -0
  144. package/dist/mock_assistant/extensions/zone.extension.js +51 -0
  145. package/dist/mock_assistant/extensions/zone.extension.js.map +1 -0
  146. package/dist/mock_assistant/helpers/fixtures.js +1 -2
  147. package/dist/mock_assistant/helpers/index.d.ts +0 -1
  148. package/dist/mock_assistant/helpers/index.js +1 -5
  149. package/dist/mock_assistant/helpers/index.js.map +1 -1
  150. package/dist/mock_assistant/index.js +3 -6
  151. package/dist/mock_assistant/index.js.map +1 -1
  152. package/dist/mock_assistant/main.js +11 -15
  153. package/dist/mock_assistant/main.js.map +1 -1
  154. package/dist/mock_assistant/mock-assistant.module.d.ts +156 -3
  155. package/dist/mock_assistant/mock-assistant.module.js +56 -11
  156. package/dist/mock_assistant/mock-assistant.module.js.map +1 -1
  157. package/dist/quickboot.module.js +5 -8
  158. package/dist/quickboot.module.js.map +1 -1
  159. package/dist/testing/area.spec.js +106 -194
  160. package/dist/testing/area.spec.js.map +1 -1
  161. package/dist/testing/backup.spec.js +97 -139
  162. package/dist/testing/backup.spec.js.map +1 -1
  163. package/dist/testing/config.spec.js +79 -153
  164. package/dist/testing/config.spec.js.map +1 -1
  165. package/dist/testing/device.spec.js +35 -69
  166. package/dist/testing/device.spec.js.map +1 -1
  167. package/dist/testing/entity.spec.js +94 -149
  168. package/dist/testing/entity.spec.js.map +1 -1
  169. package/dist/testing/events.spec.js +33 -57
  170. package/dist/testing/events.spec.js.map +1 -1
  171. package/dist/testing/fetch-api.spec.js +242 -427
  172. package/dist/testing/fetch-api.spec.js.map +1 -1
  173. package/dist/testing/fixtures.spec.d.ts +1 -0
  174. package/dist/testing/fixtures.spec.js +150 -0
  175. package/dist/testing/fixtures.spec.js.map +1 -0
  176. package/dist/testing/floor.spec.js +106 -194
  177. package/dist/testing/floor.spec.js.map +1 -1
  178. package/dist/testing/id-by.spec.js +68 -107
  179. package/dist/testing/id-by.spec.js.map +1 -1
  180. package/dist/testing/label.spec.js +106 -194
  181. package/dist/testing/label.spec.js.map +1 -1
  182. package/dist/testing/ref-by.spec.js +155 -219
  183. package/dist/testing/ref-by.spec.js.map +1 -1
  184. package/dist/testing/websocket.spec.d.ts +1 -8
  185. package/dist/testing/websocket.spec.js +35 -50
  186. package/dist/testing/websocket.spec.js.map +1 -1
  187. package/dist/testing/workflow.spec.js +82 -81
  188. package/dist/testing/workflow.spec.js.map +1 -1
  189. package/dist/testing/zone.spec.js +61 -113
  190. package/dist/testing/zone.spec.js.map +1 -1
  191. package/package.json +62 -44
  192. package/scripts/mock-assistant.sh +5 -0
  193. package/scripts/run-e2e.sh +7 -0
  194. package/scripts/test.sh +2 -0
  195. package/src/dynamic.ts +4254 -0
  196. package/src/extensions/area.extension.ts +118 -0
  197. package/src/extensions/backup.extension.ts +63 -0
  198. package/src/extensions/call-proxy.extension.ts +113 -0
  199. package/src/extensions/config.extension.ts +119 -0
  200. package/src/extensions/conversation.extension.ts +46 -0
  201. package/src/extensions/device.extension.ts +56 -0
  202. package/src/extensions/entity.extension.ts +344 -0
  203. package/src/extensions/events.extension.ts +25 -0
  204. package/src/extensions/fetch-api.extension.ts +269 -0
  205. package/src/extensions/floor.extension.ts +76 -0
  206. package/src/extensions/id-by.extension.ts +157 -0
  207. package/src/extensions/index.ts +16 -0
  208. package/src/extensions/internal.extension.ts +145 -0
  209. package/src/extensions/label.extension.ts +83 -0
  210. package/src/extensions/reference.extension.ts +330 -0
  211. package/src/extensions/registry.extension.ts +44 -0
  212. package/src/extensions/websocket-api.extension.ts +554 -0
  213. package/src/extensions/zone.extension.ts +69 -0
  214. package/src/hass.module.ts +217 -0
  215. package/src/helpers/backup.helper.ts +11 -0
  216. package/src/helpers/constants.helper.ts +30 -0
  217. package/src/helpers/device.helper.ts +25 -0
  218. package/src/helpers/entity-state.helper.ts +171 -0
  219. package/src/helpers/features.helper.ts +580 -0
  220. package/src/helpers/fetch/calendar.ts +54 -0
  221. package/src/helpers/fetch/configuration.ts +75 -0
  222. package/src/helpers/fetch/index.ts +5 -0
  223. package/src/helpers/fetch/server-log.ts +28 -0
  224. package/src/helpers/fetch/service-list.ts +64 -0
  225. package/src/helpers/fetch/weather-forecasts.ts +86 -0
  226. package/src/helpers/fetch.helper.ts +328 -0
  227. package/src/helpers/id-by.helper.ts +53 -0
  228. package/src/helpers/index.ts +13 -0
  229. package/src/helpers/interfaces.helper.ts +340 -0
  230. package/src/helpers/manifest.helper.ts +0 -0
  231. package/src/helpers/notify.helper.ts +302 -0
  232. package/src/helpers/registry.ts +281 -0
  233. package/src/helpers/utility.helper.ts +147 -0
  234. package/src/helpers/websocket.helper.ts +117 -0
  235. package/src/index.ts +5 -0
  236. package/src/mock_assistant/extensions/area.extension.ts +62 -0
  237. package/src/mock_assistant/extensions/config.extension.ts +33 -0
  238. package/src/mock_assistant/extensions/device.extension.ts +44 -0
  239. package/src/mock_assistant/extensions/entity-registry.extension.ts +41 -0
  240. package/src/mock_assistant/extensions/entity.extension.ts +114 -0
  241. package/src/mock_assistant/extensions/events.extension.ts +37 -0
  242. package/src/mock_assistant/extensions/fetch.extension.ts +3 -0
  243. package/src/mock_assistant/extensions/fixtures.extension.ts +79 -0
  244. package/src/mock_assistant/extensions/floor.extension.ts +64 -0
  245. package/src/mock_assistant/extensions/index.ts +12 -0
  246. package/src/mock_assistant/extensions/label.extension.ts +64 -0
  247. package/src/mock_assistant/extensions/services.extension.ts +25 -0
  248. package/src/mock_assistant/extensions/websocket-api.extension.ts +84 -0
  249. package/src/mock_assistant/extensions/zone.extension.ts +65 -0
  250. package/src/mock_assistant/helpers/fixtures.ts +22 -0
  251. package/src/mock_assistant/helpers/index.ts +1 -0
  252. package/src/mock_assistant/index.ts +3 -0
  253. package/src/mock_assistant/main.ts +46 -0
  254. package/src/mock_assistant/mock-assistant.module.ts +90 -0
  255. package/src/quickboot.module.ts +23 -0
  256. package/src/testing/area.spec.ts +189 -0
  257. package/src/testing/backup.spec.ts +157 -0
  258. package/src/testing/config.spec.ts +188 -0
  259. package/src/testing/device.spec.ts +89 -0
  260. package/src/testing/entity.spec.ts +171 -0
  261. package/src/testing/events.spec.ts +78 -0
  262. package/src/testing/fetch-api.spec.ts +410 -0
  263. package/src/testing/fixtures.spec.ts +158 -0
  264. package/src/testing/floor.spec.ts +186 -0
  265. package/src/testing/id-by.spec.ts +140 -0
  266. package/src/testing/label.spec.ts +186 -0
  267. package/src/testing/ref-by.spec.ts +300 -0
  268. package/src/testing/websocket.spec.ts +63 -0
  269. package/src/testing/workflow.spec.ts +195 -0
  270. package/src/testing/zone.spec.ts +109 -0
  271. package/dist/helpers/metrics.helper.d.ts +0 -29
  272. package/dist/helpers/metrics.helper.js +0 -62
  273. package/dist/helpers/metrics.helper.js.map +0 -1
  274. package/dist/mock_assistant/helpers/utils.d.ts +0 -4
  275. package/dist/mock_assistant/helpers/utils.js +0 -57
  276. package/dist/mock_assistant/helpers/utils.js.map +0 -1
@@ -0,0 +1,41 @@
1
+ import { TServiceParams } from "@digital-alchemy/core";
2
+
3
+ import { TRawEntityIds } from "../../dynamic";
4
+ import { EntityRegistryItem } from "../../helpers";
5
+
6
+ export function MockEntityRegistryExtension({ mock_assistant, hass }: TServiceParams) {
7
+ let entityRegistry = new Map<TRawEntityIds, EntityRegistryItem<TRawEntityIds>>();
8
+
9
+ hass.entity.registry.list = async () => [...entityRegistry.values()];
10
+
11
+ const sendUpdate = () =>
12
+ mock_assistant.socket.sendMessage({
13
+ event: { event_type: "entity_registry_updated" },
14
+ type: "event",
15
+ });
16
+
17
+ mock_assistant.socket.onMessage<{ entity_id: TRawEntityIds }>(
18
+ "config/entity_registry/get",
19
+ message => {
20
+ mock_assistant.socket.sendMessage({
21
+ id: message.id,
22
+ result: entityRegistry.get(message.entity_id),
23
+ type: "result",
24
+ });
25
+ },
26
+ );
27
+
28
+ return {
29
+ /**
30
+ * does not imply sendUpdate
31
+ */
32
+ loadFixtures(incoming: EntityRegistryItem<TRawEntityIds>[]) {
33
+ entityRegistry = new Map(incoming.map(i => [i.entity_id, i]));
34
+ },
35
+
36
+ /**
37
+ * emit entity_registry_updated
38
+ */
39
+ sendUpdate,
40
+ };
41
+ }
@@ -0,0 +1,114 @@
1
+ import { deepExtend, InternalError, is, sleep, TServiceParams } from "@digital-alchemy/core";
2
+
3
+ import { TRawEntityIds } from "../../dynamic";
4
+ import { ENTITY_STATE, PICK_ENTITY } from "../../helpers";
5
+
6
+ export function MockEntityExtension({
7
+ hass,
8
+ internal,
9
+ context,
10
+ logger,
11
+ config,
12
+ mock_assistant,
13
+ }: TServiceParams) {
14
+ let entities = new Map<TRawEntityIds, ENTITY_STATE<TRawEntityIds>>();
15
+
16
+ const origGetAll = hass.fetch.getAllEntities;
17
+
18
+ hass.fetch.getAllEntities = async () => [...entities.values()];
19
+
20
+ function setupState(incoming: SetupStateOptions) {
21
+ if (internal.boot.completedLifecycleEvents.has("PreInit")) {
22
+ logger.error(`run [setupState] as part of the .setup command of your test`);
23
+ throw new InternalError(context, "LATE_SETUP", "Must call setupState before preInit");
24
+ }
25
+ const list = Object.keys(incoming) as PICK_ENTITY[];
26
+ list.forEach((key: PICK_ENTITY) => {
27
+ const data = entities.get(key);
28
+ entities.set(key, {
29
+ ...data,
30
+ state: incoming[key].state,
31
+ });
32
+ });
33
+ }
34
+
35
+ async function emitChange<ENTITY extends PICK_ENTITY>(
36
+ entity: ENTITY,
37
+ update: PartialUpdate<ENTITY>,
38
+ ) {
39
+ const old_state = entities.get(entity);
40
+ if (hass.socket.connectionState !== "connected") {
41
+ throw new InternalError(context, "EARLY_CHANGE", "Websocket does not identify as connected");
42
+ }
43
+ if (!old_state) {
44
+ throw new InternalError(
45
+ context,
46
+ "MISSING_ENTITY",
47
+ "Cannot find existing entity for old_state",
48
+ );
49
+ }
50
+ const new_state = deepExtend({}, old_state);
51
+ if ("state" in update) {
52
+ new_state.state = update.state;
53
+ }
54
+ if (!is.empty(update.attributes)) {
55
+ new_state.attributes = deepExtend(new_state.attributes, update.attributes);
56
+ }
57
+
58
+ mock_assistant.socket.sendMessage({
59
+ event: {
60
+ data: { new_state, old_state },
61
+ event_type: "state_changed",
62
+ },
63
+ type: "event",
64
+ });
65
+
66
+ // allow changes to propagate properly
67
+ await sleep(config.mock_assistant.EMIT_SLEEP);
68
+ }
69
+
70
+ return {
71
+ /**
72
+ *
73
+ */
74
+ emitChange,
75
+
76
+ /**
77
+ * @internal
78
+ */
79
+ loadFixtures(incoming: ENTITY_STATE<TRawEntityIds>[]) {
80
+ if (!is.empty(entities)) {
81
+ // this should not be possible, the dependency resolution order of tests SHOULD prevent
82
+ // if you get this error, let me know how
83
+ throw new InternalError(
84
+ context,
85
+ "FIXTURES_ALREADY_LOADED",
86
+ "There is data in the entity fixtures already, order of operations wrong",
87
+ );
88
+ }
89
+ entities = new Map(incoming.map(i => [i.entity_id, i]));
90
+ },
91
+
92
+ /**
93
+ * @internal
94
+ *
95
+ * restores code references, only used for testing internals
96
+ */
97
+ monkeyReset() {
98
+ hass.fetch.getAllEntities = origGetAll;
99
+ },
100
+
101
+ /**
102
+ * Does not emit update event
103
+ *
104
+ * Intended for test setup
105
+ */
106
+ setupState,
107
+ };
108
+ }
109
+
110
+ type PartialUpdate<ENTITY extends PICK_ENTITY> = Partial<
111
+ Pick<ENTITY_STATE<ENTITY>, "state" | "attributes">
112
+ >;
113
+
114
+ type SetupStateOptions = Partial<{ [ENTITY in PICK_ENTITY]: PartialUpdate<ENTITY> }>;
@@ -0,0 +1,37 @@
1
+ import { sleep, TServiceParams } from "@digital-alchemy/core";
2
+
3
+ import { ANY_ENTITY, ENTITY_STATE, EntityUpdateEvent } from "../../helpers";
4
+
5
+ const SUPER_SHORT = 1;
6
+
7
+ export function MockEvents({ mock_assistant, hass }: TServiceParams) {
8
+ let id = 1000;
9
+
10
+ async function emitEvent(event: string, data: object) {
11
+ id++;
12
+ await hass.socket.onMessage({
13
+ event: {
14
+ data,
15
+ event_type: event,
16
+ } as EntityUpdateEvent,
17
+ id: id,
18
+ type: "event",
19
+ });
20
+ }
21
+
22
+ async function emitEntityUpdate<ENTITY extends ANY_ENTITY>(
23
+ entity: ENTITY,
24
+ new_state: Partial<ENTITY_STATE<ENTITY>>,
25
+ ) {
26
+ const old_state = mock_assistant.fixtures.byId(entity);
27
+ new_state = mock_assistant.fixtures.replace(entity, new_state);
28
+ await emitEvent("state_changed", { new_state, old_state });
29
+ // help ensure all the async flows settle
30
+ await sleep(SUPER_SHORT);
31
+ }
32
+
33
+ return {
34
+ emitEntityUpdate,
35
+ emitEvent,
36
+ };
37
+ }
@@ -0,0 +1,3 @@
1
+ export function MockFetchExtension() {
2
+ //
3
+ }
@@ -0,0 +1,79 @@
1
+ import { BootstrapException, is, TServiceParams } from "@digital-alchemy/core";
2
+ import { existsSync, readFileSync } from "fs";
3
+
4
+ import { ANY_ENTITY, ENTITY_STATE } from "../../helpers";
5
+ import { ScannerCacheData } from "../helpers";
6
+
7
+ type StateOptions = Partial<{
8
+ [entity in ANY_ENTITY]: Partial<ENTITY_STATE<entity>>;
9
+ }>;
10
+
11
+ // this naming pattern is confusing sometimes
12
+ // don't think about it too much
13
+ export function MockFixtures({
14
+ lifecycle,
15
+ config,
16
+ internal,
17
+ context,
18
+ mock_assistant,
19
+ }: TServiceParams) {
20
+ // This file DELIBERATELY breaks some rules
21
+ // Setup actions that depend on config are not NORMALLY expected to run inside constructor
22
+
23
+ const { FIXTURES_FILE } = config.mock_assistant;
24
+ if (!existsSync(FIXTURES_FILE)) {
25
+ throw new BootstrapException(
26
+ context,
27
+ "MISSING_FIXTURES_FILE",
28
+ `${FIXTURES_FILE} does not exist`,
29
+ );
30
+ }
31
+ if (is.empty(config.hass.TOKEN)) {
32
+ // prevents throwing errors
33
+ internal.boilerplate.configuration.set("hass", "TOKEN", "--");
34
+ }
35
+ const data = JSON.parse(readFileSync(FIXTURES_FILE, "utf8")) as ScannerCacheData;
36
+ mock_assistant.device.loadFixtures(data.devices);
37
+ mock_assistant.floor.loadFixtures(data.floors);
38
+ mock_assistant.area.loadFixtures(data.areas);
39
+ mock_assistant.label.loadFixtures(data.labels);
40
+ mock_assistant.config.loadFixtures(data.config);
41
+ mock_assistant.entity.loadFixtures(data.entities);
42
+ mock_assistant.entity_registry.loadFixtures(data.entity_registry);
43
+ mock_assistant.services.loadFixtures(data.services);
44
+ // TODO zones are not currently included in fixtures
45
+ // more of a completion thing than them having any particular use
46
+ //
47
+ // mock_assistant.zone.set(data.);
48
+
49
+ function setState(options: StateOptions) {
50
+ lifecycle.onPreInit(() => {
51
+ const entities = Object.keys(options) as ANY_ENTITY[];
52
+ entities.forEach(i => replace(i, options[i]));
53
+ });
54
+ }
55
+
56
+ function byId(entity: ANY_ENTITY) {
57
+ return mock_assistant.fixtures.data.entities.find(i => i.entity_id === entity);
58
+ }
59
+
60
+ function replace<ENTITY extends ANY_ENTITY>(
61
+ entity: ENTITY,
62
+ new_state: Partial<ENTITY_STATE<ENTITY>>,
63
+ ): ENTITY_STATE<ENTITY> {
64
+ const old_state = byId(entity);
65
+ const { data } = mock_assistant.fixtures;
66
+ data.entities = data.entities.filter(i => i.entity_id !== entity);
67
+
68
+ const updated = { ...old_state, ...new_state } as ENTITY_STATE<ENTITY>;
69
+ mock_assistant.fixtures.data.entities.push(updated);
70
+ return updated;
71
+ }
72
+
73
+ return {
74
+ byId,
75
+ data,
76
+ replace,
77
+ setState,
78
+ };
79
+ }
@@ -0,0 +1,64 @@
1
+ import { TServiceParams } from "@digital-alchemy/core";
2
+
3
+ import { TFloorId } from "../../dynamic";
4
+ import { FloorDetails } from "../../helpers";
5
+
6
+ export function MockFloorExtension({ mock_assistant }: TServiceParams) {
7
+ let floors = new Map<TFloorId, FloorDetails>();
8
+
9
+ mock_assistant.socket.onMessage("config/floor_registry/list", message => {
10
+ mock_assistant.socket.sendMessage({
11
+ id: message.id,
12
+ result: [...floors.values()],
13
+ type: "result",
14
+ });
15
+ });
16
+
17
+ mock_assistant.socket.onMessage<{ floor_id: TFloorId }>(
18
+ "config/floor_registry/delete",
19
+ message => {
20
+ floors.delete(message.floor_id);
21
+ sendUpdate();
22
+ mock_assistant.socket.sendMessage({
23
+ id: message.id,
24
+ result: null,
25
+ type: "result",
26
+ });
27
+ },
28
+ );
29
+
30
+ mock_assistant.socket.onMessage<FloorDetails>("config/floor_registry/create", message => {
31
+ message.floor_id = message.name as TFloorId;
32
+ floors.set(message.floor_id as TFloorId, message);
33
+ sendUpdate();
34
+ mock_assistant.socket.sendMessage({
35
+ id: message.id,
36
+ result: null,
37
+ type: "result",
38
+ });
39
+ });
40
+ mock_assistant.socket.onMessage<FloorDetails>("config/floor_registry/update", message => {
41
+ floors.set(message.floor_id as TFloorId, message);
42
+ sendUpdate();
43
+ mock_assistant.socket.sendMessage({
44
+ id: message.id,
45
+ result: null,
46
+ type: "result",
47
+ });
48
+ });
49
+
50
+ const sendUpdate = () =>
51
+ mock_assistant.socket.sendMessage({
52
+ event: { event_type: "floor_registry_updated" },
53
+ type: "event",
54
+ });
55
+
56
+ return {
57
+ /**
58
+ * @internal
59
+ */
60
+ loadFixtures(incoming: FloorDetails[]) {
61
+ floors = new Map(incoming.map(i => [i.floor_id, i]));
62
+ },
63
+ };
64
+ }
@@ -0,0 +1,12 @@
1
+ export * from "./area.extension";
2
+ export * from "./config.extension";
3
+ export * from "./device.extension";
4
+ export * from "./entity.extension";
5
+ export * from "./entity-registry.extension";
6
+ export * from "./events.extension";
7
+ export * from "./fixtures.extension";
8
+ export * from "./floor.extension";
9
+ export * from "./label.extension";
10
+ export * from "./services.extension";
11
+ export * from "./websocket-api.extension";
12
+ export * from "./zone.extension";
@@ -0,0 +1,64 @@
1
+ import { TServiceParams } from "@digital-alchemy/core";
2
+
3
+ import { TLabelId } from "../../dynamic";
4
+ import { LabelDefinition } from "../../helpers";
5
+
6
+ export function MockLabelExtension({ mock_assistant }: TServiceParams) {
7
+ let labels = new Map<TLabelId, LabelDefinition>();
8
+
9
+ mock_assistant.socket.onMessage("config/label_registry/list", message => {
10
+ mock_assistant.socket.sendMessage({
11
+ id: message.id,
12
+ result: [...labels.values()],
13
+ type: "result",
14
+ });
15
+ });
16
+
17
+ mock_assistant.socket.onMessage<{ label_id: TLabelId }>(
18
+ "config/label_registry/delete",
19
+ message => {
20
+ labels.delete(message.label_id);
21
+ sendUpdate();
22
+ mock_assistant.socket.sendMessage({
23
+ id: message.id,
24
+ result: null,
25
+ type: "result",
26
+ });
27
+ },
28
+ );
29
+
30
+ mock_assistant.socket.onMessage<LabelDefinition>("config/label_registry/create", message => {
31
+ message.label_id = message.name as TLabelId;
32
+ labels.set(message.label_id as TLabelId, message);
33
+ sendUpdate();
34
+ mock_assistant.socket.sendMessage({
35
+ id: message.id,
36
+ result: null,
37
+ type: "result",
38
+ });
39
+ });
40
+ mock_assistant.socket.onMessage<LabelDefinition>("config/label_registry/update", message => {
41
+ labels.set(message.label_id as TLabelId, message);
42
+ sendUpdate();
43
+ mock_assistant.socket.sendMessage({
44
+ id: message.id,
45
+ result: null,
46
+ type: "result",
47
+ });
48
+ });
49
+
50
+ const sendUpdate = () =>
51
+ mock_assistant.socket.sendMessage({
52
+ event: { event_type: "label_registry_updated" },
53
+ type: "event",
54
+ });
55
+
56
+ return {
57
+ /**
58
+ * @internal
59
+ */
60
+ loadFixtures(incoming: LabelDefinition[]) {
61
+ labels = new Map(incoming.map(i => [i.label_id, i]));
62
+ },
63
+ };
64
+ }
@@ -0,0 +1,25 @@
1
+ import { TServiceParams } from "@digital-alchemy/core";
2
+
3
+ import { HassServiceDTO } from "../../helpers";
4
+
5
+ export function MockServices({ hass }: TServiceParams) {
6
+ let services: HassServiceDTO[];
7
+
8
+ const origList = hass.fetch.listServices;
9
+ hass.fetch.listServices = async () => services;
10
+
11
+ return {
12
+ /**
13
+ * @internal
14
+ */
15
+ loadFixtures(incoming: HassServiceDTO[]) {
16
+ services = incoming;
17
+ },
18
+ /**
19
+ * @internal
20
+ */
21
+ monkeyReset() {
22
+ hass.fetch.listServices = origList;
23
+ },
24
+ };
25
+ }
@@ -0,0 +1,84 @@
1
+ import { START, TBlackHole, TServiceParams } from "@digital-alchemy/core";
2
+ import EventEmitter from "events";
3
+ import { PartialDeep, WritableDeep } from "type-fest";
4
+ import WS from "ws";
5
+
6
+ import { SocketMessageDTO } from "../../helpers";
7
+
8
+ const CONNECTION_CLOSED = 0;
9
+ // const CONNECTION_OPEN = 1;
10
+ // const CONNECTION_FAILED = 2;
11
+ const UNLIMITED = 0;
12
+
13
+ export const INTERNAL_MESSAGE = "INTERNAL_MESSAGE";
14
+
15
+ export function MockWebsocketAPI({ hass, config, lifecycle }: TServiceParams) {
16
+ const connection = new EventEmitter() as WritableDeep<WS>;
17
+ connection.setMaxListeners(UNLIMITED);
18
+ lifecycle.onShutdownStart(() => {
19
+ connection.removeAllListeners();
20
+ });
21
+
22
+ connection.readyState = CONNECTION_CLOSED;
23
+ let id = START;
24
+ connection.close = () => {
25
+ connection.readyState = CONNECTION_CLOSED;
26
+ };
27
+ // connection.send = (...data) =>
28
+
29
+ hass.socket.createConnection = () => {
30
+ setImmediate(() => {
31
+ if (!config.mock_assistant.PASS_AUTH) {
32
+ return;
33
+ }
34
+ sendMessage({ type: "auth_ok" });
35
+ });
36
+ return connection;
37
+ };
38
+
39
+ connection.send = (data: string) => {
40
+ const payload = JSON.parse(data) as { type: string; id: number };
41
+ connection.emit(INTERNAL_MESSAGE, payload);
42
+ switch (payload.type) {
43
+ case "ping": {
44
+ sendMessage({ id: id++, type: "pong" });
45
+ return;
46
+ }
47
+ case "auth": {
48
+ return;
49
+ }
50
+ default: {
51
+ setImmediate(() => {
52
+ sendMessage({
53
+ id: payload.id,
54
+ result: null,
55
+ type: "result",
56
+ });
57
+ });
58
+ }
59
+ }
60
+ };
61
+
62
+ function sendMessage(data: PartialDeep<SocketMessageDTO>) {
63
+ setImmediate(() => {
64
+ connection.emit("message", JSON.stringify(data));
65
+ });
66
+ }
67
+
68
+ return {
69
+ connection: connection as WS,
70
+ onMessage<DATA extends object>(
71
+ type: string,
72
+ callback: (data: DATA & MessageData) => TBlackHole,
73
+ ) {
74
+ connection.on(INTERNAL_MESSAGE, (data: DATA & MessageData) => {
75
+ if (data.type === type) {
76
+ callback(data as DATA & MessageData);
77
+ }
78
+ });
79
+ },
80
+ sendMessage,
81
+ };
82
+ }
83
+
84
+ type MessageData = { id: number; type: string };
@@ -0,0 +1,65 @@
1
+ import { TServiceParams } from "@digital-alchemy/core";
2
+
3
+ import { TZoneId } from "../../dynamic";
4
+ import { ZoneDetails } from "../../helpers";
5
+
6
+ export function MockZoneExtension({ mock_assistant }: TServiceParams) {
7
+ let zones = new Map<TZoneId, ZoneDetails>();
8
+
9
+ mock_assistant.socket.onMessage("config/zone_registry/list", message => {
10
+ mock_assistant.socket.sendMessage({
11
+ id: message.id,
12
+ result: [...zones.values()],
13
+ type: "result",
14
+ });
15
+ });
16
+
17
+ mock_assistant.socket.onMessage<{ zone_id: TZoneId }>("config/zone_registry/delete", message => {
18
+ zones.delete(message.zone_id);
19
+ sendUpdate();
20
+ mock_assistant.socket.sendMessage({
21
+ id: message.id,
22
+ result: null,
23
+ type: "result",
24
+ });
25
+ });
26
+
27
+ mock_assistant.socket.onMessage<ZoneDetails>(
28
+ "config/zone_registry/create",
29
+ (message: ZoneDetails) => {
30
+ message.id = message.name as TZoneId;
31
+ zones.set(message.id as TZoneId, message);
32
+ sendUpdate();
33
+ mock_assistant.socket.sendMessage({
34
+ id: message.id,
35
+ result: null,
36
+ type: "result",
37
+ });
38
+ },
39
+ );
40
+
41
+ mock_assistant.socket.onMessage<ZoneDetails>("config/zone_registry/update", message => {
42
+ zones.set(message.id as TZoneId, message);
43
+ sendUpdate();
44
+ mock_assistant.socket.sendMessage({
45
+ id: message.id,
46
+ result: null,
47
+ type: "result",
48
+ });
49
+ });
50
+
51
+ const sendUpdate = () =>
52
+ mock_assistant.socket.sendMessage({
53
+ event: { event_type: "zone_registry_updated" },
54
+ type: "event",
55
+ });
56
+
57
+ return {
58
+ /**
59
+ * @internal
60
+ */
61
+ loadFixtures(incoming: ZoneDetails[]) {
62
+ zones = new Map(incoming.map(i => [i.id, i]));
63
+ },
64
+ };
65
+ }
@@ -0,0 +1,22 @@
1
+ import {
2
+ ANY_ENTITY,
3
+ AreaDetails,
4
+ DeviceDetails,
5
+ ENTITY_STATE,
6
+ EntityRegistryItem,
7
+ FloorDetails,
8
+ HassConfig,
9
+ HassServiceDTO as HassServiceDefinition,
10
+ LabelDefinition,
11
+ } from "../../helpers";
12
+
13
+ export type ScannerCacheData = {
14
+ areas: AreaDetails[];
15
+ config: HassConfig;
16
+ devices: DeviceDetails[];
17
+ entities: ENTITY_STATE<ANY_ENTITY>[];
18
+ entity_registry: EntityRegistryItem<ANY_ENTITY>[];
19
+ floors: FloorDetails[];
20
+ labels: LabelDefinition[];
21
+ services: HassServiceDefinition[];
22
+ };
@@ -0,0 +1 @@
1
+ export * from "./fixtures";
@@ -0,0 +1,3 @@
1
+ export * from "./extensions";
2
+ export * from "./helpers";
3
+ export * from "./mock-assistant.module";
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env node
2
+ import { CreateApplication, TServiceParams } from "@digital-alchemy/core";
3
+ import { writeFileSync } from "fs";
4
+ import { join } from "path";
5
+ import { cwd } from "process";
6
+
7
+ import { LIB_HASS } from "..";
8
+ import { ScannerCacheData } from "./helpers";
9
+
10
+ const writeFixtures = CreateApplication({
11
+ configuration: {
12
+ FIXTURES_FILE: {
13
+ default: join(cwd(), "fixtures.json"),
14
+ description: [],
15
+ type: "string",
16
+ },
17
+ },
18
+ libraries: [LIB_HASS],
19
+ name: "mock_assistant",
20
+ services: {
21
+ Write({ hass, lifecycle, config }: TServiceParams) {
22
+ lifecycle.onReady(async () => {
23
+ writeFileSync(
24
+ config.mock_assistant.FIXTURES_FILE,
25
+ JSON.stringify(
26
+ {
27
+ areas: hass.area.current,
28
+ config: await hass.fetch.getConfig(),
29
+ devices: hass.device.current,
30
+ entities: hass.entity.listEntities().map(i => hass.entity.getCurrentState(i)),
31
+ entity_registry: hass.entity.registry.current,
32
+ floors: hass.floor.current,
33
+ labels: hass.label.current,
34
+ services: await hass.fetch.listServices(),
35
+ } as ScannerCacheData,
36
+ undefined,
37
+ " ",
38
+ ),
39
+ "utf8",
40
+ );
41
+ process.exit();
42
+ });
43
+ },
44
+ },
45
+ });
46
+ setImmediate(async () => writeFixtures.bootstrap());