139 lines
4.8 KiB
JavaScript
139 lines
4.8 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { buildApp } from '../dist/app.js';
|
|
import { signAccessToken } from '../dist/auth/jwt.js';
|
|
import {
|
|
DeviceControlError,
|
|
DeviceControlService
|
|
} from '../dist/devices/device-control-service.js';
|
|
|
|
const managerAccess = {
|
|
roles: ['STORE_ADMIN'],
|
|
capabilities: ['device.read', 'device.write'],
|
|
storeIds: ['11']
|
|
};
|
|
const issued = [];
|
|
const service = new DeviceControlService({
|
|
async execute(sql) {
|
|
if (sql.includes('FROM qipai_devices')) {
|
|
return [[{
|
|
id: 51, deviceId: 'BOX_001', storeId: 11, roomId: 31,
|
|
deviceType: 'CONTROL_BOX', status: 'ONLINE'
|
|
}], []];
|
|
}
|
|
return [[], []];
|
|
}
|
|
}, {
|
|
async issue(input) {
|
|
const payload = input.payloadFactory('1782120000001');
|
|
issued.push({ ...input, payload });
|
|
return { commandId: '1782120000001', status: 'PUBLISHED' };
|
|
}
|
|
});
|
|
const context = {
|
|
tenantId: '7', storeId: '11', roomId: '31', traceId: 'trace',
|
|
access: managerAccess
|
|
};
|
|
|
|
await service.controlPower(context, { slot1: 'on', slot3: 'off' });
|
|
assert.equal(issued.at(-1).payload.action, 'ConctolPower');
|
|
await service.controlDoor(context, { order: 'open', holdopen: 1, delayTime: 4 });
|
|
assert.equal(issued.at(-1).payload.action, 'Crldoor');
|
|
await service.playTts(context, { content: '欢迎光临' });
|
|
assert.equal(issued.at(-1).payload.action, 'PlayTTS');
|
|
await service.stopTts(context);
|
|
assert.equal(issued.at(-1).payload.action, 'stopTTS');
|
|
await service.controlLed(context, 30);
|
|
assert.equal(issued.at(-1).payload.action, 'CrlLED');
|
|
await service.startTask(context, { minute: 120, type: 2, subID: 'SUB001' });
|
|
assert.equal(issued.at(-1).payload.action, 'task');
|
|
await service.extendTask(context, 30);
|
|
assert.equal(issued.at(-1).payload.action, 'addtask');
|
|
await service.cancelTask(context);
|
|
assert.equal(issued.at(-1).payload.action, 'canceltask');
|
|
await service.pairSubLock(context, 60);
|
|
assert.equal(issued.at(-1).payload.action, 'AddDevice');
|
|
await service.controlSubLock(context, {
|
|
subID: 'SUB001', order: 'open', delayTime: 4
|
|
});
|
|
assert.equal(issued.at(-1).payload.action, 'CtrlDevice');
|
|
|
|
await assert.rejects(
|
|
() => service.controlSubLock(context, {
|
|
subID: 'SUB001', order: 'factoryreset', dangerConfirmation: 'CONFIRM_FACTORY_RESET'
|
|
}),
|
|
(error) => error instanceof DeviceControlError
|
|
&& error.code === 'DEVICE_DANGEROUS_ACTION_FORBIDDEN'
|
|
);
|
|
const platformContext = {
|
|
...context,
|
|
access: { roles: ['PLATFORM_ADMIN'], capabilities: [], storeIds: [] }
|
|
};
|
|
await service.controlSubLock(platformContext, {
|
|
subID: 'SUB001', order: 'factoryreset', dangerConfirmation: 'CONFIRM_FACTORY_RESET'
|
|
});
|
|
assert.equal(issued.at(-1).payload.order, 'factoryreset');
|
|
assert.equal('dangerConfirmation' in issued.at(-1).payload, false);
|
|
|
|
await assert.rejects(
|
|
() => service.controlPower({
|
|
...context,
|
|
access: { roles: ['STAFF'], capabilities: ['device.write'], storeIds: ['12'] }
|
|
}, { slot1: 'on' }),
|
|
(error) => error.code === 'DEVICE_SCOPE_FORBIDDEN'
|
|
);
|
|
|
|
const secret = 'test-only-jwt-secret-with-at-least-32-characters';
|
|
const token = signAccessToken({
|
|
sub: '22', sid: '5c4d3af8-c63c-4edb-bf95-b84127bb3f6e',
|
|
tid: '7', aid: '9', rv: 1
|
|
}, secret, 900);
|
|
let routed;
|
|
const app = await buildApp({
|
|
deviceControl: {
|
|
jwtSecret: secret,
|
|
authRepository: {
|
|
async validateSession() {
|
|
return {
|
|
id: '5c4d3af8-c63c-4edb-bf95-b84127bb3f6e',
|
|
tenantId: '7', platformAppId: '9', expiresAt: new Date(Date.now() + 60000),
|
|
user: {
|
|
id: '22', tenantId: '7', userType: 'STAFF', status: 'ACTIVE',
|
|
roleVersion: 1, nickname: '', avatarUrl: '', phone: ''
|
|
}
|
|
};
|
|
}
|
|
},
|
|
accessControl: { async getAccessProfile() { return managerAccess; } },
|
|
service: {
|
|
async controlPower(_context, input) { routed = input; return { status: 'PUBLISHED' }; },
|
|
async controlDoor() { return {}; },
|
|
async playTts() { return {}; },
|
|
async stopTts() { return {}; },
|
|
async controlLed() { return {}; },
|
|
async startTask() { return {}; },
|
|
async extendTask() { return {}; },
|
|
async cancelTask() { return {}; },
|
|
async pairSubLock() { return {}; },
|
|
async controlSubLock() { return {}; }
|
|
}
|
|
}
|
|
});
|
|
const response = await app.inject({
|
|
method: 'POST',
|
|
url: '/admin-api/device-control/power',
|
|
headers: { authorization: `Bearer ${token}` },
|
|
payload: { storeId: '11', roomId: '31', slot1: 'on' }
|
|
});
|
|
assert.equal(response.statusCode, 200);
|
|
assert.equal(routed.slot1, 'on');
|
|
const invalid = await app.inject({
|
|
method: 'POST',
|
|
url: '/admin-api/device-control/door',
|
|
headers: { authorization: `Bearer ${token}` },
|
|
payload: { storeId: '11', roomId: '31', order: 'open', delayTime: 15 }
|
|
});
|
|
assert.equal(invalid.statusCode, 400);
|
|
await app.close();
|
|
|
|
console.log('PASS: M06-D control-box and Sub-1G command gates and routes work.');
|