@iobroker/testing 5.1.1 → 5.2.1
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.
|
@@ -94,11 +94,28 @@ async function locateAdapterMainFile(adapterDir) {
|
|
|
94
94
|
debug(` => found ${mainFile}`);
|
|
95
95
|
return ret;
|
|
96
96
|
}
|
|
97
|
+
// If the specified file doesn't exist and ends with .js, try the .ts equivalent
|
|
98
|
+
if (mainFile.endsWith('.js')) {
|
|
99
|
+
const tsFile = mainFile.replace(/\.js$/, '.ts');
|
|
100
|
+
ret = path.join(adapterDir, tsFile);
|
|
101
|
+
debug(` => trying ${ret}`);
|
|
102
|
+
if (await (0, fs_extra_1.pathExists)(ret)) {
|
|
103
|
+
debug(` => found ${tsFile}`);
|
|
104
|
+
return ret;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
97
107
|
// If both don't exist, JS-Controller uses <adapter name>.js as another fallback
|
|
98
108
|
ret = path.join(adapterDir, `${ioPackage.common.name}.js`);
|
|
99
109
|
debug(` => trying ${ret}`);
|
|
100
110
|
if (await (0, fs_extra_1.pathExists)(ret)) {
|
|
101
|
-
debug(` => found ${
|
|
111
|
+
debug(` => found ${ioPackage.common.name}.js`);
|
|
112
|
+
return ret;
|
|
113
|
+
}
|
|
114
|
+
// Also try <adapter name>.ts as a fallback
|
|
115
|
+
ret = path.join(adapterDir, `${ioPackage.common.name}.ts`);
|
|
116
|
+
debug(` => trying ${ret}`);
|
|
117
|
+
if (await (0, fs_extra_1.pathExists)(ret)) {
|
|
118
|
+
debug(` => found ${ioPackage.common.name}.ts`);
|
|
102
119
|
return ret;
|
|
103
120
|
}
|
|
104
121
|
throw new Error(`The adapter main file was not found in ${adapterDir}`);
|
|
@@ -75,9 +75,13 @@ class AdapterSetup {
|
|
|
75
75
|
// Therefore pack it into a tarball and put it in the test dir for installation
|
|
76
76
|
const packResult = await (0, executeCommand_1.executeCommand)('npm', ['pack', '--loglevel', 'silent'], {
|
|
77
77
|
stdout: 'pipe',
|
|
78
|
+
stderr: 'pipe',
|
|
78
79
|
});
|
|
79
80
|
if (packResult.exitCode !== 0 || typeof packResult.stdout !== 'string') {
|
|
80
|
-
|
|
81
|
+
const errorMessage = packResult.stderr
|
|
82
|
+
? `Packing the adapter tarball failed!\nstderr: ${packResult.stderr}`
|
|
83
|
+
: `Packing the adapter tarball failed!`;
|
|
84
|
+
throw new Error(errorMessage);
|
|
81
85
|
}
|
|
82
86
|
// The last non-empty line of `npm pack`s STDOUT contains the tarball path
|
|
83
87
|
const stdoutLines = packResult.stdout.trim().split(/[\r\n]+/);
|
|
@@ -92,6 +96,7 @@ class AdapterSetup {
|
|
|
92
96
|
debug('Removing the adapter from package-lock.json');
|
|
93
97
|
await (0, executeCommand_1.executeCommand)('npm', ['uninstall', this.adapterFullName, '--package-lock-only', '--omit=dev'], {
|
|
94
98
|
cwd: this.testDir,
|
|
99
|
+
stderr: 'pipe',
|
|
95
100
|
});
|
|
96
101
|
// Complete the package.json, so npm can do it's magic
|
|
97
102
|
debug('Saving the adapter in package.json');
|
|
@@ -114,6 +119,7 @@ class AdapterSetup {
|
|
|
114
119
|
// Defer to npm to install the controller (if it wasn't already)
|
|
115
120
|
await (0, executeCommand_1.executeCommand)('npm', ['i', '--omit=dev'], {
|
|
116
121
|
cwd: this.testDir,
|
|
122
|
+
stderr: 'pipe',
|
|
117
123
|
});
|
|
118
124
|
debug(' => done!');
|
|
119
125
|
}
|
|
@@ -126,9 +132,13 @@ class AdapterSetup {
|
|
|
126
132
|
const addResult = await (0, executeCommand_1.executeCommand)('node', [`${this.appName}.js`, 'add', this.adapterName, '--enabled', 'false'], {
|
|
127
133
|
cwd: this.testControllerDir,
|
|
128
134
|
stdout: 'ignore',
|
|
135
|
+
stderr: 'pipe',
|
|
129
136
|
});
|
|
130
137
|
if (addResult.exitCode !== 0) {
|
|
131
|
-
|
|
138
|
+
const errorMessage = addResult.stderr
|
|
139
|
+
? `Adding the adapter instance failed!\nstderr: ${addResult.stderr}`
|
|
140
|
+
: `Adding the adapter instance failed!`;
|
|
141
|
+
throw new Error(errorMessage);
|
|
132
142
|
}
|
|
133
143
|
debug(' => done!');
|
|
134
144
|
}
|
|
@@ -80,6 +80,8 @@ class ControllerSetup {
|
|
|
80
80
|
license: 'ISC',
|
|
81
81
|
dependencies: {
|
|
82
82
|
[`${this.appName}.js-controller`]: controllerVersion,
|
|
83
|
+
// @alcalzone/esbuild-register is needed to run TypeScript adapters
|
|
84
|
+
'@alcalzone/esbuild-register': '^2.5.1-1',
|
|
83
85
|
},
|
|
84
86
|
description: '',
|
|
85
87
|
};
|
|
@@ -102,6 +104,7 @@ class ControllerSetup {
|
|
|
102
104
|
debug('(Re-)installing JS Controller...');
|
|
103
105
|
await (0, executeCommand_1.executeCommand)('npm', ['i', '--omit=dev'], {
|
|
104
106
|
cwd: this.testDir,
|
|
107
|
+
stderr: 'pipe',
|
|
105
108
|
});
|
|
106
109
|
// Prepare/clean the databases and config
|
|
107
110
|
if (wasJsControllerInstalled) {
|
|
@@ -182,13 +185,18 @@ class ControllerSetup {
|
|
|
182
185
|
await (0, executeCommand_1.executeCommand)('node', [`${this.appName}.js`, 'stop'], {
|
|
183
186
|
cwd: this.testControllerDir,
|
|
184
187
|
stdout: 'ignore',
|
|
188
|
+
stderr: 'pipe',
|
|
185
189
|
});
|
|
186
190
|
const setupResult = await (0, executeCommand_1.executeCommand)('node', [`${this.appName}.js`, 'setup', 'first', '--console'], {
|
|
187
191
|
cwd: this.testControllerDir,
|
|
188
192
|
stdout: 'ignore',
|
|
193
|
+
stderr: 'pipe',
|
|
189
194
|
});
|
|
190
195
|
if (setupResult.exitCode !== 0) {
|
|
191
|
-
|
|
196
|
+
const errorMessage = setupResult.stderr
|
|
197
|
+
? `${this.appName} setup first failed!\nstderr: ${setupResult.stderr}`
|
|
198
|
+
: `${this.appName} setup first failed!`;
|
|
199
|
+
throw new Error(errorMessage);
|
|
192
200
|
}
|
|
193
201
|
debug(' => done!');
|
|
194
202
|
}
|
|
@@ -164,7 +164,13 @@ class TestHarness extends node_events_1.EventEmitter {
|
|
|
164
164
|
this._adapterExit = code != undefined ? code : signal;
|
|
165
165
|
this.emit('failed', this._adapterExit);
|
|
166
166
|
};
|
|
167
|
-
|
|
167
|
+
// Determine if we need to use esbuild-register for TypeScript files
|
|
168
|
+
const isTypeScript = mainFileAbsolute.endsWith('.ts');
|
|
169
|
+
const command = isWindows ? 'node.exe' : 'node';
|
|
170
|
+
const args = isTypeScript
|
|
171
|
+
? ['-r', '@alcalzone/esbuild-register', mainFileRelative, '--console']
|
|
172
|
+
: [mainFileRelative, '--console'];
|
|
173
|
+
this._adapterProcess = (0, node_child_process_1.spawn)(command, args, {
|
|
168
174
|
cwd: this.testAdapterDir,
|
|
169
175
|
stdio: ['inherit', 'inherit', 'inherit'],
|
|
170
176
|
env: { ...process.env, ...env },
|
|
@@ -32,11 +32,15 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.validatePackageFiles = validatePackageFiles;
|
|
37
40
|
const typeguards_1 = require("alcalzone-shared/typeguards");
|
|
38
41
|
const chai_1 = require("chai");
|
|
39
42
|
const fs = __importStar(require("fs"));
|
|
43
|
+
const json5_1 = __importDefault(require("json5"));
|
|
40
44
|
const path = __importStar(require("path"));
|
|
41
45
|
/**
|
|
42
46
|
* Tests if the adapter files are valid.
|
|
@@ -72,6 +76,26 @@ function validatePackageFiles(adapterDir) {
|
|
|
72
76
|
}
|
|
73
77
|
});
|
|
74
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Recursively find all files matching a pattern in a directory
|
|
81
|
+
*/
|
|
82
|
+
function findFiles(dir, pattern, results = []) {
|
|
83
|
+
if (!fs.existsSync(dir)) {
|
|
84
|
+
return results;
|
|
85
|
+
}
|
|
86
|
+
const files = fs.readdirSync(dir);
|
|
87
|
+
for (const file of files) {
|
|
88
|
+
const filePath = path.join(dir, file);
|
|
89
|
+
const stat = fs.statSync(filePath);
|
|
90
|
+
if (stat.isDirectory()) {
|
|
91
|
+
findFiles(filePath, pattern, results);
|
|
92
|
+
}
|
|
93
|
+
else if (pattern.test(file)) {
|
|
94
|
+
results.push(filePath);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return results;
|
|
98
|
+
}
|
|
75
99
|
describe(`Validate the package files`, () => {
|
|
76
100
|
describe(`Ensure they are readable`, () => {
|
|
77
101
|
for (const filename of ['package.json', 'io-package.json']) {
|
|
@@ -142,6 +166,19 @@ function validatePackageFiles(adapterDir) {
|
|
|
142
166
|
}
|
|
143
167
|
}
|
|
144
168
|
});
|
|
169
|
+
it('iobroker.js-controller is not listed as a dependency', () => {
|
|
170
|
+
for (const depType of [
|
|
171
|
+
'dependencies',
|
|
172
|
+
'devDependencies',
|
|
173
|
+
'optionalDependencies',
|
|
174
|
+
'peerDependencies',
|
|
175
|
+
]) {
|
|
176
|
+
if ((0, typeguards_1.isObject)(packageContent[depType]) && 'iobroker.js-controller' in packageContent[depType]) {
|
|
177
|
+
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
178
|
+
throw new chai_1.AssertionError(`iobroker.js-controller must not be listed in ${depType}, found "${packageContent[depType]['iobroker.js-controller']}"!`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
});
|
|
145
182
|
});
|
|
146
183
|
describe(`Check contents of io-package.json`, () => {
|
|
147
184
|
beforeEach(function () {
|
|
@@ -253,22 +290,63 @@ function validatePackageFiles(adapterDir) {
|
|
|
253
290
|
}
|
|
254
291
|
});
|
|
255
292
|
});
|
|
293
|
+
describe(`Validate JSON files`, () => {
|
|
294
|
+
// Find all JSON and JSON5 files in admin/ directory (recursively)
|
|
295
|
+
const adminDir = path.join(adapterDir, 'admin');
|
|
296
|
+
const allAdminJsonFiles = findFiles(adminDir, /\.json$/);
|
|
297
|
+
const allAdminJson5Files = findFiles(adminDir, /\.json5$/);
|
|
298
|
+
// Split JSON files into admin/*.json and admin/i18n/**/*.json
|
|
299
|
+
const adminDirectJsonFiles = allAdminJsonFiles.filter(file => !file.includes(`${path.sep}i18n${path.sep}`));
|
|
300
|
+
const i18nJsonFiles = allAdminJsonFiles.filter(file => file.includes(`${path.sep}i18n${path.sep}`));
|
|
301
|
+
if (adminDirectJsonFiles.length > 0) {
|
|
302
|
+
describe(`admin/*.json files`, () => {
|
|
303
|
+
for (const filePath of adminDirectJsonFiles) {
|
|
304
|
+
const relativePath = path.relative(adapterDir, filePath);
|
|
305
|
+
it(`${relativePath} contains valid JSON`, () => {
|
|
306
|
+
(0, chai_1.expect)(() => {
|
|
307
|
+
JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
308
|
+
}, `${relativePath} contains invalid JSON!`).not.to.throw();
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
if (allAdminJson5Files.length > 0) {
|
|
314
|
+
describe(`admin/*.json5 files`, () => {
|
|
315
|
+
for (const filePath of allAdminJson5Files) {
|
|
316
|
+
const relativePath = path.relative(adapterDir, filePath);
|
|
317
|
+
it(`${relativePath} contains valid JSON5`, () => {
|
|
318
|
+
(0, chai_1.expect)(() => {
|
|
319
|
+
json5_1.default.parse(fs.readFileSync(filePath, 'utf8'));
|
|
320
|
+
}, `${relativePath} contains invalid JSON5!`).not.to.throw();
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
if (i18nJsonFiles.length > 0) {
|
|
326
|
+
describe(`admin/i18n/**/*.json files`, () => {
|
|
327
|
+
for (const filePath of i18nJsonFiles) {
|
|
328
|
+
const relativePath = path.relative(adapterDir, filePath);
|
|
329
|
+
it(`${relativePath} contains valid JSON`, () => {
|
|
330
|
+
(0, chai_1.expect)(() => {
|
|
331
|
+
JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
332
|
+
}, `${relativePath} contains invalid JSON!`).not.to.throw();
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
});
|
|
339
|
+
describe(`Check additional files`, () => {
|
|
340
|
+
it('README.md exists', () => {
|
|
341
|
+
(0, chai_1.expect)(fs.existsSync(path.join(adapterDir, 'README.md')), `README.md is missing in the adapter dir. Please create it!`).to.be.true;
|
|
342
|
+
});
|
|
343
|
+
it('LICENSE exists or is present in the README.md', () => {
|
|
344
|
+
const licenseExists = fs.existsSync(path.join(adapterDir, 'LICENSE'));
|
|
345
|
+
if (licenseExists) {
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
const readmeContent = fs.readFileSync(path.join(adapterDir, 'README.md'), 'utf8');
|
|
349
|
+
(0, chai_1.expect)(readmeContent).to.match(/## LICENSE/i, `The license should be in a file "LICENSE" or be included in "README.md" as a 2nd level headline!`);
|
|
350
|
+
});
|
|
256
351
|
});
|
|
257
|
-
// describe(`Check additional files`, () => {
|
|
258
|
-
// it("README.md exists", () => {
|
|
259
|
-
// expect(
|
|
260
|
-
// fs.existsSync(path.join(adapterDir, "README.md")),
|
|
261
|
-
// `README.md is missing in the adapter dir. Please create it!`,
|
|
262
|
-
// ).to.be.true;
|
|
263
|
-
// });
|
|
264
|
-
// it("LICENSE exists or is present in the README.md", () => {
|
|
265
|
-
// const licenseExists = fs.existsSync(path.join(adapterDir, "LICENSE"));
|
|
266
|
-
// if (licenseExists) return;
|
|
267
|
-
// const readmeContent = fs.readFileSync(path.join(adapterDir, "README.md"), "utf8");
|
|
268
|
-
// expect(readmeContent).to.match(
|
|
269
|
-
// /## LICENSE/i,
|
|
270
|
-
// `The license should be in a file "LICENSE" or be included in "README.md" as a 2nd level headline!`,
|
|
271
|
-
// );
|
|
272
|
-
// });
|
|
273
|
-
// });
|
|
274
352
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iobroker/testing",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.1",
|
|
4
4
|
"description": "Shared utilities for adapter and module testing in ioBroker",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"build/"
|
|
9
9
|
],
|
|
10
10
|
"scripts": {
|
|
11
|
-
"test": "mocha src/**/*.test.ts",
|
|
12
|
-
"test:watch": "mocha src/**/*.test.ts --watch",
|
|
11
|
+
"test": "mocha \"src/**/*.test.ts\"",
|
|
12
|
+
"test:watch": "mocha \"src/**/*.test.ts\" --watch",
|
|
13
13
|
"prebuild": "rimraf ./build",
|
|
14
14
|
"build": "tsc -p tsconfig.build.json",
|
|
15
15
|
"prewatch": "rimraf ./build",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
24
24
|
"type": "git",
|
|
25
|
-
"url": "
|
|
25
|
+
"url": "https://github.com/ioBroker/testing"
|
|
26
26
|
},
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"access": "public"
|
|
@@ -39,21 +39,22 @@
|
|
|
39
39
|
},
|
|
40
40
|
"homepage": "https://github.com/AlCalzone/testing#readme",
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@alcalzone/release-script": "^
|
|
43
|
-
"@alcalzone/release-script-plugin-license": "^
|
|
42
|
+
"@alcalzone/release-script": "^5.0.0",
|
|
43
|
+
"@alcalzone/release-script-plugin-license": "^4.0.0",
|
|
44
44
|
"@iobroker/adapter-core": "^3.3.2",
|
|
45
|
-
"@iobroker/eslint-config": "^2.
|
|
45
|
+
"@iobroker/eslint-config": "^2.2.0",
|
|
46
46
|
"@iobroker/types": "^7.0.7",
|
|
47
|
-
"@tsconfig/node16": "^16.1.
|
|
47
|
+
"@tsconfig/node16": "^16.1.6",
|
|
48
48
|
"@types/debug": "^4.1.12",
|
|
49
49
|
"@types/fs-extra": "^11.0.4",
|
|
50
|
-
"@types/node": "^24.
|
|
51
|
-
"rimraf": "^6.0
|
|
50
|
+
"@types/node": "^24.6.1",
|
|
51
|
+
"rimraf": "^6.1.0",
|
|
52
52
|
"source-map-support": "^0.5.21",
|
|
53
53
|
"ts-node": "^10.9.2",
|
|
54
|
-
"typescript": "~5.9.
|
|
54
|
+
"typescript": "~5.9.3"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
+
"@alcalzone/esbuild-register": "^2.5.1-1",
|
|
57
58
|
"@types/chai": "^4.3.20",
|
|
58
59
|
"@types/chai-as-promised": "^7.1.8",
|
|
59
60
|
"@types/mocha": "^10.0.10",
|
|
@@ -62,9 +63,10 @@
|
|
|
62
63
|
"alcalzone-shared": "~5.0.0",
|
|
63
64
|
"chai": "^4.5.0",
|
|
64
65
|
"chai-as-promised": "^7.1.2",
|
|
65
|
-
"debug": "^4.4.
|
|
66
|
-
"fs-extra": "^11.3.
|
|
67
|
-
"
|
|
66
|
+
"debug": "^4.4.3",
|
|
67
|
+
"fs-extra": "^11.3.2",
|
|
68
|
+
"json5": "^2.2.3",
|
|
69
|
+
"mocha": "^11.7.3",
|
|
68
70
|
"sinon": "^21.0.0",
|
|
69
71
|
"sinon-chai": "^3.7.0"
|
|
70
72
|
}
|