Files
adam-acs/tests/unit/services/configuration_engine.spec.ts
Kevin L'Hereec 76e75132d5
Build & Deploy / build (push) Successful in 34s
Build & Deploy / docker (push) Successful in 1m19s
Build & Deploy / deploy (push) Successful in 37s
test: raise business-logic unit test coverage across core services
Adds targeted unit tests to cover previously untested branches/functions
in the business-critical (non-protocol) services:

- device_service.ts: 35.8% -> 98.76% (CRUD, executeCommand, refreshDevice,
  calculatePagination, IPv4/IPv6 Connection Request fallback chain)
- domain/device/device.ts: 76.7% -> 96.59% (GPN circuit breaker state
  machine, session lifecycle, canExecuteCommands, detectDataModelFromParams)
- services/references/reference_engine.ts: 78.66% -> 100% (provider
  loading edge cases, rejection handling, singleton lifecycle)
- services/configuration_engine.ts: 70.96% -> 98.7% (matchesCriteria
  branches, shouldApplyOnce policy variants, dryRun, complianceReport)
- services/cwmp/state_machine_manager.ts: 70.9% -> 87.27% (getHashIdForRemoteIp,
  ensureSessionForRemote IP fallback, task callback error handling)
- services/cwmp/main_state_machine.ts: 57.79% -> 94.29% (ExecutingCommandsState
  full command-type dispatch, MAX_STATE_TRANSITIONS guard, error->
  PartialOperationalState transitions)

Also fixes:
- reference_engine_full.spec.ts previously depended on the real
  config/references.json (production config, wassup provider legitimately
  disabled there) instead of a test fixture — now mocks config_loader
  for deterministic, isolated assertions
- main_state_machine.spec.ts CommandModel.getCollection mock returned a
  new object per call instead of a shared instance, breaking
  setupCommandMock() for the new command-dispatch tests

Overall project coverage (excluding protocol/): 72.76% -> 82.34% statements.
Full suite: 1005/1005 tests passing.
2026-07-22 11:45:50 +02:00

