fix(M06-C-R1): 校正控制箱与门锁协议
This commit is contained in:
@@ -2108,10 +2108,23 @@ async function assertIotMessages(pool, context) {
|
||||
roomId: String(device.roomId),
|
||||
commandId: '1782120000002',
|
||||
commandType: 'AddDevice',
|
||||
payload: { action: 'AddDevice', id: '1782120000002', timeout: 60 },
|
||||
payload: { action: 'AddDevice', time: 60, id: '1782120000002' },
|
||||
traceId: 'm06d-pair-test'
|
||||
});
|
||||
await service.markPublished(context.tenantId, '1782120000002');
|
||||
await service.handle(`/devicesend/${device.deviceId}`, Buffer.from(JSON.stringify({
|
||||
DeviceID: device.deviceId,
|
||||
id: '1782120000002',
|
||||
action: 'AddDevice',
|
||||
result: 'ok',
|
||||
timestamp: 1782120002
|
||||
})));
|
||||
const [pairPendingRows] = await pool.query(
|
||||
`SELECT status FROM qipai_iot_commands
|
||||
WHERE tenant_id = ? AND command_id = '1782120000002'`,
|
||||
[context.tenantId]
|
||||
);
|
||||
assert.deepEqual(pairPendingRows, [{ status: 'PUBLISHED' }]);
|
||||
await service.handle(`/devicesend/${device.deviceId}`, Buffer.from(JSON.stringify({
|
||||
DeviceID: device.deviceId,
|
||||
id: '1782120000002',
|
||||
@@ -2130,6 +2143,144 @@ async function assertIotMessages(pool, context) {
|
||||
[context.tenantId, device.id]
|
||||
);
|
||||
assert.deepEqual(linkRows, [{ subId: 'SUB-AUTO-001', subtype: '14', model: '701C' }]);
|
||||
|
||||
for (const [commandId, result, failureCode] of [
|
||||
['1782120000003', 'busy', 'DEVICE_BUSY'],
|
||||
['1782120000004', 'unconfirm', 'DEVICE_UNCONFIRM'],
|
||||
['1782120000005', 'fail', 'DEVICE_FAIL']
|
||||
]) {
|
||||
await service.createCommand({
|
||||
tenantId: context.tenantId,
|
||||
assetId: String(device.id),
|
||||
storeId: String(device.storeId),
|
||||
roomId: String(device.roomId),
|
||||
commandId,
|
||||
commandType: 'task',
|
||||
payload: { action: 'task', id: commandId, minute: 30, type: 1 },
|
||||
traceId: `m06c-task-${result}`
|
||||
});
|
||||
await service.markPublished(context.tenantId, commandId);
|
||||
await service.handle(`/devicesend/${device.deviceId}`, Buffer.from(JSON.stringify({
|
||||
DeviceID: device.deviceId, id: commandId, action: 'task', result
|
||||
})));
|
||||
const [failedRows] = await pool.query(
|
||||
`SELECT status, failure_code AS failureCode
|
||||
FROM qipai_iot_commands WHERE tenant_id = ? AND command_id = ?`,
|
||||
[context.tenantId, commandId]
|
||||
);
|
||||
assert.deepEqual(failedRows, [{ status: 'FAILED', failureCode }]);
|
||||
}
|
||||
|
||||
await service.createCommand({
|
||||
tenantId: context.tenantId,
|
||||
assetId: String(device.id),
|
||||
storeId: String(device.storeId),
|
||||
roomId: String(device.roomId),
|
||||
commandId: '1782120000006',
|
||||
commandType: 'task',
|
||||
payload: { action: 'task', id: '1782120000006', minute: 30, type: 1 },
|
||||
traceId: 'm06c-unknown-result'
|
||||
});
|
||||
await service.markPublished(context.tenantId, '1782120000006');
|
||||
await service.handle(`/devicesend/${device.deviceId}`, Buffer.from(JSON.stringify({
|
||||
DeviceID: device.deviceId,
|
||||
id: '1782120000006',
|
||||
action: 'task',
|
||||
result: 'vendor-new-code'
|
||||
})));
|
||||
const [unknownCommandRows] = await pool.query(
|
||||
`SELECT status, failure_code AS failureCode
|
||||
FROM qipai_iot_commands
|
||||
WHERE tenant_id = ? AND command_id = '1782120000006'`,
|
||||
[context.tenantId]
|
||||
);
|
||||
assert.deepEqual(unknownCommandRows, [{
|
||||
status: 'FAILED', failureCode: 'UNKNOWN_VENDOR_RESULT'
|
||||
}]);
|
||||
const [unknownEventRows] = await pool.query(
|
||||
`SELECT processing_status AS processingStatus,
|
||||
JSON_UNQUOTE(JSON_EXTRACT(raw_payload, '$.result')) AS rawResult,
|
||||
JSON_UNQUOTE(JSON_EXTRACT(normalized_payload, '$.result')) AS normalizedResult
|
||||
FROM qipai_iot_device_events
|
||||
WHERE tenant_id = ? AND command_id = '1782120000006'`,
|
||||
[context.tenantId]
|
||||
);
|
||||
assert.deepEqual(unknownEventRows, [{
|
||||
processingStatus: 'PROTOCOL_ERROR',
|
||||
rawResult: 'vendor-new-code',
|
||||
normalizedResult: 'UNKNOWN_VENDOR_RESULT'
|
||||
}]);
|
||||
const [unknownDeadRows] = await pool.query(
|
||||
`SELECT error_code AS errorCode
|
||||
FROM qipai_iot_dead_letters
|
||||
WHERE tenant_id = ? AND topic = ? AND error_code = 'UNKNOWN_VENDOR_RESULT'`,
|
||||
[context.tenantId, `/devicesend/${device.deviceId}`]
|
||||
);
|
||||
assert.deepEqual(unknownDeadRows, [{ errorCode: 'UNKNOWN_VENDOR_RESULT' }]);
|
||||
|
||||
await service.createCommand({
|
||||
tenantId: context.tenantId,
|
||||
assetId: String(device.id),
|
||||
storeId: String(device.storeId),
|
||||
roomId: String(device.roomId),
|
||||
commandId: '1782120000007',
|
||||
commandType: 'CtrlDevice',
|
||||
payload: {
|
||||
action: 'CtrlDevice', id: '1782120000007', subtype: '14', subID: 'SUB-AUTO-001',
|
||||
order: 'setkey', value: '123456'
|
||||
},
|
||||
traceId: 'm06c-credential-redaction'
|
||||
});
|
||||
const [credentialRows] = await pool.query(
|
||||
`SELECT JSON_UNQUOTE(JSON_EXTRACT(request_payload, '$.value')) AS value,
|
||||
JSON_UNQUOTE(JSON_EXTRACT(request_payload, '$.valueHash')) AS valueHash
|
||||
FROM qipai_iot_commands
|
||||
WHERE tenant_id = ? AND command_id = '1782120000007'`,
|
||||
[context.tenantId]
|
||||
);
|
||||
assert.equal(credentialRows[0].value, null);
|
||||
assert.match(credentialRows[0].valueHash, /^[a-f0-9]{64}$/);
|
||||
|
||||
const [commandCountRows] = await pool.query(
|
||||
`SELECT COUNT(*) AS total FROM qipai_iot_commands WHERE tenant_id = ?`,
|
||||
[context.tenantId]
|
||||
);
|
||||
const commandCountBeforeEvents = Number(commandCountRows[0].total);
|
||||
for (const [event, timestamp] of [['magstate', 1782120010], ['taskfinish', 1782120011]]) {
|
||||
await service.handle(`/devicesend/${device.deviceId}`, Buffer.from(JSON.stringify({
|
||||
DeviceID: device.deviceId, event, state: 'close', timestamp
|
||||
})));
|
||||
}
|
||||
const [commandCountAfterRows] = await pool.query(
|
||||
`SELECT COUNT(*) AS total FROM qipai_iot_commands WHERE tenant_id = ?`,
|
||||
[context.tenantId]
|
||||
);
|
||||
assert.equal(Number(commandCountAfterRows[0].total), commandCountBeforeEvents);
|
||||
const [nonAckRows] = await pool.query(
|
||||
`SELECT event_type AS eventType, command_id AS commandId
|
||||
FROM qipai_iot_device_events
|
||||
WHERE tenant_id = ? AND event_type IN ('magstate', 'taskfinish')
|
||||
ORDER BY event_type`,
|
||||
[context.tenantId]
|
||||
);
|
||||
assert.deepEqual(nonAckRows, [
|
||||
{ eventType: 'magstate', commandId: null },
|
||||
{ eventType: 'taskfinish', commandId: null }
|
||||
]);
|
||||
|
||||
await service.handle(`/devicewill/${device.deviceId}`, Buffer.from('close'));
|
||||
const [offlineRows] = await pool.query(
|
||||
`SELECT status, JSON_UNQUOTE(JSON_EXTRACT(state_snapshot, '$.will')) AS willValue
|
||||
FROM qipai_devices WHERE tenant_id = ? AND id = ?`,
|
||||
[context.tenantId, device.id]
|
||||
);
|
||||
assert.deepEqual(offlineRows, [{ status: 'OFFLINE', willValue: 'close' }]);
|
||||
const [willDeadRows] = await pool.query(
|
||||
`SELECT COUNT(*) AS total FROM qipai_iot_dead_letters
|
||||
WHERE tenant_id = ? AND topic = ? AND error_code = 'MQTT_PAYLOAD_INVALID'`,
|
||||
[context.tenantId, `/devicewill/${device.deviceId}`]
|
||||
);
|
||||
assert.equal(Number(willDeadRows[0].total), 0);
|
||||
}
|
||||
|
||||
async function assertCleaningTaskTransactions(pool, context) {
|
||||
@@ -2756,7 +2907,13 @@ try {
|
||||
,
|
||||
'door record credential hashing and masking',
|
||||
'low-battery alert upsert',
|
||||
'AddDevice ACK creates 701C parent-child topology'
|
||||
'AddDevice first ACK waits for final subtype and subID',
|
||||
'AddDevice final ACK creates 701C parent-child topology',
|
||||
'task busy unconfirm and fail remain failed command states',
|
||||
'unknown vendor result event dead-letter alert and command failure',
|
||||
'CtrlDevice credential request hashing and masking',
|
||||
'magstate and taskfinish remain events rather than ACKs',
|
||||
'raw devicewill close marks the device offline without invalid-payload dead-letter'
|
||||
,
|
||||
'concurrent cleaning claim has one winner',
|
||||
'cleaning state and event transaction rollback',
|
||||
|
||||
Reference in New Issue
Block a user