172 lines
6.5 KiB
TypeScript
172 lines
6.5 KiB
TypeScript
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')
|
|
})
|
|
})
|