Files
adam-acs/tests/unit/services/cwmp/device_action_executor.spec.ts
T
2026-06-08 16:46:38 +02:00

218 lines
8.2 KiB
TypeScript

import { describe, it, expect, beforeEach, vi } from 'vitest'
import { DeviceActionExecutor } from '../../../../app/services/cwmp/device_action_executor.js'
import { CWMPTaskQueue, ResultStatus } from '../../../../app/services/cwmp/task_queue.js'
import { Device } from '../../../../app/domain/device/device.js'
import { DeviceId } from '../../../../app/domain/device/device_id.js'
import { DeviceProperty } from '../../../../app/domain/device/device_parameter.js'
import { ParameterType } from '../../../../app/domain/shared/types.js'
// Mock AdonisJS logger
vi.mock('@adonisjs/core/services/logger', () => ({
default: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
}))
// Mock DeviceModel to avoid MongoDB dependency
vi.mock('../../../../app/models/device.js', () => ({
default: { createOrUpdate: vi.fn().mockResolvedValue(undefined) },
}))
function makeDevice(): Device {
return new Device(new DeviceId('TestMfr', 'AABBCC', 'TestBox', 'SN001'))
}
describe('DeviceActionExecutor', () => {
let queue: CWMPTaskQueue
let executor: DeviceActionExecutor
beforeEach(() => {
queue = new CWMPTaskQueue()
executor = new DeviceActionExecutor(queue)
})
describe('getParameterValues', () => {
it('returns SOAP XML for GPV', async () => {
const device = makeDevice()
const xml = await executor.getParameterValues(device, ['Device.Info.SW'], async () => '')
expect(xml).toContain('GetParameterValues')
expect(xml).toContain('Device.Info.SW')
})
})
describe('getParameterNames', () => {
it('returns SOAP XML for GPN', async () => {
const device = makeDevice()
const xml = await executor.getParameterNames(device, 'Device.ManagementServer.', async () => '')
expect(xml).toContain('GetParameterNames')
expect(xml).toContain('Device.ManagementServer.')
})
})
describe('getParameterAttributes', () => {
it('returns SOAP XML for GPA', async () => {
const device = makeDevice()
const xml = await executor.getParameterAttributes(device, ['Device.Info.'], async () => '')
expect(xml).toContain('GetParameterAttributes')
})
})
describe('applySetParameterValues', () => {
it('returns SOAP XML for SPV', async () => {
const device = makeDevice()
const xml = await executor.applySetParameterValues(
device,
[{ parameterName: 'Device.X', value: '42', type: 'unsignedInt' }],
async () => ''
)
expect(xml).toContain('SetParameterValues')
expect(xml).toContain('Device.X')
})
})
describe('applySetParameterAttributes', () => {
it('returns SOAP XML for SPA', async () => {
const device = makeDevice()
const xml = await executor.applySetParameterAttributes(
device,
[{ name: 'Device.X', notificationChange: true, notification: 2, accessListChange: false, accessList: [] }],
async () => ''
)
expect(xml).toContain('SetParameterAttributes')
})
})
describe('applyScheduleInform', () => {
it('returns SOAP XML for ScheduleInform', async () => {
const device = makeDevice()
const xml = await executor.applyScheduleInform(device, { delayInSeconds: 300 }, async () => '')
expect(xml).toContain('ScheduleInform')
expect(xml).toContain('300')
})
})
describe('applyReboot', () => {
it('returns SOAP XML for Reboot', async () => {
const device = makeDevice()
const xml = await executor.applyReboot(device, { commandKey: 'reboot-1' }, async () => '')
expect(xml).toContain('Reboot')
expect(xml).toContain('reboot-1')
})
})
describe('applyFactoryReset', () => {
it('returns SOAP XML for FactoryReset', async () => {
const device = makeDevice()
const xml = await executor.applyFactoryReset(device, {}, async () => '')
expect(xml).toContain('FactoryReset')
})
})
describe('applyDownload', () => {
it('returns SOAP XML for Download', async () => {
const device = makeDevice()
const xml = await executor.applyDownload(
device,
{ commandKey: 'fw', fileType: '1 Firmware Upgrade Image', url: 'http://fw.bin' },
async () => ''
)
expect(xml).toContain('Download')
expect(xml).toContain('http://fw.bin')
})
})
describe('applyUpload', () => {
it('returns SOAP XML for Upload', async () => {
const device = makeDevice()
const xml = await executor.applyUpload(
device,
{ commandKey: 'cfg', fileType: '3 Vendor Configuration File', url: 'http://up.bin' },
async () => ''
)
expect(xml).toContain('Upload')
expect(xml).toContain('http://up.bin')
})
})
describe('insertProvisioningCode', () => {
it('returns SOAP XML for SPV with provisioning code', async () => {
const device = makeDevice()
device.setDataModel('tr-181')
device.tmpPC = 'my-prov-code'
const xml = await executor.insertProvisioningCode(device, async () => '')
expect(xml).toContain('SetParameterValues')
expect(xml).toContain('ProvisioningCode')
})
it('stores ProvisioningCode as a writable parameter (not property) after SPV success', async () => {
const device = makeDevice()
device.setDataModel('tr-181')
device.tmpPC = 'code-xyz'
// Device has no prior ProvisioningCode entry
await executor.insertProvisioningCode(device, async () => '')
const task = queue.getTaskForDevice(device.deviceId)
await queue.executeTaskCallback(task!.id, ResultStatus.Success, {})
// Must be in parameters (writable), not properties (read-only)
const asParam = device.getParameter('Device.DeviceInfo.ProvisioningCode')
const asProp = device.getProperty('Device.DeviceInfo.ProvisioningCode')
expect(asParam).not.toBeNull()
expect(asProp).toBeNull()
expect(asParam!.value).toBe('code-xyz')
})
it('keeps ProvisioningCode as parameter when it already exists as property (reclassifies)', async () => {
const device = makeDevice()
device.setDataModel('tr-181')
device.tmpPC = 'new-code'
// Simulate device where ProvisioningCode was incorrectly stored as property
device.setProperty(new DeviceProperty('Device.DeviceInfo.ProvisioningCode', 'old-code', ParameterType.STRING))
await executor.insertProvisioningCode(device, async () => '')
const task = queue.getTaskForDevice(device.deviceId)
await queue.executeTaskCallback(task!.id, ResultStatus.Success, {})
// After SPV success, must be a parameter, not a property
const asParam = device.getParameter('Device.DeviceInfo.ProvisioningCode')
const asProp = device.getProperty('Device.DeviceInfo.ProvisioningCode')
expect(asParam).not.toBeNull()
expect(asProp).toBeNull()
expect(asParam!.value).toBe('new-code')
})
})
describe('callbacks', () => {
it('GPV callback updates device on success', async () => {
const device = makeDevice()
let callbackCalled = false
executor.getParameterValues(device, ['Device.Info.SW'], async () => { callbackCalled = true; return '' })
// Simulate task completion
const task = queue.getTaskForDevice(device.deviceId)
expect(task).toBeDefined()
await queue.executeTaskCallback(task!.id, ResultStatus.Success, {
ParameterList: { ParameterValueStruct: [{ Name: 'Device.Info.SW', Value: { _: '2.0', $: { Type: 'xsd:string' } } }] },
})
expect(callbackCalled).toBe(true)
})
it('GPN callback sets gpnStatus to supported on success', async () => {
const device = makeDevice()
executor.getParameterNames(device, 'Device.', async () => '')
const task = queue.getTaskForDevice(device.deviceId)
await queue.executeTaskCallback(task!.id, ResultStatus.Success, {
ParameterList: { ParameterInfoStruct: [{ Name: 'Device.Info.SW', Writable: '1' }] },
})
expect(device.gpnStatus).toBe('supported')
})
it('GPN callback sets gpnStatus to unsupported on fault 9001', async () => {
const device = makeDevice()
executor.getParameterNames(device, 'Device.', async () => '')
const task = queue.getTaskForDevice(device.deviceId)
await queue.executeTaskCallback(task!.id, ResultStatus.Error, { FaultCode: 9001 })
expect(device.gpnStatus).toBe('unsupported')
})
})
})