@iobroker/testing 5.2.1 → 5.3.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/LICENSE +1 -1
- package/README.md +63 -2
- package/build/lib/adapterTools.js +1 -1
- package/build/lib/executeCommand.js +2 -2
- package/build/lib/str2regex.js +1 -1
- package/build/tests/integration/index.d.ts +3 -1
- package/build/tests/integration/lib/adapterSetup.js +7 -0
- package/build/tests/integration/lib/controllerSetup.js +15 -2
- package/build/tests/integration/lib/dbConnection.js +60 -50
- package/build/tests/integration/lib/harness.js +10 -1
- package/build/tests/packageFiles/index.js +15 -1
- package/build/tests/unit/mocks/mockDatabase.js +2 -4
- package/package.json +15 -15
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2024-
|
|
3
|
+
Copyright (c) 2024-2026 AlCalzone <d.griesel@gmx.net>
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -148,11 +148,72 @@ These methods take a mock database and adapter and create a set of asserts for y
|
|
|
148
148
|
|
|
149
149
|
#### MockDatabase
|
|
150
150
|
|
|
151
|
-
|
|
151
|
+
`MockDatabase` is a minimalistic reimplementation of ioBroker's Objects and States DB that operates purely on in-memory `Map`s. It is created for you by `createMocks()`, but you can also instantiate it directly:
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
const { MockDatabase } = require("@iobroker/testing");
|
|
155
|
+
const database = new MockDatabase();
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Objects and states are stored under their fully qualified ID (e.g. `"adapter.0.some.state"`). Wherever a method comes in two overloads, you can either pass the full ID as a single string or pass the namespace and the rest of the ID separately (they are joined with `"."`). Pattern-based getters accept `ioBroker`-style wildcards (`*`).
|
|
159
|
+
|
|
160
|
+
Populating the database:
|
|
161
|
+
|
|
162
|
+
- `publishObject(obj: ioBroker.PartialObject)` adds or replaces an object. The object **must** have an `_id` and a `type`; missing `common`/`native` are filled from a default template.
|
|
163
|
+
- `publishObjects(...objects)` publishes multiple objects at once.
|
|
164
|
+
- `publishStateObjects(...objects)` / `publishChannelObjects(...objects)` / `publishDeviceObjects(...objects)` publish objects while forcing their `type` to `state` / `channel` / `device`.
|
|
165
|
+
- `publishState(id: string, state: Partial<ioBroker.State> | null | undefined)` sets a state (missing properties are filled from a default template). Passing `null`/`undefined` deletes it.
|
|
166
|
+
- `publishStates(states: Record<string, Partial<ioBroker.State> | null | undefined>)` sets multiple states at once.
|
|
167
|
+
|
|
168
|
+
Reading and querying:
|
|
169
|
+
|
|
170
|
+
- `hasObject(id)` / `hasObject(namespace, id)` returns whether an object exists.
|
|
171
|
+
- `getObject(id)` / `getObject(namespace, id)` returns an object or `undefined`.
|
|
172
|
+
- `getObjects(pattern, type?)` / `getObjects(namespace, pattern, type?)` returns all matching objects as a `Record<string, ioBroker.Object>`, optionally filtered by `type`.
|
|
173
|
+
- `hasState(id)` / `hasState(namespace, id)` returns whether a state exists.
|
|
174
|
+
- `getState(id)` / `getState(namespace, id)` returns a state or `undefined`.
|
|
175
|
+
- `getStates(pattern)` returns all matching states as a `Record<string, ioBroker.State>`.
|
|
176
|
+
|
|
177
|
+
Deleting and resetting:
|
|
178
|
+
|
|
179
|
+
- `deleteObject(objOrID)` removes an object (accepts the object or its ID).
|
|
180
|
+
- `deleteState(id)` removes a state.
|
|
181
|
+
- `clearObjects()` / `clearStates()` empty the respective store, `clear()` empties both. Call this between tests to start with a fresh database.
|
|
152
182
|
|
|
153
183
|
#### MockAdapter
|
|
154
184
|
|
|
155
|
-
|
|
185
|
+
`MockAdapter` is a mock of ioBroker's `Adapter` class in which every method is replaced by a [Sinon stub](https://sinonjs.org/releases/latest/stubs/). It is created by `createMocks()` and is backed by a `MockDatabase`, so the DB-related methods actually read from and write to that database instead of a real DB.
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
const { database, adapter } = utils.unit.createMocks();
|
|
189
|
+
// or with custom adapter options (name, instance, config, ...)
|
|
190
|
+
const { database, adapter } = utils.unit.createMocks({ name: "my-adapter" });
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Because the methods are Sinon stubs, you can assert on how your adapter code called them and override their behavior in individual tests:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
// Assert that a state was set
|
|
197
|
+
adapter.setState.callCount.should.equal(1);
|
|
198
|
+
adapter.setState.getCall(0).args[0].should.equal("info.connection");
|
|
199
|
+
|
|
200
|
+
// Override the return value / behavior for a single test
|
|
201
|
+
adapter.getForeignObjectAsync.resolves({ common: { name: "test" } });
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Key features:
|
|
205
|
+
|
|
206
|
+
- **Connected to the database:** methods like `getObject`, `setObject`, `setObjectNotExists`, `extendObject`, `getState`, `setState`, `setStateChanged`, `delState`, their `Foreign` variants and `getObjectView` / `getObjectList` are implemented to operate on the backing `MockDatabase`. IDs that are not fully qualified are automatically prefixed with the adapter namespace. The remaining methods are plain stubs that do nothing until you give them a behavior.
|
|
207
|
+
- **Promisified methods:** for every callback-style method, the corresponding `...Async` version (e.g. `getStateAsync`) is available, just like on a real adapter.
|
|
208
|
+
- **Event handlers:** the handlers your adapter registers (via `adapter.on(...)` or the adapter options) are captured and exposed so you can invoke them from your tests: `readyHandler`, `stateChangeHandler`, `objectChangeHandler`, `messageHandler` and `unloadHandler`. For example, call `await adapter.readyHandler()` to simulate the adapter's startup.
|
|
209
|
+
- **Logger:** `adapter.log` is a `MockLogger` whose `debug` / `info` / `warn` / `error` methods are stubs you can assert on.
|
|
210
|
+
- **`terminate()`** throws an error (carrying the exit reason) instead of exiting the process, so you can assert that your adapter tried to terminate.
|
|
211
|
+
|
|
212
|
+
Resetting the mock between tests:
|
|
213
|
+
|
|
214
|
+
- `resetMockHistory()` clears the recorded call history (call counts, arguments, ...) of all stubs and the logger, but keeps any custom behavior you configured.
|
|
215
|
+
- `resetMockBehavior()` restores the default behavior of all stubs, discarding your overrides.
|
|
216
|
+
- `resetMock()` does both.
|
|
156
217
|
|
|
157
218
|
### Example
|
|
158
219
|
|
|
@@ -51,7 +51,7 @@ exports.getAdapterDependencies = getAdapterDependencies;
|
|
|
51
51
|
const typeguards_1 = require("alcalzone-shared/typeguards");
|
|
52
52
|
const debug_1 = __importDefault(require("debug"));
|
|
53
53
|
const fs_extra_1 = require("fs-extra");
|
|
54
|
-
const path = __importStar(require("path"));
|
|
54
|
+
const path = __importStar(require("node:path"));
|
|
55
55
|
const debug = (0, debug_1.default)('testing:unit:adapterTools');
|
|
56
56
|
/**
|
|
57
57
|
* Loads an adapter's package.json
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.executeCommand = executeCommand;
|
|
4
|
-
const
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
5
|
const isWindows = /^win/.test(process.platform);
|
|
6
6
|
/**
|
|
7
7
|
* Executes a command and returns the exit code and (if requested) the stdout
|
|
@@ -55,7 +55,7 @@ function executeCommand(command, argsOrOptions, options) {
|
|
|
55
55
|
try {
|
|
56
56
|
let bufferedStdout;
|
|
57
57
|
let bufferedStderr;
|
|
58
|
-
const cmd = (0,
|
|
58
|
+
const cmd = (0, node_child_process_1.spawn)(command, args, spawnOptions).on('close', (code, signal) => {
|
|
59
59
|
resolve({
|
|
60
60
|
exitCode: code ?? undefined,
|
|
61
61
|
signal: signal ?? undefined,
|
package/build/lib/str2regex.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.str2regex = str2regex;
|
|
4
4
|
function str2regex(pattern) {
|
|
5
5
|
return new RegExp(pattern
|
|
6
|
-
.replace(/\\/g, '\\\\') // Backslashes
|
|
6
|
+
.replace(/\\/g, '\\\\') // Backslashes escape
|
|
7
7
|
.replace(/\./g, '\\.') // Punkte als solche matchen
|
|
8
8
|
.replace(/\*/g, '.*') // Wildcard in Regex umsetzen
|
|
9
9
|
.replace(/!/g, '?!'));
|
|
@@ -6,7 +6,9 @@ export interface TestAdapterOptions {
|
|
|
6
6
|
/** How long to wait before the adapter startup is considered successful */
|
|
7
7
|
waitBeforeStartupSuccess?: number;
|
|
8
8
|
/**
|
|
9
|
-
* Which JS-Controller version or dist-tag should be used for the tests.
|
|
9
|
+
* Which JS-Controller version or dist-tag should be used for the tests.
|
|
10
|
+
* Default: `dev`, except on Node.js <= 20 where it defaults to `7.2.2`, the last
|
|
11
|
+
* js-controller version that still supports Node.js 18 and 20.
|
|
10
12
|
* This should only be changed during active development.
|
|
11
13
|
*/
|
|
12
14
|
controllerVersion?: string;
|
|
@@ -46,6 +46,8 @@ const executeCommand_1 = require("../../../lib/executeCommand");
|
|
|
46
46
|
const tools_1 = require("./tools");
|
|
47
47
|
const debug = (0, debug_1.default)('testing:integration:AdapterSetup');
|
|
48
48
|
class AdapterSetup {
|
|
49
|
+
adapterDir;
|
|
50
|
+
testDir;
|
|
49
51
|
constructor(adapterDir, testDir) {
|
|
50
52
|
this.adapterDir = adapterDir;
|
|
51
53
|
this.testDir = testDir;
|
|
@@ -61,6 +63,11 @@ class AdapterSetup {
|
|
|
61
63
|
debug(` appName: ${this.appName}`);
|
|
62
64
|
debug(` adapterName: ${this.adapterName}`);
|
|
63
65
|
}
|
|
66
|
+
testAdapterDir;
|
|
67
|
+
adapterName;
|
|
68
|
+
adapterFullName;
|
|
69
|
+
appName;
|
|
70
|
+
testControllerDir;
|
|
64
71
|
/**
|
|
65
72
|
* Tests if the adapter is already installed in the test directory
|
|
66
73
|
*/
|
|
@@ -47,6 +47,8 @@ const executeCommand_1 = require("../../../lib/executeCommand");
|
|
|
47
47
|
const tools_1 = require("./tools");
|
|
48
48
|
const debug = (0, debug_1.default)('testing:integration:ControllerSetup');
|
|
49
49
|
class ControllerSetup {
|
|
50
|
+
adapterDir;
|
|
51
|
+
testDir;
|
|
50
52
|
constructor(adapterDir, testDir) {
|
|
51
53
|
this.adapterDir = adapterDir;
|
|
52
54
|
this.testDir = testDir;
|
|
@@ -63,7 +65,19 @@ class ControllerSetup {
|
|
|
63
65
|
debug(` appName: ${this.appName}`);
|
|
64
66
|
debug(` adapterName: ${this.adapterName}`);
|
|
65
67
|
}
|
|
66
|
-
|
|
68
|
+
appName;
|
|
69
|
+
adapterName;
|
|
70
|
+
testAdapterDir;
|
|
71
|
+
testControllerDir;
|
|
72
|
+
testDataDir;
|
|
73
|
+
async prepareTestDir(controllerVersion) {
|
|
74
|
+
const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10);
|
|
75
|
+
// js-controller 7.2.3 dropped support for Node.js 18 and 20. If no specific
|
|
76
|
+
// version was requested, and we are running on such a Node.js version, pin
|
|
77
|
+
// js-controller to the last version that still supports it.
|
|
78
|
+
if (!controllerVersion) {
|
|
79
|
+
controllerVersion = nodeMajorVersion <= 20 ? '7.2.2' : 'dev';
|
|
80
|
+
}
|
|
67
81
|
debug(`Preparing the test directory. JS-Controller version: "${controllerVersion}"...`);
|
|
68
82
|
// Make sure the test dir exists
|
|
69
83
|
await (0, fs_extra_1.ensureDir)(this.testDir);
|
|
@@ -94,7 +108,6 @@ class ControllerSetup {
|
|
|
94
108
|
await (0, fs_extra_1.unlink)(pckLockPath);
|
|
95
109
|
}
|
|
96
110
|
// Set the engineStrict flag on new Node.js versions to be in line with newer ioBroker installations
|
|
97
|
-
const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10);
|
|
98
111
|
if (nodeMajorVersion >= 10) {
|
|
99
112
|
await (0, fs_extra_1.writeFile)(path.join(this.testDir, '.npmrc'), 'engine-strict=true', 'utf8');
|
|
100
113
|
}
|
|
@@ -45,6 +45,9 @@ const tools_1 = require("./tools");
|
|
|
45
45
|
const debug = (0, debug_1.default)('testing:integration:DBConnection');
|
|
46
46
|
/** The DB connection capsules access to the states and objects DB */
|
|
47
47
|
class DBConnection extends node_events_1.default {
|
|
48
|
+
appName;
|
|
49
|
+
testDir;
|
|
50
|
+
logger;
|
|
48
51
|
/**
|
|
49
52
|
* @param appName The branded name of "iobroker"
|
|
50
53
|
* @param testDir The directory the integration tests are executed in
|
|
@@ -55,63 +58,20 @@ class DBConnection extends node_events_1.default {
|
|
|
55
58
|
this.appName = appName;
|
|
56
59
|
this.testDir = testDir;
|
|
57
60
|
this.logger = logger;
|
|
58
|
-
this._isRunning = false;
|
|
59
|
-
this.getObject = id => {
|
|
60
|
-
if (!this._objectsClient) {
|
|
61
|
-
throw new Error('Objects DB is not running');
|
|
62
|
-
}
|
|
63
|
-
return this._objectsClient.getObjectAsync(id);
|
|
64
|
-
};
|
|
65
|
-
this.setObject = (...args) => {
|
|
66
|
-
if (!this._objectsClient) {
|
|
67
|
-
throw new Error('Objects DB is not running');
|
|
68
|
-
}
|
|
69
|
-
return this._objectsClient.setObjectAsync(...args);
|
|
70
|
-
};
|
|
71
|
-
this.delObject = (...args) => {
|
|
72
|
-
if (!this._objectsClient) {
|
|
73
|
-
throw new Error('Objects DB is not running');
|
|
74
|
-
}
|
|
75
|
-
return this._objectsClient.delObjectAsync(...args);
|
|
76
|
-
};
|
|
77
|
-
this.getState = (id) => {
|
|
78
|
-
if (!this._statesClient) {
|
|
79
|
-
throw new Error('States DB is not running');
|
|
80
|
-
}
|
|
81
|
-
return this._statesClient.getStateAsync(id);
|
|
82
|
-
};
|
|
83
|
-
this.setState = ((...args) => {
|
|
84
|
-
if (!this._statesClient) {
|
|
85
|
-
throw new Error('States DB is not running');
|
|
86
|
-
}
|
|
87
|
-
return this._statesClient.setStateAsync(...args);
|
|
88
|
-
});
|
|
89
|
-
this.delState = async (...args) => {
|
|
90
|
-
if (!this._statesClient) {
|
|
91
|
-
throw new Error('States DB is not running');
|
|
92
|
-
}
|
|
93
|
-
await new Promise((resolve, reject) => this._statesClient.delState(args[0], (err) => {
|
|
94
|
-
if (err) {
|
|
95
|
-
reject(err);
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
resolve();
|
|
99
|
-
}
|
|
100
|
-
}));
|
|
101
|
-
};
|
|
102
|
-
this.getObjectViewAsync = (...args) => {
|
|
103
|
-
if (!this._objectsClient) {
|
|
104
|
-
throw new Error('Objects DB is not running');
|
|
105
|
-
}
|
|
106
|
-
return this._objectsClient.getObjectViewAsync(...args);
|
|
107
|
-
};
|
|
108
61
|
this.testControllerDir = (0, tools_1.getTestControllerDir)(this.appName, testDir);
|
|
109
62
|
this.testDataDir = (0, tools_1.getTestDataDir)(appName, testDir);
|
|
110
63
|
}
|
|
64
|
+
testDataDir;
|
|
65
|
+
testControllerDir;
|
|
66
|
+
// TODO: These could use some better type definitions
|
|
67
|
+
_objectsServer;
|
|
68
|
+
_statesServer;
|
|
69
|
+
_objectsClient;
|
|
111
70
|
/** The underlying objects client instance that can be used to access the objects DB */
|
|
112
71
|
get objectsClient() {
|
|
113
72
|
return this._objectsClient;
|
|
114
73
|
}
|
|
74
|
+
_statesClient;
|
|
115
75
|
/** The underlying states client instance that can be used to access the states DB */
|
|
116
76
|
get statesClient() {
|
|
117
77
|
return this._statesClient;
|
|
@@ -157,6 +117,7 @@ class DBConnection extends node_events_1.default {
|
|
|
157
117
|
const systemFilename = path.join(this.testDataDir, `${this.appName}.json`);
|
|
158
118
|
(0, fs_extra_1.writeJSONSync)(systemFilename, systemConfig, { spaces: 2 });
|
|
159
119
|
}
|
|
120
|
+
_isRunning = false;
|
|
160
121
|
get isRunning() {
|
|
161
122
|
return this._isRunning;
|
|
162
123
|
}
|
|
@@ -277,6 +238,49 @@ class DBConnection extends node_events_1.default {
|
|
|
277
238
|
});
|
|
278
239
|
debug(' => done!');
|
|
279
240
|
}
|
|
241
|
+
getObject = id => {
|
|
242
|
+
if (!this._objectsClient) {
|
|
243
|
+
throw new Error('Objects DB is not running');
|
|
244
|
+
}
|
|
245
|
+
return this._objectsClient.getObjectAsync(id);
|
|
246
|
+
};
|
|
247
|
+
setObject = (...args) => {
|
|
248
|
+
if (!this._objectsClient) {
|
|
249
|
+
throw new Error('Objects DB is not running');
|
|
250
|
+
}
|
|
251
|
+
return this._objectsClient.setObjectAsync(...args);
|
|
252
|
+
};
|
|
253
|
+
delObject = (...args) => {
|
|
254
|
+
if (!this._objectsClient) {
|
|
255
|
+
throw new Error('Objects DB is not running');
|
|
256
|
+
}
|
|
257
|
+
return this._objectsClient.delObjectAsync(...args);
|
|
258
|
+
};
|
|
259
|
+
getState = (id) => {
|
|
260
|
+
if (!this._statesClient) {
|
|
261
|
+
throw new Error('States DB is not running');
|
|
262
|
+
}
|
|
263
|
+
return this._statesClient.getStateAsync(id);
|
|
264
|
+
};
|
|
265
|
+
setState = ((...args) => {
|
|
266
|
+
if (!this._statesClient) {
|
|
267
|
+
throw new Error('States DB is not running');
|
|
268
|
+
}
|
|
269
|
+
return this._statesClient.setStateAsync(...args);
|
|
270
|
+
});
|
|
271
|
+
delState = async (...args) => {
|
|
272
|
+
if (!this._statesClient) {
|
|
273
|
+
throw new Error('States DB is not running');
|
|
274
|
+
}
|
|
275
|
+
await new Promise((resolve, reject) => this._statesClient.delState(args[0], (err) => {
|
|
276
|
+
if (err) {
|
|
277
|
+
reject(err);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
resolve();
|
|
281
|
+
}
|
|
282
|
+
}));
|
|
283
|
+
};
|
|
280
284
|
subscribeMessage(id) {
|
|
281
285
|
if (!this._statesClient) {
|
|
282
286
|
throw new Error('States DB is not running');
|
|
@@ -289,6 +293,12 @@ class DBConnection extends node_events_1.default {
|
|
|
289
293
|
}
|
|
290
294
|
this._statesClient.pushMessage(instanceId, msg, callback);
|
|
291
295
|
}
|
|
296
|
+
getObjectViewAsync = (...args) => {
|
|
297
|
+
if (!this._objectsClient) {
|
|
298
|
+
throw new Error('Objects DB is not running');
|
|
299
|
+
}
|
|
300
|
+
return this._objectsClient.getObjectViewAsync(...args);
|
|
301
|
+
};
|
|
292
302
|
getStateIDs(pattern = '*') {
|
|
293
303
|
if (!this._statesClient) {
|
|
294
304
|
throw new Error('States DB is not running');
|
|
@@ -53,6 +53,9 @@ const fromAdapterID = 'system.adapter.test.0';
|
|
|
53
53
|
* Use it in every test to start a fresh adapter instance
|
|
54
54
|
*/
|
|
55
55
|
class TestHarness extends node_events_1.EventEmitter {
|
|
56
|
+
adapterDir;
|
|
57
|
+
testDir;
|
|
58
|
+
dbConnection;
|
|
56
59
|
/**
|
|
57
60
|
* @param adapterDir The root directory of the adapter
|
|
58
61
|
* @param testDir The directory the integration tests are executed in
|
|
@@ -62,7 +65,6 @@ class TestHarness extends node_events_1.EventEmitter {
|
|
|
62
65
|
this.adapterDir = adapterDir;
|
|
63
66
|
this.testDir = testDir;
|
|
64
67
|
this.dbConnection = dbConnection;
|
|
65
|
-
this.sendToID = 1;
|
|
66
68
|
debug('Creating instance');
|
|
67
69
|
this.adapterName = (0, adapterTools_1.getAdapterName)(this.adapterDir);
|
|
68
70
|
this.appName = (0, adapterTools_1.getAppName)(adapterDir);
|
|
@@ -80,6 +82,10 @@ class TestHarness extends node_events_1.EventEmitter {
|
|
|
80
82
|
this.emit('stateChange', id, state);
|
|
81
83
|
});
|
|
82
84
|
}
|
|
85
|
+
adapterName;
|
|
86
|
+
appName;
|
|
87
|
+
testControllerDir;
|
|
88
|
+
testAdapterDir;
|
|
83
89
|
/** Gives direct access to the Objects DB */
|
|
84
90
|
get objects() {
|
|
85
91
|
if (!this.dbConnection.objectsClient) {
|
|
@@ -94,10 +100,12 @@ class TestHarness extends node_events_1.EventEmitter {
|
|
|
94
100
|
}
|
|
95
101
|
return this.dbConnection.statesClient;
|
|
96
102
|
}
|
|
103
|
+
_adapterProcess;
|
|
97
104
|
/** The process the adapter is running in */
|
|
98
105
|
get adapterProcess() {
|
|
99
106
|
return this._adapterProcess;
|
|
100
107
|
}
|
|
108
|
+
_adapterExit;
|
|
101
109
|
/** Contains the adapter exit code or signal if it was terminated unexpectedly */
|
|
102
110
|
get adapterExit() {
|
|
103
111
|
return this._adapterExit;
|
|
@@ -266,6 +274,7 @@ class TestHarness extends node_events_1.EventEmitter {
|
|
|
266
274
|
});
|
|
267
275
|
this.dbConnection.subscribeMessage(fromAdapterID);
|
|
268
276
|
}
|
|
277
|
+
sendToID = 1;
|
|
269
278
|
/** Sends a message to an adapter instance */
|
|
270
279
|
sendTo(target, command, message, callback) {
|
|
271
280
|
const stateChangedHandler = (id, state) => {
|
|
@@ -291,12 +291,26 @@ function validatePackageFiles(adapterDir) {
|
|
|
291
291
|
});
|
|
292
292
|
});
|
|
293
293
|
describe(`Validate JSON files`, () => {
|
|
294
|
+
// Validate base directory JSON files
|
|
295
|
+
describe(`Base directory JSON files`, () => {
|
|
296
|
+
for (const filename of ['package.json', 'io-package.json']) {
|
|
297
|
+
const filePath = path.join(adapterDir, filename);
|
|
298
|
+
if (fs.existsSync(filePath)) {
|
|
299
|
+
it(`${filename} contains valid JSON`, () => {
|
|
300
|
+
(0, chai_1.expect)(() => {
|
|
301
|
+
JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
302
|
+
}, `${filename} contains invalid JSON!`).not.to.throw();
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
});
|
|
294
307
|
// Find all JSON and JSON5 files in admin/ directory (recursively)
|
|
295
308
|
const adminDir = path.join(adapterDir, 'admin');
|
|
296
309
|
const allAdminJsonFiles = findFiles(adminDir, /\.json$/);
|
|
297
310
|
const allAdminJson5Files = findFiles(adminDir, /\.json5$/);
|
|
298
311
|
// Split JSON files into admin/*.json and admin/i18n/**/*.json
|
|
299
|
-
|
|
312
|
+
// Exclude tsconfig.json as it may contain JSON5 syntax (comments, trailing commas)
|
|
313
|
+
const adminDirectJsonFiles = allAdminJsonFiles.filter(file => !file.includes(`${path.sep}i18n${path.sep}`) && !file.endsWith(`${path.sep}tsconfig.json`));
|
|
300
314
|
const i18nJsonFiles = allAdminJsonFiles.filter(file => file.includes(`${path.sep}i18n${path.sep}`));
|
|
301
315
|
if (adminDirectJsonFiles.length > 0) {
|
|
302
316
|
describe(`admin/*.json files`, () => {
|
|
@@ -17,10 +17,8 @@ const stateTemplate = Object.freeze({
|
|
|
17
17
|
* A minimalistic version of ioBroker's Objects and States DB that just operates on a Map
|
|
18
18
|
*/
|
|
19
19
|
class MockDatabase {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
this.states = new Map();
|
|
23
|
-
}
|
|
20
|
+
objects = new Map();
|
|
21
|
+
states = new Map();
|
|
24
22
|
clearObjects() {
|
|
25
23
|
this.objects.clear();
|
|
26
24
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iobroker/testing",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
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",
|
|
@@ -39,35 +39,35 @@
|
|
|
39
39
|
},
|
|
40
40
|
"homepage": "https://github.com/AlCalzone/testing#readme",
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@alcalzone/release-script": "^5.
|
|
43
|
-
"@alcalzone/release-script-plugin-license": "^
|
|
44
|
-
"@iobroker/adapter-core": "^3.
|
|
45
|
-
"@iobroker/eslint-config": "^2.
|
|
46
|
-
"@iobroker/types": "^7.
|
|
47
|
-
"@tsconfig/
|
|
48
|
-
"@types/debug": "^4.1.
|
|
42
|
+
"@alcalzone/release-script": "^5.2.1",
|
|
43
|
+
"@alcalzone/release-script-plugin-license": "^5.2.0",
|
|
44
|
+
"@iobroker/adapter-core": "^3.4.1",
|
|
45
|
+
"@iobroker/eslint-config": "^2.3.4",
|
|
46
|
+
"@iobroker/types": "^7.2.2",
|
|
47
|
+
"@tsconfig/node18": "^18.2.6",
|
|
48
|
+
"@types/debug": "^4.1.13",
|
|
49
49
|
"@types/fs-extra": "^11.0.4",
|
|
50
|
-
"@types/node": "^
|
|
51
|
-
"rimraf": "^6.1.
|
|
50
|
+
"@types/node": "^26.1.1",
|
|
51
|
+
"rimraf": "^6.1.3",
|
|
52
52
|
"source-map-support": "^0.5.21",
|
|
53
53
|
"ts-node": "^10.9.2",
|
|
54
|
-
"typescript": "~
|
|
54
|
+
"typescript": "~6.0.3"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@alcalzone/esbuild-register": "^2.5.1-1",
|
|
58
58
|
"@types/chai": "^4.3.20",
|
|
59
59
|
"@types/chai-as-promised": "^7.1.8",
|
|
60
60
|
"@types/mocha": "^10.0.10",
|
|
61
|
-
"@types/sinon": "^
|
|
61
|
+
"@types/sinon": "^22.0.0",
|
|
62
62
|
"@types/sinon-chai": "^3.2.12",
|
|
63
63
|
"alcalzone-shared": "~5.0.0",
|
|
64
64
|
"chai": "^4.5.0",
|
|
65
65
|
"chai-as-promised": "^7.1.2",
|
|
66
66
|
"debug": "^4.4.3",
|
|
67
|
-
"fs-extra": "^11.3.
|
|
67
|
+
"fs-extra": "^11.3.6",
|
|
68
68
|
"json5": "^2.2.3",
|
|
69
|
-
"mocha": "^11.7.
|
|
70
|
-
"sinon": "^
|
|
69
|
+
"mocha": "^11.7.6",
|
|
70
|
+
"sinon": "^22.0.0",
|
|
71
71
|
"sinon-chai": "^3.7.0"
|
|
72
72
|
}
|
|
73
73
|
}
|