diff --git a/tests/unit/services/cwmp/config_eval_extra.spec.ts b/tests/unit/services/cwmp/config_eval_extra.spec.ts index 34f4661..c2a667f 100644 --- a/tests/unit/services/cwmp/config_eval_extra.spec.ts +++ b/tests/unit/services/cwmp/config_eval_extra.spec.ts @@ -226,3 +226,117 @@ describe('ComplianceUpdateState — provCode write when different', () => { expect((result.nextState as any).name).toBe('executing-commands') }) }) + +describe('ConfigurationEvaluationState — inventory GPN phase (device supports GPN)', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('sends GPN for inventory paths when device supports GPN', async () => { + const config = makeConfig({ + desiredParameters: [{ name: 'Device.ManagementServer.PeriodicInformInterval', value: '300', type: 'unsignedInt' }], + inventory: [{ path: 'Device.ManagementServer.' }], + }) + vi.mocked(ConfigurationModel.find).mockResolvedValue([config]) + + const device = makeDevice() + device.setGpnStatus('supported') // Device supports GPN + // Set a different value so isAlreadyCompliant = false (triggers inventory) + 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) + + // Should send GetParameterNames for the inventory path + expect(result.response).toBeDefined() + expect(result.response).toContain('GetParameterNames') + expect(result.response).toContain('Device.ManagementServer.') + }) + + it('inventory GPN response then sends SPV for diffs after inventory completes', async () => { + const config = makeConfig({ + desiredParameters: [{ name: 'Device.ManagementServer.PeriodicInformInterval', value: '300', type: 'unsignedInt' }], + inventory: [{ path: 'Device.ManagementServer.' }], + }) + vi.mocked(ConfigurationModel.find).mockResolvedValue([config]) + + const device = makeDevice() + device.setGpnStatus('supported') + device.setParameter(new DeviceParameter( + 'Device.ManagementServer.PeriodicInformInterval', '60', ParameterType.UNSIGNED_INT + )) + const context = makeContext(device) + + const state = new ConfigurationEvaluationState() + // First call: sends GPN for inventory + const result1 = await state.handle(context as any) + expect(result1.response).toContain('GetParameterNames') + + // Second call: GPN response received, inventory done, should send SPV for diffs + context.message = { success: true, data: {} } + const result2 = await state.handle(context as any) + // After inventory finishes (no more paths), should send SPV for the diff + expect(result2.response).toContain('SetParameterValues') + }) +}) + +describe('FirmwareUpgradeState — error handling', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('returns isComplete on download error', async () => { + const device = makeDevice() + const taskQueue = new CWMPTaskQueue() + const context = { + device, + session: 'test-session', + taskQueue, + services: { + deviceAction: { + applyDownload: vi.fn().mockRejectedValue(new Error('Network timeout')), + }, + }, + } + + const steps = [{ fromVersion: '4.0', toVersion: '5.0', downloadUrl: 'http://fw.bin', fileSize: 1024, checksum: '' }] + const engineResult = { configuration: makeConfig() } as any + + const state = new FirmwareUpgradeState(steps, 0, engineResult) + const result = await state.handle(context as any) + + expect(result.isComplete).toBe(true) + }) +}) + +describe('ComplianceUpdateState — second call after provCode write', () => { + beforeEach(() => { + vi.clearAllMocks() + }) + + it('transitions to ExecutingCommandsState on second call', async () => { + const device = makeDevice() + // Device has a DIFFERENT provCode so first call sends SPV + 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) + + // First call: sends SPV for provisioning code + const result1 = await state.handle(context as any) + expect(result1.response).toContain('SetParameterValues') + + // Second call: after SPV response, transitions to ExecutingCommandsState + const result2 = await state.handle(context as any) + expect(result2.nextState).toBeDefined() + expect((result2.nextState as any).name).toBe('executing-commands') + }) +}) +