@matterbridge/core 3.7.5-dev-20260419-96eefd1 → 3.7.5-dev-20260421-b7fd269
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/jestutils/export.d.ts +6 -1
- package/dist/jestutils/export.js +6 -1
- package/dist/jestutils/jestFlushAsync.d.ts +1 -0
- package/dist/jestutils/jestFlushAsync.js +8 -0
- package/dist/jestutils/jestLogAlive.d.ts +2 -0
- package/dist/jestutils/jestLogAlive.js +31 -0
- package/dist/jestutils/jestMatterRequest.d.ts +10 -0
- package/dist/jestutils/jestMatterRequest.js +78 -0
- package/dist/jestutils/jestMatterTest.d.ts +19 -0
- package/dist/jestutils/jestMatterTest.js +218 -0
- package/dist/jestutils/jestMatterbridgeEndpointSpy.d.ts +5 -0
- package/dist/jestutils/jestMatterbridgeEndpointSpy.js +1 -0
- package/dist/jestutils/jestMatterbridgeTest.d.ts +21 -0
- package/dist/jestutils/jestMatterbridgeTest.js +252 -0
- package/dist/jestutils/jestSetupTest.d.ts +21 -0
- package/dist/jestutils/jestSetupTest.js +78 -0
- package/package.json +5 -5
- package/dist/jestutils/jestHelpers.d.ts +0 -68
- package/dist/jestutils/jestHelpers.js +0 -644
|
@@ -1,644 +0,0 @@
|
|
|
1
|
-
import { rmSync } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { inspect } from 'node:util';
|
|
4
|
-
import { Environment, Lifecycle, LogFormat as MatterLogFormat, LogLevel as MatterLogLevel } from '@matter/general';
|
|
5
|
-
import { Endpoint, ServerNode, ServerNodeStore } from '@matter/node';
|
|
6
|
-
import { AggregatorEndpoint } from '@matter/node/endpoints';
|
|
7
|
-
import { MdnsService } from '@matter/protocol';
|
|
8
|
-
import { ColorControl } from '@matter/types/clusters/color-control';
|
|
9
|
-
import { DeviceTypeId, VendorId } from '@matter/types/datatype';
|
|
10
|
-
import { MATTER_STORAGE_DIR, NODE_STORAGE_DIR } from '@matterbridge/types';
|
|
11
|
-
import { AnsiLogger, er, rs, UNDERLINE, UNDERLINEOFF } from 'node-ansi-logger';
|
|
12
|
-
import { NodeStorageManager } from 'node-persist-manager';
|
|
13
|
-
import { Matterbridge } from '../matterbridge.js';
|
|
14
|
-
import { bridge } from '../matterbridgeDeviceTypes.js';
|
|
15
|
-
export const originalProcessArgv = Object.freeze([...process.argv]);
|
|
16
|
-
export const originalProcessEnv = Object.freeze({ ...process.env });
|
|
17
|
-
export let loggerLogSpy;
|
|
18
|
-
export let loggerDebugSpy;
|
|
19
|
-
export let loggerInfoSpy;
|
|
20
|
-
export let loggerNoticeSpy;
|
|
21
|
-
export let loggerWarnSpy;
|
|
22
|
-
export let loggerErrorSpy;
|
|
23
|
-
export let loggerFatalSpy;
|
|
24
|
-
export let consoleLogSpy;
|
|
25
|
-
export let consoleDebugSpy;
|
|
26
|
-
export let consoleInfoSpy;
|
|
27
|
-
export let consoleWarnSpy;
|
|
28
|
-
export let consoleErrorSpy;
|
|
29
|
-
export let NAME;
|
|
30
|
-
export let HOMEDIR;
|
|
31
|
-
export let matterbridge;
|
|
32
|
-
export let frontend;
|
|
33
|
-
export let plugins;
|
|
34
|
-
export let devices;
|
|
35
|
-
export let environment;
|
|
36
|
-
export let server;
|
|
37
|
-
export let aggregator;
|
|
38
|
-
export let log;
|
|
39
|
-
export async function setupTest(name, debug = false) {
|
|
40
|
-
expect(name).toBeDefined();
|
|
41
|
-
expect(typeof name).toBe('string');
|
|
42
|
-
expect(name.length).toBeGreaterThanOrEqual(4);
|
|
43
|
-
NAME = name;
|
|
44
|
-
HOMEDIR = path.join('.cache', 'jest', name);
|
|
45
|
-
rmSync(HOMEDIR, { recursive: true, force: true });
|
|
46
|
-
const { jest } = await import('@jest/globals');
|
|
47
|
-
loggerDebugSpy = jest.spyOn(AnsiLogger.prototype, 'debug');
|
|
48
|
-
loggerInfoSpy = jest.spyOn(AnsiLogger.prototype, 'info');
|
|
49
|
-
loggerNoticeSpy = jest.spyOn(AnsiLogger.prototype, 'notice');
|
|
50
|
-
loggerWarnSpy = jest.spyOn(AnsiLogger.prototype, 'warn');
|
|
51
|
-
loggerErrorSpy = jest.spyOn(AnsiLogger.prototype, 'error');
|
|
52
|
-
loggerFatalSpy = jest.spyOn(AnsiLogger.prototype, 'fatal');
|
|
53
|
-
if (debug) {
|
|
54
|
-
loggerLogSpy = jest.spyOn(AnsiLogger.prototype, 'log');
|
|
55
|
-
consoleLogSpy = jest.spyOn(console, 'log');
|
|
56
|
-
consoleDebugSpy = jest.spyOn(console, 'debug');
|
|
57
|
-
consoleInfoSpy = jest.spyOn(console, 'info');
|
|
58
|
-
consoleWarnSpy = jest.spyOn(console, 'warn');
|
|
59
|
-
consoleErrorSpy = jest.spyOn(console, 'error');
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
loggerLogSpy = jest.spyOn(AnsiLogger.prototype, 'log').mockImplementation(() => { });
|
|
63
|
-
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
64
|
-
consoleDebugSpy = jest.spyOn(console, 'debug').mockImplementation(() => { });
|
|
65
|
-
consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation(() => { });
|
|
66
|
-
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
|
|
67
|
-
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
export async function setDebug(debug) {
|
|
71
|
-
const { jest } = await import('@jest/globals');
|
|
72
|
-
if (debug) {
|
|
73
|
-
loggerLogSpy.mockRestore();
|
|
74
|
-
consoleLogSpy.mockRestore();
|
|
75
|
-
consoleDebugSpy.mockRestore();
|
|
76
|
-
consoleInfoSpy.mockRestore();
|
|
77
|
-
consoleWarnSpy.mockRestore();
|
|
78
|
-
consoleErrorSpy.mockRestore();
|
|
79
|
-
loggerLogSpy = jest.spyOn(AnsiLogger.prototype, 'log');
|
|
80
|
-
consoleLogSpy = jest.spyOn(console, 'log');
|
|
81
|
-
consoleDebugSpy = jest.spyOn(console, 'debug');
|
|
82
|
-
consoleInfoSpy = jest.spyOn(console, 'info');
|
|
83
|
-
consoleWarnSpy = jest.spyOn(console, 'warn');
|
|
84
|
-
consoleErrorSpy = jest.spyOn(console, 'error');
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
loggerLogSpy = jest.spyOn(AnsiLogger.prototype, 'log').mockImplementation(() => { });
|
|
88
|
-
consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(() => { });
|
|
89
|
-
consoleDebugSpy = jest.spyOn(console, 'debug').mockImplementation(() => { });
|
|
90
|
-
consoleInfoSpy = jest.spyOn(console, 'info').mockImplementation(() => { });
|
|
91
|
-
consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
|
|
92
|
-
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => { });
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
export async function startMatterbridge(bridgeMode = 'bridge', frontendPort = 8283, matterPort = 5540, passcode = 20252026, discriminator = 3840, pluginSize = 0, devicesSize = 0) {
|
|
96
|
-
process.env['MATTERBRIDGE_START_MATTER_INTERVAL_MS'] = '100';
|
|
97
|
-
process.env['MATTERBRIDGE_PAUSE_MATTER_INTERVAL_MS'] = '100';
|
|
98
|
-
process.argv.length = 0;
|
|
99
|
-
process.argv.push(...originalProcessArgv, '--novirtual', '--debug', '--verbose', '--logger', 'debug', '--matterlogger', 'debug', bridgeMode === '' ? '--test' : '--' + bridgeMode, '--homedir', HOMEDIR, '--frontend', frontendPort.toString(), '--port', matterPort.toString(), '--passcode', passcode.toString(), '--discriminator', discriminator.toString());
|
|
100
|
-
expect(Matterbridge.instance).toBeUndefined();
|
|
101
|
-
matterbridge = await Matterbridge.loadInstance(true);
|
|
102
|
-
expect(matterbridge.environment).toBeDefined();
|
|
103
|
-
expect(matterbridge.systemCheckTimeout).toBeDefined();
|
|
104
|
-
expect(matterbridge.checkUpdateTimeout).toBeDefined();
|
|
105
|
-
expect(matterbridge.checkUpdateInterval).toBeDefined();
|
|
106
|
-
clearTimeout(matterbridge.systemCheckTimeout);
|
|
107
|
-
clearTimeout(matterbridge.checkUpdateTimeout);
|
|
108
|
-
clearInterval(matterbridge.checkUpdateInterval);
|
|
109
|
-
new MdnsService(matterbridge.environment);
|
|
110
|
-
expect(matterbridge).toBeDefined();
|
|
111
|
-
expect(matterbridge.profile).toBeUndefined();
|
|
112
|
-
expect(matterbridge.bridgeMode).toBe(bridgeMode);
|
|
113
|
-
frontend = matterbridge.frontend;
|
|
114
|
-
plugins = matterbridge.plugins;
|
|
115
|
-
devices = matterbridge.devices;
|
|
116
|
-
expect(matterbridge.initialized).toBeTruthy();
|
|
117
|
-
expect(matterbridge.log).toBeDefined();
|
|
118
|
-
expect(matterbridge.rootDirectory).toBe(path.resolve('./'));
|
|
119
|
-
expect(matterbridge.homeDirectory).toBe(path.join(HOMEDIR));
|
|
120
|
-
expect(matterbridge.matterbridgeDirectory).toBe(path.join(HOMEDIR, '.matterbridge'));
|
|
121
|
-
expect(matterbridge.matterbridgePluginDirectory).toBe(path.join(HOMEDIR, 'Matterbridge'));
|
|
122
|
-
expect(matterbridge.matterbridgeCertDirectory).toBe(path.join(HOMEDIR, '.mattercert'));
|
|
123
|
-
expect(plugins).toBeDefined();
|
|
124
|
-
expect(plugins.size).toBe(pluginSize);
|
|
125
|
-
expect(devices).toBeDefined();
|
|
126
|
-
expect(devices.size).toBe(devicesSize);
|
|
127
|
-
expect(frontend).toBeDefined();
|
|
128
|
-
expect(frontend.listening).toBeTruthy();
|
|
129
|
-
expect(frontend.httpServer).toBeDefined();
|
|
130
|
-
expect(frontend.httpsServer).toBeUndefined();
|
|
131
|
-
expect(frontend.expressApp).toBeDefined();
|
|
132
|
-
expect(frontend.webSocketServer).toBeDefined();
|
|
133
|
-
expect(matterbridge.nodeStorage).toBeDefined();
|
|
134
|
-
expect(matterbridge.nodeContext).toBeDefined();
|
|
135
|
-
expect(Environment.default.vars.get('path.root')).toBe(path.join(matterbridge.matterbridgeDirectory, MATTER_STORAGE_DIR));
|
|
136
|
-
expect(matterbridge.matterStorageService).toBeDefined();
|
|
137
|
-
expect(matterbridge.matterStorageManager).toBeDefined();
|
|
138
|
-
expect(matterbridge.matterbridgeContext).toBeDefined();
|
|
139
|
-
expect(matterbridge.controllerContext).toBeUndefined();
|
|
140
|
-
if (bridgeMode === 'bridge') {
|
|
141
|
-
expect(matterbridge.serverNode).toBeDefined();
|
|
142
|
-
expect(matterbridge.aggregatorNode).toBeDefined();
|
|
143
|
-
}
|
|
144
|
-
expect(matterbridge.mdnsInterface).toBe(undefined);
|
|
145
|
-
expect(matterbridge.port).toBe(matterPort + (bridgeMode === 'bridge' ? 1 : 0));
|
|
146
|
-
expect(matterbridge.passcode).toBe(passcode + (bridgeMode === 'bridge' ? 1 : 0));
|
|
147
|
-
expect(matterbridge.discriminator).toBe(discriminator + (bridgeMode === 'bridge' ? 1 : 0));
|
|
148
|
-
if (bridgeMode === 'bridge') {
|
|
149
|
-
const started = new Promise((resolve) => {
|
|
150
|
-
matterbridge.once('bridge_started', () => {
|
|
151
|
-
resolve();
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
const online = new Promise((resolve) => {
|
|
155
|
-
matterbridge.once('online', (name) => {
|
|
156
|
-
if (name === 'Matterbridge')
|
|
157
|
-
resolve();
|
|
158
|
-
});
|
|
159
|
-
});
|
|
160
|
-
await Promise.all([started, online]);
|
|
161
|
-
}
|
|
162
|
-
else if (bridgeMode === 'childbridge') {
|
|
163
|
-
await new Promise((resolve) => {
|
|
164
|
-
matterbridge.once('childbridge_started', () => {
|
|
165
|
-
resolve();
|
|
166
|
-
});
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
expect(loggerLogSpy).toHaveBeenCalledWith("info", `The frontend http server is listening on ${UNDERLINE}http://${matterbridge.systemInformation.ipv4Address}:${frontendPort}${UNDERLINEOFF}${rs}`);
|
|
170
|
-
if (bridgeMode === 'bridge') {
|
|
171
|
-
expect(loggerLogSpy).toHaveBeenCalledWith("notice", `Starting Matterbridge server node`);
|
|
172
|
-
expect(loggerLogSpy).toHaveBeenCalledWith("notice", `Server node for Matterbridge is online`);
|
|
173
|
-
expect(loggerLogSpy).toHaveBeenCalledWith("debug", `Starting start matter interval in bridge mode...`);
|
|
174
|
-
expect(loggerLogSpy).toHaveBeenCalledWith("debug", `Cleared startMatterInterval interval in bridge mode`);
|
|
175
|
-
expect(loggerLogSpy).toHaveBeenCalledWith("notice", `Matterbridge bridge started successfully`);
|
|
176
|
-
}
|
|
177
|
-
else if (bridgeMode === 'childbridge') {
|
|
178
|
-
expect(loggerLogSpy).toHaveBeenCalledWith("debug", `Starting start matter interval in childbridge mode...`);
|
|
179
|
-
expect(loggerLogSpy).toHaveBeenCalledWith("debug", `Cleared startMatterInterval interval in childbridge mode`);
|
|
180
|
-
expect(loggerLogSpy).toHaveBeenCalledWith("notice", `Matterbridge childbridge started successfully`);
|
|
181
|
-
}
|
|
182
|
-
return matterbridge;
|
|
183
|
-
}
|
|
184
|
-
export async function stopMatterbridge(cleanupPause = 10, destroyPause = 250, closeMdns = true) {
|
|
185
|
-
await destroyMatterbridgeEnvironment(cleanupPause, destroyPause, closeMdns);
|
|
186
|
-
}
|
|
187
|
-
export async function createMatterbridgeEnvironment(name, createOnly = false) {
|
|
188
|
-
log = new AnsiLogger({ logName: name, logTimestampFormat: 4, logLevel: "debug" });
|
|
189
|
-
matterbridge = await Matterbridge.loadInstance(false);
|
|
190
|
-
expect(matterbridge).toBeDefined();
|
|
191
|
-
expect(matterbridge).toBeInstanceOf(Matterbridge);
|
|
192
|
-
matterbridge.matterbridgeVersion = '3.7.5';
|
|
193
|
-
matterbridge.bridgeMode = 'bridge';
|
|
194
|
-
matterbridge.rootDirectory = path.join(HOMEDIR);
|
|
195
|
-
matterbridge.homeDirectory = path.join(HOMEDIR);
|
|
196
|
-
matterbridge.matterbridgeDirectory = path.join(HOMEDIR, '.matterbridge');
|
|
197
|
-
matterbridge.matterbridgePluginDirectory = path.join(HOMEDIR, 'Matterbridge');
|
|
198
|
-
matterbridge.matterbridgeCertDirectory = path.join(HOMEDIR, '.mattercert');
|
|
199
|
-
matterbridge.log.logLevel = "debug";
|
|
200
|
-
frontend = matterbridge.frontend;
|
|
201
|
-
plugins = matterbridge.plugins;
|
|
202
|
-
devices = matterbridge.devices;
|
|
203
|
-
matterbridge.environment = createTestEnvironment(name, createOnly);
|
|
204
|
-
expect(matterbridge.environment).toBeDefined();
|
|
205
|
-
expect(matterbridge.environment).toBeInstanceOf(Environment);
|
|
206
|
-
return matterbridge;
|
|
207
|
-
}
|
|
208
|
-
export async function startMatterbridgeEnvironment(port = 5540, createOnly = false) {
|
|
209
|
-
matterbridge.nodeStorage = new NodeStorageManager({
|
|
210
|
-
dir: path.join(matterbridge.matterbridgeDirectory, NODE_STORAGE_DIR),
|
|
211
|
-
writeQueue: false,
|
|
212
|
-
expiredInterval: undefined,
|
|
213
|
-
logging: false,
|
|
214
|
-
});
|
|
215
|
-
matterbridge.nodeContext = await matterbridge.nodeStorage.createStorage('matterbridge');
|
|
216
|
-
await matterbridge.startMatterStorage();
|
|
217
|
-
expect(matterbridge.matterStorageService).toBeDefined();
|
|
218
|
-
expect(matterbridge.matterStorageManager).toBeDefined();
|
|
219
|
-
expect(matterbridge.matterbridgeContext).toBeDefined();
|
|
220
|
-
server = await matterbridge.createServerNode(matterbridge.matterbridgeContext, port);
|
|
221
|
-
expect(server).toBeDefined();
|
|
222
|
-
expect(server).toBeDefined();
|
|
223
|
-
expect(server.lifecycle.isReady).toBeTruthy();
|
|
224
|
-
matterbridge.serverNode = server;
|
|
225
|
-
aggregator = await matterbridge.createAggregatorNode(matterbridge.matterbridgeContext);
|
|
226
|
-
expect(aggregator).toBeDefined();
|
|
227
|
-
matterbridge.aggregatorNode = aggregator;
|
|
228
|
-
expect(await server.add(aggregator)).toBeDefined();
|
|
229
|
-
expect(server.parts.has(aggregator.id)).toBeTruthy();
|
|
230
|
-
expect(server.parts.has(aggregator)).toBeTruthy();
|
|
231
|
-
expect(aggregator.lifecycle.isReady).toBeTruthy();
|
|
232
|
-
if (createOnly) {
|
|
233
|
-
await flushAsync();
|
|
234
|
-
return [server, aggregator];
|
|
235
|
-
}
|
|
236
|
-
expect(server.lifecycle.isOnline).toBeFalsy();
|
|
237
|
-
await new Promise((resolve) => {
|
|
238
|
-
server.lifecycle.online.on(async () => {
|
|
239
|
-
resolve();
|
|
240
|
-
});
|
|
241
|
-
server.start();
|
|
242
|
-
});
|
|
243
|
-
expect(server.lifecycle.isReady).toBeTruthy();
|
|
244
|
-
expect(server.lifecycle.isOnline).toBeTruthy();
|
|
245
|
-
expect(server.lifecycle.isCommissioned).toBeFalsy();
|
|
246
|
-
expect(server.lifecycle.isPartsReady).toBeTruthy();
|
|
247
|
-
expect(server.lifecycle.hasId).toBeTruthy();
|
|
248
|
-
expect(server.lifecycle.hasNumber).toBeTruthy();
|
|
249
|
-
expect(aggregator.lifecycle.isReady).toBeTruthy();
|
|
250
|
-
expect(aggregator.lifecycle.isInstalled).toBeTruthy();
|
|
251
|
-
expect(aggregator.lifecycle.isPartsReady).toBeTruthy();
|
|
252
|
-
expect(aggregator.lifecycle.hasId).toBeTruthy();
|
|
253
|
-
expect(aggregator.lifecycle.hasNumber).toBeTruthy();
|
|
254
|
-
await flushAsync();
|
|
255
|
-
return [server, aggregator];
|
|
256
|
-
}
|
|
257
|
-
export function addMatterbridgePlatform(platform, name) {
|
|
258
|
-
expect(platform).toBeDefined();
|
|
259
|
-
platform.setMatterNode?.(matterbridge.addBridgedEndpoint.bind(matterbridge), matterbridge.removeBridgedEndpoint.bind(matterbridge), matterbridge.removeAllBridgedEndpoints.bind(matterbridge), matterbridge.addVirtualEndpoint.bind(matterbridge));
|
|
260
|
-
if (name)
|
|
261
|
-
platform.config.name = name;
|
|
262
|
-
expect(platform.config.name).toBeDefined();
|
|
263
|
-
expect(platform.config.type).toBeDefined();
|
|
264
|
-
expect(platform.type).toBeDefined();
|
|
265
|
-
expect(platform.config.version).toBeDefined();
|
|
266
|
-
expect(platform.version).toBeDefined();
|
|
267
|
-
expect(platform.config.debug).toBeDefined();
|
|
268
|
-
expect(platform.config.unregisterOnShutdown).toBeDefined();
|
|
269
|
-
matterbridge.plugins._plugins.set(platform.config.name, {
|
|
270
|
-
name: platform.config.name,
|
|
271
|
-
path: './',
|
|
272
|
-
type: platform.type,
|
|
273
|
-
version: platform.version,
|
|
274
|
-
description: 'Plugin ' + platform.config.name,
|
|
275
|
-
author: 'Unknown',
|
|
276
|
-
enabled: true,
|
|
277
|
-
});
|
|
278
|
-
platform['name'] = platform.config.name;
|
|
279
|
-
}
|
|
280
|
-
export async function stopMatterbridgeEnvironment(createOnly = false) {
|
|
281
|
-
expect(matterbridge).toBeDefined();
|
|
282
|
-
expect(server).toBeDefined();
|
|
283
|
-
expect(aggregator).toBeDefined();
|
|
284
|
-
await flushAllEndpointNumberPersistence(server);
|
|
285
|
-
await assertAllEndpointNumbersPersisted(server);
|
|
286
|
-
expect(server.lifecycle.isReady).toBeTruthy();
|
|
287
|
-
if (!createOnly) {
|
|
288
|
-
expect(server.lifecycle.isOnline).toBeTruthy();
|
|
289
|
-
}
|
|
290
|
-
await server.close();
|
|
291
|
-
expect(server.lifecycle.isReady).toBeFalsy();
|
|
292
|
-
expect(server.lifecycle.isOnline).toBeFalsy();
|
|
293
|
-
await matterbridge.stopMatterStorage();
|
|
294
|
-
expect(matterbridge.matterStorageService).not.toBeDefined();
|
|
295
|
-
expect(matterbridge.matterStorageManager).not.toBeDefined();
|
|
296
|
-
expect(matterbridge.matterbridgeContext).not.toBeDefined();
|
|
297
|
-
await matterbridge.nodeContext?.close();
|
|
298
|
-
matterbridge.nodeContext = undefined;
|
|
299
|
-
await matterbridge.nodeStorage?.close();
|
|
300
|
-
matterbridge.nodeStorage = undefined;
|
|
301
|
-
await flushAsync();
|
|
302
|
-
}
|
|
303
|
-
export async function destroyMatterbridgeEnvironment(cleanupPause = 10, destroyPause = 250, closeMdns = true) {
|
|
304
|
-
await destroyInstance(matterbridge, cleanupPause, destroyPause);
|
|
305
|
-
if (closeMdns) {
|
|
306
|
-
await closeMdnsInstance(matterbridge);
|
|
307
|
-
}
|
|
308
|
-
Matterbridge.instance = undefined;
|
|
309
|
-
}
|
|
310
|
-
export async function destroyInstance(matterbridge, cleanupPause = 10, destroyPause = 250) {
|
|
311
|
-
await matterbridge.cleanup('destroying instance...', false, cleanupPause);
|
|
312
|
-
if (destroyPause > 0)
|
|
313
|
-
await flushAsync(undefined, undefined, destroyPause);
|
|
314
|
-
}
|
|
315
|
-
export async function closeMdnsInstance(matterbridge) {
|
|
316
|
-
const mdns = matterbridge.environment.get(MdnsService);
|
|
317
|
-
await mdns.close();
|
|
318
|
-
}
|
|
319
|
-
export function createTestEnvironment(name, createOnly = false) {
|
|
320
|
-
expect(name).toBeDefined();
|
|
321
|
-
expect(typeof name).toBe('string');
|
|
322
|
-
expect(name.length).toBeGreaterThanOrEqual(4);
|
|
323
|
-
log = new AnsiLogger({ logName: name, logTimestampFormat: 4, logLevel: "debug" });
|
|
324
|
-
rmSync(path.join(HOMEDIR), { recursive: true, force: true });
|
|
325
|
-
environment = Environment.default;
|
|
326
|
-
environment.vars.set('log.level', MatterLogLevel.DEBUG);
|
|
327
|
-
environment.vars.set('log.format', MatterLogFormat.ANSI);
|
|
328
|
-
environment.vars.set('path.root', path.join(HOMEDIR, '.matterbridge', MATTER_STORAGE_DIR));
|
|
329
|
-
environment.vars.set('runtime.signals', false);
|
|
330
|
-
environment.vars.set('runtime.exitcode', false);
|
|
331
|
-
if (createOnly)
|
|
332
|
-
return environment;
|
|
333
|
-
new MdnsService(environment);
|
|
334
|
-
return environment;
|
|
335
|
-
}
|
|
336
|
-
export async function destroyTestEnvironment(createOnly = false) {
|
|
337
|
-
if (createOnly)
|
|
338
|
-
return;
|
|
339
|
-
const mdns = environment.get(MdnsService);
|
|
340
|
-
await mdns.close();
|
|
341
|
-
}
|
|
342
|
-
export async function flushAsync(ticks = 3, microTurns = 10, pause = 250) {
|
|
343
|
-
for (let i = 0; i < ticks; i++)
|
|
344
|
-
await new Promise((resolve) => setImmediate(resolve));
|
|
345
|
-
for (let i = 0; i < microTurns; i++)
|
|
346
|
-
await Promise.resolve();
|
|
347
|
-
if (pause)
|
|
348
|
-
await new Promise((resolve) => setTimeout(resolve, pause));
|
|
349
|
-
}
|
|
350
|
-
export function logKeepAlives(log) {
|
|
351
|
-
const handles = process._getActiveHandles?.() ?? [];
|
|
352
|
-
const requests = process._getActiveRequests?.() ?? [];
|
|
353
|
-
const fmtHandle = (h, i) => {
|
|
354
|
-
const ctor = h?.constructor?.name ?? 'Unknown';
|
|
355
|
-
const hasRef = typeof h?.hasRef === 'function' ? h.hasRef() : undefined;
|
|
356
|
-
const isPort = h?.constructor?.name?.includes('MessagePort');
|
|
357
|
-
const fd = h?.fd ?? h?._handle?.fd;
|
|
358
|
-
return { i, type: ctor, hasRef, isPort, fd };
|
|
359
|
-
};
|
|
360
|
-
const fmtReq = (r, i) => {
|
|
361
|
-
const ctor = r?.constructor?.name ?? 'Unknown';
|
|
362
|
-
return { i, type: ctor };
|
|
363
|
-
};
|
|
364
|
-
const summary = {
|
|
365
|
-
handles: handles.map(fmtHandle),
|
|
366
|
-
requests: requests.map(fmtReq),
|
|
367
|
-
};
|
|
368
|
-
if (summary.handles.length === 0 && summary.requests.length === 0) {
|
|
369
|
-
log?.debug('KeepAlive: no active handles or requests.');
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
log?.debug(`KeepAlive:${rs}\n${inspect(summary, { depth: 5, colors: true })}`);
|
|
373
|
-
if (!log) {
|
|
374
|
-
process.stdout.write(`KeepAlive:\n${inspect(summary, { depth: 5, colors: true })}\n`);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
return summary.handles.length + summary.requests.length;
|
|
378
|
-
}
|
|
379
|
-
export async function flushAllEndpointNumberPersistence(targetServer, rounds = 3, pause = 10) {
|
|
380
|
-
const nodeStore = targetServer.env.get(ServerNodeStore);
|
|
381
|
-
for (let i = 0; i < rounds; i++) {
|
|
382
|
-
await flushAsync(undefined, undefined, pause);
|
|
383
|
-
await nodeStore.endpointStores.close();
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
function getOwningServerNode(owner) {
|
|
387
|
-
let current = owner;
|
|
388
|
-
while (current.owner) {
|
|
389
|
-
current = current.owner;
|
|
390
|
-
}
|
|
391
|
-
return current;
|
|
392
|
-
}
|
|
393
|
-
function collectAllEndpoints(root) {
|
|
394
|
-
const list = [];
|
|
395
|
-
const walk = (ep) => {
|
|
396
|
-
list.push(ep);
|
|
397
|
-
if (ep.parts) {
|
|
398
|
-
for (const child of ep.parts) {
|
|
399
|
-
walk(child);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
};
|
|
403
|
-
walk(root);
|
|
404
|
-
return list;
|
|
405
|
-
}
|
|
406
|
-
export async function assertAllEndpointNumbersPersisted(targetServer) {
|
|
407
|
-
const nodeStore = targetServer.env.get(ServerNodeStore);
|
|
408
|
-
await nodeStore.endpointStores.close();
|
|
409
|
-
const all = collectAllEndpoints(targetServer);
|
|
410
|
-
for (const ep of all) {
|
|
411
|
-
const store = nodeStore.storeForEndpoint(ep);
|
|
412
|
-
if (ep.maybeNumber === 0) {
|
|
413
|
-
expect(store.number ?? 0).toBe(0);
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
expect(store.number).toBeGreaterThan(0);
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
return all.length;
|
|
420
|
-
}
|
|
421
|
-
export async function closeServerNodeStores(targetServer) {
|
|
422
|
-
if (!targetServer)
|
|
423
|
-
targetServer = server;
|
|
424
|
-
await targetServer?.env.get(ServerNodeStore)?.endpointStores.close();
|
|
425
|
-
}
|
|
426
|
-
export async function createServerNode(name, port, deviceType = bridge.code, ticks = 3, microTurns = 10, pause = 100) {
|
|
427
|
-
const { randomBytes } = await import('node:crypto');
|
|
428
|
-
const random = randomBytes(8).toString('hex');
|
|
429
|
-
server = await ServerNode.create({
|
|
430
|
-
id: name + 'ServerNode',
|
|
431
|
-
environment,
|
|
432
|
-
productDescription: {
|
|
433
|
-
name: name + 'ServerNode',
|
|
434
|
-
deviceType: DeviceTypeId(deviceType),
|
|
435
|
-
vendorId: VendorId(0xfff1),
|
|
436
|
-
productId: 0x8000,
|
|
437
|
-
},
|
|
438
|
-
basicInformation: {
|
|
439
|
-
vendorId: VendorId(0xfff1),
|
|
440
|
-
vendorName: 'Matterbridge',
|
|
441
|
-
productId: 0x8000,
|
|
442
|
-
productName: 'Matterbridge ' + name,
|
|
443
|
-
nodeLabel: name + 'ServerNode',
|
|
444
|
-
hardwareVersion: 1,
|
|
445
|
-
softwareVersion: 1,
|
|
446
|
-
reachable: true,
|
|
447
|
-
serialNumber: 'SN' + random,
|
|
448
|
-
uniqueId: 'UI' + random,
|
|
449
|
-
},
|
|
450
|
-
network: {
|
|
451
|
-
listeningAddressIpv4: undefined,
|
|
452
|
-
listeningAddressIpv6: undefined,
|
|
453
|
-
port,
|
|
454
|
-
},
|
|
455
|
-
operationalCredentials: {
|
|
456
|
-
certification: undefined,
|
|
457
|
-
},
|
|
458
|
-
});
|
|
459
|
-
expect(server).toBeDefined();
|
|
460
|
-
expect(server.lifecycle.isReady).toBeTruthy();
|
|
461
|
-
aggregator = new Endpoint(AggregatorEndpoint, {
|
|
462
|
-
id: name + 'AggregatorNode',
|
|
463
|
-
});
|
|
464
|
-
expect(aggregator).toBeDefined();
|
|
465
|
-
await server.add(aggregator);
|
|
466
|
-
expect(server.parts.has(aggregator.id)).toBeTruthy();
|
|
467
|
-
expect(server.parts.has(aggregator)).toBeTruthy();
|
|
468
|
-
expect(aggregator.lifecycle.isReady).toBeTruthy();
|
|
469
|
-
expect(server.lifecycle.isOnline).toBeFalsy();
|
|
470
|
-
await flushAsync(ticks, microTurns, pause);
|
|
471
|
-
return [server, aggregator];
|
|
472
|
-
}
|
|
473
|
-
export async function startServerNode(name, port, deviceType = bridge.code, createOnly = false, ticks = 3, microTurns = 10, pause = 100) {
|
|
474
|
-
if (!server || !aggregator) {
|
|
475
|
-
await createServerNode(name, port, deviceType);
|
|
476
|
-
}
|
|
477
|
-
if (createOnly) {
|
|
478
|
-
return [server, aggregator];
|
|
479
|
-
}
|
|
480
|
-
await new Promise((resolve) => {
|
|
481
|
-
server.lifecycle.online.on(async () => {
|
|
482
|
-
resolve();
|
|
483
|
-
});
|
|
484
|
-
server.start();
|
|
485
|
-
});
|
|
486
|
-
expect(server.lifecycle.isReady).toBeTruthy();
|
|
487
|
-
expect(server.lifecycle.isOnline).toBeTruthy();
|
|
488
|
-
expect(server.lifecycle.isCommissioned).toBeFalsy();
|
|
489
|
-
expect(server.lifecycle.isPartsReady).toBeTruthy();
|
|
490
|
-
expect(server.lifecycle.hasId).toBeTruthy();
|
|
491
|
-
expect(server.lifecycle.hasNumber).toBeTruthy();
|
|
492
|
-
expect(aggregator.lifecycle.isReady).toBeTruthy();
|
|
493
|
-
expect(aggregator.lifecycle.isInstalled).toBeTruthy();
|
|
494
|
-
expect(aggregator.lifecycle.isPartsReady).toBeTruthy();
|
|
495
|
-
expect(aggregator.lifecycle.hasId).toBeTruthy();
|
|
496
|
-
expect(aggregator.lifecycle.hasNumber).toBeTruthy();
|
|
497
|
-
await flushAsync(ticks, microTurns, pause);
|
|
498
|
-
return [server, aggregator];
|
|
499
|
-
}
|
|
500
|
-
export async function stopServerNode(server, createOnly = false, ticks = 3, microTurns = 10, pause = 100) {
|
|
501
|
-
await flushAllEndpointNumberPersistence(server);
|
|
502
|
-
await assertAllEndpointNumbersPersisted(server);
|
|
503
|
-
expect(server).toBeDefined();
|
|
504
|
-
expect(server.lifecycle.isReady).toBeTruthy();
|
|
505
|
-
if (!createOnly) {
|
|
506
|
-
expect(server.lifecycle.isOnline).toBeTruthy();
|
|
507
|
-
}
|
|
508
|
-
await server.close();
|
|
509
|
-
expect(server.lifecycle.isReady).toBeFalsy();
|
|
510
|
-
expect(server.lifecycle.isOnline).toBeFalsy();
|
|
511
|
-
await flushAsync(ticks, microTurns, pause);
|
|
512
|
-
}
|
|
513
|
-
export async function addDevice(owner, device, pause = 10) {
|
|
514
|
-
expect(owner).toBeDefined();
|
|
515
|
-
expect(device).toBeDefined();
|
|
516
|
-
expect(owner.lifecycle.isReady).toBeTruthy();
|
|
517
|
-
expect(owner.construction.status).toBe(Lifecycle.Status.Active);
|
|
518
|
-
expect(owner.lifecycle.isPartsReady).toBeTruthy();
|
|
519
|
-
const targetServer = getOwningServerNode(owner);
|
|
520
|
-
await flushAllEndpointNumberPersistence(targetServer, 3, pause);
|
|
521
|
-
try {
|
|
522
|
-
await owner.add(device);
|
|
523
|
-
}
|
|
524
|
-
catch (error) {
|
|
525
|
-
const errorMessage = error instanceof Error ? error.message : error;
|
|
526
|
-
const errorInspect = inspect(error, { depth: 10 });
|
|
527
|
-
process.stderr.write(`${er}Error adding device ${device.maybeId}.${device.maybeNumber}: ${errorMessage}${rs}\nStack: ${errorInspect}\n`);
|
|
528
|
-
return false;
|
|
529
|
-
}
|
|
530
|
-
await device.construction.ready;
|
|
531
|
-
expect(owner.parts.has(device)).toBeTruthy();
|
|
532
|
-
expect(owner.lifecycle.isPartsReady).toBeTruthy();
|
|
533
|
-
expect(device.lifecycle.isReady).toBeTruthy();
|
|
534
|
-
expect(device.lifecycle.isInstalled).toBeTruthy();
|
|
535
|
-
expect(device.lifecycle.hasId).toBeTruthy();
|
|
536
|
-
expect(device.lifecycle.hasNumber).toBeTruthy();
|
|
537
|
-
expect(device.construction.status).toBe(Lifecycle.Status.Active);
|
|
538
|
-
await flushAllEndpointNumberPersistence(targetServer, 3, pause);
|
|
539
|
-
return true;
|
|
540
|
-
}
|
|
541
|
-
export async function deleteDevice(owner, device, pause = 10) {
|
|
542
|
-
expect(owner).toBeDefined();
|
|
543
|
-
expect(device).toBeDefined();
|
|
544
|
-
expect(owner.lifecycle.isReady).toBeTruthy();
|
|
545
|
-
expect(owner.construction.status).toBe(Lifecycle.Status.Active);
|
|
546
|
-
expect(owner.lifecycle.isPartsReady).toBeTruthy();
|
|
547
|
-
const targetServer = getOwningServerNode(owner);
|
|
548
|
-
await flushAllEndpointNumberPersistence(targetServer, 3, pause);
|
|
549
|
-
try {
|
|
550
|
-
await device.delete();
|
|
551
|
-
}
|
|
552
|
-
catch (error) {
|
|
553
|
-
const errorMessage = error instanceof Error ? error.message : error;
|
|
554
|
-
const errorInspect = inspect(error, { depth: 10 });
|
|
555
|
-
process.stderr.write(`${er}Error deleting device ${device.maybeId}.${device.maybeNumber}: ${errorMessage}${rs}\nStack: ${errorInspect}\n`);
|
|
556
|
-
return false;
|
|
557
|
-
}
|
|
558
|
-
expect(owner.parts.has(device)).toBeFalsy();
|
|
559
|
-
expect(owner.lifecycle.isPartsReady).toBeTruthy();
|
|
560
|
-
expect(device.lifecycle.isReady).toBeFalsy();
|
|
561
|
-
expect(device.lifecycle.isInstalled).toBeFalsy();
|
|
562
|
-
expect(device.lifecycle.hasId).toBeTruthy();
|
|
563
|
-
expect(device.lifecycle.hasNumber).toBeTruthy();
|
|
564
|
-
expect(device.construction.status).toBe(Lifecycle.Status.Destroyed);
|
|
565
|
-
await flushAllEndpointNumberPersistence(targetServer, 3, pause);
|
|
566
|
-
return true;
|
|
567
|
-
}
|
|
568
|
-
export function getMoveToLevelRequest(level, transitionTime, executeIfOff) {
|
|
569
|
-
const request = {
|
|
570
|
-
level,
|
|
571
|
-
transitionTime,
|
|
572
|
-
optionsMask: { executeIfOff, coupleColorTempToLevel: false },
|
|
573
|
-
optionsOverride: { executeIfOff, coupleColorTempToLevel: false },
|
|
574
|
-
};
|
|
575
|
-
return request;
|
|
576
|
-
}
|
|
577
|
-
export function getMoveToColorTemperatureRequest(colorTemperatureMireds, transitionTime, executeIfOff) {
|
|
578
|
-
const request = {
|
|
579
|
-
colorTemperatureMireds,
|
|
580
|
-
transitionTime,
|
|
581
|
-
optionsMask: { executeIfOff },
|
|
582
|
-
optionsOverride: { executeIfOff },
|
|
583
|
-
};
|
|
584
|
-
return request;
|
|
585
|
-
}
|
|
586
|
-
export function getMoveToHueRequest(hue, transitionTime, executeIfOff) {
|
|
587
|
-
const request = {
|
|
588
|
-
hue,
|
|
589
|
-
transitionTime,
|
|
590
|
-
direction: ColorControl.Direction.Shortest,
|
|
591
|
-
optionsMask: { executeIfOff },
|
|
592
|
-
optionsOverride: { executeIfOff },
|
|
593
|
-
};
|
|
594
|
-
return request;
|
|
595
|
-
}
|
|
596
|
-
export function getEnhancedMoveToHueRequest(enhancedHue, transitionTime, executeIfOff) {
|
|
597
|
-
const request = {
|
|
598
|
-
enhancedHue,
|
|
599
|
-
transitionTime,
|
|
600
|
-
direction: ColorControl.Direction.Shortest,
|
|
601
|
-
optionsMask: { executeIfOff },
|
|
602
|
-
optionsOverride: { executeIfOff },
|
|
603
|
-
};
|
|
604
|
-
return request;
|
|
605
|
-
}
|
|
606
|
-
export function getMoveToSaturationRequest(saturation, transitionTime, executeIfOff) {
|
|
607
|
-
const request = {
|
|
608
|
-
saturation,
|
|
609
|
-
transitionTime,
|
|
610
|
-
optionsMask: { executeIfOff },
|
|
611
|
-
optionsOverride: { executeIfOff },
|
|
612
|
-
};
|
|
613
|
-
return request;
|
|
614
|
-
}
|
|
615
|
-
export function getMoveToHueAndSaturationRequest(hue, saturation, transitionTime, executeIfOff) {
|
|
616
|
-
const request = {
|
|
617
|
-
hue,
|
|
618
|
-
saturation,
|
|
619
|
-
transitionTime,
|
|
620
|
-
optionsMask: { executeIfOff },
|
|
621
|
-
optionsOverride: { executeIfOff },
|
|
622
|
-
};
|
|
623
|
-
return request;
|
|
624
|
-
}
|
|
625
|
-
export function getEnhancedMoveToHueAndSaturationRequest(enhancedHue, saturation, transitionTime, executeIfOff) {
|
|
626
|
-
const request = {
|
|
627
|
-
enhancedHue,
|
|
628
|
-
saturation,
|
|
629
|
-
transitionTime,
|
|
630
|
-
optionsMask: { executeIfOff },
|
|
631
|
-
optionsOverride: { executeIfOff },
|
|
632
|
-
};
|
|
633
|
-
return request;
|
|
634
|
-
}
|
|
635
|
-
export function getMoveToColorRequest(colorX, colorY, transitionTime, executeIfOff) {
|
|
636
|
-
const request = {
|
|
637
|
-
colorX,
|
|
638
|
-
colorY,
|
|
639
|
-
transitionTime,
|
|
640
|
-
optionsMask: { executeIfOff },
|
|
641
|
-
optionsOverride: { executeIfOff },
|
|
642
|
-
};
|
|
643
|
-
return request;
|
|
644
|
-
}
|