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,118 @@
1
+ import type { AnsiLogger } from 'matterbridge/logger';
2
+ import { vi } from 'vitest';
3
+
4
+ import type { GatewayData } from '../../services/thinq/gateway.js';
5
+ import type { ThinqDeviceData } from '../../services/thinq/thinqApiClient.js';
6
+
7
+ /**
8
+ * Creates a mock AnsiLogger for testing.
9
+ * All methods are vitest spies that can be asserted against.
10
+ */
11
+ export function createMockLogger(): AnsiLogger {
12
+ return {
13
+ debug: vi.fn(),
14
+ info: vi.fn(),
15
+ notice: vi.fn(),
16
+ warn: vi.fn(),
17
+ error: vi.fn(),
18
+ fatal: vi.fn(),
19
+ setMaxListeners: vi.fn(),
20
+ getMaxListeners: vi.fn(),
21
+ addListener: vi.fn(),
22
+ on: vi.fn(),
23
+ once: vi.fn(),
24
+ removeListener: vi.fn(),
25
+ off: vi.fn(),
26
+ removeAllListeners: vi.fn(),
27
+ listeners: vi.fn(),
28
+ rawListeners: vi.fn(),
29
+ emit: vi.fn(),
30
+ listenerCount: vi.fn(),
31
+ prependListener: vi.fn(),
32
+ prependOnceListener: vi.fn(),
33
+ eventNames: vi.fn(),
34
+ } as unknown as AnsiLogger;
35
+ }
36
+
37
+ /**
38
+ * Helper to create a partial object of a type without `as any`.
39
+ * Used for test fixtures where only specific properties are needed.
40
+ */
41
+ export function asPartial<T>(partial: Partial<T>): T {
42
+ return partial as T;
43
+ }
44
+
45
+ /**
46
+ * Builds a mock GatewayData response for testing.
47
+ */
48
+ export function buildGatewayResponse(overrides?: Partial<GatewayData>): GatewayData {
49
+ return {
50
+ empTermsUri: 'https://emp-terms.lgecloud.com',
51
+ empSpxUri: 'https://emp-spx.lgecloud.com',
52
+ thinq2Uri: 'https://aic-service.lgecloud.com',
53
+ thinq1Uri: 'https://v1aic-service.lgecloud.com',
54
+ countryCode: 'US',
55
+ languageCode: 'en',
56
+ ...overrides,
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Builds a mock ThinqDeviceData response for testing.
62
+ */
63
+ export function buildThinqDeviceData(overrides?: Partial<ThinqDeviceData>): ThinqDeviceData {
64
+ return {
65
+ deviceId: '12345678-1234-1234-1234-123456789012',
66
+ alias: 'Living Room AC',
67
+ modelJsonUri: 'https://example.com/model.json',
68
+ deviceType: 401,
69
+ modelName: 'DualCool Plus',
70
+ manufacture: {
71
+ macAddress: '00:00:00:00:00:00',
72
+ salesModel: 'AC123',
73
+ serialNo: 'SN12345',
74
+ manufactureModel: 'AC123-MANU',
75
+ },
76
+ modemInfo: {
77
+ appVersion: '1.0',
78
+ modelName: 'AC123',
79
+ },
80
+ snapshot: {
81
+ online: true,
82
+ 'airState.operation': 1,
83
+ 'airState.tempState.current': 24,
84
+ 'airState.tempState.target': 22,
85
+ 'airState.opMode': 0,
86
+ 'airState.windStrength': 2,
87
+ },
88
+ platformType: 'THINQ',
89
+ online: true,
90
+ ...overrides,
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Creates a mock node-persist LocalStorage for testing.
96
+ */
97
+ export function createMockNodePersist() {
98
+ return {
99
+ getItem: vi.fn(),
100
+ setItem: vi.fn(),
101
+ removeItem: vi.fn(),
102
+ clear: vi.fn(),
103
+ keys: vi.fn(),
104
+ length: vi.fn(),
105
+ };
106
+ }
107
+
108
+ /**
109
+ * Helper to set a readonly property on an object for testing.
110
+ * Useful for testing readonly class properties.
111
+ */
112
+ export function setReadOnlyProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
113
+ Object.defineProperty(obj, key, {
114
+ value,
115
+ writable: false,
116
+ configurable: true,
117
+ });
118
+ }
@@ -0,0 +1,81 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { createDefaultAdvancedFeature, createDefaultThinqConfig } from '../../model/LgThinqPluginPlatformConfig.js';
4
+
5
+ describe('LgThinqPluginPlatformConfig', () => {
6
+ describe('createDefaultAdvancedFeature', () => {
7
+ it('should return expected default values', () => {
8
+ const result = createDefaultAdvancedFeature();
9
+ expect(result).toEqual({
10
+ settings: {
11
+ debug: false,
12
+ clearStorageOnStartup: false,
13
+ forceAuthentication: false,
14
+ unregisterOnShutdown: false,
15
+ overrideMatterConfiguration: false,
16
+ matterOverrideSettings: {
17
+ matterVendorName: 'Matterbridge',
18
+ matterVendorId: 0xfff1,
19
+ },
20
+ },
21
+ });
22
+ });
23
+
24
+ it('should include overrideMatterConfiguration field with value false', () => {
25
+ const result = createDefaultAdvancedFeature();
26
+ expect(result.settings.overrideMatterConfiguration).toBe(false);
27
+ });
28
+
29
+ it('should include matterOverrideSettings with default vendor names only', () => {
30
+ const result = createDefaultAdvancedFeature();
31
+ expect(result.settings.matterOverrideSettings).toEqual({
32
+ matterVendorName: 'Matterbridge',
33
+ matterVendorId: 0xfff1,
34
+ });
35
+ expect(result.settings.matterOverrideSettings).not.toHaveProperty('matterProductName');
36
+ expect(result.settings.matterOverrideSettings).not.toHaveProperty('matterProductId');
37
+ });
38
+
39
+ it('should return a fresh object on each call', () => {
40
+ const first = createDefaultAdvancedFeature();
41
+ const second = createDefaultAdvancedFeature();
42
+
43
+ // Verify both are equal
44
+ expect(first).toEqual(second);
45
+
46
+ // Mutate the first object
47
+ first.settings.debug = true;
48
+
49
+ // Verify the second object is unaffected
50
+ expect(second.settings.debug).toBe(false);
51
+ });
52
+ });
53
+
54
+ describe('createDefaultThinqConfig', () => {
55
+ it('should return expected default values', () => {
56
+ const result = createDefaultThinqConfig();
57
+ expect(result).toEqual({
58
+ loginType: 'account',
59
+ country: 'US',
60
+ language: 'en-US',
61
+ devices: [],
62
+ });
63
+ });
64
+
65
+ it('should return a fresh object on each call', () => {
66
+ const first = createDefaultThinqConfig();
67
+ const second = createDefaultThinqConfig();
68
+
69
+ // Verify both are equal
70
+ expect(first).toEqual(second);
71
+
72
+ // Mutate the first object
73
+ first.loginType = 'token';
74
+ first.devices.push({ deviceId: 'test' });
75
+
76
+ // Verify the second object is unaffected
77
+ expect(second.loginType).toBe('account');
78
+ expect(second.devices).toHaveLength(0);
79
+ });
80
+ });
81
+ });
@@ -0,0 +1,177 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { DeviceRegistry } from '../../platform/deviceRegistry.js';
4
+
5
+ describe('DeviceRegistry', () => {
6
+ describe('register', () => {
7
+ it('should register a device and retrieve it', () => {
8
+ const registry = new DeviceRegistry();
9
+ const device = { id: '123', name: 'Test Device' };
10
+
11
+ registry.register('device-1', device);
12
+ expect(registry.getDevice('device-1')).toBe(device);
13
+ });
14
+
15
+ it('should not register device with empty id', () => {
16
+ const registry = new DeviceRegistry();
17
+ const device = { id: '123', name: 'Test Device' };
18
+
19
+ registry.register('', device);
20
+ expect(registry.getDevice('')).toBeUndefined();
21
+ expect(registry.size).toBe(0);
22
+ });
23
+
24
+ it('should overwrite device when registering with same id', () => {
25
+ const registry = new DeviceRegistry();
26
+ const device1 = { id: '123', name: 'Device 1' };
27
+ const device2 = { id: '456', name: 'Device 2' };
28
+
29
+ registry.register('device-1', device1);
30
+ registry.register('device-1', device2);
31
+
32
+ expect(registry.getDevice('device-1')).toBe(device2);
33
+ expect(registry.size).toBe(1);
34
+ });
35
+ });
36
+
37
+ describe('unregister', () => {
38
+ it('should remove a registered device', () => {
39
+ const registry = new DeviceRegistry();
40
+ const device = { id: '123', name: 'Test Device' };
41
+
42
+ registry.register('device-1', device);
43
+ expect(registry.size).toBe(1);
44
+
45
+ registry.unregister('device-1');
46
+ expect(registry.getDevice('device-1')).toBeUndefined();
47
+ expect(registry.size).toBe(0);
48
+ });
49
+
50
+ it('should silently ignore unregistering non-existent device', () => {
51
+ const registry = new DeviceRegistry();
52
+ expect(() => registry.unregister('non-existent')).not.toThrow();
53
+ expect(registry.size).toBe(0);
54
+ });
55
+ });
56
+
57
+ describe('getDevice', () => {
58
+ it('should return undefined for unregistered device', () => {
59
+ const registry = new DeviceRegistry();
60
+ expect(registry.getDevice('non-existent')).toBeUndefined();
61
+ });
62
+
63
+ it('should return registered device', () => {
64
+ const registry = new DeviceRegistry();
65
+ const device = { id: '123', name: 'Test Device' };
66
+
67
+ registry.register('device-1', device);
68
+ expect(registry.getDevice('device-1')).toBe(device);
69
+ });
70
+ });
71
+
72
+ describe('getAllDevices', () => {
73
+ it('should return empty array when no devices registered', () => {
74
+ const registry = new DeviceRegistry();
75
+ expect(registry.getAllDevices()).toEqual([]);
76
+ });
77
+
78
+ it('should return all registered devices', () => {
79
+ const registry = new DeviceRegistry();
80
+ const device1 = { id: '123', name: 'Device 1' };
81
+ const device2 = { id: '456', name: 'Device 2' };
82
+ const device3 = { id: '789', name: 'Device 3' };
83
+
84
+ registry.register('device-1', device1);
85
+ registry.register('device-2', device2);
86
+ registry.register('device-3', device3);
87
+
88
+ const allDevices = registry.getAllDevices();
89
+ expect(allDevices).toHaveLength(3);
90
+ expect(allDevices).toEqual(expect.arrayContaining([device1, device2, device3]));
91
+ });
92
+ });
93
+
94
+ describe('size getter', () => {
95
+ it('should return 0 when no devices registered', () => {
96
+ const registry = new DeviceRegistry();
97
+ expect(registry.size).toBe(0);
98
+ });
99
+
100
+ it('should return count after register', () => {
101
+ const registry = new DeviceRegistry();
102
+ registry.register('device-1', { id: '123' });
103
+ registry.register('device-2', { id: '456' });
104
+
105
+ expect(registry.size).toBe(2);
106
+ });
107
+
108
+ it('should decrement count after unregister', () => {
109
+ const registry = new DeviceRegistry();
110
+ registry.register('device-1', { id: '123' });
111
+ registry.register('device-2', { id: '456' });
112
+
113
+ registry.unregister('device-1');
114
+ expect(registry.size).toBe(1);
115
+ });
116
+
117
+ it('should return 0 after clear', () => {
118
+ const registry = new DeviceRegistry();
119
+ registry.register('device-1', { id: '123' });
120
+ registry.register('device-2', { id: '456' });
121
+
122
+ registry.clear();
123
+ expect(registry.size).toBe(0);
124
+ });
125
+ });
126
+
127
+ describe('hasDevices', () => {
128
+ it('should return false when no devices registered', () => {
129
+ const registry = new DeviceRegistry();
130
+ expect(registry.hasDevices()).toBe(false);
131
+ });
132
+
133
+ it('should return true when devices are registered', () => {
134
+ const registry = new DeviceRegistry();
135
+ registry.register('device-1', { id: '123' });
136
+
137
+ expect(registry.hasDevices()).toBe(true);
138
+ });
139
+
140
+ it('should return false after all devices removed', () => {
141
+ const registry = new DeviceRegistry();
142
+ registry.register('device-1', { id: '123' });
143
+ registry.register('device-2', { id: '456' });
144
+
145
+ registry.unregister('device-1');
146
+ expect(registry.hasDevices()).toBe(true);
147
+
148
+ registry.unregister('device-2');
149
+ expect(registry.hasDevices()).toBe(false);
150
+ });
151
+ });
152
+
153
+ describe('clear', () => {
154
+ it('should remove all devices', () => {
155
+ const registry = new DeviceRegistry();
156
+ registry.register('device-1', { id: '123' });
157
+ registry.register('device-2', { id: '456' });
158
+ registry.register('device-3', { id: '789' });
159
+
160
+ expect(registry.size).toBe(3);
161
+ registry.clear();
162
+ expect(registry.size).toBe(0);
163
+ expect(registry.hasDevices()).toBe(false);
164
+ });
165
+
166
+ it('should allow re-registering after clear', () => {
167
+ const registry = new DeviceRegistry();
168
+ registry.register('device-1', { id: '123' });
169
+
170
+ registry.clear();
171
+ expect(registry.size).toBe(0);
172
+
173
+ registry.register('device-1', { id: '123' });
174
+ expect(registry.size).toBe(1);
175
+ });
176
+ });
177
+ });