diff --git a/tests/unit/domain/device.spec.ts b/tests/unit/domain/device.spec.ts index 92fab5d..990c504 100644 --- a/tests/unit/domain/device.spec.ts +++ b/tests/unit/domain/device.spec.ts @@ -130,4 +130,99 @@ describe('Device', () => { const prop = d.getProperty('Device.Info.SW') expect(prop!.value).toBe('new') }) + + it('updateProperty updates value of existing property', () => { + const d = makeDevice() + d.setProperty(new DeviceProperty('Device.Info.SW', 'v1')) + d.updateProperty('Device.Info.SW', 'v2') + expect(d.getProperty('Device.Info.SW')!.value).toBe('v2') + }) + + it('getConnectionRequestURL returns URL from properties', () => { + const d = makeDevice() + d.setProperty(new DeviceProperty('Device.ManagementServer.ConnectionRequestURL', 'http://example.com:7547/')) + const url = d.getConnectionRequestURL() + expect(url).not.toBeNull() + expect(url!.hostname).toBe('example.com') + }) + + it('getConnectionRequestURL returns null when not present', () => { + const d = makeDevice() + expect(d.getConnectionRequestURL()).toBeNull() + }) + + it('clearTmpValues resets temporary state', () => { + const d = makeDevice() + d.setTmpParamValues([{ x: 1 }]) + d.tmpPC = 'code' + d.clearTmpValues() + expect(d.getTmpParamValues()).toEqual([]) + expect(d.getTmpPC()).toBeNull() + }) + + it('organizeParameters moves writable property to parameter', () => { + const d = makeDevice() + d.setProperty(new DeviceProperty('Device.X', 'val')) + d.organizeParameters({ + ParameterList: { ParameterInfoStruct: [{ Name: 'Device.X', Writable: '1' }] }, + }, new Date()) + expect(d.getParameter('Device.X')).toBeDefined() + expect(d.getProperty('Device.X')).toBeNull() + }) + + it('organizeParameters moves non-writable parameter to property', () => { + const d = makeDevice() + d.setParameter(new DeviceParameter('Device.Y', 'val')) + d.organizeParameters({ + ParameterList: { ParameterInfoStruct: [{ Name: 'Device.Y', Writable: 0 }] }, + }, new Date()) + expect(d.getProperty('Device.Y')).toBeDefined() + expect(d.getParameter('Device.Y')).toBeNull() + }) + + it('organizeParameters adds new writable as parameter', () => { + const d = makeDevice() + d.organizeParameters({ + ParameterList: { ParameterInfoStruct: [{ Name: 'Device.New.Param', Writable: '1' }] }, + }, new Date()) + expect(d.getParameter('Device.New.Param')).toBeDefined() + }) + + it('organizeParameters adds new non-writable as property', () => { + const d = makeDevice() + d.organizeParameters({ + ParameterList: { ParameterInfoStruct: [{ Name: 'Device.New.Prop', Writable: '0' }] }, + }, new Date()) + expect(d.getProperty('Device.New.Prop')).toBeDefined() + }) + + it('organizeParameters skips object paths (ending with dot)', () => { + const d = makeDevice() + d.organizeParameters({ + ParameterList: { ParameterInfoStruct: [{ Name: 'Device.Info.', Writable: '1' }] }, + }, new Date()) + expect(d.getParameter('Device.Info.')).toBeNull() + expect(d.getProperty('Device.Info.')).toBeNull() + }) + + it('updateFromValues maps xsd:int type', () => { + const d = makeDevice() + d.updateFromValues([{ Name: 'Device.Port', Value: { _: '8080', $: { Type: 'xsd:int' } } }]) + const prop = d.getProperty('Device.Port') + expect(prop).toBeDefined() + }) + + it('updateFromValues maps xsd:boolean type', () => { + const d = makeDevice() + d.updateFromValues([{ Name: 'Device.Enabled', Value: { _: 'true', $: { Type: 'xsd:boolean' } } }]) + const prop = d.getProperty('Device.Enabled') + expect(prop).toBeDefined() + }) + + it('updateFromValues updates existing parameter value', () => { + const d = makeDevice() + d.setParameter(new DeviceParameter('Device.X', 'old')) + d.updateFromValues([{ Name: 'Device.X', Value: { _: 'new', $: { Type: 'xsd:string' } } }]) + expect(d.getParameter('Device.X')!.value).toBe('new') + }) }) diff --git a/tests/unit/services/cwmp/main_state_machine_extended.spec.ts b/tests/unit/services/cwmp/main_state_machine_extended.spec.ts index 8f7efbb..e352d4b 100644 --- a/tests/unit/services/cwmp/main_state_machine_extended.spec.ts +++ b/tests/unit/services/cwmp/main_state_machine_extended.spec.ts @@ -64,6 +64,48 @@ describe('MainStateMachine', () => { const response = await sm.start() expect(response).toContain('InternetGatewayDevice.ManagementServer.') }) + + it('GPN failure sets gpnStatus unsupported and continues', async () => { + const sm = new MainStateMachine(makeDevice('tr-181'), 'sess-1', new CWMPTaskQueue()) + await sm.start() + // Simulate GPN fault (9001 = method not supported) + await sm.handleResponse(false, { FaultCode: 9001, FaultString: 'Method not supported' }) + // Should have transitioned past discovering-parameters + expect(sm.getCurrentStateName()).not.toBe('discovering-parameters') + }) + + it('full lifecycle: idle → discovering → config-eval → compliance → executing → completed', async () => { + const sm = new MainStateMachine(makeDevice('tr-181'), 'sess-1', new CWMPTaskQueue()) + // Step 1: start → GPN + await sm.start() + expect(sm.getCurrentStateName()).toBe('discovering-parameters') + + // Step 2: GPN response → config-eval + await sm.handleResponse(true, { ParameterList: { ParameterInfoStruct: [] } }) + expect(sm.getCurrentStateName()).toBe('configuration-evaluation') + + // Step 3+: config eval will process its queue and eventually reach compliance-update + // Since our mock engine returns inventoryPaths, it will send GPN requests + // After all config eval actions complete, it reaches compliance-update → executing-commands → completed + // Simulate multiple responses to drain the config eval state + let guard = 0 + while (!sm.isCompleted() && guard++ < 20) { + await sm.handleResponse(true, { Status: 0, ParameterList: { ParameterInfoStruct: [], ParameterValueStruct: [] } }) + } + expect(sm.isCompleted()).toBe(true) + }) + + it('error in state transitions to completed gracefully', async () => { + const sm = new MainStateMachine(makeDevice('tr-181'), 'sess-1', new CWMPTaskQueue()) + await sm.start() + // Force an unexpected error by passing malformed data many times + for (let i = 0; i < 20; i++) { + await sm.handleResponse(false, { error: 'unexpected' }) + if (sm.isCompleted()) break + } + // Should eventually complete (safety guard or error handling) + expect(sm.isCompleted()).toBe(true) + }) }) describe('ExecutingCommandsState', () => {