import { describe, it, expect } from 'vitest' import { Device } from '../../../app/domain/device/device.js' import { DeviceId } from '../../../app/domain/device/device_id.js' import { DeviceParameter, DeviceProperty } from '../../../app/domain/device/device_parameter.js' function makeDevice() { return new Device(new DeviceId('Sagemcom', 'F8B7FF', 'Livebox 6', 'SN001')) } describe('Device', () => { it('creates with deviceId', () => { const d = makeDevice() expect(d.deviceId.manufacturer).toBe('Sagemcom') expect(d.gpnStatus).toBe('unknown') expect(d.dataModel).toBe('unknown') }) it('setProperty and getProperty', () => { const d = makeDevice() d.setProperty(new DeviceProperty('Device.Info.SW', '1.0')) expect(d.getProperty('Device.Info.SW')).toBeDefined() expect(d.getProperty('Device.Info.SW')!.value).toBe('1.0') }) it('setParameter and getParameter', () => { const d = makeDevice() d.setParameter(new DeviceParameter('Device.X', '42')) expect(d.getParameter('Device.X')).toBeDefined() expect(d.getParameter('Device.X')!.value).toBe('42') }) it('getProperties returns all properties', () => { const d = makeDevice() d.setProperty(new DeviceProperty('A', '1')) d.setProperty(new DeviceProperty('B', '2')) expect(d.getProperties()).toHaveLength(2) }) it('getParameters returns all parameters', () => { const d = makeDevice() d.setParameter(new DeviceParameter('X', '1')) expect(d.getParameters()).toHaveLength(1) }) it('setGpnStatus updates gpnStatus', () => { const d = makeDevice() d.setGpnStatus('supported') expect(d.gpnStatus).toBe('supported') }) it('setDataModel updates dataModel', () => { const d = makeDevice() d.setDataModel('tr-181') expect(d.dataModel).toBe('tr-181') }) it('updateLastSeen does not crash', () => { const d = makeDevice() expect(() => d.updateLastSeen()).not.toThrow() }) it('updateFromValues adds new properties from Inform ParameterValueStruct', () => { const d = makeDevice() d.updateFromValues([ { Name: 'Device.Info.SW', Value: { _: '3.0', $: { Type: 'xsd:string' } } }, { Name: 'Device.Info.HW', Value: { _: '1.0', $: { Type: 'xsd:string' } } }, ]) expect(d.getProperties().length).toBeGreaterThanOrEqual(2) }) it('toSnapshot returns serializable object', () => { const d = makeDevice() d.setProperty(new DeviceProperty('A', '1')) d.setParameter(new DeviceParameter('X', '2')) d.setGpnStatus('supported') d.setDataModel('tr-181') const snap = d.toSnapshot() expect(snap.identifier).toBeDefined() expect(snap.deviceId).toBeDefined() expect(snap.properties).toHaveLength(1) expect(snap.parameters).toHaveLength(1) expect(snap.gpnStatus).toBe('supported') expect(snap.dataModel).toBe('tr-181') }) it('organizeParameters classifies properties vs parameters from GPN response', () => { const d = makeDevice() // Simulate a GPN response d.setProperty(new DeviceProperty('Device.ManagementServer.PeriodicInformInterval', '60')) d.setProperty(new DeviceProperty('Device.DeviceInfo.Manufacturer', 'Sagemcom')) d.organizeParameters( { ParameterList: { ParameterInfoStruct: [ { Name: 'Device.ManagementServer.PeriodicInformInterval', Writable: '1' }, { Name: 'Device.DeviceInfo.Manufacturer', Writable: '0' }, ], }, }, new Date() ) // Writable=1 → parameter, Writable=0 → property expect(d.getParameter('Device.ManagementServer.PeriodicInformInterval')).toBeDefined() // Manufacturer stays as property (Writable=0) expect(d.getProperty('Device.DeviceInfo.Manufacturer')).toBeDefined() }) it('setTmpParamValues stores values', () => { const d = makeDevice() d.setTmpParamValues([{ Name: 'X', Value: { _: 'v' } }]) // No crash — internal state }) it('identifier is computed from deviceId', () => { const d = makeDevice() expect(d.identifier).toBeDefined() expect(d.identifier!.length).toBe(24) }) it('updateFromValues handles empty array', () => { const d = makeDevice() d.updateFromValues([]) expect(d.getProperties()).toHaveLength(0) }) it('updateFromValues updates existing property value', () => { const d = makeDevice() d.setProperty(new DeviceProperty('Device.Info.SW', 'old')) d.updateFromValues([{ Name: 'Device.Info.SW', Value: { _: 'new', $: { Type: 'xsd:string' } } }]) 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') }) }) describe('Device.reclassifyInformWritables', () => { it('promotes ProvisioningCode from property to parameter', () => { const d = makeDevice() // Simulate what updateFromValues does on Inform: stores as property (unknown writability) d.setProperty(new DeviceProperty('InternetGatewayDevice.DeviceInfo.ProvisioningCode', 'abc')) d.reclassifyInformWritables([ { Name: 'InternetGatewayDevice.DeviceInfo.ProvisioningCode', Value: { _: 'abc' } }, ]) expect(d.getParameter('InternetGatewayDevice.DeviceInfo.ProvisioningCode')).not.toBeNull() expect(d.getProperty('InternetGatewayDevice.DeviceInfo.ProvisioningCode')).toBeNull() }) it('promotes ParameterKey from property to parameter', () => { const d = makeDevice() d.setProperty(new DeviceProperty('InternetGatewayDevice.ManagementServer.ParameterKey', '')) d.reclassifyInformWritables([ { Name: 'InternetGatewayDevice.ManagementServer.ParameterKey', Value: { _: '' } }, ]) expect(d.getParameter('InternetGatewayDevice.ManagementServer.ParameterKey')).not.toBeNull() expect(d.getProperty('InternetGatewayDevice.ManagementServer.ParameterKey')).toBeNull() }) it('does not touch params that are already parameters', () => { const d = makeDevice() d.setParameter(new DeviceParameter('Device.DeviceInfo.ProvisioningCode', 'existing')) d.reclassifyInformWritables([ { Name: 'Device.DeviceInfo.ProvisioningCode', Value: { _: 'existing' } }, ]) expect(d.getParameter('Device.DeviceInfo.ProvisioningCode')!.value).toBe('existing') expect(d.getProperty('Device.DeviceInfo.ProvisioningCode')).toBeNull() }) it('does not reclassify unknown names (e.g. SoftwareVersion stays property)', () => { const d = makeDevice() d.setProperty(new DeviceProperty('InternetGatewayDevice.DeviceInfo.SoftwareVersion', 'v1')) d.reclassifyInformWritables([ { Name: 'InternetGatewayDevice.DeviceInfo.SoftwareVersion', Value: { _: 'v1' } }, ]) // SoftwareVersion is NOT in the writable list — should stay as property expect(d.getProperty('InternetGatewayDevice.DeviceInfo.SoftwareVersion')).not.toBeNull() expect(d.getParameter('InternetGatewayDevice.DeviceInfo.SoftwareVersion')).toBeNull() }) it('handles Name as object node (XMLParser artefact)', () => { const d = makeDevice() d.setProperty(new DeviceProperty('Device.DeviceInfo.ProvisioningCode', 'code')) d.reclassifyInformWritables([ { Name: { _: 'Device.DeviceInfo.ProvisioningCode' }, Value: { _: 'code' } }, ]) expect(d.getParameter('Device.DeviceInfo.ProvisioningCode')).not.toBeNull() expect(d.getProperty('Device.DeviceInfo.ProvisioningCode')).toBeNull() }) })