@iobroker/testing 6.1.0 → 6.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.
package/README.md
CHANGED
|
@@ -144,8 +144,8 @@ Lines that are not in the ioBroker log format - e.g. output of a plain `console.
|
|
|
144
144
|
If you defined your own tests, they should still work.
|
|
145
145
|
|
|
146
146
|
```ts
|
|
147
|
-
const path = require(
|
|
148
|
-
const { tests } = require(
|
|
147
|
+
const path = require('node:path');
|
|
148
|
+
const { tests } = require('@iobroker/testing');
|
|
149
149
|
|
|
150
150
|
tests.unit(path.join(__dirname, ".."), {
|
|
151
151
|
// ~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
@@ -154,7 +154,7 @@ tests.unit(path.join(__dirname, ".."), {
|
|
|
154
154
|
// Define your own tests inside defineAdditionalTests.
|
|
155
155
|
// If you need predefined objects etc. here, you need to take care of it yourself
|
|
156
156
|
defineAdditionalTests() {
|
|
157
|
-
it(
|
|
157
|
+
it('works', () => {
|
|
158
158
|
// see below how these could look like
|
|
159
159
|
});
|
|
160
160
|
},
|
|
@@ -2,4 +2,6 @@
|
|
|
2
2
|
* Tests if the adapter files are valid.
|
|
3
3
|
* This is meant to be executed in a mocha context.
|
|
4
4
|
*/
|
|
5
|
-
export declare function validatePackageFiles(adapterDir: string
|
|
5
|
+
export declare function validatePackageFiles(adapterDir: string, options?: {
|
|
6
|
+
ignoreJsonConfigValidation?: boolean;
|
|
7
|
+
}): void;
|
|
@@ -42,11 +42,110 @@ const chai_1 = require("chai");
|
|
|
42
42
|
const fs = __importStar(require("fs"));
|
|
43
43
|
const json5_1 = __importDefault(require("json5"));
|
|
44
44
|
const path = __importStar(require("path"));
|
|
45
|
+
const ajv_1 = require("ajv");
|
|
46
|
+
const axios_1 = __importDefault(require("axios"));
|
|
47
|
+
const jsonValidators = {};
|
|
48
|
+
/** URL to the JSON config schema */
|
|
49
|
+
const JSON_CONFIG_SCHEMA_URL = 'https://raw.githubusercontent.com/ioBroker/json-config/main/schemas/jsonConfig.json';
|
|
50
|
+
/** Timeout for downloading the JSON config schema, so a hanging request cannot block the test run */
|
|
51
|
+
const JSON_CONFIG_SCHEMA_TIMEOUT_MS = 10000;
|
|
52
|
+
/**
|
|
53
|
+
* A JSON tab (`common.adminTab.link`) has the same format as `jsonConfig.json`, with two differences:
|
|
54
|
+
* its root may have a `command` (message that is sent to the instance when the tab is opened),
|
|
55
|
+
* and its root `type` may be omitted, because it defaults to `panel`.
|
|
56
|
+
*
|
|
57
|
+
* @param schema the jsonConfig schema. It will be modified in place
|
|
58
|
+
*/
|
|
59
|
+
function adaptSchemaForTab(schema) {
|
|
60
|
+
// The root of the schema is an "if type === 'tabs' then ... else ..." construction
|
|
61
|
+
const roots = [schema.then, schema.else].filter(root => !!root);
|
|
62
|
+
if (!roots.length) {
|
|
63
|
+
roots.push(schema);
|
|
64
|
+
}
|
|
65
|
+
for (const root of roots) {
|
|
66
|
+
root.properties ||= {};
|
|
67
|
+
root.properties.command = {
|
|
68
|
+
description: 'Message that is sent to the instance as the tab is opened',
|
|
69
|
+
type: 'string',
|
|
70
|
+
};
|
|
71
|
+
if (Array.isArray(root.required)) {
|
|
72
|
+
root.required = root.required.filter((name) => name !== 'type');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Compile the JSON schema for `jsonConfig.json` or for a JSON tab and cache the result,
|
|
78
|
+
* as the schema is quite big and it is used with every opened config page or tab
|
|
79
|
+
*
|
|
80
|
+
* @param type `config` for `admin/jsonConfig.json(5)`, `tab` for the JSON file of an admin tab
|
|
81
|
+
*/
|
|
82
|
+
async function getJsonValidator(type) {
|
|
83
|
+
const subType = type === 'custom' ? 'config' : type;
|
|
84
|
+
if (jsonValidators[subType]) {
|
|
85
|
+
return jsonValidators[subType];
|
|
86
|
+
}
|
|
87
|
+
let schema;
|
|
88
|
+
try {
|
|
89
|
+
console.debug(`retrieving json schema from ${JSON_CONFIG_SCHEMA_URL}`);
|
|
90
|
+
const schemaRes = await axios_1.default.get(JSON_CONFIG_SCHEMA_URL, { timeout: JSON_CONFIG_SCHEMA_TIMEOUT_MS });
|
|
91
|
+
schema = schemaRes.data;
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
console.error(`Could not get jsonConfig schema: ${e.message}`);
|
|
95
|
+
throw new Error(`Could not get jsonConfig schema`);
|
|
96
|
+
}
|
|
97
|
+
if (type === 'tab') {
|
|
98
|
+
adaptSchemaForTab(schema);
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const ajv = new ajv_1.Ajv({
|
|
102
|
+
allErrors: false,
|
|
103
|
+
strict: 'log',
|
|
104
|
+
});
|
|
105
|
+
jsonValidators[subType] = ajv.compile(schema);
|
|
106
|
+
return jsonValidators[subType];
|
|
107
|
+
}
|
|
108
|
+
catch (e) {
|
|
109
|
+
console.debug(`Could not compile jsonConfig schema: ${e.message}`);
|
|
110
|
+
throw new Error(`Could not compile jsonConfig schema`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** Checks that the given path exists and is a file, not a directory */
|
|
114
|
+
function isFile(filePath) {
|
|
115
|
+
return fs.existsSync(filePath) && fs.statSync(filePath).isFile();
|
|
116
|
+
}
|
|
117
|
+
async function validateJsonConfig(adapterDir, type = 'config', tabFile) {
|
|
118
|
+
let config;
|
|
119
|
+
if (type === 'config' && fs.existsSync(path.join(adapterDir, 'admin/jsonConfig.json'))) {
|
|
120
|
+
config = JSON.parse(fs.readFileSync(path.join(adapterDir, 'admin/jsonConfig.json'), 'utf-8'));
|
|
121
|
+
}
|
|
122
|
+
else if (type === 'config' && fs.existsSync(path.join(adapterDir, 'admin/jsonConfig.json5'))) {
|
|
123
|
+
config = json5_1.default.parse(fs.readFileSync(path.join(adapterDir, 'admin/jsonConfig.json5'), 'utf-8'));
|
|
124
|
+
}
|
|
125
|
+
else if (type === 'tab' && tabFile && isFile(path.join(adapterDir, `admin/${tabFile}`))) {
|
|
126
|
+
const tabPath = path.join(adapterDir, `admin/${tabFile}`);
|
|
127
|
+
const tabContent = fs.readFileSync(tabPath, 'utf-8');
|
|
128
|
+
config = tabFile.endsWith('5') ? json5_1.default.parse(tabContent) : JSON.parse(tabContent);
|
|
129
|
+
}
|
|
130
|
+
else if (type === 'custom' && fs.existsSync(path.join(adapterDir, `admin/jsonCustom.json`))) {
|
|
131
|
+
config = JSON.parse(fs.readFileSync(path.join(adapterDir, 'admin/jsonCustom.json'), 'utf-8'));
|
|
132
|
+
}
|
|
133
|
+
else if (type === 'custom' && fs.existsSync(path.join(adapterDir, `admin/jsonCustom.json5`))) {
|
|
134
|
+
config = json5_1.default.parse(fs.readFileSync(path.join(adapterDir, 'admin/jsonCustom.json5'), 'utf-8'));
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
const validate = await getJsonValidator(type);
|
|
140
|
+
if (!validate(config)) {
|
|
141
|
+
throw new Error(`Invalid ${type} schema for ${adapterDir}: ${JSON.stringify(validate.errors, null, 2)}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
45
144
|
/**
|
|
46
145
|
* Tests if the adapter files are valid.
|
|
47
146
|
* This is meant to be executed in a mocha context.
|
|
48
147
|
*/
|
|
49
|
-
function validatePackageFiles(adapterDir) {
|
|
148
|
+
function validatePackageFiles(adapterDir, options) {
|
|
50
149
|
const packageJsonPath = path.join(adapterDir, 'package.json');
|
|
51
150
|
const ioPackageJsonPath = path.join(adapterDir, 'io-package.json');
|
|
52
151
|
// This allows us to skip tests that require valid JSON files
|
|
@@ -274,6 +373,34 @@ function validatePackageFiles(adapterDir) {
|
|
|
274
373
|
.true;
|
|
275
374
|
});
|
|
276
375
|
}
|
|
376
|
+
if (iopackContent.common.adminUI?.config === 'json') {
|
|
377
|
+
it('The JSON config file exists', () => {
|
|
378
|
+
(0, chai_1.expect)(fs.existsSync(path.join(adapterDir, 'admin/jsonConfig.json')) ||
|
|
379
|
+
fs.existsSync(path.join(adapterDir, 'admin/jsonConfig.json5')), 'common.adminUI.config is "json", so admin/jsonConfig.json or admin/jsonConfig.json5 must exist!').to.be.true;
|
|
380
|
+
});
|
|
381
|
+
if (!options?.ignoreJsonConfigValidation) {
|
|
382
|
+
it('Check JSON config file', () => validateJsonConfig(adapterDir, 'config')).timeout(10000);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
if (iopackContent.common.adminUI?.custom === 'json') {
|
|
386
|
+
it('The JSON custom config file exists', () => {
|
|
387
|
+
(0, chai_1.expect)(fs.existsSync(path.join(adapterDir, 'admin/jsonCustom.json')) ||
|
|
388
|
+
fs.existsSync(path.join(adapterDir, 'admin/jsonCustom.json5')), 'common.adminUI.custom is "json", so admin/jsonCustom.json or admin/jsonCustom.json5 must exist!').to.be.true;
|
|
389
|
+
});
|
|
390
|
+
if (!options?.ignoreJsonConfigValidation) {
|
|
391
|
+
it('Check JSON custom config file', () => validateJsonConfig(adapterDir, 'custom')).timeout(10000);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
if (iopackContent.common.adminUI?.tab === 'json') {
|
|
395
|
+
const link = (iopackContent.common.adminTab?.link || '').split('?')[0];
|
|
396
|
+
it('The JSON tab file is referenced correctly', () => {
|
|
397
|
+
(0, chai_1.expect)(link.endsWith('.json') || link.endsWith('.json5'), 'common.adminUI.tab is "json", so common.adminTab.link must point to a .json or .json5 file!').to.be.true;
|
|
398
|
+
(0, chai_1.expect)(!link.includes('..') && !link.includes('://') && !link.includes('%'), 'common.adminTab.link must be a file name relative to the admin directory!').to.be.true;
|
|
399
|
+
});
|
|
400
|
+
if (!options?.ignoreJsonConfigValidation) {
|
|
401
|
+
it('Check JSON tab file', () => validateJsonConfig(adapterDir, 'tab', link)).timeout(10000);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
277
404
|
});
|
|
278
405
|
describe(`Compare contents of package.json and io-package.json`, () => {
|
|
279
406
|
beforeEach(function () {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iobroker/testing",
|
|
3
|
-
"version": "6.1
|
|
3
|
+
"version": "6.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",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"author": "AlCalzone",
|
|
39
39
|
"license": "MIT",
|
|
40
40
|
"bugs": {
|
|
41
|
-
"url": "https://github.com/
|
|
41
|
+
"url": "https://github.com/ioBroker/testing/issues"
|
|
42
42
|
},
|
|
43
|
-
"homepage": "https://github.com/
|
|
43
|
+
"homepage": "https://github.com/ioBroker/testing#readme",
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@alcalzone/release-script": "^5.2.1",
|
|
46
46
|
"@alcalzone/release-script-plugin-license": "^5.2.2",
|
|
@@ -63,6 +63,8 @@
|
|
|
63
63
|
"@types/mocha": "^10.0.10",
|
|
64
64
|
"@types/sinon": "^22.0.0",
|
|
65
65
|
"@types/sinon-chai": "^3.2.12",
|
|
66
|
+
"ajv": "^8.20.0",
|
|
67
|
+
"axios": "^1.20.0",
|
|
66
68
|
"alcalzone-shared": "~5.0.0",
|
|
67
69
|
"chai": "^4.5.0",
|
|
68
70
|
"chai-as-promised": "^7.1.2",
|