Files
adam-acs/tests/unit/services/cwmp/config_eval_extra.spec.ts
T

229 lines
7.7 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 ConfigurationModel
vi.mock('#models/configuration', () => ({
default: {
find: vi.fn().mockResolvedValue([]),
findOne: vi.fn().mockResolvedValue(null),
},
}))
// Mock FirmwareCatalogService
vi.mock('../../../../app/services/firmware_catalog_service.js', () => ({
default: class { async resolveUpgradePath() { return [] } },
FirmwareCatalogService: class { async resolveUpgradePath() { return [] } },
}))
// Mock schedule_window
vi.mock('../../../../app/utils/schedule_window.js', () => ({
isWithinSchedule: vi.fn().mockReturnValue(true),
}))
// Mock parameter_writability
vi.mock('../../../../app/services/parameter_writability.js', () => ({
reclassifyDeviceFromCatalog: vi.fn(),
}))
// 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 { ConfigurationEvaluationState, FirmwareUpgradeState, ComplianceUpdateState } from '../../../../app/services/cwmp/configuration_evaluation_state.js'
import { Device } from '../../../../app/domain/device/device.js'
import { DeviceId } from '../../../../app/domain/device/device_id.js'
import { DeviceParameter } from '../../../../app/domain/device/device_parameter.js'
import { CWMPTaskQueue } from '../../../../app/services/cwmp/task_queue.js'
import { DeviceActionExecutor } from '../../../../app/services/cwmp/device_action_executor.js'
import { ParameterType } from '../../../../app/domain/shared/types.js'
import ConfigurationModel from '#models/configuration'
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
}
function makeContext(device: Device) {
const taskQueue = new CWMPTaskQueue()
return {
device,
session: 'test-session',
taskQueue,
services: { deviceAction: new DeviceActionExecutor(taskQueue) },
}
}
function makeConfig(overrides: any = {}) {
return {
identifier: 'test-config',
name: 'Test Config',
version: '1.0.0',
priority: 1,
policy: 'maintain',
enabled: true,
criteria: { manufacturer: 'Sagemcom', productClass: 'Livebox 4' },
inventory: [{ path: 'Device.ManagementServer.' }],
desiredParameters: [
{ name: 'Device.ManagementServer.PeriodicInformInterval', value: '300', type: 'unsignedInt' },
],
firmwareTarget: null,
schedule: null,
tags: [],
...overrides,
}
}
describe('ConfigurationEvaluationState — inventory and diffs flow', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('sends SPV when parameterDiffs exist', async () => {
const config = makeConfig()
vi.mocked(ConfigurationModel.find).mockResolvedValue([config])
const device = makeDevice()
// Device has the param but with wrong value
device.setParameter(new DeviceParameter(
'Device.ManagementServer.PeriodicInformInterval', '60', ParameterType.UNSIGNED_INT
))
const context = makeContext(device)
const state = new ConfigurationEvaluationState()
const result = await state.handle(context as any)
// Since gpnStatus=unsupported, inventory GPN is skipped
// Should produce SPV for the diff
expect(result.response).toContain('SetParameterValues')
expect(result.response).toContain('PeriodicInformInterval')
expect(result.response).toContain('300')
})
it('skips inventory GPN when device gpnStatus is unsupported', async () => {
const config = makeConfig({ desiredParameters: [] })
vi.mocked(ConfigurationModel.find).mockResolvedValue([config])
const device = makeDevice()
const context = makeContext(device)
const state = new ConfigurationEvaluationState()
const result = await state.handle(context as any)
// No diffs, inventory skipped → should go to compliance-update
expect(result.nextState).toBeDefined()
// Should NOT send GPN
expect(result.response || '').not.toContain('GetParameterNames')
})
it('handles evaluation error gracefully with PartialOperationalState', async () => {
// Force evaluate to throw
vi.mocked(ConfigurationModel.find).mockRejectedValue(new Error('DB connection lost'))
const device = makeDevice()
const context = makeContext(device)
const state = new ConfigurationEvaluationState()
const result = await state.handle(context as any)
// Should transition to PartialOperationalState
expect(result.nextState).toBeDefined()
expect((result.nextState as any).name).toBe('partial_operational')
})
})
describe('FirmwareUpgradeState', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('sends Download RPC for firmware step', async () => {
const device = makeDevice()
const context = makeContext(device)
const steps = [
{ fromVersion: '4.0', toVersion: '5.0', downloadUrl: 'http://fw.example.com/5.0.bin', fileSize: 1024, checksum: 'abc' },
]
const engineResult = { configuration: makeConfig(), isAlreadyCompliant: false } as any
const state = new FirmwareUpgradeState(steps, 0, engineResult)
const result = await state.handle(context as any)
expect(result.response).toContain('Download')
expect(result.response).toContain('http://fw.example.com/5.0.bin')
expect(result.response).toContain('1 Firmware Upgrade Image')
})
it('transitions to ComplianceUpdateState when no more steps', async () => {
const device = makeDevice()
const context = makeContext(device)
const engineResult = { configuration: makeConfig(), isAlreadyCompliant: false } as any
const state = new FirmwareUpgradeState([], 0, engineResult) // empty steps
const result = await state.handle(context as any)
expect(result.nextState).toBeDefined()
expect((result.nextState as any).name).toBe('compliance-update')
})
})
describe('ComplianceUpdateState — provCode write when different', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('sends SPV for provisioning code when device has different value', async () => {
const device = makeDevice()
// Set a DIFFERENT provisioning code on the device
device.setParameter(new DeviceParameter(
'Device.DeviceInfo.ProvisioningCode', 'old-code', ParameterType.STRING
))
const context = makeContext(device)
const engineResult = {
configuration: makeConfig(),
isAlreadyCompliant: true,
} as any
const state = new ComplianceUpdateState(engineResult)
const result = await state.handle(context as any)
// Should produce an SPV for the provisioning code
expect(result.response).toContain('SetParameterValues')
expect(result.response).toContain('ProvisioningCode')
})
it('transitions to ExecutingCommandsState when no configuration', async () => {
const device = makeDevice()
const context = makeContext(device)
const engineResult = { configuration: null, isAlreadyCompliant: true } as any
const state = new ComplianceUpdateState(engineResult)
const result = await state.handle(context as any)
expect(result.nextState).toBeDefined()
expect((result.nextState as any).name).toBe('executing-commands')
})
})