matterbridge-lg-thinq-plugin 0.1.0

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 (210) hide show
  1. package/.claude/CLAUDE.md +43 -0
  2. package/.claude/agents/compiler.md +91 -0
  3. package/.claude/agents/direct-executor.md +98 -0
  4. package/.claude/agents/documenter.md +78 -0
  5. package/.claude/agents/finalizer.md +233 -0
  6. package/.claude/agents/implementer.md +112 -0
  7. package/.claude/agents/investigator.md +100 -0
  8. package/.claude/agents/release-manager.md +203 -0
  9. package/.claude/agents/reviewer.md +131 -0
  10. package/.claude/agents/technical-architect.md +403 -0
  11. package/.claude/agents/test-writer.md +159 -0
  12. package/.claude/agents/wiki-manager.md +147 -0
  13. package/.claude/instructions/agent-prompts.md +90 -0
  14. package/.claude/instructions/shared-rules.md +11 -0
  15. package/.claude/instructions/team-orchestrator-policy.md +165 -0
  16. package/.claude/memory.md +186 -0
  17. package/.claude/output-styles/engineer-manager.md +38 -0
  18. package/.claude/settings.json +40 -0
  19. package/.claude/settings.local.json +30 -0
  20. package/.claude/skills/load-policy/SKILL.md +13 -0
  21. package/.claude/skills/ref-idea/SKILL.md +44 -0
  22. package/.claude/skills/status-of/SKILL.md +38 -0
  23. package/.claude/skills/workspace-console/SKILL.md +49 -0
  24. package/.claude/statusline-command.sh +130 -0
  25. package/.claude/statusline.md +53 -0
  26. package/.claude/templates/approval-packet.template.html +171 -0
  27. package/.claude/templates/reference-workspaces.md +16 -0
  28. package/.claude/tools/cli-runner-server.cjs +220 -0
  29. package/.claude/tools/discord-send-server.cjs +188 -0
  30. package/.claude/tools/github-release-server.cjs +181 -0
  31. package/.claude/tools/glob-grep-server.cjs +267 -0
  32. package/.claude/tools/read-log-server.cjs +175 -0
  33. package/.github/codeql/codeql-config.yml +14 -0
  34. package/.github/workflows/build.yml +54 -0
  35. package/.github/workflows/codeql.yml +59 -0
  36. package/.github/workflows/coverage.yml +70 -0
  37. package/.github/workflows/publish.yml +103 -0
  38. package/.mcp.json +34 -0
  39. package/CHANGELOG.md +195 -0
  40. package/LICENSE +21 -0
  41. package/dist/cli/commands/devices.js +52 -0
  42. package/dist/cli/commands/energy.js +122 -0
  43. package/dist/cli/commands/login.js +54 -0
  44. package/dist/cli/help.js +50 -0
  45. package/dist/cli/main.js +56 -0
  46. package/dist/cli/session.js +13 -0
  47. package/dist/cli/types.js +1 -0
  48. package/dist/cli/utils.js +29 -0
  49. package/dist/cli.js +2 -0
  50. package/dist/constants/index.js +1 -0
  51. package/dist/core/domain/entities/ThinqDevice.js +43 -0
  52. package/dist/core/domain/value-objects/AirConditionerCapabilities.js +22 -0
  53. package/dist/core/domain/value-objects/ThinqFilterState.js +35 -0
  54. package/dist/core/domain/value-objects/ThinqSnapshot.js +125 -0
  55. package/dist/errors/AuthenticationError.js +26 -0
  56. package/dist/errors/BaseError.js +23 -0
  57. package/dist/errors/index.js +2 -0
  58. package/dist/model/LgThinkqPluginPlatformConfig.js +23 -0
  59. package/dist/model/LgThinqPluginPlatformConfig.js +23 -0
  60. package/dist/module.js +212 -0
  61. package/dist/platform/deviceRegistry.js +26 -0
  62. package/dist/platform/platformConfigManager.js +89 -0
  63. package/dist/platform/platformState.js +9 -0
  64. package/dist/platform/thinq/thinqAirConditionerCommandHandlers.js +190 -0
  65. package/dist/platform/thinq/thinqAirConditionerEndpointFactory.js +53 -0
  66. package/dist/platform/thinq/thinqAirConditionerFilterResetCommandHandler.js +54 -0
  67. package/dist/platform/thinq/thinqAirConditionerFilterStateSync.js +19 -0
  68. package/dist/platform/thinq/thinqAirConditionerStateSync.js +205 -0
  69. package/dist/platform/thinq/thinqDeviceConfigReconciler.js +26 -0
  70. package/dist/platform/thinq/thinqDeviceConfigurator.js +124 -0
  71. package/dist/platform/thinq/thinqDeviceDiscovery.js +33 -0
  72. package/dist/platform/thinq/thinqWasherCommandHandlers.js +54 -0
  73. package/dist/platform/thinq/thinqWasherEndpointFactory.js +6 -0
  74. package/dist/platform/thinq/thinqWasherStateSync.js +31 -0
  75. package/dist/platform/thinq/thinqWasherStopCommandResolver.js +38 -0
  76. package/dist/services/authentication/AccountAuthStrategy.js +135 -0
  77. package/dist/services/authentication/AuthContext.js +1 -0
  78. package/dist/services/authentication/AuthenticationCoordinator.js +29 -0
  79. package/dist/services/authentication/IAuthStrategy.js +1 -0
  80. package/dist/services/authentication/TokenAuthStrategy.js +27 -0
  81. package/dist/services/authentication/UserDataRepository.js +32 -0
  82. package/dist/services/thinq/gateway.js +61 -0
  83. package/dist/services/thinq/mqtt/mqttCertificate.js +63 -0
  84. package/dist/services/thinq/mqtt/mqttConnectionEvents.js +37 -0
  85. package/dist/services/thinq/mqtt/mqttKeyRepository.js +33 -0
  86. package/dist/services/thinq/mqtt/mqttRetry.js +25 -0
  87. package/dist/services/thinq/mqtt/thinqMqttListener.js +97 -0
  88. package/dist/services/thinq/serviceContainer.js +73 -0
  89. package/dist/services/thinq/session.js +48 -0
  90. package/dist/services/thinq/thinqApiClient.js +258 -0
  91. package/dist/services/thinq/thinqDeviceService.js +75 -0
  92. package/dist/services/thinq/thinqFilterMonitoringService.js +40 -0
  93. package/dist/settings.js +9 -0
  94. package/dist/tests/helpers/testUtils.js +87 -0
  95. package/dist/utils/mask.js +39 -0
  96. package/docs/README_CLI.md +166 -0
  97. package/eslint.config.js +194 -0
  98. package/matterbridge-lg-thinq-plugin.config.json +27 -0
  99. package/matterbridge-lg-thinq-plugin.schema.json +520 -0
  100. package/package.json +107 -0
  101. package/prettier.config.js +49 -0
  102. package/scripts/parse-junit-failures.mjs +45 -0
  103. package/scripts/parse-tsc-failures.mjs +48 -0
  104. package/scripts/run-build-local-summary.mjs +29 -0
  105. package/scripts/run-format-summary.mjs +36 -0
  106. package/scripts/run-lint-fix-summary.mjs +38 -0
  107. package/scripts/run-precommit-summary.mjs +94 -0
  108. package/src/cli/commands/devices.ts +67 -0
  109. package/src/cli/commands/energy.ts +160 -0
  110. package/src/cli/commands/login.ts +74 -0
  111. package/src/cli/help.ts +53 -0
  112. package/src/cli/main.ts +66 -0
  113. package/src/cli/session.ts +15 -0
  114. package/src/cli/types.ts +10 -0
  115. package/src/cli/utils.ts +30 -0
  116. package/src/cli.ts +3 -0
  117. package/src/constants/index.ts +8 -0
  118. package/src/core/domain/entities/ThinqDevice.test.ts +224 -0
  119. package/src/core/domain/entities/ThinqDevice.ts +74 -0
  120. package/src/core/domain/value-objects/AirConditionerCapabilities.ts +50 -0
  121. package/src/core/domain/value-objects/ThinqFilterState.ts +50 -0
  122. package/src/core/domain/value-objects/ThinqSnapshot.ts +162 -0
  123. package/src/errors/AuthenticationError.ts +37 -0
  124. package/src/errors/BaseError.ts +38 -0
  125. package/src/errors/index.ts +24 -0
  126. package/src/model/LgThinqPluginPlatformConfig.ts +94 -0
  127. package/src/module.ts +298 -0
  128. package/src/platform/deviceRegistry.ts +58 -0
  129. package/src/platform/platformConfigManager.ts +133 -0
  130. package/src/platform/platformState.ts +16 -0
  131. package/src/platform/thinq/thinqAirConditionerCommandHandlers.ts +291 -0
  132. package/src/platform/thinq/thinqAirConditionerEndpointFactory.ts +145 -0
  133. package/src/platform/thinq/thinqAirConditionerFilterResetCommandHandler.ts +96 -0
  134. package/src/platform/thinq/thinqAirConditionerFilterStateSync.ts +42 -0
  135. package/src/platform/thinq/thinqAirConditionerStateSync.ts +343 -0
  136. package/src/platform/thinq/thinqDeviceConfigReconciler.ts +42 -0
  137. package/src/platform/thinq/thinqDeviceConfigurator.test.ts +90 -0
  138. package/src/platform/thinq/thinqDeviceConfigurator.ts +191 -0
  139. package/src/platform/thinq/thinqDeviceDiscovery.test.ts +173 -0
  140. package/src/platform/thinq/thinqDeviceDiscovery.ts +51 -0
  141. package/src/platform/thinq/thinqWasherCommandHandlers.ts +120 -0
  142. package/src/platform/thinq/thinqWasherEndpointFactory.ts +37 -0
  143. package/src/platform/thinq/thinqWasherStateSync.ts +60 -0
  144. package/src/platform/thinq/thinqWasherStopCommandResolver.ts +62 -0
  145. package/src/services/authentication/AccountAuthStrategy.test.ts +358 -0
  146. package/src/services/authentication/AccountAuthStrategy.ts +209 -0
  147. package/src/services/authentication/AuthContext.ts +19 -0
  148. package/src/services/authentication/AuthenticationCoordinator.test.ts +217 -0
  149. package/src/services/authentication/AuthenticationCoordinator.ts +59 -0
  150. package/src/services/authentication/IAuthStrategy.ts +10 -0
  151. package/src/services/authentication/TokenAuthStrategy.test.ts +167 -0
  152. package/src/services/authentication/TokenAuthStrategy.ts +40 -0
  153. package/src/services/authentication/UserDataRepository.test.ts +149 -0
  154. package/src/services/authentication/UserDataRepository.ts +46 -0
  155. package/src/services/thinq/gateway.ts +86 -0
  156. package/src/services/thinq/mqtt/mqttCertificate.test.ts +344 -0
  157. package/src/services/thinq/mqtt/mqttCertificate.ts +102 -0
  158. package/src/services/thinq/mqtt/mqttConnectionEvents.test.ts +276 -0
  159. package/src/services/thinq/mqtt/mqttConnectionEvents.ts +76 -0
  160. package/src/services/thinq/mqtt/mqttKeyRepository.test.ts +164 -0
  161. package/src/services/thinq/mqtt/mqttKeyRepository.ts +43 -0
  162. package/src/services/thinq/mqtt/mqttRetry.test.ts +239 -0
  163. package/src/services/thinq/mqtt/mqttRetry.ts +43 -0
  164. package/src/services/thinq/mqtt/thinqMqttListener.test.ts +349 -0
  165. package/src/services/thinq/mqtt/thinqMqttListener.ts +120 -0
  166. package/src/services/thinq/serviceContainer.test.ts +203 -0
  167. package/src/services/thinq/serviceContainer.ts +105 -0
  168. package/src/services/thinq/session.test.ts +287 -0
  169. package/src/services/thinq/session.ts +69 -0
  170. package/src/services/thinq/thinqApiClient.test.ts +702 -0
  171. package/src/services/thinq/thinqApiClient.ts +418 -0
  172. package/src/services/thinq/thinqDeviceService.test.ts +415 -0
  173. package/src/services/thinq/thinqDeviceService.ts +101 -0
  174. package/src/services/thinq/thinqFilterMonitoringService.test.ts +264 -0
  175. package/src/services/thinq/thinqFilterMonitoringService.ts +53 -0
  176. package/src/settings.ts +18 -0
  177. package/src/tests/cli/commands/devices.test.ts +293 -0
  178. package/src/tests/cli/commands/login.test.ts +240 -0
  179. package/src/tests/cli/help.test.ts +46 -0
  180. package/src/tests/cli/main.test.ts +214 -0
  181. package/src/tests/cli/session.test.ts +198 -0
  182. package/src/tests/cli/utils.test.ts +194 -0
  183. package/src/tests/core/domain/value-objects/AirConditionerCapabilities.test.ts +685 -0
  184. package/src/tests/core/domain/value-objects/ThinqFilterState.test.ts +210 -0
  185. package/src/tests/core/domain/value-objects/ThinqSnapshot.test.ts +1035 -0
  186. package/src/tests/helpers/testUtils.ts +118 -0
  187. package/src/tests/model/LgThinqPluginPlatformConfig.test.ts +81 -0
  188. package/src/tests/platform/deviceRegistry.test.ts +177 -0
  189. package/src/tests/platform/platformConfigManager.test.ts +790 -0
  190. package/src/tests/platform/platformState.test.ts +41 -0
  191. package/src/tests/platform/thinq/thinqAirConditionerCommandHandlers.test.ts +879 -0
  192. package/src/tests/platform/thinq/thinqAirConditionerEndpointFactory.test.ts +993 -0
  193. package/src/tests/platform/thinq/thinqAirConditionerFilterResetCommandHandler.test.ts +508 -0
  194. package/src/tests/platform/thinq/thinqAirConditionerFilterStateSync.test.ts +212 -0
  195. package/src/tests/platform/thinq/thinqAirConditionerStateSync.test.ts +1586 -0
  196. package/src/tests/platform/thinq/thinqDeviceConfigReconciler.test.ts +269 -0
  197. package/src/tests/platform/thinq/thinqDeviceConfigurator.test.ts +492 -0
  198. package/src/tests/platform/thinq/thinqWasherCommandHandlers.test.ts +594 -0
  199. package/src/tests/platform/thinq/thinqWasherEndpointFactory.test.ts +84 -0
  200. package/src/tests/platform/thinq/thinqWasherStateSync.test.ts +373 -0
  201. package/src/tests/platform/thinq/thinqWasherStopCommandResolver.test.ts +392 -0
  202. package/src/tests/settings.test.ts +33 -0
  203. package/src/utils/mask.ts +50 -0
  204. package/tsconfig.base.json +25 -0
  205. package/tsconfig.json +9 -0
  206. package/tsconfig.production.json +13 -0
  207. package/tsconfig.vitest.json +5 -0
  208. package/vite.config.ts +43 -0
  209. package/wiki/memory-archive.md +12 -0
  210. package/wiki/reference-workspaces.md +23 -0
