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') }) })