feat(M06-B): 建立设备资产与拓扑管理
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { buildApp } from '../dist/app.js';
|
||||
import { signAccessToken } from '../dist/auth/jwt.js';
|
||||
import { DeviceError, DeviceRepository } from '../dist/devices/device-repository.js';
|
||||
|
||||
const storeActor = {
|
||||
tenantId: '7', userId: '22',
|
||||
access: {
|
||||
roles: ['STORE_ADMIN'], capabilities: ['device.read', 'device.write'], storeIds: ['11']
|
||||
},
|
||||
traceId: 'trace', ip: '127.0.0.1', userAgent: 'test'
|
||||
};
|
||||
const repository = new DeviceRepository({
|
||||
async execute(sql) {
|
||||
if (sql.includes('FROM qipai_devices d')) {
|
||||
return [[{
|
||||
id: 51, storeId: 11, roomId: 31, deviceId: 'BOX_001', imei: '123',
|
||||
iccid: null, deviceType: 'CONTROL_BOX', model: 'BOX', firmwareVersion: '1.0',
|
||||
status: 'ONLINE', signalStrength: 20, capabilities: '["LOCK","POWER"]',
|
||||
stateSnapshot: '{"power":true}', lastSeenAt: null, lastHeartbeatAt: null,
|
||||
maintenanceStatus: 'NORMAL'
|
||||
}], []];
|
||||
}
|
||||
return [[], []];
|
||||
}
|
||||
});
|
||||
const assets = await repository.listAssets(storeActor, '11');
|
||||
assert.equal(assets[0].id, '51');
|
||||
assert.deepEqual(assets[0].capabilities, ['LOCK', 'POWER']);
|
||||
assert.deepEqual(assets[0].stateSnapshot, { power: true });
|
||||
await assert.rejects(
|
||||
() => repository.listAssets(storeActor, '12'),
|
||||
(error) => error instanceof DeviceError && 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 assetInput;
|
||||
let channelInput;
|
||||
const app = await buildApp({
|
||||
devices: {
|
||||
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 storeActor.access; } },
|
||||
repository: {
|
||||
async listAssets() { return []; },
|
||||
async getTopology() {
|
||||
return { assets: [], channels: [], links: [], openAlerts: [], maintenance: [] };
|
||||
},
|
||||
async createAsset(_actor, input) { assetInput = input; return { assetId: '51' }; },
|
||||
async bindChannel(_actor, input) { channelInput = input; return { assetId: '51' }; },
|
||||
async bindSubDevice() { return { childAssetId: '52', parentAssetId: '51' }; },
|
||||
async recordStatus() { return { assetId: '51' }; },
|
||||
async addMaintenance() { return { maintenanceId: '61' }; }
|
||||
}
|
||||
}
|
||||
});
|
||||
const created = await app.inject({
|
||||
method: 'POST', url: '/admin-api/devices',
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: {
|
||||
storeId: '11', roomId: '31', deviceId: 'BOX_001', imei: '123456789012345',
|
||||
deviceType: 'CONTROL_BOX', model: 'JL-BOX', firmwareVersion: '1.0.0',
|
||||
signalStrength: 18, capabilities: ['POWER', 'LOCK', 'POWER']
|
||||
}
|
||||
});
|
||||
assert.equal(created.statusCode, 201);
|
||||
assert.equal(created.json().data.assetId, '51');
|
||||
assert.equal(assetInput.deviceType, 'CONTROL_BOX');
|
||||
|
||||
const channel = await app.inject({
|
||||
method: 'POST', url: '/admin-api/device-channels',
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: {
|
||||
assetId: '51', storeId: '11', roomId: '31',
|
||||
channelCode: 'SLOT1', purpose: 'ROOM_POWER'
|
||||
}
|
||||
});
|
||||
assert.equal(channel.statusCode, 201);
|
||||
assert.equal(channelInput.purpose, 'ROOM_POWER');
|
||||
|
||||
const invalid = await app.inject({
|
||||
method: 'POST', url: '/admin-api/device-channels',
|
||||
headers: { authorization: `Bearer ${token}` },
|
||||
payload: {
|
||||
assetId: '51', storeId: '11', roomId: '31',
|
||||
channelCode: 'SLOT9', purpose: 'ROOM_POWER'
|
||||
}
|
||||
});
|
||||
assert.equal(invalid.statusCode, 400);
|
||||
await app.close();
|
||||
|
||||
console.log('PASS: M06-B device assets, scope, topology routes and target validation work.');
|
||||
Reference in New Issue
Block a user