import assert from 'node:assert/strict'; import { OrderDeviceAutomationError, OrderDeviceAutomationService } from '../dist/devices/order-device-automation-service.js'; import { DeviceControlError } from '../dist/devices/device-control-service.js'; const calls = []; let currentOrder = { id: 31, tenantId: 7, storeId: 11, roomId: 51, status: 'IN_PROGRESS', startAt: new Date(Date.now() - 30 * 60_000), endAt: new Date(Date.now() + 90 * 60_000) }; const service = new OrderDeviceAutomationService({ async execute(sql, params) { if (sql.includes('FROM qipai_orders')) { assert.equal(params[0], '7'); assert.equal(params[1], '31'); return [[currentOrder], []]; } return [[], []]; } }, { async startTask(context, input) { calls.push(['startTask', context, input]); return { status: 'PUBLISHED' }; }, async extendTask(context, addminute) { calls.push(['extendTask', context, addminute]); return { status: 'PUBLISHED' }; }, async cancelTask(context) { calls.push(['cancelTask', context]); return { status: 'PUBLISHED' }; }, async switchSmartSocket(context, input) { calls.push(['socket', context, input]); if (context.roomId === '52') { throw new DeviceControlError('SMART_SOCKET_NOT_BOUND'); } return { status: 'PUBLISHED' }; } }); function task(payload) { return { id: '1', tenantId: '7', taskType: 'device.command', idempotencyKey: `device:${payload.orderId}:${payload.event}`, payload, status: 'RUNNING', attempts: 1, maxAttempts: 8 }; } await service.handleTask(task({ tenantId: '7', orderId: '31', event: 'ORDER_STARTED', traceId: 'm06f-start' })); assert.equal(calls[0][0], 'startTask'); assert.equal(calls[0][2].type, 2); assert.equal(calls[0][1].access.roles.includes('PLATFORM_ADMIN'), true); assert.equal(calls[1][0], 'socket'); assert.equal(calls[1][2].on, true); await service.handleTask(task({ tenantId: '7', orderId: '31', event: 'ORDER_RENEWED', addMinutes: 45, traceId: 'm06f-renew' })); assert.equal(calls.at(-2)[0], 'extendTask'); assert.equal(calls.at(-2)[2], 45); assert.equal(calls.at(-1)[2].on, true); await service.handleTask(task({ tenantId: '7', orderId: '31', event: 'ORDER_CANCELLED', traceId: 'm06f-cancel' })); assert.equal(calls.at(-2)[0], 'cancelTask'); assert.equal(calls.at(-1)[2].on, false); currentOrder = { ...currentOrder, roomId: 52, status: 'IN_PROGRESS' }; const changed = await service.handleTask(task({ tenantId: '7', orderId: '31', event: 'ORDER_ROOM_CHANGED', previousRoomId: '51', traceId: 'm06f-room' })); assert.equal(changed.action, 'ROOM_CHANGED'); assert.equal(calls.at(-3)[0], 'socket'); assert.equal(calls.at(-3)[1].roomId, '51'); assert.equal(calls.at(-3)[2].on, false); assert.equal(calls.at(-2)[0], 'startTask'); assert.equal(calls.at(-2)[1].roomId, '52'); currentOrder = { ...currentOrder, status: 'CLOSED' }; const skipped = await service.handleTask(task({ tenantId: '7', orderId: '31', event: 'ORDER_STARTED', traceId: 'm06f-stale' })); assert.equal(skipped.skipped, true); await assert.rejects( () => service.handleTask({ ...task({ tenantId: '8', orderId: '31', event: 'ORDER_STARTED' }), tenantId: '7' }), (error) => error instanceof OrderDeviceAutomationError && error.code === 'DEVICE_TASK_TENANT_MISMATCH' ); console.log('PASS: M06-F order device automation dispatches start, renew, cancel and room-change tasks safely.');