tsvesync 1.0.13 → 1.0.15
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/dist/lib/vesyncOutlet.js +21 -2
- package/package.json +1 -1
- package/src/lib/vesyncOutlet.ts +21 -3
- package/dist/lib/helpers.js.map +0 -1
- package/dist/lib/vesync.js.map +0 -1
- package/dist/lib/vesyncBaseDevice.js.map +0 -1
- package/dist/lib/vesyncBulb.js.map +0 -1
- package/dist/lib/vesyncFan.js.map +0 -1
- package/dist/lib/vesyncKitchen.d.ts +0 -0
- package/dist/lib/vesyncKitchen.js +0 -2
- package/dist/lib/vesyncKitchen.js.map +0 -1
- package/dist/lib/vesyncOutlet.js.map +0 -1
- package/dist/lib/vesyncSwitch.js.map +0 -1
- package/dist/src/lib/helpers.d.ts +0 -88
- package/dist/src/lib/helpers.js +0 -244
- package/dist/src/lib/vesync.d.ts +0 -89
- package/dist/src/lib/vesync.js +0 -430
- package/dist/src/lib/vesyncBaseDevice.d.ts +0 -43
- package/dist/src/lib/vesyncBaseDevice.js +0 -87
- package/dist/src/lib/vesyncBulb.d.ts +0 -79
- package/dist/src/lib/vesyncBulb.js +0 -198
- package/dist/src/lib/vesyncBulbImpl.d.ts +0 -38
- package/dist/src/lib/vesyncBulbImpl.js +0 -161
- package/dist/src/lib/vesyncFan.d.ts +0 -80
- package/dist/src/lib/vesyncFan.js +0 -406
- package/dist/src/lib/vesyncFanImpl.d.ts +0 -137
- package/dist/src/lib/vesyncFanImpl.js +0 -643
- package/dist/src/lib/vesyncOutlet.d.ts +0 -53
- package/dist/src/lib/vesyncOutlet.js +0 -98
- package/dist/src/lib/vesyncOutletImpl.d.ts +0 -70
- package/dist/src/lib/vesyncOutletImpl.js +0 -206
- package/dist/src/lib/vesyncSwitch.d.ts +0 -169
- package/dist/src/lib/vesyncSwitch.js +0 -414
- package/dist/src/lib/vesyncSwitchImpl.d.ts +0 -82
- package/dist/src/lib/vesyncSwitchImpl.js +0 -290
- package/dist/test/testFeatures.d.ts +0 -4
- package/dist/test/testFeatures.js +0 -394
- package/dist/test/testService.d.ts +0 -62
- package/dist/test/testService.js +0 -540
- package/dist/test/testSpecial.d.ts +0 -5
- package/dist/test/testSpecial.js +0 -227
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VeSync API Test Service
|
|
3
|
-
* Tests core functionality of the VeSync API implementation
|
|
4
|
-
*/
|
|
5
|
-
export declare class VeSyncTestService {
|
|
6
|
-
private manager;
|
|
7
|
-
private testResults;
|
|
8
|
-
private verbose;
|
|
9
|
-
constructor(verbose?: boolean);
|
|
10
|
-
/**
|
|
11
|
-
* Get directory for device type and category
|
|
12
|
-
*/
|
|
13
|
-
private getDeviceDirectory;
|
|
14
|
-
/**
|
|
15
|
-
* Find YAML spec file for a device
|
|
16
|
-
*/
|
|
17
|
-
private findDeviceSpec;
|
|
18
|
-
/**
|
|
19
|
-
* Convert snake_case to camelCase
|
|
20
|
-
*/
|
|
21
|
-
private snakeToCamel;
|
|
22
|
-
/**
|
|
23
|
-
* Convert camelCase to snake_case
|
|
24
|
-
*/
|
|
25
|
-
private camelToSnake;
|
|
26
|
-
/**
|
|
27
|
-
* Convert spec method name to implementation method name
|
|
28
|
-
*/
|
|
29
|
-
private specToImplMethod;
|
|
30
|
-
/**
|
|
31
|
-
* Convert implementation method name to spec method name
|
|
32
|
-
*/
|
|
33
|
-
private implToSpecMethod;
|
|
34
|
-
/**
|
|
35
|
-
* Analyze methods for a device
|
|
36
|
-
*/
|
|
37
|
-
private analyzeMethods;
|
|
38
|
-
/**
|
|
39
|
-
* Test device methods against YAML spec
|
|
40
|
-
*/
|
|
41
|
-
private testDeviceMethods;
|
|
42
|
-
/**
|
|
43
|
-
* Generate test parameters for a method
|
|
44
|
-
*/
|
|
45
|
-
private generateTestParameters;
|
|
46
|
-
/**
|
|
47
|
-
* Validate an API call against its spec
|
|
48
|
-
*/
|
|
49
|
-
private validateApiCall;
|
|
50
|
-
/**
|
|
51
|
-
* Generate method stub from YAML spec
|
|
52
|
-
*/
|
|
53
|
-
private generateMethodStub;
|
|
54
|
-
/**
|
|
55
|
-
* Display test summary
|
|
56
|
-
*/
|
|
57
|
-
private displaySummary;
|
|
58
|
-
/**
|
|
59
|
-
* Run all tests
|
|
60
|
-
*/
|
|
61
|
-
run(): Promise<void>;
|
|
62
|
-
}
|
package/dist/test/testService.js
DELETED
|
@@ -1,540 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* VeSync API Test Service
|
|
4
|
-
* Tests core functionality of the VeSync API implementation
|
|
5
|
-
*/
|
|
6
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
7
|
-
if (k2 === undefined) k2 = k;
|
|
8
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
9
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
10
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
11
|
-
}
|
|
12
|
-
Object.defineProperty(o, k2, desc);
|
|
13
|
-
}) : (function(o, m, k, k2) {
|
|
14
|
-
if (k2 === undefined) k2 = k;
|
|
15
|
-
o[k2] = m[k];
|
|
16
|
-
}));
|
|
17
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
18
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
19
|
-
}) : function(o, v) {
|
|
20
|
-
o["default"] = v;
|
|
21
|
-
});
|
|
22
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
23
|
-
var ownKeys = function(o) {
|
|
24
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
25
|
-
var ar = [];
|
|
26
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
27
|
-
return ar;
|
|
28
|
-
};
|
|
29
|
-
return ownKeys(o);
|
|
30
|
-
};
|
|
31
|
-
return function (mod) {
|
|
32
|
-
if (mod && mod.__esModule) return mod;
|
|
33
|
-
var result = {};
|
|
34
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
35
|
-
__setModuleDefault(result, mod);
|
|
36
|
-
return result;
|
|
37
|
-
};
|
|
38
|
-
})();
|
|
39
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
-
exports.VeSyncTestService = void 0;
|
|
41
|
-
// Set environment variables for testing
|
|
42
|
-
process.env.VESYNC_API_URL = 'http://localhost:8000';
|
|
43
|
-
const vesync_1 = require("../src/lib/vesync");
|
|
44
|
-
const vesyncBaseDevice_1 = require("../src/lib/vesyncBaseDevice");
|
|
45
|
-
const fs = __importStar(require("fs"));
|
|
46
|
-
const path = __importStar(require("path"));
|
|
47
|
-
const yaml = __importStar(require("js-yaml"));
|
|
48
|
-
const helpers_1 = require("../src/lib/helpers");
|
|
49
|
-
// Constants for device type to directory mapping
|
|
50
|
-
const TYPE_TO_DIRECTORY = {
|
|
51
|
-
// Switches (check these first as they're more specific)
|
|
52
|
-
'ESWL': 'vesyncswitch',
|
|
53
|
-
'ESWD': 'vesyncswitch',
|
|
54
|
-
// Outlets
|
|
55
|
-
'wifi-switch': 'vesyncoutlet',
|
|
56
|
-
'ESW': 'vesyncoutlet',
|
|
57
|
-
'ESO': 'vesyncoutlet',
|
|
58
|
-
// Bulbs
|
|
59
|
-
'ESL': 'vesyncbulb',
|
|
60
|
-
'XYD': 'vesyncbulb',
|
|
61
|
-
// Fans
|
|
62
|
-
'Core': 'vesyncfan',
|
|
63
|
-
'LAP': 'vesyncfan',
|
|
64
|
-
'LUH': 'vesyncfan',
|
|
65
|
-
'LEH': 'vesyncfan',
|
|
66
|
-
'LTF': 'vesyncfan',
|
|
67
|
-
'LV-PUR': 'vesyncfan'
|
|
68
|
-
};
|
|
69
|
-
class VeSyncTestService {
|
|
70
|
-
constructor(verbose = false) {
|
|
71
|
-
this.testResults = new Map();
|
|
72
|
-
this.verbose = false;
|
|
73
|
-
this.manager = new vesync_1.VeSync('test@example.com', 'test123');
|
|
74
|
-
this.verbose = verbose;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Get directory for device type and category
|
|
78
|
-
*/
|
|
79
|
-
getDeviceDirectory(deviceType, category) {
|
|
80
|
-
// Find the correct subdirectory based on device type prefix
|
|
81
|
-
for (const [prefix, dir] of Object.entries(TYPE_TO_DIRECTORY)) {
|
|
82
|
-
if (deviceType.startsWith(prefix)) {
|
|
83
|
-
return dir;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// If no mapping found, use the category name
|
|
87
|
-
switch (category.toLowerCase()) {
|
|
88
|
-
case 'switches':
|
|
89
|
-
return 'vesyncswitch';
|
|
90
|
-
case 'outlets':
|
|
91
|
-
return 'vesyncoutlet';
|
|
92
|
-
case 'bulbs':
|
|
93
|
-
return 'vesyncbulb';
|
|
94
|
-
case 'fans':
|
|
95
|
-
return 'vesyncfan';
|
|
96
|
-
default:
|
|
97
|
-
const baseName = category.toLowerCase();
|
|
98
|
-
return `vesync${baseName.endsWith('s') ? baseName.slice(0, -1) : baseName}`;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Find YAML spec file for a device
|
|
103
|
-
*/
|
|
104
|
-
findDeviceSpec(device) {
|
|
105
|
-
const deviceType = device.deviceType;
|
|
106
|
-
const apiDir = path.join(__dirname, '..', 'api');
|
|
107
|
-
const subDir = this.getDeviceDirectory(deviceType, device.deviceCategory);
|
|
108
|
-
// Look for exact match first
|
|
109
|
-
const exactPath = path.join(apiDir, subDir, `${deviceType}.yaml`);
|
|
110
|
-
if (fs.existsSync(exactPath)) {
|
|
111
|
-
return exactPath;
|
|
112
|
-
}
|
|
113
|
-
// Look for partial matches
|
|
114
|
-
const files = fs.readdirSync(path.join(apiDir, subDir));
|
|
115
|
-
for (const file of files) {
|
|
116
|
-
if (file.endsWith('.yaml') && deviceType.includes(file.replace('.yaml', ''))) {
|
|
117
|
-
return path.join(apiDir, subDir, file);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
console.warn(`Could not find API spec file for device type: ${deviceType}`);
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Convert snake_case to camelCase
|
|
125
|
-
*/
|
|
126
|
-
snakeToCamel(str) {
|
|
127
|
-
return str.replace(/_([a-z])/g, (g) => g[1].toUpperCase());
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Convert camelCase to snake_case
|
|
131
|
-
*/
|
|
132
|
-
camelToSnake(str) {
|
|
133
|
-
return str.replace(/[A-Z]/g, letter => `_${letter.toLowerCase()}`);
|
|
134
|
-
}
|
|
135
|
-
/**
|
|
136
|
-
* Convert spec method name to implementation method name
|
|
137
|
-
*/
|
|
138
|
-
specToImplMethod(specMethod) {
|
|
139
|
-
return this.snakeToCamel(specMethod);
|
|
140
|
-
}
|
|
141
|
-
/**
|
|
142
|
-
* Convert implementation method name to spec method name
|
|
143
|
-
*/
|
|
144
|
-
implToSpecMethod(implMethod) {
|
|
145
|
-
return this.camelToSnake(implMethod);
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Analyze methods for a device
|
|
149
|
-
*/
|
|
150
|
-
analyzeMethods(specMethods, implMethods) {
|
|
151
|
-
// Get all methods (for display purposes)
|
|
152
|
-
const allImplMethods = Array.from(implMethods);
|
|
153
|
-
const allSpecMethods = specMethods;
|
|
154
|
-
// Filter out utility methods from implementation
|
|
155
|
-
const utilityMethods = ['update', 'display', 'displayJSON', 'hasFeature'];
|
|
156
|
-
const filteredImplMethods = allImplMethods.filter(m => !utilityMethods.includes(m));
|
|
157
|
-
// Find matching methods
|
|
158
|
-
const matchedMethods = filteredImplMethods.filter(m => allSpecMethods.includes(this.implToSpecMethod(m)));
|
|
159
|
-
// Find missing methods (excluding update)
|
|
160
|
-
const missingMethods = allSpecMethods.filter(m => m !== 'update' && !matchedMethods.includes(this.specToImplMethod(m)));
|
|
161
|
-
// Find extra methods
|
|
162
|
-
const extraMethods = filteredImplMethods.filter(m => !allSpecMethods.includes(this.implToSpecMethod(m)));
|
|
163
|
-
return {
|
|
164
|
-
allImplMethods,
|
|
165
|
-
allSpecMethods,
|
|
166
|
-
matchedMethods,
|
|
167
|
-
missingMethods,
|
|
168
|
-
extraMethods
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Test device methods against YAML spec
|
|
173
|
-
*/
|
|
174
|
-
async testDeviceMethods(device) {
|
|
175
|
-
const specFile = this.findDeviceSpec(device);
|
|
176
|
-
if (!specFile)
|
|
177
|
-
return;
|
|
178
|
-
const spec = yaml.load(fs.readFileSync(specFile, 'utf8'));
|
|
179
|
-
if (!spec) {
|
|
180
|
-
console.warn(`Empty or invalid YAML spec for device type: ${device.deviceType}`);
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
console.log(`\nTesting methods for device: ${device.deviceName} (${device.deviceType})`);
|
|
184
|
-
console.log(` Using spec file: ${specFile}\n`);
|
|
185
|
-
// Get methods from spec and implementation
|
|
186
|
-
const specMethods = Object.keys(spec);
|
|
187
|
-
console.log('\nSpec methods:', specMethods);
|
|
188
|
-
const implMethods = new Set();
|
|
189
|
-
// Get all methods from the prototype chain up to but not including VeSyncBaseDevice
|
|
190
|
-
let proto = Object.getPrototypeOf(device);
|
|
191
|
-
while (proto && proto.constructor !== vesyncBaseDevice_1.VeSyncBaseDevice) {
|
|
192
|
-
const methods = Object.getOwnPropertyNames(proto)
|
|
193
|
-
.filter(name => {
|
|
194
|
-
if (name === 'constructor' || name.startsWith('_'))
|
|
195
|
-
return false;
|
|
196
|
-
const descriptor = Object.getOwnPropertyDescriptor(proto, name);
|
|
197
|
-
return descriptor && typeof descriptor.value === 'function';
|
|
198
|
-
});
|
|
199
|
-
console.log('Found methods in', proto.constructor.name + ':', methods);
|
|
200
|
-
methods.forEach(name => implMethods.add(name));
|
|
201
|
-
proto = Object.getPrototypeOf(proto);
|
|
202
|
-
}
|
|
203
|
-
console.log('All implementation methods:', Array.from(implMethods));
|
|
204
|
-
// Analyze methods
|
|
205
|
-
const analysis = this.analyzeMethods(specMethods, implMethods);
|
|
206
|
-
// Store test results
|
|
207
|
-
const testResult = {
|
|
208
|
-
deviceType: device.deviceType,
|
|
209
|
-
category: device.deviceCategory,
|
|
210
|
-
specMethods,
|
|
211
|
-
implMethods,
|
|
212
|
-
missingMethods: analysis.missingMethods,
|
|
213
|
-
extraMethods: analysis.extraMethods,
|
|
214
|
-
logs: []
|
|
215
|
-
};
|
|
216
|
-
this.testResults.set(device.deviceType, testResult);
|
|
217
|
-
console.log(` Methods defined in YAML spec: ${analysis.allSpecMethods.join(', ')}`);
|
|
218
|
-
console.log(` Methods in implementation: ${analysis.allImplMethods.join(', ')}\n`);
|
|
219
|
-
if (analysis.missingMethods.length > 0) {
|
|
220
|
-
console.log(` ✗ Missing methods: ${analysis.missingMethods.join(', ')}`);
|
|
221
|
-
throw new Error(`Device ${device.deviceType} is missing required methods: ${analysis.missingMethods.join(', ')}`);
|
|
222
|
-
}
|
|
223
|
-
if (analysis.extraMethods.length > 0) {
|
|
224
|
-
console.log(` ! Extra methods not in spec: ${analysis.extraMethods.join(', ')}`);
|
|
225
|
-
}
|
|
226
|
-
// Test each method in the spec
|
|
227
|
-
for (const method of specMethods) {
|
|
228
|
-
if (method === 'update')
|
|
229
|
-
continue; // Skip testing update method
|
|
230
|
-
console.log(`\n Testing method: ${method}`);
|
|
231
|
-
console.log(' YAML Specification:');
|
|
232
|
-
console.log(` URL: ${spec[method].url}`);
|
|
233
|
-
console.log(` Method: ${spec[method].method}`);
|
|
234
|
-
console.log(' Headers:', JSON.stringify(spec[method].headers, null, 2));
|
|
235
|
-
console.log(' Body:', JSON.stringify(spec[method].body, null, 2));
|
|
236
|
-
const implMethod = this.specToImplMethod(method);
|
|
237
|
-
if (analysis.matchedMethods.includes(implMethod)) {
|
|
238
|
-
console.log('\n Executing method...');
|
|
239
|
-
try {
|
|
240
|
-
// Generate test parameters based on method signature
|
|
241
|
-
const params = this.generateTestParameters(device, implMethod, spec[method]);
|
|
242
|
-
// Create a proxy to intercept API calls and logs
|
|
243
|
-
const manager = device.manager;
|
|
244
|
-
const originalCallApi = manager.callApi;
|
|
245
|
-
let apiCallMade = false;
|
|
246
|
-
let lastRequest = null;
|
|
247
|
-
let lastResponse = null;
|
|
248
|
-
let lastError = null;
|
|
249
|
-
// Intercept logError calls
|
|
250
|
-
const originalLogError = device.logError;
|
|
251
|
-
device.logError = (method, error) => {
|
|
252
|
-
const errorMsg = `Error in ${device.deviceName}.${method}: ${error?.message || error}`;
|
|
253
|
-
if (error?.response?.data) {
|
|
254
|
-
const responseData = error.response.data;
|
|
255
|
-
const msg = responseData.msg || responseData.message || JSON.stringify(responseData);
|
|
256
|
-
testResult.logs.push(`${errorMsg} - ${msg}`);
|
|
257
|
-
console.log(` ${errorMsg}`);
|
|
258
|
-
console.log(` Response: ${msg}`);
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
testResult.logs.push(errorMsg);
|
|
262
|
-
console.log(` ${errorMsg}`);
|
|
263
|
-
}
|
|
264
|
-
lastError = error;
|
|
265
|
-
};
|
|
266
|
-
// Intercept API calls
|
|
267
|
-
manager.callApi = async (url, method, body, headers) => {
|
|
268
|
-
apiCallMade = true;
|
|
269
|
-
lastRequest = { url, method, body, headers };
|
|
270
|
-
try {
|
|
271
|
-
lastResponse = await originalCallApi.call(manager, url, method, body, headers);
|
|
272
|
-
if (lastResponse[0]?.msg) {
|
|
273
|
-
console.log(' Response message:', lastResponse[0].msg);
|
|
274
|
-
}
|
|
275
|
-
return lastResponse;
|
|
276
|
-
}
|
|
277
|
-
catch (error) {
|
|
278
|
-
lastError = error;
|
|
279
|
-
throw error;
|
|
280
|
-
}
|
|
281
|
-
};
|
|
282
|
-
// Execute the method with parameters
|
|
283
|
-
const result = await device[implMethod](...params);
|
|
284
|
-
// Restore original functions
|
|
285
|
-
manager.callApi = originalCallApi;
|
|
286
|
-
device.logError = originalLogError;
|
|
287
|
-
// Validate API call was made
|
|
288
|
-
if (!apiCallMade) {
|
|
289
|
-
console.log(' ⚠ Warning: Method did not make any API calls');
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
292
|
-
// Validate request against spec
|
|
293
|
-
this.validateApiCall(lastRequest, spec[method]);
|
|
294
|
-
// Print response details
|
|
295
|
-
if (lastResponse) {
|
|
296
|
-
const [data, status] = lastResponse;
|
|
297
|
-
console.log(` Response status: ${status}`);
|
|
298
|
-
if (data) {
|
|
299
|
-
console.log(' Response data:', JSON.stringify(data, null, 2));
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
// Print any error details
|
|
303
|
-
if (lastError) {
|
|
304
|
-
console.log(' Error details:');
|
|
305
|
-
if (lastError.response) {
|
|
306
|
-
console.log(' Status:', lastError.response.status);
|
|
307
|
-
console.log(' Data:', JSON.stringify(lastError.response.data, null, 2));
|
|
308
|
-
}
|
|
309
|
-
else {
|
|
310
|
-
console.log(' Message:', lastError.message);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
// Log result
|
|
315
|
-
console.log(` ✓ Method executed (returned: ${JSON.stringify(result)})`);
|
|
316
|
-
}
|
|
317
|
-
catch (error) {
|
|
318
|
-
console.log(' ✗ Method execution failed:');
|
|
319
|
-
if (error instanceof Error) {
|
|
320
|
-
console.log(` Type: ${error.constructor.name}`);
|
|
321
|
-
console.log(` Message: ${error.message}`);
|
|
322
|
-
if (this.verbose) {
|
|
323
|
-
console.log(' Stack:', error.stack);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
else {
|
|
327
|
-
console.log(` Error: ${error}`);
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
else {
|
|
332
|
-
console.log(' ✗ Method not implemented');
|
|
333
|
-
console.log(' Implementation required based on YAML spec:');
|
|
334
|
-
this.generateMethodStub(method, spec[method]);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Generate test parameters for a method
|
|
340
|
-
*/
|
|
341
|
-
generateTestParameters(device, method, spec) {
|
|
342
|
-
const params = [];
|
|
343
|
-
// Extract parameters from the YAML spec's json_object
|
|
344
|
-
if (spec.json_object?.payload?.data) {
|
|
345
|
-
const data = spec.json_object.payload.data;
|
|
346
|
-
switch (method) {
|
|
347
|
-
case 'setColorTemp':
|
|
348
|
-
if ('colorTempe' in data)
|
|
349
|
-
params.push(data.colorTempe);
|
|
350
|
-
break;
|
|
351
|
-
case 'setBrightness':
|
|
352
|
-
if ('brightness' in data)
|
|
353
|
-
params.push(data.brightness);
|
|
354
|
-
break;
|
|
355
|
-
case 'changeFanSpeed':
|
|
356
|
-
if ('level' in data)
|
|
357
|
-
params.push(data.level);
|
|
358
|
-
break;
|
|
359
|
-
case 'setMistLevel':
|
|
360
|
-
if ('level' in data && data.type === 'mist')
|
|
361
|
-
params.push(data.level);
|
|
362
|
-
break;
|
|
363
|
-
case 'setWarmLevel':
|
|
364
|
-
if ('level' in data && data.type === 'warm')
|
|
365
|
-
params.push(data.level);
|
|
366
|
-
break;
|
|
367
|
-
case 'setTimer':
|
|
368
|
-
if ('total' in data)
|
|
369
|
-
params.push(data.total);
|
|
370
|
-
break;
|
|
371
|
-
case 'setTargetHumidity':
|
|
372
|
-
if ('humidity' in data)
|
|
373
|
-
params.push(data.humidity);
|
|
374
|
-
break;
|
|
375
|
-
case 'setDryingModeEnabled':
|
|
376
|
-
if ('autoDryingSwitch' in data)
|
|
377
|
-
params.push(data.autoDryingSwitch === 1);
|
|
378
|
-
break;
|
|
379
|
-
default:
|
|
380
|
-
// For methods without parameters or unknown methods
|
|
381
|
-
break;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
// If no parameters found in spec, use defaults
|
|
385
|
-
if (params.length === 0) {
|
|
386
|
-
switch (method) {
|
|
387
|
-
case 'setColorTemp':
|
|
388
|
-
params.push(2700);
|
|
389
|
-
break;
|
|
390
|
-
case 'setBrightness':
|
|
391
|
-
params.push(50);
|
|
392
|
-
break;
|
|
393
|
-
case 'changeFanSpeed':
|
|
394
|
-
case 'setMistLevel':
|
|
395
|
-
case 'setWarmLevel':
|
|
396
|
-
params.push(1);
|
|
397
|
-
break;
|
|
398
|
-
case 'setTimer':
|
|
399
|
-
params.push(30);
|
|
400
|
-
break;
|
|
401
|
-
case 'setTargetHumidity':
|
|
402
|
-
params.push(50);
|
|
403
|
-
break;
|
|
404
|
-
case 'setDryingModeEnabled':
|
|
405
|
-
params.push(true);
|
|
406
|
-
break;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
return params;
|
|
410
|
-
}
|
|
411
|
-
/**
|
|
412
|
-
* Validate an API call against its spec
|
|
413
|
-
*/
|
|
414
|
-
validateApiCall(request, spec) {
|
|
415
|
-
if (!request)
|
|
416
|
-
return;
|
|
417
|
-
let hasError = false;
|
|
418
|
-
// Check URL
|
|
419
|
-
if (request.url !== spec.url) {
|
|
420
|
-
console.log(` ⚠ Warning: URL mismatch`);
|
|
421
|
-
console.log(` Expected: ${spec.url}`);
|
|
422
|
-
console.log(` Actual: ${request.url}`);
|
|
423
|
-
hasError = true;
|
|
424
|
-
}
|
|
425
|
-
// Check method
|
|
426
|
-
if (request.method.toLowerCase() !== spec.method.toLowerCase()) {
|
|
427
|
-
console.log(` ⚠ Warning: HTTP method mismatch`);
|
|
428
|
-
console.log(` Expected: ${spec.method}`);
|
|
429
|
-
console.log(` Actual: ${request.method}`);
|
|
430
|
-
hasError = true;
|
|
431
|
-
}
|
|
432
|
-
// Check required headers
|
|
433
|
-
for (const [key, value] of Object.entries(spec.headers || {})) {
|
|
434
|
-
if (!request.headers[key]) {
|
|
435
|
-
console.log(` ⚠ Warning: Missing required header: ${key}`);
|
|
436
|
-
hasError = true;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
// Check required body fields
|
|
440
|
-
if (spec.body) {
|
|
441
|
-
for (const [key, value] of Object.entries(spec.body)) {
|
|
442
|
-
if (!(key in request.body)) {
|
|
443
|
-
console.log(` ⚠ Warning: Missing required body field: ${key}`);
|
|
444
|
-
hasError = true;
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
if (!hasError) {
|
|
449
|
-
console.log(' ✓ API call matches specification');
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
/**
|
|
453
|
-
* Generate method stub from YAML spec
|
|
454
|
-
*/
|
|
455
|
-
generateMethodStub(method, spec) {
|
|
456
|
-
const camelMethod = this.snakeToCamel(method);
|
|
457
|
-
console.log(` async ${camelMethod}(): Promise<boolean> {`);
|
|
458
|
-
console.log(' // TODO: Implement according to YAML spec');
|
|
459
|
-
console.log(` // URL: ${spec.url}`);
|
|
460
|
-
console.log(` // Method: ${spec.method.toUpperCase()}`);
|
|
461
|
-
console.log(' // Required headers:', JSON.stringify(spec.headers, null, 2));
|
|
462
|
-
console.log(' // Required body:', JSON.stringify(spec.body, null, 2));
|
|
463
|
-
console.log(' }');
|
|
464
|
-
}
|
|
465
|
-
/**
|
|
466
|
-
* Display test summary
|
|
467
|
-
*/
|
|
468
|
-
displaySummary() {
|
|
469
|
-
console.log('\n=== Test Summary ===\n');
|
|
470
|
-
// Group devices by category
|
|
471
|
-
const devicesByCategory = new Map();
|
|
472
|
-
for (const result of this.testResults.values()) {
|
|
473
|
-
if (!devicesByCategory.has(result.category)) {
|
|
474
|
-
devicesByCategory.set(result.category, []);
|
|
475
|
-
}
|
|
476
|
-
devicesByCategory.get(result.category).push(result);
|
|
477
|
-
}
|
|
478
|
-
// Display results by category
|
|
479
|
-
for (const [category, devices] of devicesByCategory) {
|
|
480
|
-
console.log(`${category}:`);
|
|
481
|
-
for (const device of devices) {
|
|
482
|
-
console.log(` ${device.deviceType}:`);
|
|
483
|
-
// Group errors by type
|
|
484
|
-
const apiErrors = device.logs.filter(log => log.includes('Invalid response code'));
|
|
485
|
-
const otherErrors = device.logs.filter(log => !log.includes('Invalid response code'));
|
|
486
|
-
if (apiErrors.length > 0) {
|
|
487
|
-
console.log(' API Errors:');
|
|
488
|
-
apiErrors.forEach(error => {
|
|
489
|
-
const [method, code] = error.match(/\.(\w+): Invalid response code: (\d+)/)?.slice(1) || [];
|
|
490
|
-
console.log(` ${method}: Response code ${code}`);
|
|
491
|
-
});
|
|
492
|
-
}
|
|
493
|
-
if (otherErrors.length > 0) {
|
|
494
|
-
console.log(' Other Errors:');
|
|
495
|
-
otherErrors.forEach(error => console.log(` ${error}`));
|
|
496
|
-
}
|
|
497
|
-
if (device.missingMethods.length > 0) {
|
|
498
|
-
console.log(` Missing Methods: ${device.missingMethods.join(', ')}`);
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
console.log();
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
/**
|
|
505
|
-
* Run all tests
|
|
506
|
-
*/
|
|
507
|
-
async run() {
|
|
508
|
-
(0, helpers_1.setApiBaseUrl)('http://localhost:8000');
|
|
509
|
-
console.log('Using credentials:', {
|
|
510
|
-
username: this.manager.username,
|
|
511
|
-
password: this.manager.password
|
|
512
|
-
});
|
|
513
|
-
console.log('\nTesting login...');
|
|
514
|
-
if (!await this.manager.login()) {
|
|
515
|
-
console.error('Login failed');
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
console.log('\nTesting device discovery...');
|
|
519
|
-
await this.manager.getDevices();
|
|
520
|
-
// Test devices if any are found
|
|
521
|
-
if (this.manager.devices && this.manager.devices.length > 0) {
|
|
522
|
-
for (const device of this.manager.devices) {
|
|
523
|
-
await this.testDeviceMethods(device);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
else {
|
|
527
|
-
console.log('No devices found. Tests will be skipped.');
|
|
528
|
-
}
|
|
529
|
-
// Display test summary
|
|
530
|
-
this.displaySummary();
|
|
531
|
-
console.log('\n✓ All tests completed');
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
exports.VeSyncTestService = VeSyncTestService;
|
|
535
|
-
// Run tests if this file is executed directly
|
|
536
|
-
if (require.main === module) {
|
|
537
|
-
const verbose = process.argv.includes('--verbose') || process.argv.includes('-v');
|
|
538
|
-
const testService = new VeSyncTestService(verbose);
|
|
539
|
-
testService.run();
|
|
540
|
-
}
|