test: 507 tests, 79% line coverage — near 80% threshold

- Device domain: full coverage of organizeParameters, updateFromValues
- MainStateMachine: full lifecycle test, error handling, GPN failure
- Coverage: 52% → 79% lines (507 tests)
This commit is contained in:
Kevin L'Hereec committed 2026-06-02 16:49:43 +02:00
1 parent 12a1787151
commit 575975e211
2 files changed
+137

No files matched your search

+95
View File
@@ -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')
})
})
@@ -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', () => {