meross-cli 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/CHANGELOG.md +28 -0
- package/LICENSE +21 -0
- package/README.md +110 -0
- package/cli/commands/control/execute.js +23 -0
- package/cli/commands/control/index.js +12 -0
- package/cli/commands/control/menu.js +193 -0
- package/cli/commands/control/params/generic.js +229 -0
- package/cli/commands/control/params/index.js +56 -0
- package/cli/commands/control/params/light.js +188 -0
- package/cli/commands/control/params/thermostat.js +166 -0
- package/cli/commands/control/params/timer.js +242 -0
- package/cli/commands/control/params/trigger.js +206 -0
- package/cli/commands/dump.js +35 -0
- package/cli/commands/index.js +34 -0
- package/cli/commands/info.js +221 -0
- package/cli/commands/list.js +112 -0
- package/cli/commands/mqtt.js +187 -0
- package/cli/commands/sniffer/device-sniffer.js +217 -0
- package/cli/commands/sniffer/fake-app.js +233 -0
- package/cli/commands/sniffer/index.js +7 -0
- package/cli/commands/sniffer/message-queue.js +65 -0
- package/cli/commands/sniffer/sniffer-menu.js +676 -0
- package/cli/commands/stats.js +90 -0
- package/cli/commands/status/device-status.js +1403 -0
- package/cli/commands/status/hub-status.js +72 -0
- package/cli/commands/status/index.js +50 -0
- package/cli/commands/status/subdevices/hub-smoke-detector.js +82 -0
- package/cli/commands/status/subdevices/hub-temp-hum-sensor.js +43 -0
- package/cli/commands/status/subdevices/hub-thermostat-valve.js +83 -0
- package/cli/commands/status/subdevices/hub-water-leak-sensor.js +27 -0
- package/cli/commands/status/subdevices/index.js +23 -0
- package/cli/commands/test/index.js +185 -0
- package/cli/config/users.js +108 -0
- package/cli/control-registry.js +875 -0
- package/cli/helpers/client.js +89 -0
- package/cli/helpers/meross.js +106 -0
- package/cli/menu/index.js +10 -0
- package/cli/menu/main.js +648 -0
- package/cli/menu/settings.js +789 -0
- package/cli/meross-cli.js +547 -0
- package/cli/tests/README.md +365 -0
- package/cli/tests/test-alarm.js +144 -0
- package/cli/tests/test-child-lock.js +248 -0
- package/cli/tests/test-config.js +133 -0
- package/cli/tests/test-control.js +189 -0
- package/cli/tests/test-diffuser.js +505 -0
- package/cli/tests/test-dnd.js +246 -0
- package/cli/tests/test-electricity.js +209 -0
- package/cli/tests/test-encryption.js +281 -0
- package/cli/tests/test-garage.js +259 -0
- package/cli/tests/test-helper.js +313 -0
- package/cli/tests/test-hub-mts100.js +355 -0
- package/cli/tests/test-hub-sensors.js +489 -0
- package/cli/tests/test-light.js +253 -0
- package/cli/tests/test-presence.js +497 -0
- package/cli/tests/test-registry.js +419 -0
- package/cli/tests/test-roller-shutter.js +628 -0
- package/cli/tests/test-runner.js +415 -0
- package/cli/tests/test-runtime.js +234 -0
- package/cli/tests/test-screen.js +133 -0
- package/cli/tests/test-sensor-history.js +146 -0
- package/cli/tests/test-smoke-config.js +138 -0
- package/cli/tests/test-spray.js +131 -0
- package/cli/tests/test-temp-unit.js +133 -0
- package/cli/tests/test-template.js +238 -0
- package/cli/tests/test-thermostat.js +919 -0
- package/cli/tests/test-timer.js +372 -0
- package/cli/tests/test-toggle.js +342 -0
- package/cli/tests/test-trigger.js +279 -0
- package/cli/utils/display.js +86 -0
- package/cli/utils/terminal.js +137 -0
- package/package.json +53 -0
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Toggle/Switch Device Tests
|
|
5
|
+
* Tests on/off control for switches and smart plugs
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { findDevicesByAbility, waitForDeviceConnection, getDeviceName, OnlineStatus } = require('./test-helper');
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Test metadata
|
|
12
|
+
*/
|
|
13
|
+
const metadata = {
|
|
14
|
+
name: 'switch',
|
|
15
|
+
description: 'Tests on/off control for switches and smart plugs',
|
|
16
|
+
requiredAbilities: ['Appliance.Control.ToggleX', 'Appliance.Control.Toggle'],
|
|
17
|
+
minDevices: 1
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Helper function to find all toggle-capable devices
|
|
22
|
+
* @param {Object} manager - MerossManager instance
|
|
23
|
+
* @returns {Promise<Array>} Array of toggle devices
|
|
24
|
+
*/
|
|
25
|
+
async function findAllToggleDevices(manager) {
|
|
26
|
+
const toggleXDevices = await findDevicesByAbility(manager, 'Appliance.Control.ToggleX', OnlineStatus.ONLINE);
|
|
27
|
+
const toggleDevices = await findDevicesByAbility(manager, 'Appliance.Control.Toggle', OnlineStatus.ONLINE);
|
|
28
|
+
|
|
29
|
+
// Combine and deduplicate
|
|
30
|
+
const allToggleDevices = [...toggleXDevices];
|
|
31
|
+
for (const device of toggleDevices) {
|
|
32
|
+
const uuid = device.dev?.uuid || device.uuid;
|
|
33
|
+
if (!allToggleDevices.find(d => (d.dev?.uuid || d.uuid) === uuid)) {
|
|
34
|
+
allToggleDevices.push(device);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return allToggleDevices;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Runs all tests for toggle/switch devices
|
|
42
|
+
* @param {Object} context - Test context object
|
|
43
|
+
* @param {Object} context.manager - MerossManager instance (already connected)
|
|
44
|
+
* @param {Array<Object>} context.devices - Pre-filtered devices (from CLI selection or auto-discovery)
|
|
45
|
+
* @param {Object} context.options - Test options (timeout, verbose, etc.)
|
|
46
|
+
* @returns {Promise<Array>} Array of test result objects
|
|
47
|
+
*/
|
|
48
|
+
async function runTests(context) {
|
|
49
|
+
const { manager, devices, options = {} } = context;
|
|
50
|
+
const timeout = options.timeout || 30000;
|
|
51
|
+
const results = [];
|
|
52
|
+
|
|
53
|
+
// If no devices provided, discover them
|
|
54
|
+
let toggleDevices = devices || [];
|
|
55
|
+
if (toggleDevices.length === 0) {
|
|
56
|
+
toggleDevices = await findAllToggleDevices(manager);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Wait for devices to be connected
|
|
60
|
+
for (const device of toggleDevices) {
|
|
61
|
+
await waitForDeviceConnection(device, timeout);
|
|
62
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Test 1: Check if devices were found
|
|
66
|
+
if (toggleDevices.length === 0) {
|
|
67
|
+
results.push({
|
|
68
|
+
name: 'should find devices with toggle capability',
|
|
69
|
+
passed: false,
|
|
70
|
+
skipped: true,
|
|
71
|
+
error: 'No devices with toggle capability found',
|
|
72
|
+
device: null
|
|
73
|
+
});
|
|
74
|
+
return results; // Early return if no devices
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
results.push({
|
|
78
|
+
name: 'should find devices with toggle capability',
|
|
79
|
+
passed: true,
|
|
80
|
+
skipped: false,
|
|
81
|
+
error: null,
|
|
82
|
+
device: null
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const testDevice = toggleDevices[0];
|
|
86
|
+
const deviceName = getDeviceName(testDevice);
|
|
87
|
+
|
|
88
|
+
// Test 2: Get toggle state
|
|
89
|
+
try {
|
|
90
|
+
if (typeof testDevice.getToggleState === 'function') {
|
|
91
|
+
const toggleState = await testDevice.getToggleState(0);
|
|
92
|
+
|
|
93
|
+
if (!toggleState) {
|
|
94
|
+
results.push({
|
|
95
|
+
name: 'should get toggle state',
|
|
96
|
+
passed: false,
|
|
97
|
+
skipped: false,
|
|
98
|
+
error: 'getToggleState returned null or undefined',
|
|
99
|
+
device: deviceName
|
|
100
|
+
});
|
|
101
|
+
} else {
|
|
102
|
+
// Check cached toggle state
|
|
103
|
+
const cachedState = testDevice.getCachedToggleState(0);
|
|
104
|
+
const isOn = testDevice.isOn(0);
|
|
105
|
+
|
|
106
|
+
results.push({
|
|
107
|
+
name: 'should get toggle state',
|
|
108
|
+
passed: true,
|
|
109
|
+
skipped: false,
|
|
110
|
+
error: null,
|
|
111
|
+
device: deviceName,
|
|
112
|
+
details: {
|
|
113
|
+
toggleState: toggleState,
|
|
114
|
+
cachedState: cachedState,
|
|
115
|
+
isOn: isOn
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
results.push({
|
|
121
|
+
name: 'should get toggle state',
|
|
122
|
+
passed: false,
|
|
123
|
+
skipped: true,
|
|
124
|
+
error: 'Device does not support getToggleState',
|
|
125
|
+
device: deviceName
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
results.push({
|
|
130
|
+
name: 'should get toggle state',
|
|
131
|
+
passed: false,
|
|
132
|
+
skipped: false,
|
|
133
|
+
error: error.message,
|
|
134
|
+
device: deviceName
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Test 3: Control toggle state (turn on/off)
|
|
139
|
+
try {
|
|
140
|
+
// Get initial state
|
|
141
|
+
let initialState = undefined;
|
|
142
|
+
if (typeof testDevice.getToggleState === 'function') {
|
|
143
|
+
const stateResponse = await testDevice.getToggleState(0);
|
|
144
|
+
if (stateResponse && stateResponse.togglex) {
|
|
145
|
+
initialState = stateResponse.togglex[0]?.onoff || stateResponse.togglex.onoff;
|
|
146
|
+
}
|
|
147
|
+
} else if (typeof testDevice.isOn === 'function') {
|
|
148
|
+
initialState = testDevice.isOn(0);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Test turnOn if available
|
|
152
|
+
if (typeof testDevice.turnOn === 'function') {
|
|
153
|
+
const turnOnResult = await testDevice.turnOn(0);
|
|
154
|
+
|
|
155
|
+
if (!turnOnResult) {
|
|
156
|
+
results.push({
|
|
157
|
+
name: 'should control toggle state (turn on)',
|
|
158
|
+
passed: false,
|
|
159
|
+
skipped: false,
|
|
160
|
+
error: 'turnOn() returned null or undefined',
|
|
161
|
+
device: deviceName
|
|
162
|
+
});
|
|
163
|
+
} else {
|
|
164
|
+
// Wait a bit for state to update
|
|
165
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
166
|
+
|
|
167
|
+
// Verify state
|
|
168
|
+
if (typeof testDevice.isOn === 'function') {
|
|
169
|
+
const isOnAfter = testDevice.isOn(0);
|
|
170
|
+
if (!isOnAfter) {
|
|
171
|
+
results.push({
|
|
172
|
+
name: 'should control toggle state (turn on)',
|
|
173
|
+
passed: false,
|
|
174
|
+
skipped: false,
|
|
175
|
+
error: 'Device did not turn on after turnOn() call',
|
|
176
|
+
device: deviceName
|
|
177
|
+
});
|
|
178
|
+
} else {
|
|
179
|
+
results.push({
|
|
180
|
+
name: 'should control toggle state (turn on)',
|
|
181
|
+
passed: true,
|
|
182
|
+
skipped: false,
|
|
183
|
+
error: null,
|
|
184
|
+
device: deviceName
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
} else {
|
|
188
|
+
// Can't verify, but command succeeded
|
|
189
|
+
results.push({
|
|
190
|
+
name: 'should control toggle state (turn on)',
|
|
191
|
+
passed: true,
|
|
192
|
+
skipped: false,
|
|
193
|
+
error: null,
|
|
194
|
+
device: deviceName,
|
|
195
|
+
details: { note: 'turnOn succeeded but cannot verify state (isOn not available)' }
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
results.push({
|
|
201
|
+
name: 'should control toggle state (turn on)',
|
|
202
|
+
passed: false,
|
|
203
|
+
skipped: true,
|
|
204
|
+
error: 'Device does not support turnOn',
|
|
205
|
+
device: deviceName
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Test turnOff if available
|
|
210
|
+
if (typeof testDevice.turnOff === 'function') {
|
|
211
|
+
const turnOffResult = await testDevice.turnOff(0);
|
|
212
|
+
|
|
213
|
+
if (!turnOffResult) {
|
|
214
|
+
results.push({
|
|
215
|
+
name: 'should control toggle state (turn off)',
|
|
216
|
+
passed: false,
|
|
217
|
+
skipped: false,
|
|
218
|
+
error: 'turnOff() returned null or undefined',
|
|
219
|
+
device: deviceName
|
|
220
|
+
});
|
|
221
|
+
} else {
|
|
222
|
+
// Wait a bit for state to update
|
|
223
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
224
|
+
|
|
225
|
+
// Verify state
|
|
226
|
+
if (typeof testDevice.isOn === 'function') {
|
|
227
|
+
const isOnAfter = testDevice.isOn(0);
|
|
228
|
+
if (isOnAfter) {
|
|
229
|
+
results.push({
|
|
230
|
+
name: 'should control toggle state (turn off)',
|
|
231
|
+
passed: false,
|
|
232
|
+
skipped: false,
|
|
233
|
+
error: 'Device did not turn off after turnOff() call',
|
|
234
|
+
device: deviceName
|
|
235
|
+
});
|
|
236
|
+
} else {
|
|
237
|
+
results.push({
|
|
238
|
+
name: 'should control toggle state (turn off)',
|
|
239
|
+
passed: true,
|
|
240
|
+
skipped: false,
|
|
241
|
+
error: null,
|
|
242
|
+
device: deviceName
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
// Can't verify, but command succeeded
|
|
247
|
+
results.push({
|
|
248
|
+
name: 'should control toggle state (turn off)',
|
|
249
|
+
passed: true,
|
|
250
|
+
skipped: false,
|
|
251
|
+
error: null,
|
|
252
|
+
device: deviceName,
|
|
253
|
+
details: { note: 'turnOff succeeded but cannot verify state (isOn not available)' }
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
} else {
|
|
258
|
+
results.push({
|
|
259
|
+
name: 'should control toggle state (turn off)',
|
|
260
|
+
passed: false,
|
|
261
|
+
skipped: true,
|
|
262
|
+
error: 'Device does not support turnOff',
|
|
263
|
+
device: deviceName
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Restore initial state if we changed it
|
|
268
|
+
if (initialState !== undefined && typeof testDevice.turnOn === 'function' && typeof testDevice.turnOff === 'function') {
|
|
269
|
+
if (initialState === 1 || initialState === true) {
|
|
270
|
+
await testDevice.turnOn(0);
|
|
271
|
+
} else {
|
|
272
|
+
await testDevice.turnOff(0);
|
|
273
|
+
}
|
|
274
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
} catch (error) {
|
|
278
|
+
results.push({
|
|
279
|
+
name: 'should control toggle state',
|
|
280
|
+
passed: false,
|
|
281
|
+
skipped: false,
|
|
282
|
+
error: error.message,
|
|
283
|
+
device: deviceName
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Test 4: Handle toggle push notifications
|
|
288
|
+
try {
|
|
289
|
+
const testDeviceForPush = toggleDevices[0];
|
|
290
|
+
|
|
291
|
+
// Set up listener for toggle push notifications
|
|
292
|
+
let receivedNotification = false;
|
|
293
|
+
const notificationHandler = (notification) => {
|
|
294
|
+
if (notification.namespace === 'Appliance.Control.ToggleX' ||
|
|
295
|
+
notification.namespace === 'Appliance.Control.Toggle') {
|
|
296
|
+
receivedNotification = true;
|
|
297
|
+
}
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
testDeviceForPush.on('pushNotification', notificationHandler);
|
|
301
|
+
|
|
302
|
+
// Get toggle state (may trigger a push notification)
|
|
303
|
+
if (typeof testDeviceForPush.getToggleState === 'function') {
|
|
304
|
+
await testDeviceForPush.getToggleState(0);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Wait a bit for potential push notifications
|
|
308
|
+
await new Promise(resolve => setTimeout(resolve, 5000));
|
|
309
|
+
|
|
310
|
+
// Remove listener
|
|
311
|
+
testDeviceForPush.removeListener('pushNotification', notificationHandler);
|
|
312
|
+
|
|
313
|
+
// Note: We don't assert on receivedNotification since push notifications
|
|
314
|
+
// are device-initiated and may not occur during testing
|
|
315
|
+
// This test just verifies the listener mechanism works
|
|
316
|
+
results.push({
|
|
317
|
+
name: 'should handle toggle push notifications',
|
|
318
|
+
passed: true,
|
|
319
|
+
skipped: false,
|
|
320
|
+
error: null,
|
|
321
|
+
device: getDeviceName(testDeviceForPush),
|
|
322
|
+
details: { notificationReceived: receivedNotification }
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
} catch (error) {
|
|
326
|
+
results.push({
|
|
327
|
+
name: 'should handle toggle push notifications',
|
|
328
|
+
passed: false,
|
|
329
|
+
skipped: false,
|
|
330
|
+
error: error.message,
|
|
331
|
+
device: toggleDevices.length > 0 ? getDeviceName(toggleDevices[0]) : null
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return results;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Export metadata and runTests function
|
|
339
|
+
module.exports = {
|
|
340
|
+
metadata,
|
|
341
|
+
runTests
|
|
342
|
+
};
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Trigger Tests
|
|
5
|
+
* Tests trigger creation, management, and push notifications
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { findDevicesByAbility, getDeviceName, OnlineStatus } = require('./test-helper');
|
|
9
|
+
const { TriggerType, TriggerUtils } = require('meross-iot');
|
|
10
|
+
const { createTrigger } = TriggerUtils;
|
|
11
|
+
|
|
12
|
+
const metadata = {
|
|
13
|
+
name: 'trigger',
|
|
14
|
+
description: 'Tests trigger creation, management, and push notifications',
|
|
15
|
+
requiredAbilities: ['Appliance.Control.TriggerX'],
|
|
16
|
+
minDevices: 1
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
async function runTests(context) {
|
|
20
|
+
const { manager, devices, options = {} } = context;
|
|
21
|
+
const timeout = options.timeout || 30000;
|
|
22
|
+
const results = [];
|
|
23
|
+
|
|
24
|
+
// If no devices provided, discover them
|
|
25
|
+
let testDevices = devices || [];
|
|
26
|
+
if (testDevices.length === 0) {
|
|
27
|
+
testDevices = await findDevicesByAbility(manager, 'Appliance.Control.TriggerX', OnlineStatus.ONLINE);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (testDevices.length === 0) {
|
|
31
|
+
results.push({
|
|
32
|
+
name: 'should find devices with trigger capability',
|
|
33
|
+
passed: false,
|
|
34
|
+
skipped: true,
|
|
35
|
+
error: 'No devices with trigger capability found',
|
|
36
|
+
device: null
|
|
37
|
+
});
|
|
38
|
+
return results;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
results.push({
|
|
42
|
+
name: 'should find devices with trigger capability',
|
|
43
|
+
passed: true,
|
|
44
|
+
skipped: false,
|
|
45
|
+
error: null,
|
|
46
|
+
device: null
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const testDevice = testDevices[0];
|
|
50
|
+
const deviceName = getDeviceName(testDevice);
|
|
51
|
+
|
|
52
|
+
let createdTriggerId = null;
|
|
53
|
+
|
|
54
|
+
// Test 1: Create a trigger and verify it exists
|
|
55
|
+
try {
|
|
56
|
+
// Create a test trigger using the utility function
|
|
57
|
+
const testTrigger = createTrigger({
|
|
58
|
+
alias: 'Test Trigger - CLI Test',
|
|
59
|
+
duration: '30m', // 30 minutes
|
|
60
|
+
days: ['weekday'],
|
|
61
|
+
type: TriggerType.SINGLE_POINT_WEEKLY_CYCLE,
|
|
62
|
+
channel: 0,
|
|
63
|
+
enabled: true
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Wait for push notification after SET (as per API spec: "PUSH after SET")
|
|
67
|
+
let triggerIdFromPush = null;
|
|
68
|
+
const pushNotificationPromise = new Promise((resolve) => {
|
|
69
|
+
const handler = (notification) => {
|
|
70
|
+
if (notification.namespace === 'Appliance.Control.TriggerX' && notification.triggerxData) {
|
|
71
|
+
const triggerData = Array.isArray(notification.triggerxData) ? notification.triggerxData : [notification.triggerxData];
|
|
72
|
+
const createdTrigger = triggerData.find(t =>
|
|
73
|
+
t.channel === 0 &&
|
|
74
|
+
t.alias === 'Test Trigger - CLI Test' &&
|
|
75
|
+
t.id
|
|
76
|
+
);
|
|
77
|
+
if (createdTrigger && createdTrigger.id) {
|
|
78
|
+
triggerIdFromPush = createdTrigger.id;
|
|
79
|
+
testDevice.removeListener('pushNotification', handler);
|
|
80
|
+
resolve();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
testDevice.on('pushNotification', handler);
|
|
85
|
+
|
|
86
|
+
// Timeout after 5 seconds if no push notification arrives
|
|
87
|
+
setTimeout(() => {
|
|
88
|
+
testDevice.removeListener('pushNotification', handler);
|
|
89
|
+
resolve();
|
|
90
|
+
}, 5000);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const createResult = await testDevice.setTriggerX(testTrigger);
|
|
94
|
+
|
|
95
|
+
if (!createResult) {
|
|
96
|
+
results.push({
|
|
97
|
+
name: 'should create trigger',
|
|
98
|
+
passed: false,
|
|
99
|
+
skipped: false,
|
|
100
|
+
error: 'setTriggerX returned null or undefined',
|
|
101
|
+
device: deviceName
|
|
102
|
+
});
|
|
103
|
+
return results;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Wait for push notification (contains trigger with ID)
|
|
107
|
+
await pushNotificationPromise;
|
|
108
|
+
|
|
109
|
+
// Get the trigger ID from push notification, or fallback to generated ID
|
|
110
|
+
if (triggerIdFromPush) {
|
|
111
|
+
createdTriggerId = triggerIdFromPush;
|
|
112
|
+
} else if (testTrigger.id) {
|
|
113
|
+
createdTriggerId = testTrigger.id;
|
|
114
|
+
} else {
|
|
115
|
+
// Wait a bit and query by alias to find it
|
|
116
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
117
|
+
const triggers = await testDevice.getTriggerX(0);
|
|
118
|
+
const foundTrigger = triggers?.triggerx?.find(t => t.alias === 'Test Trigger - CLI Test');
|
|
119
|
+
if (foundTrigger && foundTrigger.id) {
|
|
120
|
+
createdTriggerId = foundTrigger.id;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!createdTriggerId) {
|
|
125
|
+
results.push({
|
|
126
|
+
name: 'should create trigger',
|
|
127
|
+
passed: false,
|
|
128
|
+
skipped: false,
|
|
129
|
+
error: 'Could not get trigger ID after creation',
|
|
130
|
+
device: deviceName
|
|
131
|
+
});
|
|
132
|
+
return results;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Verify trigger exists by querying it
|
|
136
|
+
const triggerInfo = await testDevice.getTriggerX(0);
|
|
137
|
+
const triggerExists = triggerInfo?.triggerx?.some(t => t.id === createdTriggerId);
|
|
138
|
+
|
|
139
|
+
if (!triggerExists) {
|
|
140
|
+
results.push({
|
|
141
|
+
name: 'should create trigger',
|
|
142
|
+
passed: false,
|
|
143
|
+
skipped: false,
|
|
144
|
+
error: 'Created trigger not found when querying',
|
|
145
|
+
device: deviceName
|
|
146
|
+
});
|
|
147
|
+
return results;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Verify cached trigger state
|
|
151
|
+
const cachedTriggers = testDevice.getCachedTriggerX(0);
|
|
152
|
+
|
|
153
|
+
results.push({
|
|
154
|
+
name: 'should create trigger',
|
|
155
|
+
passed: true,
|
|
156
|
+
skipped: false,
|
|
157
|
+
error: null,
|
|
158
|
+
device: deviceName,
|
|
159
|
+
details: {
|
|
160
|
+
triggerId: createdTriggerId,
|
|
161
|
+
cachedTriggerCount: cachedTriggers ? cachedTriggers.length : 0
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
} catch (error) {
|
|
165
|
+
results.push({
|
|
166
|
+
name: 'should create trigger',
|
|
167
|
+
passed: false,
|
|
168
|
+
skipped: false,
|
|
169
|
+
error: error.message,
|
|
170
|
+
device: deviceName
|
|
171
|
+
});
|
|
172
|
+
return results;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Test 2: Delete the trigger
|
|
176
|
+
if (createdTriggerId) {
|
|
177
|
+
try {
|
|
178
|
+
const deleteResponse = await testDevice.deleteTriggerX(createdTriggerId, 0);
|
|
179
|
+
|
|
180
|
+
// Check what the DELETE response contains
|
|
181
|
+
const deleteResponseError = deleteResponse?.error;
|
|
182
|
+
const hasError = deleteResponseError !== null && deleteResponseError !== undefined;
|
|
183
|
+
const isIdNotFound = deleteResponseError?.code === 5050 ||
|
|
184
|
+
deleteResponseError?.detail === 'id not found';
|
|
185
|
+
|
|
186
|
+
// If DELETE returned an error other than "id not found", this is a real failure
|
|
187
|
+
if (hasError && !isIdNotFound) {
|
|
188
|
+
results.push({
|
|
189
|
+
name: 'should delete trigger',
|
|
190
|
+
passed: false,
|
|
191
|
+
skipped: false,
|
|
192
|
+
error: `DELETE command failed: ${JSON.stringify(deleteResponseError)}`,
|
|
193
|
+
device: deviceName,
|
|
194
|
+
details: {
|
|
195
|
+
triggerId: createdTriggerId,
|
|
196
|
+
deleteResponse: deleteResponse
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
} else {
|
|
200
|
+
// DELETE succeeded (no error, or error 5050)
|
|
201
|
+
// Error 5050 means "id not found" which indicates the trigger is deleted (success)
|
|
202
|
+
// Empty DELETEACK also means deletion was accepted
|
|
203
|
+
results.push({
|
|
204
|
+
name: 'should delete trigger',
|
|
205
|
+
passed: true,
|
|
206
|
+
skipped: false,
|
|
207
|
+
error: null,
|
|
208
|
+
device: deviceName,
|
|
209
|
+
details: {
|
|
210
|
+
triggerId: createdTriggerId,
|
|
211
|
+
deleteResponse: isIdNotFound ? 'Trigger deleted (error 5050 - id not found)' : 'DELETE command accepted (empty DELETEACK)'
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
} catch (error) {
|
|
216
|
+
results.push({
|
|
217
|
+
name: 'should delete trigger',
|
|
218
|
+
passed: false,
|
|
219
|
+
skipped: false,
|
|
220
|
+
error: error.message,
|
|
221
|
+
device: deviceName,
|
|
222
|
+
details: {
|
|
223
|
+
triggerId: createdTriggerId
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Test 3: Handle trigger push notifications
|
|
230
|
+
try {
|
|
231
|
+
// Set up listener for trigger push notifications
|
|
232
|
+
let receivedNotification = false;
|
|
233
|
+
const notificationHandler = (notification) => {
|
|
234
|
+
if (notification.namespace === 'Appliance.Control.TriggerX') {
|
|
235
|
+
receivedNotification = true;
|
|
236
|
+
}
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
testDevice.on('pushNotification', notificationHandler);
|
|
240
|
+
|
|
241
|
+
// Get trigger info (may trigger a push notification)
|
|
242
|
+
await testDevice.getTriggerX(0);
|
|
243
|
+
|
|
244
|
+
// Wait a bit for potential push notifications
|
|
245
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
246
|
+
|
|
247
|
+
// Remove listener
|
|
248
|
+
testDevice.removeListener('pushNotification', notificationHandler);
|
|
249
|
+
|
|
250
|
+
// Note: We don't assert on receivedNotification since push notifications
|
|
251
|
+
// are device-initiated and may not occur during testing
|
|
252
|
+
results.push({
|
|
253
|
+
name: 'should handle trigger push notifications',
|
|
254
|
+
passed: true,
|
|
255
|
+
skipped: false,
|
|
256
|
+
error: null,
|
|
257
|
+
device: deviceName,
|
|
258
|
+
details: {
|
|
259
|
+
notificationReceived: receivedNotification,
|
|
260
|
+
note: 'Push notifications are device-initiated and may not occur during testing'
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
} catch (error) {
|
|
264
|
+
results.push({
|
|
265
|
+
name: 'should handle trigger push notifications',
|
|
266
|
+
passed: false,
|
|
267
|
+
skipped: false,
|
|
268
|
+
error: error.message,
|
|
269
|
+
device: deviceName
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return results;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
module.exports = {
|
|
277
|
+
metadata,
|
|
278
|
+
runTests
|
|
279
|
+
};
|