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

220 lines
8.1 KiB
TypeScript

import { describe, it, expect, vi } from 'vitest'
// Mock logger + DeviceModel
vi.mock('@adonisjs/core/services/logger', () => ({
default: { info: vi.fn(), warn: vi.fn(), error: vi.fn(), debug: vi.fn() },
}))
vi.mock('#models/device', () => ({
default: {
updateOne: vi.fn().mockResolvedValue({ modifiedCount: 1 }),
createOrUpdate: vi.fn().mockResolvedValue({}),
},
}))
// Mock ConfigurationEngine
vi.mock('../../../../app/services/configuration_engine.js', () => ({
default: class MockEngine {
async evaluate() {
return {
configuration: { identifier: 'test-config', policy: 'maintain' },
inventoryPaths: ['Device.DeviceInfo.', 'Device.ManagementServer.'],
parameterDiffs: [{ name: 'Device.X', targetValue: '42', type: 'xsd:string' }],
firmwareMigrationPath: [],
skipFirmware: false,
isAlreadyCompliant: false,
}
}
},
}))
// Mock provisioning code
vi.mock('../../../../app/utils/provisioning_code.js', () => ({
buildProvisioningCode: () => 'test-prov-code-123',
}))
import {
ConfigurationEvaluationState,
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 { CWMPTaskQueue } from '../../../../app/services/cwmp/task_queue.js'
import { DeviceActionExecutor } from '../../../../app/services/cwmp/device_action_executor.js'
function makeContext(gpnStatus: 'supported' | 'unsupported' = 'supported') {
const device = new Device(new DeviceId('Mfr', 'OUI', 'PC', 'SN'))
device.setGpnStatus(gpnStatus)
const taskQueue = new CWMPTaskQueue()
return {
device,
session: 'sess-1',
message: undefined as { success: boolean; data: any } | undefined,
taskQueue,
services: { deviceAction: new DeviceActionExecutor(taskQueue) },
}
}
describe('ConfigurationEvaluationState', () => {
it('has name "configuration-evaluation"', () => {
const state = new ConfigurationEvaluationState()
expect(state.name).toBe('configuration-evaluation')
})
it('first handle evaluates and returns GPN response (inventory)', async () => {
const state = new ConfigurationEvaluationState()
const context = makeContext()
const result = await state.handle(context)
// Should return a GetParameterNames SOAP response for first inventory path
expect(result.response).toContain('GetParameterNames')
expect(result.response).toContain('Device.DeviceInfo.')
})
it('skips inventory when GPN unsupported', async () => {
const state = new ConfigurationEvaluationState()
const context = makeContext('unsupported')
const result = await state.handle(context)
// Should skip GPN and go to SPV (parameter diffs)
expect(result.response).toContain('SetParameterValues')
expect(result.response).toContain('Device.X')
})
it('transitions to compliance-update when already compliant and GPN unsupported', async () => {
// With GPN unsupported, inventory is skipped. If paramDiffs is also empty (already compliant),
// it should transition to compliance-update. But our mock always returns paramDiffs.
// So this test just verifies the second call returns SPV for the diffs.
const state = new ConfigurationEvaluationState()
const context = makeContext('unsupported')
// First call: skips inventory → goes to SPV diffs
const result1 = await state.handle(context)
expect(result1.response).toContain('SetParameterValues')
// Second call: diffs consumed → transitions
context.message = { success: true, data: { Status: 0 } }
const result2 = await state.handle(context)
expect(result2.nextState).toBeDefined()
expect(result2.nextState!.name).toBe('compliance-update')
})
})
describe('ConfigurationEvaluationState - full happy path', () => {
it('inventory GPN → GPV → SPV diffs → compliance-update', async () => {
const state = new ConfigurationEvaluationState()
const context = makeContext('supported')
// Step 1: First call → GPN for inventory path (Device.DeviceInfo.)
const r1 = await state.handle(context)
expect(r1.response).toContain('GetParameterNames')
expect(r1.response).toContain('Device.DeviceInfo.')
// Step 2: GPN response for DeviceInfo → sends next GPN for ManagementServer.
context.message = {
success: true,
data: {
ParameterList: {
ParameterInfoStruct: [{ Name: 'Device.DeviceInfo.Manufacturer', Writable: '0' }],
},
},
}
const r2 = await state.handle(context)
expect(r2.response).toContain('GetParameterNames')
expect(r2.response).toContain('Device.ManagementServer.')
// Step 3: GPN response for ManagementServer → extract leaves → GPV
context.message = {
success: true,
data: {
ParameterList: {
ParameterInfoStruct: [{ Name: 'Device.ManagementServer.PeriodicInformInterval', Writable: '1' }],
},
},
}
const r3 = await state.handle(context)
expect(r3.response).toContain('GetParameterValues')
// Step 4: GPV response → values saved → SPV diffs
context.message = {
success: true,
data: { ParameterList: { ParameterValueStruct: [{ Name: 'Device.ManagementServer.PeriodicInformInterval', Value: { _: '60' } }] } },
}
const r4 = await state.handle(context)
expect(r4.response).toContain('SetParameterValues')
expect(r4.response).toContain('Device.X')
// Step 5: SPV response → no firmware → transitions to compliance-update
context.message = { success: true, data: { Status: 0 } }
const r5 = await state.handle(context)
expect(r5.nextState).toBeDefined()
expect(r5.nextState!.name).toBe('compliance-update')
})
})
describe('ComplianceUpdateState', () => {
it('has name "compliance-update"', () => {
const engineResult = {
configuration: { identifier: 'cfg-1', policy: 'maintain' },
inventoryPaths: [],
parameterDiffs: [],
firmwareMigrationPath: [],
skipFirmware: false,
isAlreadyCompliant: true,
}
const state = new ComplianceUpdateState(engineResult as any)
expect(state.name).toBe('compliance-update')
})
it('first call writes provisioning code and returns SPV', async () => {
const engineResult = {
configuration: { identifier: 'cfg-1', policy: 'maintain' },
inventoryPaths: [],
parameterDiffs: [],
firmwareMigrationPath: [],
skipFirmware: false,
isAlreadyCompliant: true,
}
const state = new ComplianceUpdateState(engineResult as any)
const context = makeContext('supported')
const result = await state.handle(context)
// Should write provisioning code via SPV
expect(result.response).toContain('SetParameterValues')
expect(result.response).toContain('ProvisioningCode')
})
it('second call transitions to executing-commands', async () => {
const engineResult = {
configuration: { identifier: 'cfg-1', policy: 'maintain' },
inventoryPaths: [],
parameterDiffs: [],
firmwareMigrationPath: [],
skipFirmware: false,
isAlreadyCompliant: true,
}
const state = new ComplianceUpdateState(engineResult as any)
const context = makeContext('supported')
// First call: writes provisioning code
await state.handle(context)
// Second call: transitions to executing-commands
context.message = { success: true, data: { Status: 0 } }
const result2 = await state.handle(context)
expect(result2.nextState).toBeDefined()
expect(result2.nextState!.name).toBe('executing-commands')
})
it('without configuration transitions directly to executing-commands', async () => {
const engineResult = {
configuration: null,
inventoryPaths: [],
parameterDiffs: [],
firmwareMigrationPath: [],
skipFirmware: false,
isAlreadyCompliant: true,
}
const state = new ComplianceUpdateState(engineResult as any)
const context = makeContext('supported')
const result = await state.handle(context)
expect(result.nextState).toBeDefined()
expect(result.nextState!.name).toBe('executing-commands')
})
})