Increase device_action_executor.ts coverage to 94% stmts, 93% functions. Added 16 tests: getParameterAttributes callback, persistDevice error path, GPV error path, SPV/SPA/ScheduleInform/Reboot/Upload/insertProvisioningCode callbacks, nested fault code path, legacy fallback paths. All 782 tests pass. Build status: Success

This commit is contained in:
ATX Bot committed 2026-06-08 15:51:00 +02:00
1 parent 8a3fb3927d
commit c8c7cb32b7
1 file changed
+275
@@ -254,3 +254,278 @@ describe('DeviceActionExecutor — coverage', () => {
expect(xml).toContain('FactoryReset')
})
})
describe('DeviceActionExecutor — callback execution for remaining uncovered lines', () => {
let taskQueue: CWMPTaskQueue
let executor: DeviceActionExecutor
beforeEach(() => {
vi.clearAllMocks()
taskQueue = new CWMPTaskQueue()
executor = new DeviceActionExecutor(taskQueue)
})
// --- getParameterAttributes callback (lines 118-145) ---
it('getParameterAttributes wrappedCallback processes attributes on success', async () => {
const device = makeDevice()
device.setParameter(new DeviceParameter('Device.X.Param', 'val', ParameterType.STRING))
await executor.getParameterAttributes(device, ['Device.X.Param'], async () => '')
const taskCallbacks = (taskQueue as any).taskCallbacks as Map<string, any>
const taskId = Array.from(taskCallbacks.keys())[0]
await taskQueue.executeTaskCallback(taskId!, ResultStatus.Success, {
ParameterList: {
ParameterAttributeStruct: [
{ Name: 'Device.X.Param', Notification: 2, AccessList: { string: ['Subscriber'] } },
],
},
})
// Should have updated the parameter's attributes
const param = device.getParameter('Device.X.Param')
expect(param).not.toBeNull()
})
it('getParameterAttributes wrappedCallback handles property attributes', async () => {
const device = makeDevice()
device.setProperty(new DeviceProperty('Device.Y.Prop', 'pval', ParameterType.STRING))
await executor.getParameterAttributes(device, ['Device.Y.Prop'], async () => '')
const taskCallbacks = (taskQueue as any).taskCallbacks as Map<string, any>
const taskId = Array.from(taskCallbacks.keys())[0]
await taskQueue.executeTaskCallback(taskId!, ResultStatus.Success, {
ParameterList: {
ParameterAttributeStruct: [
{ Name: 'Device.Y.Prop', Notification: 1 },
],
},
})
const prop = device.getProperty('Device.Y.Prop')
expect(prop).not.toBeNull()
})
it('getParameterAttributes callback skips entries without Name', async () => {
const device = makeDevice()
await executor.getParameterAttributes(device, ['Device.X'], async () => '')
const taskCallbacks = (taskQueue as any).taskCallbacks as Map<string, any>
const taskId = Array.from(taskCallbacks.keys())[0]
// Entry without Name should be skipped without error
await taskQueue.executeTaskCallback(taskId!, ResultStatus.Success, {
ParameterList: { ParameterAttributeStruct: [{ Notification: 2 }] },
})
})
// --- persistDevice error path (line 81) ---
it('persistDevice logs error when createOrUpdate fails', async () => {
vi.mocked(DeviceModel.createOrUpdate).mockRejectedValueOnce(new Error('DB down'))
const device = makeDevice()
await executor.getParameterValues(device, ['Device.X'], async () => '')
const taskCallbacks = (taskQueue as any).taskCallbacks as Map<string, any>
const taskId = Array.from(taskCallbacks.keys())[0]
await taskQueue.executeTaskCallback(taskId!, ResultStatus.Success, {
ParameterList: { ParameterValueStruct: [{ Name: 'Device.X', Value: { _: 'v' } }] },
})
expect(vi.mocked(logger.error)).toHaveBeenCalled()
})
// --- getParameterValues wrappedCallback error path (line 103) ---
it('getParameterValues wrappedCallback catches errors in updateFromValues', async () => {
const device = makeDevice()
// Force updateFromValues to throw
vi.spyOn(device, 'updateFromValues').mockImplementationOnce(() => { throw new Error('parse error') })
await executor.getParameterValues(device, ['Device.X'], async () => '')
const taskCallbacks = (taskQueue as any).taskCallbacks as Map<string, any>
const taskId = Array.from(taskCallbacks.keys())[0]
await taskQueue.executeTaskCallback(taskId!, ResultStatus.Success, {
ParameterList: { ParameterValueStruct: [{ Name: 'Device.X', Value: { _: 'v' } }] },
})
expect(vi.mocked(logger.error)).toHaveBeenCalled()
})
// --- applySetParameterValues callback (line 247) ---
it('applySetParameterValues callback updates device on success', async () => {
const device = makeDevice()
const callbackResult = vi.fn().mockResolvedValue('')
await executor.applySetParameterValues(device, [{ parameterName: 'Device.X', value: '42', type: 'int' }], callbackResult)
const taskCallbacks = (taskQueue as any).taskCallbacks as Map<string, any>
const taskId = Array.from(taskCallbacks.keys())[0]
await taskQueue.executeTaskCallback(taskId!, ResultStatus.Success, {})
expect(callbackResult).toHaveBeenCalledWith(true)
})
it('applySetParameterValues callback returns false on failure', async () => {
const device = makeDevice()
const callbackResult = vi.fn().mockResolvedValue('')
await executor.applySetParameterValues(device, [{ parameterName: 'Device.X', value: '42' }], callbackResult)
const taskCallbacks = (taskQueue as any).taskCallbacks as Map<string, any>
const taskId = Array.from(taskCallbacks.keys())[0]
await taskQueue.executeTaskCallback(taskId!, ResultStatus.Error, { FaultCode: 9001 })
expect(callbackResult).toHaveBeenCalledWith(false)
})
// --- applySetParameterAttributes callback (lines 270-273) ---
it('applySetParameterAttributes callback invoked on success', async () => {
const device = makeDevice()
const cb = vi.fn().mockResolvedValue('')
await executor.applySetParameterAttributes(device, [
{ name: 'Device.X', notificationChange: true, notification: 2, accessListChange: false, accessList: [] },
], cb)
const taskCallbacks = (taskQueue as any).taskCallbacks as Map<string, any>
const taskId = Array.from(taskCallbacks.keys())[0]
await taskQueue.executeTaskCallback(taskId!, ResultStatus.Success, {})
expect(cb).toHaveBeenCalledWith(true)
})
// --- applyScheduleInform callback (line 289) ---
it('applyScheduleInform callback invoked', async () => {
const device = makeDevice()
const cb = vi.fn().mockResolvedValue('')
await executor.applyScheduleInform(device, { delayInSeconds: 600 }, cb)
const taskCallbacks = (taskQueue as any).taskCallbacks as Map<string, any>
const taskId = Array.from(taskCallbacks.keys())[0]
await taskQueue.executeTaskCallback(taskId!, ResultStatus.Success, {})
expect(cb).toHaveBeenCalledWith(true)
})
// --- applyReboot callback (line 319) ---
it('applyReboot callback invoked on success', async () => {
const device = makeDevice()
const cb = vi.fn().mockResolvedValue('')
await executor.applyReboot(device, { commandKey: 'r1' }, cb)
const taskCallbacks = (taskQueue as any).taskCallbacks as Map<string, any>
const taskId = Array.from(taskCallbacks.keys())[0]
await taskQueue.executeTaskCallback(taskId!, ResultStatus.Success, {})
expect(cb).toHaveBeenCalledWith(true)
})
// --- applyUpload callback (line 367) ---
it('applyUpload returns Upload envelope and callback invoked', async () => {
const device = makeDevice()
const cb = vi.fn().mockResolvedValue('')
const xml = await executor.applyUpload(device, {
commandKey: 'upload-1', fileType: '2 Web Content', url: 'http://upload.example.com/cfg',
}, cb)
expect(xml).toContain('Upload')
expect(xml).toContain('http://upload.example.com/cfg')
const taskCallbacks = (taskQueue as any).taskCallbacks as Map<string, any>
const taskId = Array.from(taskCallbacks.keys())[0]
await taskQueue.executeTaskCallback(taskId!, ResultStatus.Success, {})
expect(cb).toHaveBeenCalledWith(true)
})
// --- insertProvisioningCode success callback (line 409) ---
it('insertProvisioningCode callback clears tmpPC on success', async () => {
const device = makeDevice()
device.tmpPC = 'my-prov-code'
const cb = vi.fn().mockResolvedValue('')
await executor.insertProvisioningCode(device, cb)
const taskCallbacks = (taskQueue as any).taskCallbacks as Map<string, any>
const taskId = Array.from(taskCallbacks.keys())[0]
await taskQueue.executeTaskCallback(taskId!, ResultStatus.Success, {})
expect(cb).toHaveBeenCalledWith(true)
expect(device.tmpPC).toBe('')
})
it('insertProvisioningCode callback returns false on failure', async () => {
const device = makeDevice()
device.tmpPC = 'prov-code'
const cb = vi.fn().mockResolvedValue('')
await executor.insertProvisioningCode(device, cb)
const taskCallbacks = (taskQueue as any).taskCallbacks as Map<string, any>
const taskId = Array.from(taskCallbacks.keys())[0]
await taskQueue.executeTaskCallback(taskId!, ResultStatus.Error, {})
expect(cb).toHaveBeenCalledWith(false)
// tmpPC should NOT be cleared on failure
expect(device.tmpPC).toBe('prov-code')
})
// --- GPN with nested fault code path (lines 204-205) ---
it('GPN fault with nested Detail.Fault.FaultCode marks as unsupported', async () => {
const device = makeDevice()
device.setGpnStatus('unknown')
let capturedCallback: any
vi.spyOn(taskQueue, 'addTask').mockImplementation((deviceId, type, props, cb) => {
capturedCallback = cb
return { xml: '<gpn/>', taskId: 'tid-nested' }
})
await executor.getParameterNames(device, 'Device.', async () => '')
await capturedCallback({
status: ResultStatus.Error,
data: { Detail: { Fault: { FaultCode: '9003' } } },
})
expect(device.gpnStatus).toBe('unsupported')
})
// --- insertProvisioningCode legacy fallback path ---
it('insertProvisioningCode uses legacy fallback when dataModel is unknown', async () => {
const device = makeDevice('unknown')
// No Device. prefixed params, so should use InternetGatewayDevice path
device.tmpPC = 'test-pc'
const xml = await executor.insertProvisioningCode(device, async () => '')
expect(xml).toContain('InternetGatewayDevice.DeviceInfo.ProvisioningCode')
})
it('insertProvisioningCode uses Device path when device has Device. params', async () => {
const device = makeDevice('unknown')
device.setProperty(new DeviceProperty('Device.DeviceInfo.X', 'val', ParameterType.STRING))
device.tmpPC = 'test-pc'
const xml = await executor.insertProvisioningCode(device, async () => '')
expect(xml).toContain('Device.DeviceInfo.ProvisioningCode')
})
})
import logger from '@adonisjs/core/services/logger'