fix: correct orphan references after TR-069 typing refactor

- tests/device.spec.ts: fix 6× organizeParameters() calls passing wrapped
  response object instead of flat ParameterInfoStruct[] (runtime break)
- tests/device.spec.ts: fix setTmpParamValues([{x:1}]) with valid shape
- immediate_command_state_machine.ts: type handleResponse message param as
  CWMPTaskResultData (was any, gap in typing contract)
- configuration_evaluation_state.ts: replace fragile ../../../app/ dynamic
  import paths with proper static imports from ../../protocol/tr069_types.js
- main_state_machine.ts: replace all inline dynamic import() type casts with
  static imports (GetParameterNamesResponse, GetParameterValuesResponse,
  ParameterValueStruct, SetParameterAttribute, DownloadParameter, etc.)
This commit is contained in:
Kevin L'Hereec committed 2026-07-20 16:27:38 +02:00
1 parent f3eb8e1650
commit b643077e8d
4 files changed
+35 -36

No files matched your search

@@ -3,6 +3,7 @@ import type { EngineResult } from '../configuration_engine.js'
import type { DeviceStateResult, DeviceContext } from './main_state_machine.js'
import { PartialOperationalState } from './main_state_machine.js'
import DeviceModel from '#models/device'
import type { GetParameterNamesResponse, GetParameterValuesResponse } from '../../protocol/tr069_types.js'
import logger from '@adonisjs/core/services/logger'
import { ComplianceStatus } from '../../domain/configuration/compliance.js'
import { buildProvisioningCode } from '../../utils/provisioning_code.js'
@@ -95,7 +96,7 @@ export class ConfigurationEvaluationState {
if (this.awaitingPhase === 'gpn_response') {
this.awaitingPhase = null
if (context.message?.success && context.message.data) {
const gpnResp = context.message.data as import('../../../app/protocol/tr069_types.js').GetParameterNamesResponse
const gpnResp = context.message.data as GetParameterNamesResponse
const infoList = gpnResp?.ParameterList?.ParameterInfoStruct ?? []
const leafNames: string[] = infoList
.map((info) => {
@@ -116,7 +117,7 @@ export class ConfigurationEvaluationState {
} else if (this.awaitingPhase === 'gpv_response') {
this.awaitingPhase = null
if (context.message?.success && context.message.data) {
const gpvResp = context.message.data as import('../../../app/protocol/tr069_types.js').GetParameterValuesResponse
const gpvResp = context.message.data as GetParameterValuesResponse
const list = gpvResp?.ParameterList?.ParameterValueStruct ?? []
if (list.length > 0) {
const normalized = list.map((pv) => ({
@@ -2,6 +2,7 @@ import { Device } from '../../domain/device/device.js'
import { CWMPTaskQueue } from './task_queue.js'
import { DeviceActionExecutor } from './device_action_executor.js'
import { ExecutingCommandsState, DeviceContext, DeviceStateResult } from './main_state_machine.js'
import type { CWMPTaskResultData } from '../../protocol/tr069_types.js'
import logger from '@adonisjs/core/services/logger'
/**
@@ -30,7 +31,7 @@ export class ImmediateCommandStateMachine {
return this.execute()
}
async handleResponse(success: boolean, message: any): Promise<string> {
async handleResponse(success: boolean, message: CWMPTaskResultData): Promise<string> {
this.context.message = { success, data: message }
return this.execute()
}
+20 -9
View File
@@ -9,7 +9,18 @@ import { ModeType } from '../../domain/shared/mode.js'
import logger from '@adonisjs/core/services/logger'
import { ConfigurationEvaluationState } from './configuration_evaluation_state.js'
import { reclassifyDeviceFromCatalog } from '../parameter_writability.js'
import type { CWMPTaskResultData } from '../../protocol/tr069_types.js'
import type {
CWMPTaskResultData,
GetParameterNamesResponse,
GetParameterValuesResponse,
ParameterValueStruct,
} from '../../protocol/tr069_types.js'
import type {
SetParameterAttribute,
DownloadParameter,
UploadParameter,
RebootParameter,
} from './device_action_executor.js'
import { ObjectId } from 'mongodb'
// ─── Interfaces ────────────────────────────────────────────────────────────────
@@ -181,7 +192,7 @@ class DiscoveringParametersState extends DeviceState {
// GPN success — process the response
if (context.message?.success && context.message.data) {
try {
const gpnResp = context.message.data as import('../../protocol/tr069_types.js').GetParameterNamesResponse
const gpnResp = context.message.data as GetParameterNamesResponse
const infoList = gpnResp?.ParameterList?.ParameterInfoStruct ?? []
context.device.organizeParameters(infoList, new Date())
context.device.setGpnStatus('supported')
@@ -245,7 +256,7 @@ class CriticalReadsState extends DeviceState {
criticalPaths,
async (result) => {
if (result.status === 'success') {
const resp = result.data as import('../../protocol/tr069_types.js').GetParameterValuesResponse
const resp = result.data as GetParameterValuesResponse
const list = resp?.ParameterList?.ParameterValueStruct ?? []
if (list.length > 0) {
context.device.setTmpParamValues(list)
@@ -260,7 +271,7 @@ class CriticalReadsState extends DeviceState {
// After GPV response: process values from the response, then reclassify
if (context.message?.success && context.message.data) {
try {
const resp = context.message.data as import('../../protocol/tr069_types.js').GetParameterValuesResponse
const resp = context.message.data as GetParameterValuesResponse
const list = resp?.ParameterList?.ParameterValueStruct ?? []
if (list.length > 0) {
// Normalize Name fields: XMLParser may return {_: "path", "$xsi:type": "..."} when Name has attributes
@@ -337,7 +348,7 @@ export class ExecutingCommandsState extends DeviceState {
// Optimistic update for SPV
if (this.currentCommandRecord.action === CommandType.SET_PARAMETER_VALUES) {
const rawList = Array.isArray(this.currentCommandRecord.parameters) ? this.currentCommandRecord.parameters as Array<{ name?: string; value?: unknown }> : []
const pvStructs: import('../../protocol/tr069_types.js').ParameterValueStruct[] = rawList.map((p) => ({
const pvStructs: ParameterValueStruct[] = rawList.map((p) => ({
'@class': '.ParameterValueStruct',
Name: p?.name ?? '',
Value: { '@class': '.ValueObject', _: String(p?.value ?? ''), $: { '@class': '.ValueObjectAttribute', Type: 'xsd:string' } },
@@ -433,19 +444,19 @@ export class ExecutingCommandsState extends DeviceState {
case CommandType.SET_PARAMETER_ATTRIBUTES: {
const p = params as Record<string, unknown> | unknown[] | undefined
const spaParams = Array.isArray(p) ? p : ((p as Record<string, unknown>)?.attributes as unknown[] || (p as Record<string, unknown>)?.parameterAttributes as unknown[] || [])
return context.services.deviceAction.applySetParameterAttributes(context.device, spaParams as import('../../services/cwmp/device_action_executor.js').SetParameterAttribute[], () => Promise.resolve(''))
return context.services.deviceAction.applySetParameterAttributes(context.device, spaParams as SetParameterAttribute[], () => Promise.resolve(''))
}
case CommandType.DOWNLOAD: {
const p = (params as Record<string, unknown>) || {}
return context.services.deviceAction.applyDownload(context.device, p as unknown as import('../../services/cwmp/device_action_executor.js').DownloadParameter, () => Promise.resolve(''))
return context.services.deviceAction.applyDownload(context.device, p as unknown as DownloadParameter, () => Promise.resolve(''))
}
case CommandType.UPLOAD: {
const p = (params as Record<string, unknown>) || {}
return context.services.deviceAction.applyUpload(context.device, p as unknown as import('../../services/cwmp/device_action_executor.js').UploadParameter, () => Promise.resolve(''))
return context.services.deviceAction.applyUpload(context.device, p as unknown as UploadParameter, () => Promise.resolve(''))
}
case CommandType.REBOOT: {
const p = (params as Record<string, unknown>) || {}
return context.services.deviceAction.applyReboot(context.device, p as import('../../services/cwmp/device_action_executor.js').RebootParameter, () => Promise.resolve(''))
return context.services.deviceAction.applyReboot(context.device, p as RebootParameter, () => Promise.resolve(''))
}
case CommandType.FACTORY_RESET: {
return context.services.deviceAction.applyFactoryReset(context.device, {}, () => Promise.resolve(''))
+10 -24
View File
@@ -89,14 +89,10 @@ describe('Device', () => {
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' },
],
},
},
[
{ Name: 'Device.ManagementServer.PeriodicInformInterval', Writable: '1' },
{ Name: 'Device.DeviceInfo.Manufacturer', Writable: '0' },
],
new Date()
)
// Writable=1 → parameter, Writable=0 → property
@@ -153,7 +149,7 @@ describe('Device', () => {
it('clearTmpValues resets temporary state', () => {
const d = makeDevice()
d.setTmpParamValues([{ x: 1 }])
d.setTmpParamValues([{ Name: 'Device.X', Value: { _: '1' } }])
d.tmpPC = 'code'
d.clearTmpValues()
expect(d.getTmpParamValues()).toEqual([])
@@ -163,9 +159,7 @@ describe('Device', () => {
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())
d.organizeParameters([{ Name: 'Device.X', Writable: '1' }], new Date())
expect(d.getParameter('Device.X')).toBeDefined()
expect(d.getProperty('Device.X')).toBeNull()
})
@@ -173,34 +167,26 @@ describe('Device', () => {
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())
d.organizeParameters([{ 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())
d.organizeParameters([{ 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())
d.organizeParameters([{ 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())
d.organizeParameters([{ Name: 'Device.Info.', Writable: '1' }], new Date())
expect(d.getParameter('Device.Info.')).toBeNull()
expect(d.getProperty('Device.Info.')).toBeNull()
})