Files
UNITRONIX abc00b8804 feat(guest): enforce scoped guest access lifecycle
Bind guest WebSocket sessions to a single target peer, enforce grant lifecycle and atomic usage, add the guest issuer scope, and document the password-less security boundary.\n\nRefs #440\n\nThanks: INSOLVE (Honorary); Marco Jakobs (@jacotec); MyNameisStitch (@MyNameisStitch); Redspin (@playerumpknow)
2026-10-03 10:22:20 +02:00

112 lines
3.4 KiB
JavaScript

'use strict';
const assert = require('node:assert/strict');
const test = require('node:test');
const {
CDAPBridge,
Widget,
Action,
Severity,
WidgetType,
button,
createMessage,
gauge,
parseMessage,
toggle,
} = require('../src');
const {
authApiKey,
authDeviceToken,
authUserPassword,
} = require('../src/protocol');
test('protocol messages round-trip with strings and buffers', () => {
const raw = createMessage('telemetry', { temperature: 21 }, 'message-1');
const parsed = parseMessage(raw);
assert.equal(parsed.type, 'telemetry');
assert.deepEqual(parsed.payload, { temperature: 21 });
assert.equal(parsed.id, 'message-1');
assert.match(parsed.timestamp, /^\d{4}-\d{2}-\d{2}T/);
const fromBuffer = parseMessage(Buffer.from(raw));
assert.deepEqual(fromBuffer, parsed);
});
test('protocol constants and authentication payloads are stable', () => {
assert.equal(Action.SET, 'set');
assert.equal(Severity.WARNING, 'warning');
assert.equal(WidgetType.GAUGE, 'gauge');
assert.deepEqual(authApiKey('key', 'device', '2.0.0'), {
method: 'api_key',
key: 'key',
device_id: 'device',
client_version: '2.0.0',
});
assert.deepEqual(authDeviceToken('token'), {
method: 'device_token',
token: 'token',
device_id: '',
client_version: '1.0.0',
});
assert.deepEqual(authUserPassword('admin', 'password'), {
method: 'user_password',
username: 'admin',
password: 'password',
device_id: '',
client_version: '1.0.0',
});
});
test('widget factories produce CDAP-compatible manifests', () => {
const temperature = gauge('temperature', 'Temperature', { unit: 'C', max: 50 });
const heater = toggle('heater', 'Heater');
const restart = button('restart', 'Restart', { confirm: true });
assert.ok(temperature instanceof Widget);
assert.deepEqual(temperature.toJSON(), {
type: 'gauge',
id: 'temperature',
label: 'Temperature',
unit: 'C',
max: 50,
precision: 1,
permissions: { read: 'viewer' },
});
assert.deepEqual(heater.toJSON(), {
type: 'toggle',
id: 'heater',
label: 'Heater',
permissions: { read: 'viewer', control: 'operator' },
});
assert.equal(restart.toJSON().confirm, true);
});
test('bridge registration and command handling are testable without a network', async () => {
const bridge = new CDAPBridge({
server: 'ws://127.0.0.1:1/cdap',
apiKey: 'test-only-key',
deviceId: 'device-1',
heartbeatSec: 5,
});
const sent = [];
bridge._send = (type, payload) => sent.push({ type, payload });
bridge.addWidget(toggle('heater', 'Heater'));
bridge.onCommand('heater', ({ value }) => Boolean(value));
bridge._register();
assert.equal(sent[0].type, 'register');
assert.equal(sent[0].payload.widgets[0].id, 'heater');
assert.deepEqual(sent[0].payload.capabilities, ['telemetry', 'commands', 'alerts', 'logs']);
await bridge._handleCommand({
id: 'command-1',
payload: { widget_id: 'heater', action: Action.SET, value: true },
});
const response = sent.at(-1);
assert.equal(response.type, 'command_response');
assert.equal(response.payload.success, true);
assert.equal(response.payload.value, true);
});