tsvesync 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +227 -0
  3. package/api/vesync/VeSync.yaml +33 -0
  4. package/api/vesyncbulb/ESL100.yaml +74 -0
  5. package/api/vesyncbulb/ESL100CW.yaml +123 -0
  6. package/api/vesyncbulb/ESL100MC.yaml +169 -0
  7. package/api/vesyncbulb/XYD0001.yaml +193 -0
  8. package/api/vesyncfan/Classic200S.yaml +280 -0
  9. package/api/vesyncfan/Classic300S.yaml +278 -0
  10. package/api/vesyncfan/Core200S.yaml +256 -0
  11. package/api/vesyncfan/Core300S.yaml +281 -0
  12. package/api/vesyncfan/Core400S.yaml +281 -0
  13. package/api/vesyncfan/Core600S.yaml +281 -0
  14. package/api/vesyncfan/Dual200S.yaml +278 -0
  15. package/api/vesyncfan/LAP-C201S-AUSR.yaml +256 -0
  16. package/api/vesyncfan/LAP-C202S-WUSR.yaml +256 -0
  17. package/api/vesyncfan/LAP-C301S-WAAA.yaml +281 -0
  18. package/api/vesyncfan/LAP-C301S-WJP.yaml +281 -0
  19. package/api/vesyncfan/LAP-C302S-WUSB.yaml +281 -0
  20. package/api/vesyncfan/LAP-C401S-WAAA.yaml +281 -0
  21. package/api/vesyncfan/LAP-C401S-WJP.yaml +281 -0
  22. package/api/vesyncfan/LAP-C401S-WUSR.yaml +281 -0
  23. package/api/vesyncfan/LAP-C601S-WEU.yaml +281 -0
  24. package/api/vesyncfan/LAP-C601S-WUS.yaml +281 -0
  25. package/api/vesyncfan/LAP-C601S-WUSR.yaml +281 -0
  26. package/api/vesyncfan/LAP-EL551S-AEUR.yaml +167 -0
  27. package/api/vesyncfan/LAP-EL551S-AUS.yaml +167 -0
  28. package/api/vesyncfan/LAP-EL551S-WEU.yaml +167 -0
  29. package/api/vesyncfan/LAP-EL551S-WUS.yaml +167 -0
  30. package/api/vesyncfan/LAP-V102S-AASR.yaml +167 -0
  31. package/api/vesyncfan/LAP-V102S-AUSR.yaml +167 -0
  32. package/api/vesyncfan/LAP-V102S-WEU.yaml +167 -0
  33. package/api/vesyncfan/LAP-V102S-WJP.yaml +167 -0
  34. package/api/vesyncfan/LAP-V102S-WUS.yaml +167 -0
  35. package/api/vesyncfan/LAP-V201-AUSR.yaml +167 -0
  36. package/api/vesyncfan/LAP-V201S-AASR.yaml +167 -0
  37. package/api/vesyncfan/LAP-V201S-AEUR.yaml +167 -0
  38. package/api/vesyncfan/LAP-V201S-AUSR.yaml +167 -0
  39. package/api/vesyncfan/LAP-V201S-WEU.yaml +167 -0
  40. package/api/vesyncfan/LAP-V201S-WJP.yaml +167 -0
  41. package/api/vesyncfan/LAP-V201S-WUS.yaml +167 -0
  42. package/api/vesyncfan/LEH-S601S-WUS.yaml +303 -0
  43. package/api/vesyncfan/LEH-S601S-WUSR.yaml +82 -0
  44. package/api/vesyncfan/LTF-F422S-KEU.yaml +165 -0
  45. package/api/vesyncfan/LTF-F422S-WUS.yaml +165 -0
  46. package/api/vesyncfan/LTF-F422S-WUSR.yaml +167 -0
  47. package/api/vesyncfan/LTF-F422_WJP.yaml +165 -0
  48. package/api/vesyncfan/LUH-A601S-AUSW.yaml +82 -0
  49. package/api/vesyncfan/LUH-A601S-WUSB.yaml +305 -0
  50. package/api/vesyncfan/LUH-A602S-WEU.yaml +305 -0
  51. package/api/vesyncfan/LUH-A602S-WEUR.yaml +305 -0
  52. package/api/vesyncfan/LUH-A602S-WJP.yaml +305 -0
  53. package/api/vesyncfan/LUH-A602S-WUS.yaml +305 -0
  54. package/api/vesyncfan/LUH-A602S-WUSC.yaml +305 -0
  55. package/api/vesyncfan/LUH-A602S-WUSR.yaml +305 -0
  56. package/api/vesyncfan/LUH-D301S-WEU.yaml +305 -0
  57. package/api/vesyncfan/LUH-D301S-WJP.yaml +305 -0
  58. package/api/vesyncfan/LUH-D301S-WUSR.yaml +305 -0
  59. package/api/vesyncfan/LUH-M101S-WUS.yaml +278 -0
  60. package/api/vesyncfan/LUH-O451S-WEU.yaml +278 -0
  61. package/api/vesyncfan/LUH-O451S-WUS.yaml +278 -0
  62. package/api/vesyncfan/LUH-O451S-WUSR.yaml +305 -0
  63. package/api/vesyncfan/LUH-O601S-KUS.yaml +305 -0
  64. package/api/vesyncfan/LUH-O601S-WUS.yaml +305 -0
  65. package/api/vesyncfan/LV-PUR131S.yaml +108 -0
  66. package/api/vesyncfan/LV-RH131S.yaml +108 -0
  67. package/api/vesyncoutlet/ESO15-TB.yaml +124 -0
  68. package/api/vesyncoutlet/ESW01-EU.yaml +122 -0
  69. package/api/vesyncoutlet/ESW03-USA.yaml +122 -0
  70. package/api/vesyncoutlet/ESW15-USA.yaml +156 -0
  71. package/api/vesyncoutlet/wifi-switch-1.3.yaml +60 -0
  72. package/api/vesyncswitch/ESWD16.yaml +128 -0
  73. package/api/vesyncswitch/ESWL01.yaml +56 -0
  74. package/api/vesyncswitch/ESWL03.yaml +56 -0
  75. package/dist/lib/helpers.d.ts +87 -0
  76. package/dist/lib/helpers.js +222 -0
  77. package/dist/lib/helpers.js.map +1 -0
  78. package/dist/lib/vesync.d.ts +47 -0
  79. package/dist/lib/vesync.js +309 -0
  80. package/dist/lib/vesync.js.map +1 -0
  81. package/dist/lib/vesyncBaseDevice.d.ts +68 -0
  82. package/dist/lib/vesyncBaseDevice.js +116 -0
  83. package/dist/lib/vesyncBaseDevice.js.map +1 -0
  84. package/dist/lib/vesyncBulb.d.ts +0 -0
  85. package/dist/lib/vesyncBulb.js +2 -0
  86. package/dist/lib/vesyncBulb.js.map +1 -0
  87. package/dist/lib/vesyncFan.d.ts +92 -0
  88. package/dist/lib/vesyncFan.js +265 -0
  89. package/dist/lib/vesyncFan.js.map +1 -0
  90. package/dist/lib/vesyncKitchen.d.ts +0 -0
  91. package/dist/lib/vesyncKitchen.js +2 -0
  92. package/dist/lib/vesyncKitchen.js.map +1 -0
  93. package/dist/lib/vesyncOutlet.d.ts +0 -0
  94. package/dist/lib/vesyncOutlet.js +2 -0
  95. package/dist/lib/vesyncOutlet.js.map +1 -0
  96. package/dist/lib/vesyncSwitch.d.ts +0 -0
  97. package/dist/lib/vesyncSwitch.js +2 -0
  98. package/dist/lib/vesyncSwitch.js.map +1 -0
  99. package/dist/src/lib/helpers.d.ts +88 -0
  100. package/dist/src/lib/helpers.js +244 -0
  101. package/dist/src/lib/vesync.d.ts +89 -0
  102. package/dist/src/lib/vesync.js +430 -0
  103. package/dist/src/lib/vesyncBaseDevice.d.ts +43 -0
  104. package/dist/src/lib/vesyncBaseDevice.js +87 -0
  105. package/dist/src/lib/vesyncBulb.d.ts +79 -0
  106. package/dist/src/lib/vesyncBulb.js +198 -0
  107. package/dist/src/lib/vesyncBulbImpl.d.ts +38 -0
  108. package/dist/src/lib/vesyncBulbImpl.js +161 -0
  109. package/dist/src/lib/vesyncFan.d.ts +80 -0
  110. package/dist/src/lib/vesyncFan.js +406 -0
  111. package/dist/src/lib/vesyncFanImpl.d.ts +137 -0
  112. package/dist/src/lib/vesyncFanImpl.js +643 -0
  113. package/dist/src/lib/vesyncOutlet.d.ts +53 -0
  114. package/dist/src/lib/vesyncOutlet.js +98 -0
  115. package/dist/src/lib/vesyncOutletImpl.d.ts +70 -0
  116. package/dist/src/lib/vesyncOutletImpl.js +206 -0
  117. package/dist/src/lib/vesyncSwitch.d.ts +169 -0
  118. package/dist/src/lib/vesyncSwitch.js +414 -0
  119. package/dist/src/lib/vesyncSwitchImpl.d.ts +82 -0
  120. package/dist/src/lib/vesyncSwitchImpl.js +290 -0
  121. package/dist/test/testFeatures.d.ts +4 -0
  122. package/dist/test/testFeatures.js +394 -0
  123. package/dist/test/testService.d.ts +62 -0
  124. package/dist/test/testService.js +540 -0
  125. package/dist/test/testSpecial.d.ts +5 -0
  126. package/dist/test/testSpecial.js +227 -0
  127. package/package.json +53 -0
  128. package/src/lib/helpers.ts +290 -0
  129. package/src/lib/vesync.ts +530 -0
  130. package/src/lib/vesyncBaseDevice.ts +114 -0
  131. package/src/lib/vesyncBulb.ts +252 -0
  132. package/src/lib/vesyncBulbImpl.ts +215 -0
  133. package/src/lib/vesyncFan.ts +447 -0
  134. package/src/lib/vesyncFanImpl.ts +848 -0
  135. package/src/lib/vesyncOutlet.ts +148 -0
  136. package/src/lib/vesyncOutletImpl.ts +284 -0
  137. package/src/lib/vesyncSwitch.ts +546 -0
  138. package/src/lib/vesyncSwitchImpl.ts +408 -0
@@ -0,0 +1,62 @@
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
+ }
@@ -0,0 +1,540 @@
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
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Special test for VeSync API
3
+ * Uses credentials from .env file in test directory
4
+ */
5
+ export {};