fix: allow uppercase letters in configuration identifier regex
Previously /^[a-z0-9]+(-[a-z0-9]+)*$/ rejected identifiers like 'BM632w-standard'. Updated to /^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/ to allow mixed case. Generated with [Continue](https://continue.dev) Co-Authored-By: Continue <noreply@continue.dev>
This commit is contained in:
1 parent
30211d278d
commit
ca24b469d3
4 files changed
+712
-1
No files matched your search
@@ -0,0 +1,253 @@
|
||||
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({}),
|
||||
},
|
||||
}))
|
||||
|
||||
// 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'
|
||||
|
||||
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)
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user