@@ -0,0 +1,685 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import {
4
+ DEFAULT_AIR_CONDITIONER_CAPABILITIES,
5
+ resolveAirConditionerCapabilities,
6
+ } from '../../../../core/domain/value-objects/AirConditionerCapabilities.js';
7
+ import type { ThinqDeviceConfigEntry } from '../../../../model/LgThinqPluginPlatformConfig.js';
8
+
9
+ describe('AirConditionerCapabilities', () => {
10
+ describe('DEFAULT_AIR_CONDITIONER_CAPABILITIES', () => {
11
+ it('should have existing capabilities set to true', () => {
12
+ // Assert
13
+ expect(DEFAULT_AIR_CONDITIONER_CAPABILITIES.supportsHeat).toBe(true);
14
+ expect(DEFAULT_AIR_CONDITIONER_CAPABILITIES.supportsFanSpeedControl).toBe(true);
15
+ });
16
+
17
+ it('should have Phase B swing mode capability set to false (opt-in)', () => {
18
+ // Assert
19
+ expect(DEFAULT_AIR_CONDITIONER_CAPABILITIES.supportsSwingMode).toBe(false);
20
+ });
21
+
22
+ it('should have Phase C sensor capabilities set to false (opt-in)', () => {
23
+ // Assert
24
+ expect(DEFAULT_AIR_CONDITIONER_CAPABILITIES.supportsHumiditySensor).toBe(false);
25
+ expect(DEFAULT_AIR_CONDITIONER_CAPABILITIES.supportsAirQualitySensor).toBe(false);
26
+ });
27
+
28
+ it('should have Phase D energy monitoring capability set to false (opt-in)', () => {
29
+ // Assert
30
+ expect(DEFAULT_AIR_CONDITIONER_CAPABILITIES.supportsEnergyMonitoring).toBe(false);
31
+ });
32
+
33
+ it('should have filter monitoring capability set to false (opt-in)', () => {
34
+ // Assert
35
+ expect(DEFAULT_AIR_CONDITIONER_CAPABILITIES.supportsFilterMonitoring).toBe(false);
36
+ });
37
+ });
38
+
39
+ describe('resolveAirConditionerCapabilities', () => {
40
+ it('should return all-true default when devices array is undefined', () => {
41
+ // Arrange & Act
42
+ const result = resolveAirConditionerCapabilities(undefined, 'device-123');
43
+
44
+ // Assert
45
+ expect(result).toEqual(DEFAULT_AIR_CONDITIONER_CAPABILITIES);
46
+ });
47
+
48
+ it('should return all-true default when devices array is empty', () => {
49
+ // Arrange & Act
50
+ const result = resolveAirConditionerCapabilities([], 'device-123');
51
+
52
+ // Assert
53
+ expect(result).toEqual(DEFAULT_AIR_CONDITIONER_CAPABILITIES);
54
+ });
55
+
56
+ it('should return all-true default when device is not found in array', () => {
57
+ // Arrange
58
+ const devices: ThinqDeviceConfigEntry[] = [{ deviceId: 'device-999', capabilities: { supportsHeat: false } }];
59
+
60
+ // Act
61
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
62
+
63
+ // Assert
64
+ expect(result).toEqual(DEFAULT_AIR_CONDITIONER_CAPABILITIES);
65
+ });
66
+
67
+ it('should return all-true default when device is found but has no capabilities key', () => {
68
+ // Arrange
69
+ const devices: ThinqDeviceConfigEntry[] = [{ deviceId: 'device-123' }];
70
+
71
+ // Act
72
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
73
+
74
+ // Assert
75
+ expect(result).toEqual(DEFAULT_AIR_CONDITIONER_CAPABILITIES);
76
+ });
77
+
78
+ it('should override supportsHeat only when specified', () => {
79
+ // Arrange
80
+ const devices: ThinqDeviceConfigEntry[] = [
81
+ {
82
+ deviceId: 'device-123',
83
+ capabilities: { supportsHeat: false },
84
+ },
85
+ ];
86
+
87
+ // Act
88
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
89
+
90
+ // Assert
91
+ expect(result.supportsHeat).toBe(false);
92
+ expect(result.supportsFanSpeedControl).toBe(true); // default
93
+ });
94
+
95
+ it('should override supportsFanSpeedControl only when specified', () => {
96
+ // Arrange
97
+ const devices: ThinqDeviceConfigEntry[] = [
98
+ {
99
+ deviceId: 'device-123',
100
+ capabilities: { supportsFanSpeedControl: false },
101
+ },
102
+ ];
103
+
104
+ // Act
105
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
106
+
107
+ // Assert
108
+ expect(result.supportsHeat).toBe(true); // default
109
+ expect(result.supportsFanSpeedControl).toBe(false);
110
+ });
111
+
112
+ it('should override all three flags when all are specified as false', () => {
113
+ // Arrange
114
+ const devices: ThinqDeviceConfigEntry[] = [
115
+ {
116
+ deviceId: 'device-123',
117
+ capabilities: {
118
+ supportsHeat: false,
119
+ supportsFanSpeedControl: false,
120
+ },
121
+ },
122
+ ];
123
+
124
+ // Act
125
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
126
+
127
+ // Assert
128
+ expect(result.supportsHeat).toBe(false);
129
+ expect(result.supportsFanSpeedControl).toBe(false);
130
+ });
131
+
132
+ it('should resolve correct device from multiple entries in array', () => {
133
+ // Arrange
134
+ const devices: ThinqDeviceConfigEntry[] = [
135
+ { deviceId: 'device-001', capabilities: { supportsHeat: false } },
136
+ { deviceId: 'device-123', capabilities: { supportsSwingMode: true } },
137
+ { deviceId: 'device-999', capabilities: { supportsFanSpeedControl: false } },
138
+ ];
139
+
140
+ // Act
141
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
142
+
143
+ // Assert
144
+ expect(result.supportsHeat).toBe(true); // default
145
+ expect(result.supportsSwingMode).toBe(true); // from device-123
146
+ expect(result.supportsFanSpeedControl).toBe(true); // default
147
+ });
148
+
149
+ it('should preserve readonly properties', () => {
150
+ // Arrange
151
+ const devices: ThinqDeviceConfigEntry[] = [
152
+ {
153
+ deviceId: 'device-123',
154
+ capabilities: { supportsHeat: false, supportsFanSpeedControl: true },
155
+ },
156
+ ];
157
+
158
+ // Act
159
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
160
+
161
+ // Assert
162
+ expect(Object.getOwnPropertyDescriptor(result, 'supportsHeat')?.writable).not.toBe(false);
163
+ // Result is not frozen, it's just a regular object with readonly interface
164
+ expect(result).toEqual({
165
+ supportsHeat: false,
166
+ supportsFanSpeedControl: true,
167
+ supportsSwingMode: false,
168
+ supportsHumiditySensor: false,
169
+ supportsAirQualitySensor: false,
170
+ supportsEnergyMonitoring: false,
171
+ supportsFilterMonitoring: false,
172
+ });
173
+ });
174
+
175
+ it('should handle device with capabilities object but undefined individual properties', () => {
176
+ // Arrange
177
+ const devices: ThinqDeviceConfigEntry[] = [
178
+ {
179
+ deviceId: 'device-123',
180
+ capabilities: {
181
+ supportsHeat: undefined,
182
+ supportsFanSpeedControl: undefined,
183
+ },
184
+ },
185
+ ];
186
+
187
+ // Act
188
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
189
+
190
+ // Assert
191
+ expect(result.supportsHeat).toBe(true); // defaults to true when undefined
192
+ expect(result.supportsFanSpeedControl).toBe(true); // defaults to true when undefined
193
+ });
194
+
195
+ it('should handle explicit true values for all capabilities', () => {
196
+ // Arrange
197
+ const devices: ThinqDeviceConfigEntry[] = [
198
+ {
199
+ deviceId: 'device-123',
200
+ capabilities: {
201
+ supportsHeat: true,
202
+ supportsFanSpeedControl: true,
203
+ },
204
+ },
205
+ ];
206
+
207
+ // Act
208
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
209
+
210
+ // Assert
211
+ expect(result).toEqual(DEFAULT_AIR_CONDITIONER_CAPABILITIES);
212
+ });
213
+
214
+ it('should default supportsSwingMode to false when undefined (Phase B)', () => {
215
+ // Arrange
216
+ const devices: ThinqDeviceConfigEntry[] = [{ deviceId: 'device-123', capabilities: {} }];
217
+
218
+ // Act
219
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
220
+
221
+ // Assert
222
+ expect(result.supportsSwingMode).toBe(false);
223
+ });
224
+
225
+ it('should override supportsSwingMode when specified as true (Phase B)', () => {
226
+ // Arrange
227
+ const devices: ThinqDeviceConfigEntry[] = [
228
+ {
229
+ deviceId: 'device-123',
230
+ capabilities: { supportsSwingMode: true },
231
+ },
232
+ ];
233
+
234
+ // Act
235
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
236
+
237
+ // Assert
238
+ expect(result.supportsSwingMode).toBe(true);
239
+ // Verify other flags remain at their defaults
240
+ expect(result.supportsHeat).toBe(true);
241
+ expect(result.supportsFanSpeedControl).toBe(true);
242
+ });
243
+
244
+ it('should override supportsSwingMode when specified as false (Phase B)', () => {
245
+ // Arrange
246
+ const devices: ThinqDeviceConfigEntry[] = [
247
+ {
248
+ deviceId: 'device-123',
249
+ capabilities: { supportsSwingMode: false },
250
+ },
251
+ ];
252
+
253
+ // Act
254
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
255
+
256
+ // Assert
257
+ expect(result.supportsSwingMode).toBe(false);
258
+ });
259
+
260
+ it('should enable supportsSwingMode with other capabilities (Phase B)', () => {
261
+ // Arrange
262
+ const devices: ThinqDeviceConfigEntry[] = [
263
+ {
264
+ deviceId: 'device-123',
265
+ capabilities: {
266
+ supportsSwingMode: true,
267
+ supportsHeat: true,
268
+ supportsFanSpeedControl: true,
269
+ },
270
+ },
271
+ ];
272
+
273
+ // Act
274
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
275
+
276
+ // Assert
277
+ expect(result.supportsSwingMode).toBe(true);
278
+ expect(result.supportsHeat).toBe(true);
279
+ expect(result.supportsFanSpeedControl).toBe(true);
280
+ });
281
+
282
+ it('should default supportsHumiditySensor to false when undefined (Phase C)', () => {
283
+ // Arrange
284
+ const devices: ThinqDeviceConfigEntry[] = [{ deviceId: 'device-123', capabilities: {} }];
285
+
286
+ // Act
287
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
288
+
289
+ // Assert
290
+ expect(result.supportsHumiditySensor).toBe(false);
291
+ });
292
+
293
+ it('should override supportsHumiditySensor when specified as true (Phase C)', () => {
294
+ // Arrange
295
+ const devices: ThinqDeviceConfigEntry[] = [
296
+ {
297
+ deviceId: 'device-123',
298
+ capabilities: { supportsHumiditySensor: true },
299
+ },
300
+ ];
301
+
302
+ // Act
303
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
304
+
305
+ // Assert
306
+ expect(result.supportsHumiditySensor).toBe(true);
307
+ // Verify other flags remain at their defaults
308
+ expect(result.supportsHeat).toBe(true);
309
+ expect(result.supportsFanSpeedControl).toBe(true);
310
+ expect(result.supportsAirQualitySensor).toBe(false);
311
+ });
312
+
313
+ it('should default supportsAirQualitySensor to false when undefined (Phase C)', () => {
314
+ // Arrange
315
+ const devices: ThinqDeviceConfigEntry[] = [{ deviceId: 'device-123', capabilities: {} }];
316
+
317
+ // Act
318
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
319
+
320
+ // Assert
321
+ expect(result.supportsAirQualitySensor).toBe(false);
322
+ });
323
+
324
+ it('should override supportsAirQualitySensor when specified as true (Phase C)', () => {
325
+ // Arrange
326
+ const devices: ThinqDeviceConfigEntry[] = [
327
+ {
328
+ deviceId: 'device-123',
329
+ capabilities: { supportsAirQualitySensor: true },
330
+ },
331
+ ];
332
+
333
+ // Act
334
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
335
+
336
+ // Assert
337
+ expect(result.supportsAirQualitySensor).toBe(true);
338
+ // Verify other flags remain at their defaults
339
+ expect(result.supportsHeat).toBe(true);
340
+ expect(result.supportsFanSpeedControl).toBe(true);
341
+ expect(result.supportsHumiditySensor).toBe(false);
342
+ });
343
+
344
+ it('should enable both Phase C sensor flags simultaneously', () => {
345
+ // Arrange
346
+ const devices: ThinqDeviceConfigEntry[] = [
347
+ {
348
+ deviceId: 'device-123',
349
+ capabilities: {
350
+ supportsHumiditySensor: true,
351
+ supportsAirQualitySensor: true,
352
+ },
353
+ },
354
+ ];
355
+
356
+ // Act
357
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
358
+
359
+ // Assert
360
+ expect(result.supportsHumiditySensor).toBe(true);
361
+ expect(result.supportsAirQualitySensor).toBe(true);
362
+ // Verify existing flags still default to true
363
+ expect(result.supportsHeat).toBe(true);
364
+ expect(result.supportsFanSpeedControl).toBe(true);
365
+ });
366
+
367
+ it('should isolate Phase C sensor flags across multiple devices', () => {
368
+ // Arrange
369
+ const devices: ThinqDeviceConfigEntry[] = [
370
+ {
371
+ deviceId: 'device-001',
372
+ capabilities: { supportsHumiditySensor: true },
373
+ },
374
+ {
375
+ deviceId: 'device-002',
376
+ capabilities: { supportsAirQualitySensor: true },
377
+ },
378
+ {
379
+ deviceId: 'device-003',
380
+ capabilities: {},
381
+ },
382
+ ];
383
+
384
+ // Act
385
+ const result001 = resolveAirConditionerCapabilities(devices, 'device-001');
386
+ const result002 = resolveAirConditionerCapabilities(devices, 'device-002');
387
+ const result003 = resolveAirConditionerCapabilities(devices, 'device-003');
388
+
389
+ // Assert
390
+ expect(result001.supportsHumiditySensor).toBe(true);
391
+ expect(result001.supportsAirQualitySensor).toBe(false);
392
+ expect(result002.supportsHumiditySensor).toBe(false);
393
+ expect(result002.supportsAirQualitySensor).toBe(true);
394
+ expect(result003.supportsHumiditySensor).toBe(false);
395
+ expect(result003.supportsAirQualitySensor).toBe(false);
396
+ });
397
+
398
+ it('should preserve full default object with all Phase C flags included', () => {
399
+ // Arrange
400
+ const devices: ThinqDeviceConfigEntry[] = [
401
+ {
402
+ deviceId: 'device-123',
403
+ capabilities: {
404
+ supportsHeat: true,
405
+ supportsFanSpeedControl: true,
406
+ },
407
+ },
408
+ ];
409
+
410
+ // Act
411
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
412
+
413
+ // Assert
414
+ expect(result).toEqual({
415
+ supportsHeat: true,
416
+ supportsFanSpeedControl: true,
417
+ supportsSwingMode: false,
418
+ supportsHumiditySensor: false,
419
+ supportsAirQualitySensor: false,
420
+ supportsEnergyMonitoring: false,
421
+ supportsFilterMonitoring: false,
422
+ });
423
+ });
424
+
425
+ it('should default supportsEnergyMonitoring to false when undefined (Phase D)', () => {
426
+ // Arrange
427
+ const devices: ThinqDeviceConfigEntry[] = [{ deviceId: 'device-123', capabilities: {} }];
428
+
429
+ // Act
430
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
431
+
432
+ // Assert
433
+ expect(result.supportsEnergyMonitoring).toBe(false);
434
+ });
435
+
436
+ it('should override supportsEnergyMonitoring when specified as true (Phase D)', () => {
437
+ // Arrange
438
+ const devices: ThinqDeviceConfigEntry[] = [
439
+ {
440
+ deviceId: 'device-123',
441
+ capabilities: { supportsEnergyMonitoring: true },
442
+ },
443
+ ];
444
+
445
+ // Act
446
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
447
+
448
+ // Assert
449
+ expect(result.supportsEnergyMonitoring).toBe(true);
450
+ // Verify other flags remain at their defaults
451
+ expect(result.supportsHeat).toBe(true);
452
+ expect(result.supportsFanSpeedControl).toBe(true);
453
+ expect(result.supportsHumiditySensor).toBe(false);
454
+ });
455
+
456
+ it('should override supportsEnergyMonitoring when specified as false (Phase D)', () => {
457
+ // Arrange
458
+ const devices: ThinqDeviceConfigEntry[] = [
459
+ {
460
+ deviceId: 'device-123',
461
+ capabilities: { supportsEnergyMonitoring: false },
462
+ },
463
+ ];
464
+
465
+ // Act
466
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
467
+
468
+ // Assert
469
+ expect(result.supportsEnergyMonitoring).toBe(false);
470
+ });
471
+
472
+ it('should combine supportsEnergyMonitoring with other Phase D and earlier flags', () => {
473
+ // Arrange
474
+ const devices: ThinqDeviceConfigEntry[] = [
475
+ {
476
+ deviceId: 'device-123',
477
+ capabilities: {
478
+ supportsEnergyMonitoring: true,
479
+ supportsHumiditySensor: true,
480
+ supportsAirQualitySensor: true,
481
+ supportsSwingMode: true,
482
+ },
483
+ },
484
+ ];
485
+
486
+ // Act
487
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
488
+
489
+ // Assert
490
+ expect(result.supportsEnergyMonitoring).toBe(true);
491
+ expect(result.supportsHumiditySensor).toBe(true);
492
+ expect(result.supportsAirQualitySensor).toBe(true);
493
+ expect(result.supportsSwingMode).toBe(true);
494
+ // Existing flags should still default to true
495
+ expect(result.supportsHeat).toBe(true);
496
+ expect(result.supportsFanSpeedControl).toBe(true);
497
+ });
498
+
499
+ it('should isolate Phase D energy monitoring flag across multiple devices', () => {
500
+ // Arrange
501
+ const devices: ThinqDeviceConfigEntry[] = [
502
+ {
503
+ deviceId: 'device-001',
504
+ capabilities: { supportsEnergyMonitoring: true },
505
+ },
506
+ {
507
+ deviceId: 'device-002',
508
+ capabilities: { supportsEnergyMonitoring: false },
509
+ },
510
+ {
511
+ deviceId: 'device-003',
512
+ capabilities: {},
513
+ },
514
+ ];
515
+
516
+ // Act
517
+ const result001 = resolveAirConditionerCapabilities(devices, 'device-001');
518
+ const result002 = resolveAirConditionerCapabilities(devices, 'device-002');
519
+ const result003 = resolveAirConditionerCapabilities(devices, 'device-003');
520
+
521
+ // Assert
522
+ expect(result001.supportsEnergyMonitoring).toBe(true);
523
+ expect(result002.supportsEnergyMonitoring).toBe(false);
524
+ expect(result003.supportsEnergyMonitoring).toBe(false);
525
+ });
526
+
527
+ it('should preserve full default object with all Phase D flags included', () => {
528
+ // Arrange
529
+ const devices: ThinqDeviceConfigEntry[] = [
530
+ {
531
+ deviceId: 'device-123',
532
+ capabilities: {
533
+ supportsHeat: true,
534
+ supportsFanSpeedControl: true,
535
+ supportsEnergyMonitoring: false,
536
+ },
537
+ },
538
+ ];
539
+
540
+ // Act
541
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
542
+
543
+ // Assert
544
+ expect(result).toEqual({
545
+ supportsHeat: true,
546
+ supportsFanSpeedControl: true,
547
+ supportsSwingMode: false,
548
+ supportsHumiditySensor: false,
549
+ supportsAirQualitySensor: false,
550
+ supportsEnergyMonitoring: false,
551
+ supportsFilterMonitoring: false,
552
+ });
553
+ });
554
+
555
+ it('should default supportsFilterMonitoring to false when undefined', () => {
556
+ // Arrange
557
+ const devices: ThinqDeviceConfigEntry[] = [{ deviceId: 'device-123', capabilities: {} }];
558
+
559
+ // Act
560
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
561
+
562
+ // Assert
563
+ expect(result.supportsFilterMonitoring).toBe(false);
564
+ });
565
+
566
+ it('should override supportsFilterMonitoring when specified as true', () => {
567
+ // Arrange
568
+ const devices: ThinqDeviceConfigEntry[] = [
569
+ {
570
+ deviceId: 'device-123',
571
+ capabilities: { supportsFilterMonitoring: true },
572
+ },
573
+ ];
574
+
575
+ // Act
576
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
577
+
578
+ // Assert
579
+ expect(result.supportsFilterMonitoring).toBe(true);
580
+ // Verify other flags remain at their defaults
581
+ expect(result.supportsHeat).toBe(true);
582
+ expect(result.supportsFanSpeedControl).toBe(true);
583
+ expect(result.supportsEnergyMonitoring).toBe(false);
584
+ });
585
+
586
+ it('should override supportsFilterMonitoring when specified as false', () => {
587
+ // Arrange
588
+ const devices: ThinqDeviceConfigEntry[] = [
589
+ {
590
+ deviceId: 'device-123',
591
+ capabilities: { supportsFilterMonitoring: false },
592
+ },
593
+ ];
594
+
595
+ // Act
596
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
597
+
598
+ // Assert
599
+ expect(result.supportsFilterMonitoring).toBe(false);
600
+ });
601
+
602
+ it('should combine supportsFilterMonitoring with other flags', () => {
603
+ // Arrange
604
+ const devices: ThinqDeviceConfigEntry[] = [
605
+ {
606
+ deviceId: 'device-123',
607
+ capabilities: {
608
+ supportsFilterMonitoring: true,
609
+ supportsEnergyMonitoring: true,
610
+ supportsHumiditySensor: true,
611
+ supportsSwingMode: true,
612
+ },
613
+ },
614
+ ];
615
+
616
+ // Act
617
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
618
+
619
+ // Assert
620
+ expect(result.supportsFilterMonitoring).toBe(true);
621
+ expect(result.supportsEnergyMonitoring).toBe(true);
622
+ expect(result.supportsHumiditySensor).toBe(true);
623
+ expect(result.supportsSwingMode).toBe(true);
624
+ // Existing flags should still default to true
625
+ expect(result.supportsHeat).toBe(true);
626
+ expect(result.supportsFanSpeedControl).toBe(true);
627
+ });
628
+
629
+ it('should isolate filter monitoring flag across multiple devices', () => {
630
+ // Arrange
631
+ const devices: ThinqDeviceConfigEntry[] = [
632
+ {
633
+ deviceId: 'device-001',
634
+ capabilities: { supportsFilterMonitoring: true },
635
+ },
636
+ {
637
+ deviceId: 'device-002',
638
+ capabilities: { supportsFilterMonitoring: false },
639
+ },
640
+ {
641
+ deviceId: 'device-003',
642
+ capabilities: {},
643
+ },
644
+ ];
645
+
646
+ // Act
647
+ const result001 = resolveAirConditionerCapabilities(devices, 'device-001');
648
+ const result002 = resolveAirConditionerCapabilities(devices, 'device-002');
649
+ const result003 = resolveAirConditionerCapabilities(devices, 'device-003');
650
+
651
+ // Assert
652
+ expect(result001.supportsFilterMonitoring).toBe(true);
653
+ expect(result002.supportsFilterMonitoring).toBe(false);
654
+ expect(result003.supportsFilterMonitoring).toBe(false);
655
+ });
656
+
657
+ it('should preserve full default object with all flags including filter monitoring', () => {
658
+ // Arrange
659
+ const devices: ThinqDeviceConfigEntry[] = [
660
+ {
661
+ deviceId: 'device-123',
662
+ capabilities: {
663
+ supportsHeat: true,
664
+ supportsFanSpeedControl: true,
665
+ supportsFilterMonitoring: true,
666
+ },
667
+ },
668
+ ];
669
+
670
+ // Act
671
+ const result = resolveAirConditionerCapabilities(devices, 'device-123');
672
+
673
+ // Assert
674
+ expect(result).toEqual({
675
+ supportsHeat: true,
676
+ supportsFanSpeedControl: true,
677
+ supportsSwingMode: false,
678
+ supportsHumiditySensor: false,
679
+ supportsAirQualitySensor: false,
680
+ supportsEnergyMonitoring: false,
681
+ supportsFilterMonitoring: true,
682
+ });
683
+ });
684
+ });
685
+ });