import { describe, it, expect, vi, beforeEach } from 'vitest' // Mock logger vi.mock('@adonisjs/core/services/logger', () => ({ default: { info: vi.fn(), debug: vi.fn(), warn: vi.fn(), error: vi.fn() }, })) // Mock DeviceModel vi.mock('../../../../app/models/device.js', () => ({ default: { createOrUpdate: vi.fn().mockResolvedValue({}), findByDeviceId: vi.fn().mockResolvedValue(null), updateOne: vi.fn().mockResolvedValue({}), }, })) // Mock CommandModel vi.mock('../../../../app/models/command.js', () => ({ default: { getCollection: vi.fn().mockReturnValue({ find: vi.fn().mockReturnValue({ sort: vi.fn().mockReturnValue({ toArray: vi.fn().mockResolvedValue([]) }) }), countDocuments: vi.fn().mockResolvedValue(0), }), updateCommand: vi.fn().mockResolvedValue({}), }, })) import { StateMachineManager, MachineStatus } from '../../../../app/services/cwmp/state_machine_manager.js' import { Device } from '../../../../app/domain/device/device.js' import { DeviceId } from '../../../../app/domain/device/device_id.js' import { CWMPTaskQueue, ResultStatus } from '../../../../app/services/cwmp/task_queue.js' function makeDevice(): Device { const device = new Device(new DeviceId('Sagemcom', 'F8B7FF', 'Livebox 4', 'LB4-TEST-001')) device.setDataModel('tr-181') device.setGpnStatus('unsupported') return device } describe('StateMachineManager — additional coverage', () => { let manager: StateMachineManager beforeEach(() => { vi.clearAllMocks() ;(StateMachineManager as any).instance = null manager = StateMachineManager.getInstance() }) // --- Event routing for less common events --- it('routes VALUE_CHANGE to MainStateMachine', async () => { const device = makeDevice() const machine = await manager.startStateMachine(device, 'sess-vc', { events: ['4 VALUE CHANGE'] }) expect(machine.status).toBe(MachineStatus.RUNNING) expect(machine.stateMachine.getCurrentStateName()).toBeDefined() }) it('routes DIAGNOSTICS_COMPLETE to ImmediateCommandStateMachine', async () => { const device = makeDevice() const machine = await manager.startStateMachine(device, 'sess-dc', { events: ['8 DIAGNOSTICS COMPLETE'] }) expect(machine.stateMachine.getCurrentStateName()).toBe('executing-commands') }) it('routes TRANSFER_COMPLETE to ImmediateCommandStateMachine', async () => { const device = makeDevice() const machine = await manager.startStateMachine(device, 'sess-tc', { events: ['7 TRANSFER COMPLETE'] }) expect(machine.stateMachine.getCurrentStateName()).toBe('executing-commands') }) it('routes SCHEDULED to MainStateMachine', async () => { const device = makeDevice() const machine = await manager.startStateMachine(device, 'sess-sc', { events: ['3 SCHEDULED'] }) expect(machine.stateMachine.getCurrentStateName()).toBeDefined() }) it('routes unknown event to MainStateMachine', async () => { const device = makeDevice() const machine = await manager.startStateMachine(device, 'sess-unk', { events: ['99 UNKNOWN'] }) expect(machine.stateMachine.getCurrentStateName()).toBeDefined() }) // --- getHashIdForRemote --- it('getHashIdForRemote returns hash for bound remote', async () => { const device = makeDevice() await manager.startStateMachine(device, 'sess-hr', { events: ['0 BOOTSTRAP'] }) manager.bindRemoteToDevice('10.0.0.1:5000', device.deviceId) const hash = manager.getHashIdForRemote('10.0.0.1:5000') expect(hash).toBeDefined() expect(typeof hash).toBe('string') expect(hash!.length).toBeGreaterThan(0) }) it('getHashIdForRemote returns undefined for unknown remote', () => { expect(manager.getHashIdForRemote('unknown:9999')).toBeUndefined() }) // --- handleCWMPResponse with fault --- it('handleCWMPResponse calls failTask on fault response when lastTaskId set', async () => { const device = makeDevice() const machine = await manager.startStateMachine(device, 'sess-fault', { events: ['0 BOOTSTRAP'] }) await manager.continue('sess-fault') // Set lastTaskId const taskId = 'task-fail-test' machine.lastTaskId = taskId const taskQueue = manager.getTaskQueue() ;(taskQueue as any).tasks.set(taskId, { id: taskId, deviceId: device.deviceId, type: 'test', payload: {}, status: 'SENT', createdAt: new Date() }) ;(taskQueue as any).taskCallbacks.set(taskId, vi.fn().mockResolvedValue('')) await manager.handleCWMPResponse('sess-fault', false, { FaultCode: 9001 }) // lastTaskId should be cleared expect(machine.lastTaskId).toBeUndefined() }) // --- continue() serving task from queue --- it('continue() serves pending task from queue when state machine returns empty', async () => { const device = makeDevice() const machine = await manager.startStateMachine(device, 'sess-q', { events: ['6 CONNECTION REQUEST'] }) // Start the immediate command SM (completes with no commands) await manager.continue('sess-q') // After completion, continue should return '' const response = await manager.continue('sess-q') expect(response).toBe('') }) // --- CONNECTION REQUEST replaces existing immediate --- it('new CONNECTION_REQUEST completes old immediate machine and creates fresh', async () => { const device = makeDevice() const machine1 = await manager.startStateMachine(device, 'sess-cr1', { events: ['6 CONNECTION REQUEST'] }) expect(machine1.status).toBe(MachineStatus.RUNNING) // New CONNECTION REQUEST on different session const machine2 = await manager.startStateMachine(device, 'sess-cr2', { events: ['6 CONNECTION REQUEST'] }) expect(machine1.status).toBe(MachineStatus.COMPLETED) expect(machine2.session).toBe('sess-cr2') }) // --- stopCleanupTimer / startCleanupTimer --- it('startCleanupTimer and stopCleanupTimer work without error', () => { manager.startCleanupTimer(100000) manager.startCleanupTimer(100000) // second call should be no-op manager.stopCleanupTimer() manager.stopCleanupTimer() // second stop should be no-op }) // --- shutdown --- it('shutdown stops timer and cleans up', async () => { manager.startCleanupTimer(100000) await manager.shutdown() // Should not throw }) // --- enqueueImmediate --- it('enqueueImmediate adds task to queue', async () => { const device = makeDevice() await manager.enqueueImmediate(device, 'get_parameter_values', ['Device.Info.SW']) const task = manager.getTaskQueue().getTaskForDevice(device.deviceId) expect(task).toBeDefined() expect(task!.task).toBe('get_parameter_values') }) })