729 lines
26 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
// Mock logger
vi.mock('@adonisjs/core/services/logger', () => ({
default: { info: vi.fn(), debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
}))
// Mock DeviceModel
vi.mock('../../../app/models/device.js', () => ({
default: {
createOrUpdate: vi.fn().mockResolvedValue({}),
findByDeviceId: vi.fn().mockResolvedValue(null),
findOne: vi.fn().mockResolvedValue(null),
updateOne: vi.fn().mockResolvedValue({}),
getCollection: vi.fn(),
},
}))
// Mock ConfigurationModel — dynamically controlled per test
vi.mock('#models/configuration', () => ({
default: {
find: vi.fn().mockResolvedValue([]),
findOne: vi.fn().mockResolvedValue(null),
},
}))
// Mock FirmwareCatalogService
vi.mock('../../../app/services/firmware_catalog_service.js', () => ({
default: class {
async resolveUpgradePath() {
return []
}
},
FirmwareCatalogService: class {
async resolveUpgradePath() {
return []
}
},
}))
// Mock schedule_window
vi.mock('../../../app/utils/schedule_window.js', () => ({
isWithinSchedule: vi.fn().mockReturnValue(true),
}))
import ConfigurationEngine from '../../../app/services/configuration_engine.js'
import { Device } from '../../../app/domain/device/device.js'
import { DeviceId } from '../../../app/domain/device/device_id.js'
import { DeviceProperty, DeviceParameter } from '../../../app/domain/device/device_parameter.js'
import { ParameterType } from '../../../app/domain/shared/types.js'
import ConfigurationModel from '#models/configuration'
import DeviceModel from '../../../app/models/device.js'
function makeDevice(
opts: { manufacturer?: string; productClass?: string; provisioningCode?: string } = {}
): Device {
const device = new Device(
new DeviceId(
opts.manufacturer || 'Sagemcom',
'F8B7FF',
opts.productClass || 'Livebox 4',
'LB4-TEST-001'
)
)
device.setDataModel('tr-181')
if (opts.provisioningCode) {
;(device as any).provisioningCode = opts.provisioningCode
}
return device
}
function makeConfig(overrides: any = {}) {
return {
identifier: 'test-config',
name: 'Test Config',
version: '1.0.0',
priority: 1,
policy: 'maintain',
enabled: true,
criteria: { manufacturer: 'Sagemcom', productClass: 'Livebox 4' },
inventory: [{ path: 'Device.ManagementServer.' }],
desiredParameters: [
{ name: 'Device.ManagementServer.PeriodicInformInterval', value: '300', type: 'unsignedInt' },
],
firmwareTarget: null,
schedule: null,
tags: [],
...overrides,
}
}
describe('ConfigurationEngine — coverage', () => {
let engine: ConfigurationEngine
beforeEach(() => {
vi.clearAllMocks()
engine = new ConfigurationEngine()
})
// --- evaluate() ---
it('returns isAlreadyCompliant when no configs exist', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([])
const device = makeDevice()
const result = await engine.evaluate(device)
expect(result.isAlreadyCompliant).toBe(true)
expect(result.configuration).toBeNull()
})
it('returns isAlreadyCompliant when no config matches criteria', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({ criteria: { manufacturer: 'Huawei' } }),
])
const device = makeDevice()
const result = await engine.evaluate(device)
expect(result.isAlreadyCompliant).toBe(true)
expect(result.configuration).toBeNull()
})
it('selects highest priority config when multiple match', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({ identifier: 'low', priority: 1 }),
makeConfig({ identifier: 'high', priority: 10 }),
makeConfig({ identifier: 'mid', priority: 5 }),
])
const device = makeDevice()
const result = await engine.evaluate(device)
expect(result.configuration?.identifier).toBe('high')
})
it('returns parameterDiffs when device value differs from desired', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([makeConfig()])
const device = makeDevice()
// Set a different value
device.setParameter(
new DeviceParameter(
'Device.ManagementServer.PeriodicInformInterval',
'60',
ParameterType.UNSIGNED_INT
)
)
const result = await engine.evaluate(device)
expect(result.parameterDiffs.length).toBe(1)
expect(result.parameterDiffs[0].currentValue).toBe('60')
expect(result.parameterDiffs[0].targetValue).toBe('300')
expect(result.isAlreadyCompliant).toBe(false)
})
it('returns empty diffs when device value matches desired', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([makeConfig()])
const device = makeDevice()
device.setParameter(
new DeviceParameter(
'Device.ManagementServer.PeriodicInformInterval',
'300',
ParameterType.UNSIGNED_INT
)
)
const result = await engine.evaluate(device)
expect(result.parameterDiffs.length).toBe(0)
expect(result.isAlreadyCompliant).toBe(true)
})
it('treats null current value as diff', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([makeConfig()])
const device = makeDevice()
// Don't set the parameter — it's unknown
const result = await engine.evaluate(device)
expect(result.parameterDiffs.length).toBe(1)
expect(result.parameterDiffs[0].currentValue).toBeNull()
})
// --- matchesCriteria ---
it('matchesCriteria matches glob patterns on manufacturer', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({ criteria: { manufacturer: 'Sage*' } }),
])
const device = makeDevice()
const result = await engine.evaluate(device)
expect(result.configuration).not.toBeNull()
})
it('matchesCriteria matches wildcard * on productClass', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({ criteria: { manufacturer: 'Sagemcom', productClass: '*' } }),
])
const device = makeDevice()
const result = await engine.evaluate(device)
expect(result.configuration).not.toBeNull()
})
it('matchesCriteria rejects when parameterCriteria value mismatches', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
criteria: {
manufacturer: 'Sagemcom',
parameterCriteria: [{ name: 'Device.DeviceInfo.ModelName', value: 'WrongModel' }],
},
}),
])
const device = makeDevice()
device.setParameter(
new DeviceParameter('Device.DeviceInfo.ModelName', 'Livebox 4', ParameterType.STRING)
)
const result = await engine.evaluate(device)
expect(result.configuration).toBeNull()
expect(result.isAlreadyCompliant).toBe(true)
})
it('matchesCriteria rejects when parameterCriteria param is missing on device', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
criteria: {
manufacturer: 'Sagemcom',
parameterCriteria: [{ name: 'Device.DeviceInfo.ModelName', value: 'Livebox 4' }],
},
}),
])
const device = makeDevice()
// Don't set ModelName → param is missing
const result = await engine.evaluate(device)
expect(result.configuration).toBeNull()
})
// --- shouldApplyOnce (policy: once) ---
it('shouldApplyOnce returns true (re-apply) when no provisioningCode on device', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([makeConfig({ policy: 'once' })])
const device = makeDevice()
const result = await engine.evaluate(device)
// Should evaluate normally (not skip)
expect(result.configuration).not.toBeNull()
})
it('shouldApplyOnce returns false (skip) when provisioningCode hash matches', async () => {
const config = makeConfig({ policy: 'once' })
vi.mocked(ConfigurationModel.find).mockResolvedValue([config])
const device = makeDevice()
// Compute the expected provisioning code
const { buildProvisioningCode } = await import('../../../app/utils/provisioning_code.js')
const expectedPC = buildProvisioningCode(config as any)
;(device as any).provisioningCode = expectedPC
const result = await engine.evaluate(device)
expect(result.isAlreadyCompliant).toBe(true)
expect(result.parameterDiffs.length).toBe(0)
})
// --- Numeric and boolean comparison ---
it('computeParameterDiffs handles numeric comparison (unsignedInt)', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
desiredParameters: [{ name: 'Device.X.Interval', value: '300', type: 'unsignedInt' }],
}),
])
const device = makeDevice()
device.setParameter(new DeviceParameter('Device.X.Interval', '300', ParameterType.UNSIGNED_INT))
const result = await engine.evaluate(device)
expect(result.parameterDiffs.length).toBe(0) // 300 == 300
})
it('computeParameterDiffs handles boolean comparison', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
desiredParameters: [{ name: 'Device.X.Enabled', value: 'true', type: 'boolean' }],
}),
])
const device = makeDevice()
device.setParameter(new DeviceParameter('Device.X.Enabled', '1', ParameterType.BOOLEAN))
const result = await engine.evaluate(device)
// '1' and 'true' should be treated as same boolean
expect(result.parameterDiffs.length).toBe(0)
})
it('computeParameterDiffs detects boolean mismatch', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
desiredParameters: [{ name: 'Device.X.Enabled', value: 'true', type: 'boolean' }],
}),
])
const device = makeDevice()
device.setParameter(new DeviceParameter('Device.X.Enabled', 'false', ParameterType.BOOLEAN))
const result = await engine.evaluate(device)
expect(result.parameterDiffs.length).toBe(1)
})
// --- matchesCriteria: oui / productClass / serialNumber branches ---
it('matchesCriteria rejects when oui does not match', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
criteria: { oui: 'DEADBEEF' },
}),
])
const device = makeDevice()
const result = await engine.evaluate(device)
expect(result.configuration).toBeNull()
})
it('matchesCriteria matches when oui matches exactly', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
criteria: { oui: 'F8B7FF' },
}),
])
const device = makeDevice()
const result = await engine.evaluate(device)
expect(result.configuration).not.toBeNull()
})
it('matchesCriteria rejects when productClass does not match', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
criteria: { productClass: 'SomethingElse' },
}),
])
const device = makeDevice()
const result = await engine.evaluate(device)
expect(result.configuration).toBeNull()
})
it('matchesCriteria rejects when serialNumber does not match', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
criteria: { serialNumber: 'DIFFERENT-SN' },
}),
])
const device = makeDevice()
const result = await engine.evaluate(device)
expect(result.configuration).toBeNull()
})
it('matchesCriteria matches when serialNumber matches exactly', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
criteria: { serialNumber: 'LB4-TEST-001' },
}),
])
const device = makeDevice()
const result = await engine.evaluate(device)
expect(result.configuration).not.toBeNull()
})
it('matchesCriteria returns false for a device without deviceId', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([makeConfig()])
const result = await engine.evaluate({} as any)
expect(result.configuration).toBeNull()
})
it('matchesCriteria accepts a parameterCriteria match found on plain DeviceRecord (no getParameters)', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
criteria: {
manufacturer: 'Sagemcom',
parameterCriteria: [{ name: 'Device.DeviceInfo.ModelName', value: 'Livebox 4' }],
},
}),
])
// A plain DeviceRecord-like object (no getParameters/getProperties methods)
const plainDevice: any = {
deviceId: {
manufacturer: 'Sagemcom',
oui: 'F8B7FF',
productClass: 'Livebox 4',
serialNumber: 'SN1',
},
parameters: [{ name: 'Device.DeviceInfo.ModelName', value: 'Livebox 4' }],
properties: [],
}
const result = await engine.evaluate(plainDevice)
expect(result.configuration).not.toBeNull()
})
// --- shouldApplyOnce: policy branches ---
it('shouldApplyOnce always applies for an unknown/unset policy', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([makeConfig({ policy: 'weird-policy' })])
const device = makeDevice()
const result = await engine.evaluate(device)
expect(result.configuration).not.toBeNull()
})
it('shouldApplyOnce re-applies when provisioningCode has an invalid format', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([makeConfig({ policy: 'once' })])
const device = makeDevice({ provisioningCode: 'not-a-valid-format' })
const result = await engine.evaluate(device)
expect(result.isAlreadyCompliant).toBe(false)
})
it('shouldApplyOnce re-applies when provisioningCode identifier does not match config', async () => {
const config = makeConfig({ policy: 'once', identifier: 'config-a' })
vi.mocked(ConfigurationModel.find).mockResolvedValue([config])
const { buildProvisioningCode } = await import('../../../app/utils/provisioning_code.js')
const otherConfig = makeConfig({ policy: 'once', identifier: 'config-b' })
const pcForOtherConfig = buildProvisioningCode(otherConfig as any)
const device = makeDevice({ provisioningCode: pcForOtherConfig })
const result = await engine.evaluate(device)
expect(result.isAlreadyCompliant).toBe(false)
})
it('shouldApplyOnce re-applies when provisioningCode content hash changed', async () => {
const config = makeConfig({ policy: 'once' })
vi.mocked(ConfigurationModel.find).mockResolvedValue([config])
// Build a PC for the SAME identifier/version but DIFFERENT content (stale hash)
const staleConfig = makeConfig({
policy: 'once',
desiredParameters: [{ name: 'Device.Other.Param', value: 'x', type: 'string' }],
})
const { buildProvisioningCode } = await import('../../../app/utils/provisioning_code.js')
const stalePC = buildProvisioningCode(staleConfig as any)
const device = makeDevice({ provisioningCode: stalePC })
const result = await engine.evaluate(device)
expect(result.isAlreadyCompliant).toBe(false)
})
// --- getDeviceParameterValue: plain DeviceRecord fallback ---
it('computeParameterDiffs reads value via plain object fallback (no getParameter method)', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
desiredParameters: [{ name: 'Device.Plain.Param', value: '42', type: 'string' }],
}),
])
const plainDevice: any = {
deviceId: {
manufacturer: 'Sagemcom',
oui: 'F8B7FF',
productClass: 'Livebox 4',
serialNumber: 'LB4-TEST-001',
},
parameters: [{ name: 'Device.Plain.Param', value: '42' }],
properties: [],
}
const result = await engine.evaluate(plainDevice)
expect(result.parameterDiffs.length).toBe(0)
})
it('computeParameterDiffs treats numeric value 0 correctly via plain object fallback', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
desiredParameters: [{ name: 'Device.Plain.Count', value: 0, type: 'string' }],
}),
])
const plainDevice: any = {
deviceId: {
manufacturer: 'Sagemcom',
oui: 'F8B7FF',
productClass: 'Livebox 4',
serialNumber: 'LB4-TEST-001',
},
parameters: [{ name: 'Device.Plain.Count', value: 0 }],
properties: [],
}
const result = await engine.evaluate(plainDevice)
expect(result.parameterDiffs.length).toBe(0)
})
it('computeParameterDiffs treats boolean value via plain object fallback', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
desiredParameters: [{ name: 'Device.Plain.Flag', value: true, type: 'string' }],
}),
])
const plainDevice: any = {
deviceId: {
manufacturer: 'Sagemcom',
oui: 'F8B7FF',
productClass: 'Livebox 4',
serialNumber: 'LB4-TEST-001',
},
parameters: [{ name: 'Device.Plain.Flag', value: true }],
properties: [],
}
const result = await engine.evaluate(plainDevice)
expect(result.parameterDiffs.length).toBe(0)
})
it('getDeviceParameterValue returns null when parameter is not found (Device instance)', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
desiredParameters: [{ name: 'Device.Not.There', value: 'x', type: 'string' }],
}),
])
const device = makeDevice()
const result = await engine.evaluate(device)
expect(result.parameterDiffs[0].currentValue).toBeNull()
})
// --- firmwareTarget / schedule window branches ---
it('skips firmware migration when outside the schedule window', async () => {
const { isWithinSchedule } = await import('../../../app/utils/schedule_window.js')
vi.mocked(isWithinSchedule).mockReturnValueOnce(false)
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
firmwareTarget: { productClass: 'Livebox 4', targetVersion: '2.0.0' },
schedule: { start: '00:00', end: '06:00' },
}),
])
const device = makeDevice()
const result = await engine.evaluate(device)
expect(result.skipFirmware).toBe(true)
expect(result.firmwareMigrationPath).toEqual([])
})
it('resolves firmware migration path when current version differs from target', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
firmwareTarget: { productClass: 'Livebox 4', targetVersion: '2.0.0' },
}),
])
const device = makeDevice()
// getDeviceParameterValue for Device instances only checks getParameter(), not getProperty()
device.setParameter(
new DeviceParameter('Device.DeviceInfo.SoftwareVersion', '1.0.0', ParameterType.STRING)
)
const firmwareModule = await import('../../../app/services/firmware_catalog_service.js')
const FirmwareCatalogService = firmwareModule.default
vi.spyOn(FirmwareCatalogService.prototype, 'resolveUpgradePath').mockResolvedValue([
{ fromVersion: '1.0.0', toVersion: '2.0.0' } as any,
])
const result = await engine.evaluate(device)
expect(result.firmwareMigrationPath.length).toBe(1)
expect(result.isAlreadyCompliant).toBe(false)
})
it('does not resolve migration path when current version already matches target', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
firmwareTarget: { productClass: 'Livebox 4', targetVersion: '2.0.0' },
}),
])
const device = makeDevice()
device.setParameter(
new DeviceParameter('Device.DeviceInfo.SoftwareVersion', '2.0.0', ParameterType.STRING)
)
const result = await engine.evaluate(device)
expect(result.firmwareMigrationPath).toEqual([])
})
it('getDeviceVersion returns null when no version parameter exists', async () => {
vi.mocked(ConfigurationModel.find).mockResolvedValue([
makeConfig({
firmwareTarget: { productClass: 'Livebox 4', targetVersion: '2.0.0' },
}),
])
const device = makeDevice()
// No SoftwareVersion set anywhere
const result = await engine.evaluate(device)
expect(result.firmwareMigrationPath).toEqual([])
})
// --- dryRun ---
describe('dryRun', () => {
it('throws when the configuration is not found', async () => {
vi.mocked(ConfigurationModel.findOne).mockResolvedValue(null)
await expect(engine.dryRun('missing-config', 'some-device')).rejects.toThrow(
'Configuration missing-config not found'
)
})
it('throws when the device is not found', async () => {
vi.mocked(ConfigurationModel.findOne).mockResolvedValue(makeConfig() as any)
vi.mocked(DeviceModel.findOne).mockResolvedValue(null)
await expect(engine.dryRun('test-config', 'missing-device')).rejects.toThrow(
'Device missing-device not found'
)
})
it('throws when the configuration does not match the device criteria', async () => {
vi.mocked(ConfigurationModel.findOne).mockResolvedValue(
makeConfig({ criteria: { manufacturer: 'Huawei' } }) as any
)
vi.mocked(DeviceModel.findOne).mockResolvedValue(makeDevice() as any)
await expect(engine.dryRun('test-config', 'some-device')).rejects.toThrow(
/does not match device/
)
})
it('returns a DryRunResult when configuration matches the device', async () => {
vi.mocked(ConfigurationModel.findOne).mockResolvedValue(makeConfig() as any)
vi.mocked(DeviceModel.findOne).mockResolvedValue(makeDevice() as any)
const result = await engine.dryRun('test-config', 'some-device')
expect(result.configurationId).toBe('test-config')
expect(result.deviceIdentifier).toBe('some-device')
expect(result.evaluation).toBeDefined()
expect(result.timestamp).toBeInstanceOf(Date)
})
it('dryRun skips (isAlreadyCompliant) when shouldApplyOnce is false (evaluateSpecific)', async () => {
const config = makeConfig({ policy: 'once' })
vi.mocked(ConfigurationModel.findOne).mockResolvedValue(config as any)
const { buildProvisioningCode } = await import('../../../app/utils/provisioning_code.js')
const expectedPC = buildProvisioningCode(config as any)
const device = makeDevice({ provisioningCode: expectedPC })
vi.mocked(DeviceModel.findOne).mockResolvedValue(device as any)
const result = await engine.dryRun('test-config', 'some-device')
expect(result.evaluation.isAlreadyCompliant).toBe(true)
expect(result.evaluation.parameterDiffs).toEqual([])
})
it('dryRun skips firmware migration outside schedule window (evaluateSpecific)', async () => {
const { isWithinSchedule } = await import('../../../app/utils/schedule_window.js')
vi.mocked(isWithinSchedule).mockReturnValueOnce(false)
const config = makeConfig({
firmwareTarget: { productClass: 'Livebox 4', targetVersion: '2.0.0' },
schedule: { start: '00:00', end: '06:00' },
})
vi.mocked(ConfigurationModel.findOne).mockResolvedValue(config as any)
vi.mocked(DeviceModel.findOne).mockResolvedValue(makeDevice() as any)
const result = await engine.dryRun('test-config', 'some-device')
expect(result.evaluation.skipFirmware).toBe(true)
expect(result.evaluation.firmwareMigrationPath).toEqual([])
})
it('dryRun resolves firmware migration path when version differs (evaluateSpecific)', async () => {
const config = makeConfig({
firmwareTarget: { productClass: 'Livebox 4', targetVersion: '2.0.0' },
})
vi.mocked(ConfigurationModel.findOne).mockResolvedValue(config as any)
const device = makeDevice()
device.setParameter(
new DeviceParameter('Device.DeviceInfo.SoftwareVersion', '1.0.0', ParameterType.STRING)
)
vi.mocked(DeviceModel.findOne).mockResolvedValue(device as any)
const firmwareModule = await import('../../../app/services/firmware_catalog_service.js')
const FirmwareCatalogService = firmwareModule.default
vi.spyOn(FirmwareCatalogService.prototype, 'resolveUpgradePath').mockResolvedValue([
{ fromVersion: '1.0.0', toVersion: '2.0.0' } as any,
])
const result = await engine.dryRun('test-config', 'some-device')
expect(result.evaluation.firmwareMigrationPath.length).toBe(1)
expect(result.evaluation.isAlreadyCompliant).toBe(false)
})
})
// --- complianceReport ---
describe('complianceReport', () => {
it('throws when the configuration is not found', async () => {
vi.mocked(ConfigurationModel.findOne).mockResolvedValue(null)
await expect(engine.complianceReport('missing-config')).rejects.toThrow(
'Configuration missing-config not found'
)
})
it('returns compliant/non-compliant counts for matching devices', async () => {
const config = makeConfig()
vi.mocked(ConfigurationModel.findOne).mockResolvedValue(config as any)
const compliantDevice = makeDevice()
compliantDevice.setParameter(
new DeviceParameter(
'Device.ManagementServer.PeriodicInformInterval',
'300',
ParameterType.UNSIGNED_INT
)
)
const nonCompliantDevice = makeDevice()
nonCompliantDevice.setParameter(
new DeviceParameter(
'Device.ManagementServer.PeriodicInformInterval',
'60',
ParameterType.UNSIGNED_INT
)
)
const nonMatchingDevice = makeDevice({ manufacturer: 'Huawei' })
const mockCollection = {
find: vi.fn(() => ({
toArray: vi
.fn()
.mockResolvedValue([compliantDevice, nonCompliantDevice, nonMatchingDevice]),
})),
}
vi.mocked(DeviceModel.getCollection).mockReturnValue(mockCollection as any)
const report = await engine.complianceReport('test-config')
expect(report.configurationId).toBe('test-config')
expect(report.configurationName).toBe('Test Config')
expect(report.totalDevices).toBe(2) // only matching devices
expect(report.compliantCount).toBe(1)
expect(report.nonCompliantCount).toBe(1)
expect(report.devices).toHaveLength(2)
})
it('returns zero devices when none match the criteria', async () => {
const config = makeConfig({ criteria: { manufacturer: 'NoSuchVendor' } })
vi.mocked(ConfigurationModel.findOne).mockResolvedValue(config as any)
const mockCollection = {
find: vi.fn(() => ({ toArray: vi.fn().mockResolvedValue([makeDevice()]) })),
}
vi.mocked(DeviceModel.getCollection).mockReturnValue(mockCollection as any)
const report = await engine.complianceReport('test-config')
expect(report.totalDevices).toBe(0)
expect(report.compliantCount).toBe(0)
expect(report.nonCompliantCount).toBe(0)
})
})
})