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,594 @@
|
|
|
1
|
+
import { LaundryWasherMode, OnOff, OperationalState } from 'matterbridge/matter/clusters';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import type { ThinqWasherDevice } from '../../../core/domain/entities/ThinqDevice.js';
|
|
5
|
+
import { ThinqSnapshot } from '../../../core/domain/value-objects/ThinqSnapshot.js';
|
|
6
|
+
import type { ThinqWasherControlConfig } from '../../../model/LgThinqPluginPlatformConfig.js';
|
|
7
|
+
import { registerWasherCommandHandlers } from '../../../platform/thinq/thinqWasherCommandHandlers.js';
|
|
8
|
+
import type { WasherStopCommandPayload } from '../../../platform/thinq/thinqWasherStopCommandResolver.js';
|
|
9
|
+
import type { ThinqApiClient } from '../../../services/thinq/thinqApiClient.js';
|
|
10
|
+
import { asPartial, createMockLogger } from '../../helpers/testUtils.js';
|
|
11
|
+
|
|
12
|
+
function createMockWasherDevice(): ThinqWasherDevice {
|
|
13
|
+
return asPartial<ThinqWasherDevice>({
|
|
14
|
+
id: 'washer-123',
|
|
15
|
+
name: 'Living Room Washer',
|
|
16
|
+
type: 'WASHER',
|
|
17
|
+
modelName: 'VCDWL_QEUK',
|
|
18
|
+
platformType: 'THINQ',
|
|
19
|
+
online: true,
|
|
20
|
+
snapshot: new ThinqSnapshot({
|
|
21
|
+
'washerDryer.state': 'RUNNING',
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createMockApiClient(): ThinqApiClient {
|
|
27
|
+
return asPartial<ThinqApiClient>({
|
|
28
|
+
sendCommand: vi.fn().mockResolvedValue(undefined),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function createMockEndpoint(): any {
|
|
33
|
+
const endpoint = asPartial<any>({
|
|
34
|
+
log: createMockLogger(),
|
|
35
|
+
addCommandHandler: vi.fn().mockReturnValue(undefined),
|
|
36
|
+
getAttribute: vi.fn().mockImplementation((clusterId: unknown, attrName: unknown) => {
|
|
37
|
+
if (clusterId === OnOff.id && attrName === 'onOff') {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
if (clusterId === OperationalState.id && attrName === 'operationalState') {
|
|
41
|
+
return OperationalState.OperationalStateEnum.Running;
|
|
42
|
+
}
|
|
43
|
+
if (clusterId === LaundryWasherMode.id && attrName === 'currentMode') {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}),
|
|
48
|
+
updateAttribute: vi.fn().mockResolvedValue(false),
|
|
49
|
+
});
|
|
50
|
+
return endpoint;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe('registerWasherCommandHandlers', () => {
|
|
54
|
+
let mockLogger: ReturnType<typeof createMockLogger>;
|
|
55
|
+
let mockApiClient: ThinqApiClient;
|
|
56
|
+
let mockDevice: ThinqWasherDevice;
|
|
57
|
+
let mockEndpoint: any;
|
|
58
|
+
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
vi.clearAllMocks();
|
|
61
|
+
mockLogger = createMockLogger();
|
|
62
|
+
mockApiClient = createMockApiClient();
|
|
63
|
+
mockDevice = createMockWasherDevice();
|
|
64
|
+
mockEndpoint = createMockEndpoint();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
vi.clearAllMocks();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('v1 read-only commands rejection', () => {
|
|
72
|
+
it('should register exactly 6 read-only command handlers (v1)', () => {
|
|
73
|
+
// Arrange
|
|
74
|
+
const washerControl: ThinqWasherControlConfig = {};
|
|
75
|
+
const stopCommandPayload: WasherStopCommandPayload | undefined = undefined;
|
|
76
|
+
|
|
77
|
+
// Act
|
|
78
|
+
registerWasherCommandHandlers(
|
|
79
|
+
mockEndpoint,
|
|
80
|
+
mockDevice,
|
|
81
|
+
mockApiClient,
|
|
82
|
+
mockLogger,
|
|
83
|
+
washerControl,
|
|
84
|
+
stopCommandPayload,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// Assert
|
|
88
|
+
const calls = vi.mocked(mockEndpoint.addCommandHandler).mock.calls;
|
|
89
|
+
const readOnlyCommandCalls = calls.filter((call: any[]) =>
|
|
90
|
+
['on', 'off', 'pause', 'start', 'resume', 'changeToMode'].includes(call[0] as string),
|
|
91
|
+
);
|
|
92
|
+
expect(readOnlyCommandCalls).toHaveLength(6);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should register on command handler', () => {
|
|
96
|
+
// Arrange
|
|
97
|
+
const washerControl: ThinqWasherControlConfig = {};
|
|
98
|
+
const stopCommandPayload: WasherStopCommandPayload | undefined = undefined;
|
|
99
|
+
|
|
100
|
+
// Act
|
|
101
|
+
registerWasherCommandHandlers(
|
|
102
|
+
mockEndpoint,
|
|
103
|
+
mockDevice,
|
|
104
|
+
mockApiClient,
|
|
105
|
+
mockLogger,
|
|
106
|
+
washerControl,
|
|
107
|
+
stopCommandPayload,
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Assert
|
|
111
|
+
expect(mockEndpoint.addCommandHandler).toHaveBeenCalledWith('on', expect.any(Function));
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should register off command handler', () => {
|
|
115
|
+
// Arrange
|
|
116
|
+
const washerControl: ThinqWasherControlConfig = {};
|
|
117
|
+
const stopCommandPayload: WasherStopCommandPayload | undefined = undefined;
|
|
118
|
+
|
|
119
|
+
// Act
|
|
120
|
+
registerWasherCommandHandlers(
|
|
121
|
+
mockEndpoint,
|
|
122
|
+
mockDevice,
|
|
123
|
+
mockApiClient,
|
|
124
|
+
mockLogger,
|
|
125
|
+
washerControl,
|
|
126
|
+
stopCommandPayload,
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// Assert
|
|
130
|
+
expect(mockEndpoint.addCommandHandler).toHaveBeenCalledWith('off', expect.any(Function));
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should register pause command handler', () => {
|
|
134
|
+
// Arrange
|
|
135
|
+
const washerControl: ThinqWasherControlConfig = {};
|
|
136
|
+
const stopCommandPayload: WasherStopCommandPayload | undefined = undefined;
|
|
137
|
+
|
|
138
|
+
// Act
|
|
139
|
+
registerWasherCommandHandlers(
|
|
140
|
+
mockEndpoint,
|
|
141
|
+
mockDevice,
|
|
142
|
+
mockApiClient,
|
|
143
|
+
mockLogger,
|
|
144
|
+
washerControl,
|
|
145
|
+
stopCommandPayload,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
// Assert
|
|
149
|
+
expect(mockEndpoint.addCommandHandler).toHaveBeenCalledWith('pause', expect.any(Function));
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should register start command handler', () => {
|
|
153
|
+
// Arrange
|
|
154
|
+
const washerControl: ThinqWasherControlConfig = {};
|
|
155
|
+
const stopCommandPayload: WasherStopCommandPayload | undefined = undefined;
|
|
156
|
+
|
|
157
|
+
// Act
|
|
158
|
+
registerWasherCommandHandlers(
|
|
159
|
+
mockEndpoint,
|
|
160
|
+
mockDevice,
|
|
161
|
+
mockApiClient,
|
|
162
|
+
mockLogger,
|
|
163
|
+
washerControl,
|
|
164
|
+
stopCommandPayload,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// Assert
|
|
168
|
+
expect(mockEndpoint.addCommandHandler).toHaveBeenCalledWith('start', expect.any(Function));
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should register resume command handler', () => {
|
|
172
|
+
// Arrange
|
|
173
|
+
const washerControl: ThinqWasherControlConfig = {};
|
|
174
|
+
const stopCommandPayload: WasherStopCommandPayload | undefined = undefined;
|
|
175
|
+
|
|
176
|
+
// Act
|
|
177
|
+
registerWasherCommandHandlers(
|
|
178
|
+
mockEndpoint,
|
|
179
|
+
mockDevice,
|
|
180
|
+
mockApiClient,
|
|
181
|
+
mockLogger,
|
|
182
|
+
washerControl,
|
|
183
|
+
stopCommandPayload,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
// Assert
|
|
187
|
+
expect(mockEndpoint.addCommandHandler).toHaveBeenCalledWith('resume', expect.any(Function));
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should register changeToMode command handler', () => {
|
|
191
|
+
// Arrange
|
|
192
|
+
const washerControl: ThinqWasherControlConfig = {};
|
|
193
|
+
const stopCommandPayload: WasherStopCommandPayload | undefined = undefined;
|
|
194
|
+
|
|
195
|
+
// Act
|
|
196
|
+
registerWasherCommandHandlers(
|
|
197
|
+
mockEndpoint,
|
|
198
|
+
mockDevice,
|
|
199
|
+
mockApiClient,
|
|
200
|
+
mockLogger,
|
|
201
|
+
washerControl,
|
|
202
|
+
stopCommandPayload,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
// Assert
|
|
206
|
+
expect(mockEndpoint.addCommandHandler).toHaveBeenCalledWith('changeToMode', expect.any(Function));
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('on command should throw with unsupported error', async () => {
|
|
210
|
+
// Arrange
|
|
211
|
+
const washerControl: ThinqWasherControlConfig = {};
|
|
212
|
+
const stopCommandPayload: WasherStopCommandPayload | undefined = undefined;
|
|
213
|
+
registerWasherCommandHandlers(
|
|
214
|
+
mockEndpoint,
|
|
215
|
+
mockDevice,
|
|
216
|
+
mockApiClient,
|
|
217
|
+
mockLogger,
|
|
218
|
+
washerControl,
|
|
219
|
+
stopCommandPayload,
|
|
220
|
+
);
|
|
221
|
+
const onCall = vi.mocked(mockEndpoint.addCommandHandler).mock.calls.find((call: any[]) => call[0] === 'on');
|
|
222
|
+
const onHandler = onCall?.[1] as () => Promise<void>;
|
|
223
|
+
|
|
224
|
+
// Act & Assert
|
|
225
|
+
await expect(onHandler()).rejects.toThrow('not supported yet');
|
|
226
|
+
expect(mockLogger.warn).toHaveBeenCalledWith(expect.stringContaining("'on') is not supported yet"));
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('off command should throw with unsupported error', async () => {
|
|
230
|
+
// Arrange
|
|
231
|
+
const washerControl: ThinqWasherControlConfig = {};
|
|
232
|
+
const stopCommandPayload: WasherStopCommandPayload | undefined = undefined;
|
|
233
|
+
registerWasherCommandHandlers(
|
|
234
|
+
mockEndpoint,
|
|
235
|
+
mockDevice,
|
|
236
|
+
mockApiClient,
|
|
237
|
+
mockLogger,
|
|
238
|
+
washerControl,
|
|
239
|
+
stopCommandPayload,
|
|
240
|
+
);
|
|
241
|
+
const offCall = vi.mocked(mockEndpoint.addCommandHandler).mock.calls.find((call: any[]) => call[0] === 'off');
|
|
242
|
+
const offHandler = offCall?.[1] as () => Promise<void>;
|
|
243
|
+
|
|
244
|
+
// Act & Assert
|
|
245
|
+
await expect(offHandler()).rejects.toThrow('not supported yet');
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('pause command should throw with unsupported error', async () => {
|
|
249
|
+
// Arrange
|
|
250
|
+
const washerControl: ThinqWasherControlConfig = {};
|
|
251
|
+
const stopCommandPayload: WasherStopCommandPayload | undefined = undefined;
|
|
252
|
+
registerWasherCommandHandlers(
|
|
253
|
+
mockEndpoint,
|
|
254
|
+
mockDevice,
|
|
255
|
+
mockApiClient,
|
|
256
|
+
mockLogger,
|
|
257
|
+
washerControl,
|
|
258
|
+
stopCommandPayload,
|
|
259
|
+
);
|
|
260
|
+
const pauseCall = vi.mocked(mockEndpoint.addCommandHandler).mock.calls.find((call: any[]) => call[0] === 'pause');
|
|
261
|
+
const pauseHandler = pauseCall?.[1] as () => Promise<void>;
|
|
262
|
+
|
|
263
|
+
// Act & Assert
|
|
264
|
+
await expect(pauseHandler()).rejects.toThrow('not supported yet');
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('on/off commands should revert OnOff.onOff to prior value on failure', async () => {
|
|
268
|
+
// Arrange
|
|
269
|
+
const washerControl: ThinqWasherControlConfig = {};
|
|
270
|
+
const stopCommandPayload: WasherStopCommandPayload | undefined = undefined;
|
|
271
|
+
registerWasherCommandHandlers(
|
|
272
|
+
mockEndpoint,
|
|
273
|
+
mockDevice,
|
|
274
|
+
mockApiClient,
|
|
275
|
+
mockLogger,
|
|
276
|
+
washerControl,
|
|
277
|
+
stopCommandPayload,
|
|
278
|
+
);
|
|
279
|
+
const offCall = vi.mocked(mockEndpoint.addCommandHandler).mock.calls.find((call: any[]) => call[0] === 'off');
|
|
280
|
+
const offHandler = offCall?.[1] as () => Promise<void>;
|
|
281
|
+
|
|
282
|
+
// Act
|
|
283
|
+
await expect(offHandler()).rejects.toThrow();
|
|
284
|
+
|
|
285
|
+
// Assert
|
|
286
|
+
expect(mockEndpoint.updateAttribute).toHaveBeenCalledWith(
|
|
287
|
+
OnOff.id,
|
|
288
|
+
'onOff',
|
|
289
|
+
true, // Should revert to prior value (true)
|
|
290
|
+
mockEndpoint.log,
|
|
291
|
+
);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it('non-on/off commands should revert OperationalState.operationalState on failure', async () => {
|
|
295
|
+
// Arrange
|
|
296
|
+
const washerControl: ThinqWasherControlConfig = {};
|
|
297
|
+
const stopCommandPayload: WasherStopCommandPayload | undefined = undefined;
|
|
298
|
+
registerWasherCommandHandlers(
|
|
299
|
+
mockEndpoint,
|
|
300
|
+
mockDevice,
|
|
301
|
+
mockApiClient,
|
|
302
|
+
mockLogger,
|
|
303
|
+
washerControl,
|
|
304
|
+
stopCommandPayload,
|
|
305
|
+
);
|
|
306
|
+
const pauseCall = vi.mocked(mockEndpoint.addCommandHandler).mock.calls.find((call: any[]) => call[0] === 'pause');
|
|
307
|
+
const pauseHandler = pauseCall?.[1] as () => Promise<void>;
|
|
308
|
+
|
|
309
|
+
// Act
|
|
310
|
+
await expect(pauseHandler()).rejects.toThrow();
|
|
311
|
+
|
|
312
|
+
// Assert
|
|
313
|
+
expect(mockEndpoint.updateAttribute).toHaveBeenCalledWith(
|
|
314
|
+
OperationalState.id,
|
|
315
|
+
'operationalState',
|
|
316
|
+
OperationalState.OperationalStateEnum.Running, // Should revert to prior value
|
|
317
|
+
mockEndpoint.log,
|
|
318
|
+
);
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('changeToMode should revert both operationalState and currentMode on failure', async () => {
|
|
322
|
+
// Arrange
|
|
323
|
+
const washerControl: ThinqWasherControlConfig = {};
|
|
324
|
+
const stopCommandPayload: WasherStopCommandPayload | undefined = undefined;
|
|
325
|
+
registerWasherCommandHandlers(
|
|
326
|
+
mockEndpoint,
|
|
327
|
+
mockDevice,
|
|
328
|
+
mockApiClient,
|
|
329
|
+
mockLogger,
|
|
330
|
+
washerControl,
|
|
331
|
+
stopCommandPayload,
|
|
332
|
+
);
|
|
333
|
+
const changeModeCall = vi
|
|
334
|
+
.mocked(mockEndpoint.addCommandHandler)
|
|
335
|
+
.mock.calls.find((call: any[]) => call[0] === 'changeToMode');
|
|
336
|
+
const changeModeHandler = changeModeCall?.[1] as () => Promise<void>;
|
|
337
|
+
|
|
338
|
+
// Act
|
|
339
|
+
await expect(changeModeHandler()).rejects.toThrow();
|
|
340
|
+
|
|
341
|
+
// Assert
|
|
342
|
+
expect(mockEndpoint.updateAttribute).toHaveBeenCalledWith(
|
|
343
|
+
OperationalState.id,
|
|
344
|
+
'operationalState',
|
|
345
|
+
expect.any(Number),
|
|
346
|
+
mockEndpoint.log,
|
|
347
|
+
);
|
|
348
|
+
expect(mockEndpoint.updateAttribute).toHaveBeenCalledWith(
|
|
349
|
+
LaundryWasherMode.id,
|
|
350
|
+
'currentMode',
|
|
351
|
+
0, // Should revert to prior value
|
|
352
|
+
mockEndpoint.log,
|
|
353
|
+
);
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
describe('v2 real stop command', () => {
|
|
358
|
+
it('should register stop command handler', () => {
|
|
359
|
+
// Arrange
|
|
360
|
+
const washerControl: ThinqWasherControlConfig = { allowRemoteStop: true };
|
|
361
|
+
const stopCommandPayload: WasherStopCommandPayload = {
|
|
362
|
+
dataKey: 'washerDryer.state',
|
|
363
|
+
dataValue: 'STOP',
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
// Act
|
|
367
|
+
registerWasherCommandHandlers(
|
|
368
|
+
mockEndpoint,
|
|
369
|
+
mockDevice,
|
|
370
|
+
mockApiClient,
|
|
371
|
+
mockLogger,
|
|
372
|
+
washerControl,
|
|
373
|
+
stopCommandPayload,
|
|
374
|
+
);
|
|
375
|
+
|
|
376
|
+
// Assert
|
|
377
|
+
expect(mockEndpoint.addCommandHandler).toHaveBeenCalledWith('stop', expect.any(Function));
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it('stop command should throw when allowRemoteStop is false', async () => {
|
|
381
|
+
// Arrange
|
|
382
|
+
const washerControl: ThinqWasherControlConfig = { allowRemoteStop: false };
|
|
383
|
+
const stopCommandPayload: WasherStopCommandPayload = {
|
|
384
|
+
dataKey: 'washerDryer.state',
|
|
385
|
+
dataValue: 'STOP',
|
|
386
|
+
};
|
|
387
|
+
registerWasherCommandHandlers(
|
|
388
|
+
mockEndpoint,
|
|
389
|
+
mockDevice,
|
|
390
|
+
mockApiClient,
|
|
391
|
+
mockLogger,
|
|
392
|
+
washerControl,
|
|
393
|
+
stopCommandPayload,
|
|
394
|
+
);
|
|
395
|
+
const stopCall1 = vi.mocked(mockEndpoint.addCommandHandler).mock.calls.find((call: any[]) => call[0] === 'stop');
|
|
396
|
+
const stopHandler = stopCall1?.[1] as () => Promise<void>;
|
|
397
|
+
|
|
398
|
+
// Act & Assert
|
|
399
|
+
await expect(stopHandler()).rejects.toThrow('disabled by configuration');
|
|
400
|
+
expect(mockLogger.warn).toHaveBeenCalledWith(expect.stringContaining("'stop') is disabled by configuration"));
|
|
401
|
+
expect(mockApiClient.sendCommand).not.toHaveBeenCalled();
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it('stop command should throw when allowRemoteStop is undefined (falsy)', async () => {
|
|
405
|
+
// Arrange
|
|
406
|
+
const washerControl: ThinqWasherControlConfig = { allowRemoteStop: undefined };
|
|
407
|
+
const stopCommandPayload: WasherStopCommandPayload = {
|
|
408
|
+
dataKey: 'washerDryer.state',
|
|
409
|
+
dataValue: 'STOP',
|
|
410
|
+
};
|
|
411
|
+
registerWasherCommandHandlers(
|
|
412
|
+
mockEndpoint,
|
|
413
|
+
mockDevice,
|
|
414
|
+
mockApiClient,
|
|
415
|
+
mockLogger,
|
|
416
|
+
washerControl,
|
|
417
|
+
stopCommandPayload,
|
|
418
|
+
);
|
|
419
|
+
const stopCall3 = vi.mocked(mockEndpoint.addCommandHandler).mock.calls.find((call: any[]) => call[0] === 'stop');
|
|
420
|
+
const stopHandler = stopCall3?.[1] as () => Promise<void>;
|
|
421
|
+
|
|
422
|
+
// Act & Assert
|
|
423
|
+
await expect(stopHandler()).rejects.toThrow('disabled by configuration');
|
|
424
|
+
expect(mockApiClient.sendCommand).not.toHaveBeenCalled();
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('stop command should throw when stopCommandPayload is undefined', async () => {
|
|
428
|
+
// Arrange
|
|
429
|
+
const washerControl: ThinqWasherControlConfig = { allowRemoteStop: true };
|
|
430
|
+
const stopCommandPayload: WasherStopCommandPayload | undefined = undefined;
|
|
431
|
+
registerWasherCommandHandlers(
|
|
432
|
+
mockEndpoint,
|
|
433
|
+
mockDevice,
|
|
434
|
+
mockApiClient,
|
|
435
|
+
mockLogger,
|
|
436
|
+
washerControl,
|
|
437
|
+
stopCommandPayload,
|
|
438
|
+
);
|
|
439
|
+
const stopCall4 = vi.mocked(mockEndpoint.addCommandHandler).mock.calls.find((call: any[]) => call[0] === 'stop');
|
|
440
|
+
const stopHandler = stopCall4?.[1] as () => Promise<void>;
|
|
441
|
+
|
|
442
|
+
// Act & Assert
|
|
443
|
+
if (!stopHandler) throw new Error('stop handler not found');
|
|
444
|
+
await expect(stopHandler()).rejects.toThrow('not available for this device model yet');
|
|
445
|
+
expect(mockApiClient.sendCommand).not.toHaveBeenCalled();
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
it('stop command should send command when both gates are open and payload resolves', async () => {
|
|
449
|
+
// Arrange
|
|
450
|
+
const washerControl: ThinqWasherControlConfig = { allowRemoteStop: true };
|
|
451
|
+
const stopCommandPayload: WasherStopCommandPayload = {
|
|
452
|
+
dataKey: 'washerDryer.state',
|
|
453
|
+
dataValue: 'STOP',
|
|
454
|
+
};
|
|
455
|
+
registerWasherCommandHandlers(
|
|
456
|
+
mockEndpoint,
|
|
457
|
+
mockDevice,
|
|
458
|
+
mockApiClient,
|
|
459
|
+
mockLogger,
|
|
460
|
+
washerControl,
|
|
461
|
+
stopCommandPayload,
|
|
462
|
+
);
|
|
463
|
+
const stopCall5 = vi.mocked(mockEndpoint.addCommandHandler).mock.calls.find((call: any[]) => call[0] === 'stop');
|
|
464
|
+
const stopHandler = stopCall5?.[1] as () => Promise<void>;
|
|
465
|
+
|
|
466
|
+
// Act
|
|
467
|
+
await stopHandler();
|
|
468
|
+
|
|
469
|
+
// Assert
|
|
470
|
+
expect(mockApiClient.sendCommand).toHaveBeenCalledWith('washer-123', {
|
|
471
|
+
ctrlKey: 'WMControl',
|
|
472
|
+
dataKey: 'washerDryer.state',
|
|
473
|
+
dataValue: 'STOP',
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it('stop command should include full payload in sendCommand call', async () => {
|
|
478
|
+
// Arrange
|
|
479
|
+
const washerControl: ThinqWasherControlConfig = { allowRemoteStop: true };
|
|
480
|
+
const stopCommandPayload: WasherStopCommandPayload = {
|
|
481
|
+
command: 'Set',
|
|
482
|
+
dataKey: 'washerDryer.state',
|
|
483
|
+
dataValue: 'STOP',
|
|
484
|
+
};
|
|
485
|
+
registerWasherCommandHandlers(
|
|
486
|
+
mockEndpoint,
|
|
487
|
+
mockDevice,
|
|
488
|
+
mockApiClient,
|
|
489
|
+
mockLogger,
|
|
490
|
+
washerControl,
|
|
491
|
+
stopCommandPayload,
|
|
492
|
+
);
|
|
493
|
+
const stopCall6 = vi.mocked(mockEndpoint.addCommandHandler).mock.calls.find((call: any[]) => call[0] === 'stop');
|
|
494
|
+
const stopHandler = stopCall6?.[1] as () => Promise<void>;
|
|
495
|
+
|
|
496
|
+
// Act
|
|
497
|
+
if (!stopHandler) throw new Error('stop handler not found');
|
|
498
|
+
await stopHandler();
|
|
499
|
+
|
|
500
|
+
// Assert
|
|
501
|
+
expect(mockApiClient.sendCommand).toHaveBeenCalledWith('washer-123', {
|
|
502
|
+
ctrlKey: 'WMControl',
|
|
503
|
+
command: 'Set',
|
|
504
|
+
dataKey: 'washerDryer.state',
|
|
505
|
+
dataValue: 'STOP',
|
|
506
|
+
});
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('stop command should revert operationalState if sendCommand fails', async () => {
|
|
510
|
+
// Arrange
|
|
511
|
+
const washerControl: ThinqWasherControlConfig = { allowRemoteStop: true };
|
|
512
|
+
const stopCommandPayload: WasherStopCommandPayload = {
|
|
513
|
+
dataKey: 'washerDryer.state',
|
|
514
|
+
dataValue: 'STOP',
|
|
515
|
+
};
|
|
516
|
+
mockApiClient.sendCommand = vi.fn().mockRejectedValueOnce(new Error('Network error'));
|
|
517
|
+
registerWasherCommandHandlers(
|
|
518
|
+
mockEndpoint,
|
|
519
|
+
mockDevice,
|
|
520
|
+
mockApiClient,
|
|
521
|
+
mockLogger,
|
|
522
|
+
washerControl,
|
|
523
|
+
stopCommandPayload,
|
|
524
|
+
);
|
|
525
|
+
const stopCall7 = vi.mocked(mockEndpoint.addCommandHandler).mock.calls.find((call: any[]) => call[0] === 'stop');
|
|
526
|
+
const stopHandler = stopCall7?.[1] as () => Promise<void>;
|
|
527
|
+
|
|
528
|
+
// Act
|
|
529
|
+
if (!stopHandler) throw new Error('stop handler not found');
|
|
530
|
+
await expect(stopHandler()).rejects.toThrow('Network error');
|
|
531
|
+
|
|
532
|
+
// Assert
|
|
533
|
+
expect(mockEndpoint.updateAttribute).toHaveBeenCalledWith(
|
|
534
|
+
OperationalState.id,
|
|
535
|
+
'operationalState',
|
|
536
|
+
OperationalState.OperationalStateEnum.Running, // Should revert to prior value
|
|
537
|
+
mockEndpoint.log,
|
|
538
|
+
);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it('stop command should log error when sendCommand fails', async () => {
|
|
542
|
+
// Arrange
|
|
543
|
+
const washerControl: ThinqWasherControlConfig = { allowRemoteStop: true };
|
|
544
|
+
const stopCommandPayload: WasherStopCommandPayload = {
|
|
545
|
+
dataKey: 'washerDryer.state',
|
|
546
|
+
dataValue: 'STOP',
|
|
547
|
+
};
|
|
548
|
+
mockApiClient.sendCommand = vi.fn().mockRejectedValueOnce(new Error('Network error'));
|
|
549
|
+
registerWasherCommandHandlers(
|
|
550
|
+
mockEndpoint,
|
|
551
|
+
mockDevice,
|
|
552
|
+
mockApiClient,
|
|
553
|
+
mockLogger,
|
|
554
|
+
washerControl,
|
|
555
|
+
stopCommandPayload,
|
|
556
|
+
);
|
|
557
|
+
const stopCall8 = vi.mocked(mockEndpoint.addCommandHandler).mock.calls.find((call: any[]) => call[0] === 'stop');
|
|
558
|
+
const stopHandler = stopCall8?.[1] as () => Promise<void>;
|
|
559
|
+
|
|
560
|
+
// Act
|
|
561
|
+
if (!stopHandler) throw new Error('stop handler not found');
|
|
562
|
+
await expect(stopHandler()).rejects.toThrow();
|
|
563
|
+
|
|
564
|
+
// Assert
|
|
565
|
+
expect(mockLogger.error).toHaveBeenCalledWith(expect.stringContaining("ThinQ Washer command 'stop' failed"));
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
it('stop command should not crash if updateAttribute revert fails', async () => {
|
|
569
|
+
// Arrange
|
|
570
|
+
const washerControl: ThinqWasherControlConfig = { allowRemoteStop: true };
|
|
571
|
+
const stopCommandPayload: WasherStopCommandPayload = {
|
|
572
|
+
dataKey: 'washerDryer.state',
|
|
573
|
+
dataValue: 'STOP',
|
|
574
|
+
};
|
|
575
|
+
mockApiClient.sendCommand = vi.fn().mockRejectedValueOnce(new Error('Network error'));
|
|
576
|
+
mockEndpoint.updateAttribute.mockRejectedValueOnce(new Error('Revert failed'));
|
|
577
|
+
registerWasherCommandHandlers(
|
|
578
|
+
mockEndpoint,
|
|
579
|
+
mockDevice,
|
|
580
|
+
mockApiClient,
|
|
581
|
+
mockLogger,
|
|
582
|
+
washerControl,
|
|
583
|
+
stopCommandPayload,
|
|
584
|
+
);
|
|
585
|
+
const stopCall9 = vi.mocked(mockEndpoint.addCommandHandler).mock.calls.find((call: any[]) => call[0] === 'stop');
|
|
586
|
+
const stopHandler = stopCall9?.[1] as () => Promise<void>;
|
|
587
|
+
|
|
588
|
+
// Act & Assert
|
|
589
|
+
if (!stopHandler) throw new Error('stop handler not found');
|
|
590
|
+
await expect(stopHandler()).rejects.toThrow('Network error');
|
|
591
|
+
// Should still throw the original error, not the revert error
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { LaundryWasher } from 'matterbridge/devices';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import type { ThinqWasherDevice } from '../../../core/domain/entities/ThinqDevice.js';
|
|
5
|
+
import { ThinqSnapshot } from '../../../core/domain/value-objects/ThinqSnapshot.js';
|
|
6
|
+
import { buildWasherEndpoint } from '../../../platform/thinq/thinqWasherEndpointFactory.js';
|
|
7
|
+
import { asPartial } from '../../helpers/testUtils.js';
|
|
8
|
+
|
|
9
|
+
vi.mock('matterbridge/devices', () => ({
|
|
10
|
+
LaundryWasher: vi.fn(function (name: string, id: string) {
|
|
11
|
+
return {
|
|
12
|
+
name,
|
|
13
|
+
id,
|
|
14
|
+
createDefaultBasicInformationClusterServer: vi.fn().mockReturnThis(),
|
|
15
|
+
};
|
|
16
|
+
}),
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
function createMockWasherDevice(): ThinqWasherDevice {
|
|
20
|
+
return asPartial<ThinqWasherDevice>({
|
|
21
|
+
id: 'washer-123',
|
|
22
|
+
name: 'Living Room Washer',
|
|
23
|
+
type: 'WASHER',
|
|
24
|
+
modelName: 'VCDWL_QEUK',
|
|
25
|
+
platformType: 'THINQ',
|
|
26
|
+
online: true,
|
|
27
|
+
snapshot: new ThinqSnapshot({
|
|
28
|
+
'washerDryer.state': 'RUNNING',
|
|
29
|
+
}),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('thinqWasherEndpointFactory', () => {
|
|
34
|
+
beforeEach(() => {
|
|
35
|
+
vi.clearAllMocks();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('buildWasherEndpoint', () => {
|
|
39
|
+
it('should create a LaundryWasher endpoint with device name and id', () => {
|
|
40
|
+
// Arrange
|
|
41
|
+
const device = createMockWasherDevice();
|
|
42
|
+
const laundryWasherSpy = vi.mocked(LaundryWasher);
|
|
43
|
+
|
|
44
|
+
// Act
|
|
45
|
+
buildWasherEndpoint(device);
|
|
46
|
+
|
|
47
|
+
// Assert
|
|
48
|
+
expect(laundryWasherSpy).toHaveBeenCalledWith('Living Room Washer', 'washer-123');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should pass exact device name and id to LaundryWasher constructor', () => {
|
|
52
|
+
// Arrange
|
|
53
|
+
const device = asPartial<ThinqWasherDevice>({
|
|
54
|
+
id: 'custom-id-xyz',
|
|
55
|
+
name: 'Custom Washer Name',
|
|
56
|
+
type: 'WASHER',
|
|
57
|
+
modelName: 'ModelXYZ',
|
|
58
|
+
platformType: 'THINQ',
|
|
59
|
+
online: true,
|
|
60
|
+
snapshot: new ThinqSnapshot({}),
|
|
61
|
+
});
|
|
62
|
+
const laundryWasherSpy = vi.mocked(LaundryWasher);
|
|
63
|
+
|
|
64
|
+
// Act
|
|
65
|
+
buildWasherEndpoint(device);
|
|
66
|
+
|
|
67
|
+
// Assert
|
|
68
|
+
expect(laundryWasherSpy).toHaveBeenCalledWith('Custom Washer Name', 'custom-id-xyz');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should return the LaundryWasher instance', () => {
|
|
72
|
+
// Arrange
|
|
73
|
+
const device = createMockWasherDevice();
|
|
74
|
+
|
|
75
|
+
// Act
|
|
76
|
+
const endpoint = buildWasherEndpoint(device);
|
|
77
|
+
|
|
78
|
+
// Assert
|
|
79
|
+
expect(endpoint).toBeDefined();
|
|
80
|
+
expect(endpoint).toHaveProperty('name', 'Living Room Washer');
|
|
81
|
+
expect(endpoint).toHaveProperty('id', 'washer-123');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|