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.
- package/.claude/CLAUDE.md +43 -0
- package/.claude/agents/compiler.md +91 -0
- package/.claude/agents/direct-executor.md +98 -0
- package/.claude/agents/documenter.md +78 -0
- package/.claude/agents/finalizer.md +233 -0
- package/.claude/agents/implementer.md +112 -0
- package/.claude/agents/investigator.md +100 -0
- package/.claude/agents/release-manager.md +203 -0
- package/.claude/agents/reviewer.md +131 -0
- package/.claude/agents/technical-architect.md +403 -0
- package/.claude/agents/test-writer.md +159 -0
- package/.claude/agents/wiki-manager.md +147 -0
- package/.claude/instructions/agent-prompts.md +90 -0
- package/.claude/instructions/shared-rules.md +11 -0
- package/.claude/instructions/team-orchestrator-policy.md +165 -0
- package/.claude/memory.md +186 -0
- package/.claude/output-styles/engineer-manager.md +38 -0
- package/.claude/settings.json +40 -0
- package/.claude/settings.local.json +30 -0
- package/.claude/skills/load-policy/SKILL.md +13 -0
- package/.claude/skills/ref-idea/SKILL.md +44 -0
- package/.claude/skills/status-of/SKILL.md +38 -0
- package/.claude/skills/workspace-console/SKILL.md +49 -0
- package/.claude/statusline-command.sh +130 -0
- package/.claude/statusline.md +53 -0
- package/.claude/templates/approval-packet.template.html +171 -0
- package/.claude/templates/reference-workspaces.md +16 -0
- package/.claude/tools/cli-runner-server.cjs +220 -0
- package/.claude/tools/discord-send-server.cjs +188 -0
- package/.claude/tools/github-release-server.cjs +181 -0
- package/.claude/tools/glob-grep-server.cjs +267 -0
- package/.claude/tools/read-log-server.cjs +175 -0
- package/.github/codeql/codeql-config.yml +14 -0
- package/.github/workflows/build.yml +54 -0
- package/.github/workflows/codeql.yml +59 -0
- package/.github/workflows/coverage.yml +70 -0
- package/.github/workflows/publish.yml +103 -0
- package/.mcp.json +34 -0
- package/CHANGELOG.md +195 -0
- package/LICENSE +21 -0
- package/dist/cli/commands/devices.js +52 -0
- package/dist/cli/commands/energy.js +122 -0
- package/dist/cli/commands/login.js +54 -0
- package/dist/cli/help.js +50 -0
- package/dist/cli/main.js +56 -0
- package/dist/cli/session.js +13 -0
- package/dist/cli/types.js +1 -0
- package/dist/cli/utils.js +29 -0
- package/dist/cli.js +2 -0
- package/dist/constants/index.js +1 -0
- package/dist/core/domain/entities/ThinqDevice.js +43 -0
- package/dist/core/domain/value-objects/AirConditionerCapabilities.js +22 -0
- package/dist/core/domain/value-objects/ThinqFilterState.js +35 -0
- package/dist/core/domain/value-objects/ThinqSnapshot.js +125 -0
- package/dist/errors/AuthenticationError.js +26 -0
- package/dist/errors/BaseError.js +23 -0
- package/dist/errors/index.js +2 -0
- package/dist/model/LgThinkqPluginPlatformConfig.js +23 -0
- package/dist/model/LgThinqPluginPlatformConfig.js +23 -0
- package/dist/module.js +212 -0
- package/dist/platform/deviceRegistry.js +26 -0
- package/dist/platform/platformConfigManager.js +89 -0
- package/dist/platform/platformState.js +9 -0
- package/dist/platform/thinq/thinqAirConditionerCommandHandlers.js +190 -0
- package/dist/platform/thinq/thinqAirConditionerEndpointFactory.js +53 -0
- package/dist/platform/thinq/thinqAirConditionerFilterResetCommandHandler.js +54 -0
- package/dist/platform/thinq/thinqAirConditionerFilterStateSync.js +19 -0
- package/dist/platform/thinq/thinqAirConditionerStateSync.js +205 -0
- package/dist/platform/thinq/thinqDeviceConfigReconciler.js +26 -0
- package/dist/platform/thinq/thinqDeviceConfigurator.js +124 -0
- package/dist/platform/thinq/thinqDeviceDiscovery.js +33 -0
- package/dist/platform/thinq/thinqWasherCommandHandlers.js +54 -0
- package/dist/platform/thinq/thinqWasherEndpointFactory.js +6 -0
- package/dist/platform/thinq/thinqWasherStateSync.js +31 -0
- package/dist/platform/thinq/thinqWasherStopCommandResolver.js +38 -0
- package/dist/services/authentication/AccountAuthStrategy.js +135 -0
- package/dist/services/authentication/AuthContext.js +1 -0
- package/dist/services/authentication/AuthenticationCoordinator.js +29 -0
- package/dist/services/authentication/IAuthStrategy.js +1 -0
- package/dist/services/authentication/TokenAuthStrategy.js +27 -0
- package/dist/services/authentication/UserDataRepository.js +32 -0
- package/dist/services/thinq/gateway.js +61 -0
- package/dist/services/thinq/mqtt/mqttCertificate.js +63 -0
- package/dist/services/thinq/mqtt/mqttConnectionEvents.js +37 -0
- package/dist/services/thinq/mqtt/mqttKeyRepository.js +33 -0
- package/dist/services/thinq/mqtt/mqttRetry.js +25 -0
- package/dist/services/thinq/mqtt/thinqMqttListener.js +97 -0
- package/dist/services/thinq/serviceContainer.js +73 -0
- package/dist/services/thinq/session.js +48 -0
- package/dist/services/thinq/thinqApiClient.js +258 -0
- package/dist/services/thinq/thinqDeviceService.js +75 -0
- package/dist/services/thinq/thinqFilterMonitoringService.js +40 -0
- package/dist/settings.js +9 -0
- package/dist/tests/helpers/testUtils.js +87 -0
- package/dist/utils/mask.js +39 -0
- package/docs/README_CLI.md +166 -0
- package/eslint.config.js +194 -0
- package/matterbridge-lg-thinq-plugin.config.json +27 -0
- package/matterbridge-lg-thinq-plugin.schema.json +520 -0
- package/package.json +107 -0
- package/prettier.config.js +49 -0
- package/scripts/parse-junit-failures.mjs +45 -0
- package/scripts/parse-tsc-failures.mjs +48 -0
- package/scripts/run-build-local-summary.mjs +29 -0
- package/scripts/run-format-summary.mjs +36 -0
- package/scripts/run-lint-fix-summary.mjs +38 -0
- package/scripts/run-precommit-summary.mjs +94 -0
- package/src/cli/commands/devices.ts +67 -0
- package/src/cli/commands/energy.ts +160 -0
- package/src/cli/commands/login.ts +74 -0
- package/src/cli/help.ts +53 -0
- package/src/cli/main.ts +66 -0
- package/src/cli/session.ts +15 -0
- package/src/cli/types.ts +10 -0
- package/src/cli/utils.ts +30 -0
- package/src/cli.ts +3 -0
- package/src/constants/index.ts +8 -0
- package/src/core/domain/entities/ThinqDevice.test.ts +224 -0
- package/src/core/domain/entities/ThinqDevice.ts +74 -0
- package/src/core/domain/value-objects/AirConditionerCapabilities.ts +50 -0
- package/src/core/domain/value-objects/ThinqFilterState.ts +50 -0
- package/src/core/domain/value-objects/ThinqSnapshot.ts +162 -0
- package/src/errors/AuthenticationError.ts +37 -0
- package/src/errors/BaseError.ts +38 -0
- package/src/errors/index.ts +24 -0
- package/src/model/LgThinqPluginPlatformConfig.ts +94 -0
- package/src/module.ts +298 -0
- package/src/platform/deviceRegistry.ts +58 -0
- package/src/platform/platformConfigManager.ts +133 -0
- package/src/platform/platformState.ts +16 -0
- package/src/platform/thinq/thinqAirConditionerCommandHandlers.ts +291 -0
- package/src/platform/thinq/thinqAirConditionerEndpointFactory.ts +145 -0
- package/src/platform/thinq/thinqAirConditionerFilterResetCommandHandler.ts +96 -0
- package/src/platform/thinq/thinqAirConditionerFilterStateSync.ts +42 -0
- package/src/platform/thinq/thinqAirConditionerStateSync.ts +343 -0
- package/src/platform/thinq/thinqDeviceConfigReconciler.ts +42 -0
- package/src/platform/thinq/thinqDeviceConfigurator.test.ts +90 -0
- package/src/platform/thinq/thinqDeviceConfigurator.ts +191 -0
- package/src/platform/thinq/thinqDeviceDiscovery.test.ts +173 -0
- package/src/platform/thinq/thinqDeviceDiscovery.ts +51 -0
- package/src/platform/thinq/thinqWasherCommandHandlers.ts +120 -0
- package/src/platform/thinq/thinqWasherEndpointFactory.ts +37 -0
- package/src/platform/thinq/thinqWasherStateSync.ts +60 -0
- package/src/platform/thinq/thinqWasherStopCommandResolver.ts +62 -0
- package/src/services/authentication/AccountAuthStrategy.test.ts +358 -0
- package/src/services/authentication/AccountAuthStrategy.ts +209 -0
- package/src/services/authentication/AuthContext.ts +19 -0
- package/src/services/authentication/AuthenticationCoordinator.test.ts +217 -0
- package/src/services/authentication/AuthenticationCoordinator.ts +59 -0
- package/src/services/authentication/IAuthStrategy.ts +10 -0
- package/src/services/authentication/TokenAuthStrategy.test.ts +167 -0
- package/src/services/authentication/TokenAuthStrategy.ts +40 -0
- package/src/services/authentication/UserDataRepository.test.ts +149 -0
- package/src/services/authentication/UserDataRepository.ts +46 -0
- package/src/services/thinq/gateway.ts +86 -0
- package/src/services/thinq/mqtt/mqttCertificate.test.ts +344 -0
- package/src/services/thinq/mqtt/mqttCertificate.ts +102 -0
- package/src/services/thinq/mqtt/mqttConnectionEvents.test.ts +276 -0
- package/src/services/thinq/mqtt/mqttConnectionEvents.ts +76 -0
- package/src/services/thinq/mqtt/mqttKeyRepository.test.ts +164 -0
- package/src/services/thinq/mqtt/mqttKeyRepository.ts +43 -0
- package/src/services/thinq/mqtt/mqttRetry.test.ts +239 -0
- package/src/services/thinq/mqtt/mqttRetry.ts +43 -0
- package/src/services/thinq/mqtt/thinqMqttListener.test.ts +349 -0
- package/src/services/thinq/mqtt/thinqMqttListener.ts +120 -0
- package/src/services/thinq/serviceContainer.test.ts +203 -0
- package/src/services/thinq/serviceContainer.ts +105 -0
- package/src/services/thinq/session.test.ts +287 -0
- package/src/services/thinq/session.ts +69 -0
- package/src/services/thinq/thinqApiClient.test.ts +702 -0
- package/src/services/thinq/thinqApiClient.ts +418 -0
- package/src/services/thinq/thinqDeviceService.test.ts +415 -0
- package/src/services/thinq/thinqDeviceService.ts +101 -0
- package/src/services/thinq/thinqFilterMonitoringService.test.ts +264 -0
- package/src/services/thinq/thinqFilterMonitoringService.ts +53 -0
- package/src/settings.ts +18 -0
- package/src/tests/cli/commands/devices.test.ts +293 -0
- package/src/tests/cli/commands/login.test.ts +240 -0
- package/src/tests/cli/help.test.ts +46 -0
- package/src/tests/cli/main.test.ts +214 -0
- package/src/tests/cli/session.test.ts +198 -0
- package/src/tests/cli/utils.test.ts +194 -0
- package/src/tests/core/domain/value-objects/AirConditionerCapabilities.test.ts +685 -0
- package/src/tests/core/domain/value-objects/ThinqFilterState.test.ts +210 -0
- package/src/tests/core/domain/value-objects/ThinqSnapshot.test.ts +1035 -0
- package/src/tests/helpers/testUtils.ts +118 -0
- package/src/tests/model/LgThinqPluginPlatformConfig.test.ts +81 -0
- package/src/tests/platform/deviceRegistry.test.ts +177 -0
- package/src/tests/platform/platformConfigManager.test.ts +790 -0
- package/src/tests/platform/platformState.test.ts +41 -0
- package/src/tests/platform/thinq/thinqAirConditionerCommandHandlers.test.ts +879 -0
- package/src/tests/platform/thinq/thinqAirConditionerEndpointFactory.test.ts +993 -0
- package/src/tests/platform/thinq/thinqAirConditionerFilterResetCommandHandler.test.ts +508 -0
- package/src/tests/platform/thinq/thinqAirConditionerFilterStateSync.test.ts +212 -0
- package/src/tests/platform/thinq/thinqAirConditionerStateSync.test.ts +1586 -0
- package/src/tests/platform/thinq/thinqDeviceConfigReconciler.test.ts +269 -0
- package/src/tests/platform/thinq/thinqDeviceConfigurator.test.ts +492 -0
- package/src/tests/platform/thinq/thinqWasherCommandHandlers.test.ts +594 -0
- package/src/tests/platform/thinq/thinqWasherEndpointFactory.test.ts +84 -0
- package/src/tests/platform/thinq/thinqWasherStateSync.test.ts +373 -0
- package/src/tests/platform/thinq/thinqWasherStopCommandResolver.test.ts +392 -0
- package/src/tests/settings.test.ts +33 -0
- package/src/utils/mask.ts +50 -0
- package/tsconfig.base.json +25 -0
- package/tsconfig.json +9 -0
- package/tsconfig.production.json +13 -0
- package/tsconfig.vitest.json +5 -0
- package/vite.config.ts +43 -0
- package/wiki/memory-archive.md +12 -0
- package/wiki/reference-workspaces.md +23 -0
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import type { ThinqAirConditionerDevice, ThinqWasherDevice } from '../../../core/domain/entities/ThinqDevice.js';
|
|
4
|
+
import { DEFAULT_AIR_CONDITIONER_CAPABILITIES } from '../../../core/domain/value-objects/AirConditionerCapabilities.js';
|
|
5
|
+
import { ThinqSnapshot } from '../../../core/domain/value-objects/ThinqSnapshot.js';
|
|
6
|
+
import type { PlatformConfigManager } from '../../../platform/platformConfigManager.js';
|
|
7
|
+
import { registerAirConditionerCommandHandlers } from '../../../platform/thinq/thinqAirConditionerCommandHandlers.js';
|
|
8
|
+
import { buildAirConditionerEndpoint } from '../../../platform/thinq/thinqAirConditionerEndpointFactory.js';
|
|
9
|
+
import { ThinqDeviceConfigurator } from '../../../platform/thinq/thinqDeviceConfigurator.js';
|
|
10
|
+
import { registerWasherCommandHandlers } from '../../../platform/thinq/thinqWasherCommandHandlers.js';
|
|
11
|
+
import { buildWasherEndpoint } from '../../../platform/thinq/thinqWasherEndpointFactory.js';
|
|
12
|
+
import type { ThinqApiClient } from '../../../services/thinq/thinqApiClient.js';
|
|
13
|
+
import { asPartial, createMockLogger } from '../../helpers/testUtils.js';
|
|
14
|
+
|
|
15
|
+
vi.mock('../../../platform/thinq/thinqAirConditionerCommandHandlers.js');
|
|
16
|
+
vi.mock('../../../platform/thinq/thinqAirConditionerAuxiliaryToggles.js', () => ({
|
|
17
|
+
registerAuxiliaryToggleCommandHandlers: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
vi.mock('../../../platform/thinq/thinqAirConditionerEndpointFactory.js', () => ({
|
|
20
|
+
buildAirConditionerEndpoint: vi.fn(() => ({
|
|
21
|
+
log: { debug: vi.fn(), info: vi.fn(), error: vi.fn() },
|
|
22
|
+
createDefaultTemperatureMeasurementClusterServer: vi.fn().mockReturnThis(),
|
|
23
|
+
addRequiredClusterServers: vi.fn().mockReturnThis(),
|
|
24
|
+
})),
|
|
25
|
+
}));
|
|
26
|
+
vi.mock('../../../platform/thinq/thinqWasherEndpointFactory.js', () => ({
|
|
27
|
+
buildWasherEndpoint: vi.fn(() => ({
|
|
28
|
+
log: { debug: vi.fn(), info: vi.fn(), error: vi.fn() },
|
|
29
|
+
})),
|
|
30
|
+
}));
|
|
31
|
+
vi.mock('../../../platform/thinq/thinqWasherCommandHandlers.js');
|
|
32
|
+
vi.mock('../../../platform/thinq/thinqWasherStopCommandResolver.js');
|
|
33
|
+
vi.mock('../../../platform/thinq/thinqAirConditionerFilterResetCommandHandler.js', () => ({
|
|
34
|
+
registerFilterResetCommandHandler: vi.fn(),
|
|
35
|
+
}));
|
|
36
|
+
|
|
37
|
+
function createMockThinqAirConditionerDevice(): ThinqAirConditionerDevice {
|
|
38
|
+
return asPartial<ThinqAirConditionerDevice>({
|
|
39
|
+
id: 'device-123',
|
|
40
|
+
name: 'Living Room AC',
|
|
41
|
+
type: 'AC',
|
|
42
|
+
modelName: 'ModelXYZ',
|
|
43
|
+
platformType: 'THINQ',
|
|
44
|
+
online: true,
|
|
45
|
+
snapshot: new ThinqSnapshot({
|
|
46
|
+
'airState.operation': 1,
|
|
47
|
+
'airState.tempState.current': 22,
|
|
48
|
+
'airState.tempState.target': 24,
|
|
49
|
+
'airState.opMode': 0,
|
|
50
|
+
'airState.windStrength': 2,
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function createMockApiClient(): ThinqApiClient {
|
|
56
|
+
return asPartial<ThinqApiClient>({
|
|
57
|
+
sendCommand: vi.fn().mockResolvedValue(undefined),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function createMockWasherDevice(): ThinqWasherDevice {
|
|
62
|
+
return asPartial<ThinqWasherDevice>({
|
|
63
|
+
id: 'washer-456',
|
|
64
|
+
name: 'Laundry Room Washer',
|
|
65
|
+
type: 'WASHER',
|
|
66
|
+
modelName: 'VCDWL_QEUK',
|
|
67
|
+
platformType: 'THINQ',
|
|
68
|
+
online: true,
|
|
69
|
+
snapshot: new ThinqSnapshot({
|
|
70
|
+
'washerDryer.state': 'RUNNING',
|
|
71
|
+
}),
|
|
72
|
+
modelJsonUri: 'https://example.com/model.json',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function createMockConfigManager(): PlatformConfigManager {
|
|
77
|
+
return asPartial<PlatformConfigManager>({
|
|
78
|
+
getDeviceCapabilities: vi.fn().mockReturnValue(DEFAULT_AIR_CONDITIONER_CAPABILITIES),
|
|
79
|
+
getWasherControlConfig: vi.fn().mockReturnValue({}),
|
|
80
|
+
getAcFilterControlConfig: vi.fn().mockReturnValue({}),
|
|
81
|
+
overrideMatterConfiguration: false,
|
|
82
|
+
matterOverrideSettings: {
|
|
83
|
+
matterVendorName: 'Matterbridge',
|
|
84
|
+
matterVendorId: 0xfff1,
|
|
85
|
+
},
|
|
86
|
+
getProductNameForDevice: vi.fn().mockReturnValue(undefined),
|
|
87
|
+
getProductIdForDevice: vi.fn().mockReturnValue(undefined),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
describe('ThinqDeviceConfigurator', () => {
|
|
92
|
+
let mockLogger: ReturnType<typeof createMockLogger>;
|
|
93
|
+
let mockApiClient: ThinqApiClient;
|
|
94
|
+
let mockConfigManager: PlatformConfigManager;
|
|
95
|
+
let configurator: ThinqDeviceConfigurator;
|
|
96
|
+
|
|
97
|
+
beforeEach(() => {
|
|
98
|
+
vi.clearAllMocks();
|
|
99
|
+
mockLogger = createMockLogger();
|
|
100
|
+
mockApiClient = createMockApiClient();
|
|
101
|
+
mockConfigManager = createMockConfigManager();
|
|
102
|
+
configurator = new ThinqDeviceConfigurator(mockLogger, mockApiClient, mockConfigManager);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
afterEach(() => {
|
|
106
|
+
vi.clearAllMocks();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe('registerAirConditioner', () => {
|
|
110
|
+
it('should log device registration', async () => {
|
|
111
|
+
// Arrange
|
|
112
|
+
const device = createMockThinqAirConditionerDevice();
|
|
113
|
+
|
|
114
|
+
// Act
|
|
115
|
+
await configurator.registerAirConditioner(device);
|
|
116
|
+
|
|
117
|
+
// Assert
|
|
118
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
119
|
+
expect.stringContaining('Registering ThinQ AirConditioner: Living Room AC (device-123)'),
|
|
120
|
+
);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should call getDeviceCapabilities with the device id', async () => {
|
|
124
|
+
// Arrange
|
|
125
|
+
const device = createMockThinqAirConditionerDevice();
|
|
126
|
+
const getCapabilitiesSpy = vi.mocked(mockConfigManager.getDeviceCapabilities);
|
|
127
|
+
|
|
128
|
+
// Act
|
|
129
|
+
await configurator.registerAirConditioner(device);
|
|
130
|
+
|
|
131
|
+
// Assert
|
|
132
|
+
expect(getCapabilitiesSpy).toHaveBeenCalledWith('device-123');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should call registerAirConditionerCommandHandlers before returning', async () => {
|
|
136
|
+
// Arrange
|
|
137
|
+
const device = createMockThinqAirConditionerDevice();
|
|
138
|
+
const registerHandlersSpy = vi.mocked(registerAirConditionerCommandHandlers);
|
|
139
|
+
|
|
140
|
+
// Act
|
|
141
|
+
await configurator.registerAirConditioner(device);
|
|
142
|
+
|
|
143
|
+
// Assert
|
|
144
|
+
expect(registerHandlersSpy).toHaveBeenCalled();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('should return a promise that resolves to an endpoint', async () => {
|
|
148
|
+
// Arrange
|
|
149
|
+
const device = createMockThinqAirConditionerDevice();
|
|
150
|
+
|
|
151
|
+
// Act
|
|
152
|
+
const result = await configurator.registerAirConditioner(device);
|
|
153
|
+
|
|
154
|
+
// Assert
|
|
155
|
+
expect(result).toBeDefined();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should hardcode the endpoint mode to server', async () => {
|
|
159
|
+
// Arrange
|
|
160
|
+
const device = createMockThinqAirConditionerDevice();
|
|
161
|
+
|
|
162
|
+
// Act
|
|
163
|
+
const endpoint = await configurator.registerAirConditioner(device);
|
|
164
|
+
|
|
165
|
+
// Assert
|
|
166
|
+
// The AC is always exposed as its own standalone Matter node (server mode), not user-configurable.
|
|
167
|
+
expect(endpoint.mode).toBe('server');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should use device snapshot values for initial state when defined', async () => {
|
|
171
|
+
// Arrange
|
|
172
|
+
const device = createMockThinqAirConditionerDevice();
|
|
173
|
+
|
|
174
|
+
// Act
|
|
175
|
+
await configurator.registerAirConditioner(device);
|
|
176
|
+
|
|
177
|
+
// Assert
|
|
178
|
+
// Verify it was called with the correct temperature from the device snapshot (22°C current, 24°C target)
|
|
179
|
+
expect(mockLogger.info).toHaveBeenCalledWith(expect.stringContaining('Living Room AC'));
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should use default temperature when snapshot values are missing', async () => {
|
|
183
|
+
// Arrange
|
|
184
|
+
const deviceWithNoTemp = createMockThinqAirConditionerDevice();
|
|
185
|
+
// Create a new device with missing temperature data
|
|
186
|
+
const deviceWithoutTemp: ThinqAirConditionerDevice = asPartial<ThinqAirConditionerDevice>({
|
|
187
|
+
...deviceWithNoTemp,
|
|
188
|
+
snapshot: new ThinqSnapshot({
|
|
189
|
+
'airState.operation': 1,
|
|
190
|
+
'airState.opMode': 0,
|
|
191
|
+
'airState.windStrength': 2,
|
|
192
|
+
}),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Act
|
|
196
|
+
await configurator.registerAirConditioner(deviceWithoutTemp);
|
|
197
|
+
|
|
198
|
+
// Assert
|
|
199
|
+
expect(mockLogger.info).toHaveBeenCalledWith(expect.stringContaining('Registering ThinQ AirConditioner'));
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it('should call registerAirConditionerCommandHandlers with correct parameters', async () => {
|
|
203
|
+
// Arrange
|
|
204
|
+
const device = createMockThinqAirConditionerDevice();
|
|
205
|
+
const registerHandlersSpy = vi.mocked(registerAirConditionerCommandHandlers);
|
|
206
|
+
|
|
207
|
+
// Act
|
|
208
|
+
await configurator.registerAirConditioner(device);
|
|
209
|
+
|
|
210
|
+
// Assert
|
|
211
|
+
expect(registerHandlersSpy).toHaveBeenCalledWith(
|
|
212
|
+
expect.anything(), // endpoint
|
|
213
|
+
device,
|
|
214
|
+
mockApiClient,
|
|
215
|
+
mockLogger,
|
|
216
|
+
DEFAULT_AIR_CONDITIONER_CAPABILITIES,
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should pass options with default product identity when overrideMatterConfiguration is false', async () => {
|
|
221
|
+
// Arrange
|
|
222
|
+
const device = createMockThinqAirConditionerDevice();
|
|
223
|
+
const buildEndpointSpy = vi.mocked(buildAirConditionerEndpoint);
|
|
224
|
+
|
|
225
|
+
// Act
|
|
226
|
+
await configurator.registerAirConditioner(device);
|
|
227
|
+
|
|
228
|
+
// Assert
|
|
229
|
+
expect(buildEndpointSpy).toHaveBeenCalledWith(
|
|
230
|
+
device,
|
|
231
|
+
expect.anything(),
|
|
232
|
+
expect.anything(),
|
|
233
|
+
expect.anything(),
|
|
234
|
+
expect.objectContaining({
|
|
235
|
+
vendorId: undefined,
|
|
236
|
+
vendorName: undefined,
|
|
237
|
+
productId: 0x8000,
|
|
238
|
+
productName: 'LG Air Conditioner',
|
|
239
|
+
}),
|
|
240
|
+
);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('should pass options with matterOverrideSettings values when overrideMatterConfiguration is true and no per-device override', async () => {
|
|
244
|
+
// Arrange
|
|
245
|
+
const device = createMockThinqAirConditionerDevice();
|
|
246
|
+
const customSettings = {
|
|
247
|
+
matterVendorName: 'Custom Vendor',
|
|
248
|
+
matterVendorId: 0xabcd,
|
|
249
|
+
};
|
|
250
|
+
mockConfigManager = asPartial<PlatformConfigManager>({
|
|
251
|
+
getDeviceCapabilities: vi.fn().mockReturnValue(DEFAULT_AIR_CONDITIONER_CAPABILITIES),
|
|
252
|
+
getAcFilterControlConfig: vi.fn().mockReturnValue({}),
|
|
253
|
+
overrideMatterConfiguration: true,
|
|
254
|
+
matterOverrideSettings: customSettings,
|
|
255
|
+
getProductNameForDevice: vi.fn().mockReturnValue(undefined),
|
|
256
|
+
getProductIdForDevice: vi.fn().mockReturnValue(undefined),
|
|
257
|
+
});
|
|
258
|
+
configurator = new ThinqDeviceConfigurator(mockLogger, mockApiClient, mockConfigManager);
|
|
259
|
+
const buildEndpointSpy = vi.mocked(buildAirConditionerEndpoint);
|
|
260
|
+
|
|
261
|
+
// Act
|
|
262
|
+
await configurator.registerAirConditioner(device);
|
|
263
|
+
|
|
264
|
+
// Assert
|
|
265
|
+
expect(buildEndpointSpy).toHaveBeenCalledWith(
|
|
266
|
+
device,
|
|
267
|
+
expect.anything(),
|
|
268
|
+
expect.anything(),
|
|
269
|
+
expect.anything(),
|
|
270
|
+
expect.objectContaining({
|
|
271
|
+
vendorId: 0xabcd,
|
|
272
|
+
vendorName: 'Custom Vendor',
|
|
273
|
+
}),
|
|
274
|
+
);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('should use per-device productName when available and override enabled', async () => {
|
|
278
|
+
// Arrange
|
|
279
|
+
const device = createMockThinqAirConditionerDevice();
|
|
280
|
+
const customSettings = {
|
|
281
|
+
matterVendorName: 'Custom Vendor',
|
|
282
|
+
matterVendorId: 0xabcd,
|
|
283
|
+
};
|
|
284
|
+
mockConfigManager = asPartial<PlatformConfigManager>({
|
|
285
|
+
getDeviceCapabilities: vi.fn().mockReturnValue(DEFAULT_AIR_CONDITIONER_CAPABILITIES),
|
|
286
|
+
getAcFilterControlConfig: vi.fn().mockReturnValue({}),
|
|
287
|
+
overrideMatterConfiguration: true,
|
|
288
|
+
matterOverrideSettings: customSettings,
|
|
289
|
+
getProductNameForDevice: vi.fn().mockReturnValue('Device-Specific AC'),
|
|
290
|
+
getProductIdForDevice: vi.fn().mockReturnValue(0xef01),
|
|
291
|
+
});
|
|
292
|
+
configurator = new ThinqDeviceConfigurator(mockLogger, mockApiClient, mockConfigManager);
|
|
293
|
+
const buildEndpointSpy = vi.mocked(buildAirConditionerEndpoint);
|
|
294
|
+
|
|
295
|
+
// Act
|
|
296
|
+
await configurator.registerAirConditioner(device);
|
|
297
|
+
|
|
298
|
+
// Assert
|
|
299
|
+
expect(buildEndpointSpy).toHaveBeenCalledWith(
|
|
300
|
+
device,
|
|
301
|
+
expect.anything(),
|
|
302
|
+
expect.anything(),
|
|
303
|
+
expect.anything(),
|
|
304
|
+
expect.objectContaining({
|
|
305
|
+
vendorId: 0xabcd,
|
|
306
|
+
vendorName: 'Custom Vendor',
|
|
307
|
+
productId: 0xef01,
|
|
308
|
+
productName: 'Device-Specific AC', // Per-device override takes precedence
|
|
309
|
+
}),
|
|
310
|
+
);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('should call getProductNameForDevice with correct device id', async () => {
|
|
314
|
+
// Arrange
|
|
315
|
+
const device = createMockThinqAirConditionerDevice();
|
|
316
|
+
const customSettings = {
|
|
317
|
+
matterVendorName: 'Matterbridge',
|
|
318
|
+
matterVendorId: 0xfff1,
|
|
319
|
+
};
|
|
320
|
+
mockConfigManager = asPartial<PlatformConfigManager>({
|
|
321
|
+
getDeviceCapabilities: vi.fn().mockReturnValue(DEFAULT_AIR_CONDITIONER_CAPABILITIES),
|
|
322
|
+
getAcFilterControlConfig: vi.fn().mockReturnValue({}),
|
|
323
|
+
overrideMatterConfiguration: true,
|
|
324
|
+
matterOverrideSettings: customSettings,
|
|
325
|
+
getProductNameForDevice: vi.fn().mockReturnValue(undefined),
|
|
326
|
+
getProductIdForDevice: vi.fn().mockReturnValue(undefined),
|
|
327
|
+
});
|
|
328
|
+
configurator = new ThinqDeviceConfigurator(mockLogger, mockApiClient, mockConfigManager);
|
|
329
|
+
const getProductNameSpy = vi.mocked(mockConfigManager.getProductNameForDevice);
|
|
330
|
+
|
|
331
|
+
// Act
|
|
332
|
+
await configurator.registerAirConditioner(device);
|
|
333
|
+
|
|
334
|
+
// Assert
|
|
335
|
+
expect(getProductNameSpy).toHaveBeenCalledWith('device-123');
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('should call getAcFilterControlConfig with device id', async () => {
|
|
339
|
+
// Arrange
|
|
340
|
+
const device = createMockThinqAirConditionerDevice();
|
|
341
|
+
const getFilterControlConfigSpy = vi.mocked(mockConfigManager.getAcFilterControlConfig);
|
|
342
|
+
|
|
343
|
+
// Act
|
|
344
|
+
await configurator.registerAirConditioner(device);
|
|
345
|
+
|
|
346
|
+
// Assert
|
|
347
|
+
expect(getFilterControlConfigSpy).toHaveBeenCalledWith('device-123');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('should import and call registerFilterResetCommandHandler', async () => {
|
|
351
|
+
// Arrange
|
|
352
|
+
const device = createMockThinqAirConditionerDevice();
|
|
353
|
+
// Note: We use dynamic import to get the mocked version since it's mocked at the module level
|
|
354
|
+
const { registerFilterResetCommandHandler: mockedHandler } =
|
|
355
|
+
await import('../../../platform/thinq/thinqAirConditionerFilterResetCommandHandler.js');
|
|
356
|
+
|
|
357
|
+
// Act
|
|
358
|
+
await configurator.registerAirConditioner(device);
|
|
359
|
+
|
|
360
|
+
// Assert
|
|
361
|
+
expect(vi.mocked(mockedHandler)).toHaveBeenCalledOnce();
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it('should call registerFilterResetCommandHandler with correct parameters', async () => {
|
|
365
|
+
// Arrange
|
|
366
|
+
const device = createMockThinqAirConditionerDevice();
|
|
367
|
+
const filterResetControl = { allowFilterReset: true };
|
|
368
|
+
vi.mocked(mockConfigManager.getAcFilterControlConfig).mockReturnValue(filterResetControl);
|
|
369
|
+
|
|
370
|
+
// Import the mocked handler
|
|
371
|
+
const { registerFilterResetCommandHandler: mockedHandler } =
|
|
372
|
+
await import('../../../platform/thinq/thinqAirConditionerFilterResetCommandHandler.js');
|
|
373
|
+
|
|
374
|
+
// Act
|
|
375
|
+
await configurator.registerAirConditioner(device);
|
|
376
|
+
|
|
377
|
+
// Assert
|
|
378
|
+
expect(vi.mocked(mockedHandler)).toHaveBeenCalledWith(
|
|
379
|
+
expect.anything(), // endpoint
|
|
380
|
+
device,
|
|
381
|
+
mockApiClient,
|
|
382
|
+
mockLogger,
|
|
383
|
+
DEFAULT_AIR_CONDITIONER_CAPABILITIES,
|
|
384
|
+
filterResetControl,
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
describe('registerWasher', () => {
|
|
390
|
+
it('should log device registration', async () => {
|
|
391
|
+
// Arrange
|
|
392
|
+
const device = createMockWasherDevice();
|
|
393
|
+
|
|
394
|
+
// Act
|
|
395
|
+
await configurator.registerWasher(device);
|
|
396
|
+
|
|
397
|
+
// Assert
|
|
398
|
+
expect(mockLogger.info).toHaveBeenCalledWith(
|
|
399
|
+
expect.stringContaining('Registering ThinQ Washer: Laundry Room Washer (washer-456)'),
|
|
400
|
+
);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('should call buildWasherEndpoint with the device and options', async () => {
|
|
404
|
+
// Arrange
|
|
405
|
+
const device = createMockWasherDevice();
|
|
406
|
+
const buildEndpointSpy = vi.mocked(buildWasherEndpoint);
|
|
407
|
+
|
|
408
|
+
// Act
|
|
409
|
+
await configurator.registerWasher(device);
|
|
410
|
+
|
|
411
|
+
// Assert
|
|
412
|
+
expect(buildEndpointSpy).toHaveBeenCalledWith(
|
|
413
|
+
device,
|
|
414
|
+
expect.objectContaining({
|
|
415
|
+
vendorId: undefined,
|
|
416
|
+
vendorName: undefined,
|
|
417
|
+
productId: 0x8001,
|
|
418
|
+
productName: 'LG Washer',
|
|
419
|
+
}),
|
|
420
|
+
);
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('should call registerWasherCommandHandlers with correct parameters', async () => {
|
|
424
|
+
// Arrange
|
|
425
|
+
const device = createMockWasherDevice();
|
|
426
|
+
const registerHandlersSpy = vi.mocked(registerWasherCommandHandlers);
|
|
427
|
+
|
|
428
|
+
// Act
|
|
429
|
+
await configurator.registerWasher(device);
|
|
430
|
+
|
|
431
|
+
// Assert
|
|
432
|
+
expect(registerHandlersSpy).toHaveBeenCalled();
|
|
433
|
+
const call = registerHandlersSpy.mock.calls[0];
|
|
434
|
+
expect(call[1]).toBe(device); // device parameter
|
|
435
|
+
expect(call[2]).toBe(mockApiClient); // apiClient parameter
|
|
436
|
+
expect(call[3]).toBe(mockLogger); // logger parameter
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it('should return an endpoint', async () => {
|
|
440
|
+
// Arrange
|
|
441
|
+
const device = createMockWasherDevice();
|
|
442
|
+
|
|
443
|
+
// Act
|
|
444
|
+
const result = await configurator.registerWasher(device);
|
|
445
|
+
|
|
446
|
+
// Assert
|
|
447
|
+
expect(result).toBeDefined();
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it('should call getWasherControlConfig with device id', async () => {
|
|
451
|
+
// Arrange
|
|
452
|
+
const device = createMockWasherDevice();
|
|
453
|
+
const getConfigSpy = vi.mocked(mockConfigManager.getWasherControlConfig);
|
|
454
|
+
|
|
455
|
+
// Act
|
|
456
|
+
await configurator.registerWasher(device);
|
|
457
|
+
|
|
458
|
+
// Assert
|
|
459
|
+
expect(getConfigSpy).toHaveBeenCalledWith('washer-456');
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
it('should pass washerControl config to registerWasherCommandHandlers', async () => {
|
|
463
|
+
// Arrange
|
|
464
|
+
const device = createMockWasherDevice();
|
|
465
|
+
const washerControl = { allowRemoteStop: true };
|
|
466
|
+
vi.mocked(mockConfigManager.getWasherControlConfig).mockReturnValue(washerControl);
|
|
467
|
+
const registerHandlersSpy = vi.mocked(registerWasherCommandHandlers);
|
|
468
|
+
|
|
469
|
+
// Act
|
|
470
|
+
await configurator.registerWasher(device);
|
|
471
|
+
|
|
472
|
+
// Assert
|
|
473
|
+
const call = registerHandlersSpy.mock.calls[0];
|
|
474
|
+
expect(call[4]).toEqual(washerControl); // washerControl parameter
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it('should pass 5+ parameters to registerWasherCommandHandlers (includes stop payload)', async () => {
|
|
478
|
+
// Arrange
|
|
479
|
+
const device = createMockWasherDevice();
|
|
480
|
+
const registerHandlersSpy = vi.mocked(registerWasherCommandHandlers);
|
|
481
|
+
|
|
482
|
+
// Act
|
|
483
|
+
await configurator.registerWasher(device);
|
|
484
|
+
|
|
485
|
+
// Assert
|
|
486
|
+
const call = registerHandlersSpy.mock.calls[0];
|
|
487
|
+
// Verify the function is called with 6 parameters:
|
|
488
|
+
// washer, device, apiClient, logger, washerControl, stopCommandPayload
|
|
489
|
+
expect(call.length).toBe(6);
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
});
|