import { describe, it, expect } from 'vitest' import { reclassifyDeviceFromCatalog } from '../../../app/services/parameter_writability.js' 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' import { ParameterType } from '../../../app/domain/shared/types.js' // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- function makeDevice(gpnStatus: 'supported' | 'unsupported' | 'unknown' = 'unsupported'): Device { const device = new Device(new DeviceId('Sagemcom', 'F8B7FF', 'Livebox 4', 'LB4-TEST-001')) device.setGpnStatus(gpnStatus) device.setDataModel('tr-181') return device } function addProperty(device: Device, name: string, value: any = null): void { device.setProperty(new DeviceProperty(name, value, ParameterType.STRING)) } // --------------------------------------------------------------------------- // reclassifyDeviceFromCatalog // --------------------------------------------------------------------------- describe('reclassifyDeviceFromCatalog', () => { it('moves writable properties to parameters (PeriodicInformInterval)', () => { const device = makeDevice() addProperty(device, 'Device.ManagementServer.PeriodicInformInterval', '30') const count = reclassifyDeviceFromCatalog(device) expect(count).toBe(1) expect(device.getParameter('Device.ManagementServer.PeriodicInformInterval')).not.toBeNull() expect(device.getProperty('Device.ManagementServer.PeriodicInformInterval')).toBeNull() }) it('moves writable properties to parameters (ProvisioningCode)', () => { const device = makeDevice() addProperty(device, 'Device.DeviceInfo.ProvisioningCode', 'test-code') const count = reclassifyDeviceFromCatalog(device) expect(count).toBe(1) expect(device.getParameter('Device.DeviceInfo.ProvisioningCode')).not.toBeNull() expect(device.getParameter('Device.DeviceInfo.ProvisioningCode')!.value).toBe('test-code') expect(device.getProperty('Device.DeviceInfo.ProvisioningCode')).toBeNull() }) it('does NOT move read-only properties (Manufacturer)', () => { const device = makeDevice() addProperty(device, 'Device.DeviceInfo.Manufacturer', 'Sagemcom') const count = reclassifyDeviceFromCatalog(device) expect(count).toBe(0) expect(device.getProperty('Device.DeviceInfo.Manufacturer')).not.toBeNull() expect(device.getParameter('Device.DeviceInfo.Manufacturer')).toBeNull() }) it('does NOT move properties not in catalog', () => { const device = makeDevice() addProperty(device, 'Device.Vendor.CustomParam.Unknown', 'value') const count = reclassifyDeviceFromCatalog(device) // Not in catalog → stays as property (default behavior for unknown paths) expect(count).toBe(0) expect(device.getProperty('Device.Vendor.CustomParam.Unknown')).not.toBeNull() }) it('handles instance paths with numeric segments', () => { const device = makeDevice() // Device.IP.Interface.{i}.Enable is writable in catalog addProperty(device, 'Device.IP.Interface.1.Enable', 'true') const count = reclassifyDeviceFromCatalog(device) expect(count).toBe(1) expect(device.getParameter('Device.IP.Interface.1.Enable')).not.toBeNull() expect(device.getProperty('Device.IP.Interface.1.Enable')).toBeNull() }) it('reclassifies multiple properties in one call', () => { const device = makeDevice() addProperty(device, 'Device.ManagementServer.PeriodicInformInterval', '60') addProperty(device, 'Device.DeviceInfo.ProvisioningCode', 'code') addProperty(device, 'Device.DeviceInfo.Manufacturer', 'Sagemcom') // read-only, stays addProperty(device, 'Device.DeviceInfo.SerialNumber', 'SN123') // read-only, stays const count = reclassifyDeviceFromCatalog(device) expect(count).toBe(2) expect(device.getParameters()).toHaveLength(2) expect(device.getProperties()).toHaveLength(2) }) it('preserves value and type when moving property to parameter', () => { const device = makeDevice() device.setProperty( new DeviceProperty('Device.ManagementServer.PeriodicInformInterval', '120', ParameterType.UNSIGNED_INT) ) reclassifyDeviceFromCatalog(device) const param = device.getParameter('Device.ManagementServer.PeriodicInformInterval') expect(param).not.toBeNull() expect(param!.value).toBe('120') expect(param!.type).toBe(ParameterType.UNSIGNED_INT) }) it('returns 0 when device has no properties', () => { const device = makeDevice() const count = reclassifyDeviceFromCatalog(device) expect(count).toBe(0) }) it('does not touch existing parameters', () => { const device = makeDevice() device.setParameter(new DeviceParameter('Device.ManagementServer.URL', 'http://acs', ParameterType.STRING)) addProperty(device, 'Device.DeviceInfo.Manufacturer', 'Sagemcom') reclassifyDeviceFromCatalog(device) expect(device.getParameter('Device.ManagementServer.URL')!.value).toBe('http://acs') expect(device.getParameters()).toHaveLength(1) }) it('works with InternetGatewayDevice paths (TR-098) — not in TR-181 catalog', () => { const device = new Device(new DeviceId('Huawei', '202BC1', 'BM632w', '000000')) device.setGpnStatus('unsupported') device.setDataModel('tr-098') addProperty(device, 'InternetGatewayDevice.DeviceInfo.ProvisioningCode', 'code') addProperty(device, 'InternetGatewayDevice.ManagementServer.PeriodicInformInterval', '60') const count = reclassifyDeviceFromCatalog(device) // TR-098 paths are NOT in the TR-181 catalog → nothing reclassified expect(count).toBe(0) expect(device.getProperties()).toHaveLength(2) expect(device.getParameters()).toHaveLength(0) }) })