chore: initial commit — AdonisJS v7 TR-069 ACS

- Docker infrastructure (Dockerfile, docker-compose.yml, genieacs-sim)
- AdonisJS v7 app with Inertia.js + Vue 3
- MongoDB integration
- CWMP/TR-069 state machine engine
- Configuration engine with compliance tracking
- Branding: ADAM ACS (favicon, navbar, locales)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Kevin L'HereecandCopilot committed 2026-05-29 16:46:37 +02:00
commit d1c65dbf1e
164 files changed
+29127

No files matched your search

+42
View File
@@ -0,0 +1,42 @@
import { describe, it, expect } from 'vitest'
import { matchGlob } from '../../../app/utils/glob_match.js'
describe('matchGlob', () => {
it('matches exact strings', () => {
expect(matchGlob('Sagemcom', 'Sagemcom')).toBe(true)
expect(matchGlob('Sagemcom', 'sagemcom')).toBe(false)
})
it('matches * as any sequence of characters', () => {
expect(matchGlob('Livebox*', 'Livebox5')).toBe(true)
expect(matchGlob('Livebox*', 'Livebox')).toBe(true)
expect(matchGlob('Livebox*', 'LiveboxPro')).toBe(true)
expect(matchGlob('*box', 'Livebox')).toBe(true)
expect(matchGlob('*box*', 'Livebox5')).toBe(true)
})
it('matches ? as exactly one character', () => {
expect(matchGlob('BM63?w', 'BM632w')).toBe(true)
expect(matchGlob('BM63?w', 'BM63w')).toBe(false)
expect(matchGlob('BM63?w', 'BM6312w')).toBe(false)
})
it('is case-sensitive', () => {
expect(matchGlob('Livebox*', 'livebox5')).toBe(false)
expect(matchGlob('livebox*', 'Livebox5')).toBe(false)
})
it('escapes regex special characters in pattern', () => {
expect(matchGlob('device.info', 'device.info')).toBe(true)
expect(matchGlob('device.info', 'deviceXinfo')).toBe(false)
expect(matchGlob('a+b', 'a+b')).toBe(true)
expect(matchGlob('a+b', 'aab')).toBe(false)
})
it('handles empty pattern and value', () => {
expect(matchGlob('', '')).toBe(true)
expect(matchGlob('*', '')).toBe(true)
expect(matchGlob('?', '')).toBe(false)
expect(matchGlob('', 'something')).toBe(false)
})
})
@@ -0,0 +1,89 @@
import { describe, it, expect } from 'vitest'
import {
isParameterCoveredByInventory,
suggestInventoryPath,
} from '../../../app/utils/inventory_coverage.js'
describe('isParameterCoveredByInventory', () => {
it('returns true for subtree match (path ending with dot)', () => {
const inventoryPaths = [{ path: 'InternetGatewayDevice.ManagementServer.' }]
expect(
isParameterCoveredByInventory(
'InternetGatewayDevice.ManagementServer.PeriodicInformInterval',
inventoryPaths
)
).toBe(true)
})
it('returns true for exact match (path without trailing dot)', () => {
const inventoryPaths = [
{ path: 'InternetGatewayDevice.ManagementServer.PeriodicInformInterval' },
]
expect(
isParameterCoveredByInventory(
'InternetGatewayDevice.ManagementServer.PeriodicInformInterval',
inventoryPaths
)
).toBe(true)
})
it('returns false when parameter is not covered', () => {
const inventoryPaths = [{ path: 'InternetGatewayDevice.DeviceInfo.' }]
expect(
isParameterCoveredByInventory(
'InternetGatewayDevice.ManagementServer.PeriodicInformInterval',
inventoryPaths
)
).toBe(false)
})
it('returns false for empty inventory', () => {
expect(
isParameterCoveredByInventory(
'InternetGatewayDevice.ManagementServer.PeriodicInformInterval',
[]
)
).toBe(false)
})
it('handles multiple inventory paths', () => {
const inventoryPaths = [
{ path: 'InternetGatewayDevice.DeviceInfo.' },
{ path: 'InternetGatewayDevice.ManagementServer.' },
]
expect(
isParameterCoveredByInventory(
'InternetGatewayDevice.ManagementServer.PeriodicInformInterval',
inventoryPaths
)
).toBe(true)
})
it('does not match partial path names without trailing dot', () => {
const inventoryPaths = [{ path: 'InternetGatewayDevice.Man' }]
expect(
isParameterCoveredByInventory(
'InternetGatewayDevice.ManagementServer.PeriodicInformInterval',
inventoryPaths
)
).toBe(false)
})
})
describe('suggestInventoryPath', () => {
it('removes the leaf name and adds trailing dot', () => {
expect(
suggestInventoryPath('InternetGatewayDevice.ManagementServer.PeriodicInformInterval')
).toBe('InternetGatewayDevice.ManagementServer.')
})
it('handles deeply nested paths', () => {
expect(
suggestInventoryPath('InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.Name')
).toBe('InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.')
})
it('handles single-level path', () => {
expect(suggestInventoryPath('ParameterName')).toBe('.')
})
})
+119
View File
@@ -0,0 +1,119 @@
import { describe, it, expect } from 'vitest'
import {
computeContentHash,
buildProvisioningCode,
parseProvisioningCode,
} from '../../../app/utils/provisioning_code.js'
import type { Configuration } from '../../../app/domain/configuration/configuration.js'
import { PolicyType } from '../../../app/domain/configuration/configuration.js'
function makeConfig(overrides: Partial<Configuration> = {}): Configuration {
return {
identifier: 'BM632w-standard',
name: 'Configuration standard BM632w',
version: '1.0.1',
priority: 100,
policy: PolicyType.ONCE,
enabled: true,
tags: [],
criteria: { productClass: 'BM632w' },
inventory: [],
desiredParameters: [
{ name: 'Device.Param.B', value: 'hello', type: 'string' },
{ name: 'Device.Param.A', value: 42, type: 'int' },
],
firmwareTarget: { productClass: 'BM632w', targetVersion: '2.1.0' },
createdAt: new Date(),
updatedAt: new Date(),
...overrides,
}
}
describe('computeContentHash', () => {
it('returns a 12-character hex string', () => {
const hash = computeContentHash(makeConfig())
expect(hash).toMatch(/^[a-f0-9]{12}$/)
})
it('is deterministic for the same config', () => {
const config = makeConfig()
expect(computeContentHash(config)).toBe(computeContentHash(config))
})
it('sorts desiredParameters by name for consistency', () => {
const config1 = makeConfig({
desiredParameters: [
{ name: 'B.Param', value: 1, type: 'int' },
{ name: 'A.Param', value: 2, type: 'int' },
],
})
const config2 = makeConfig({
desiredParameters: [
{ name: 'A.Param', value: 2, type: 'int' },
{ name: 'B.Param', value: 1, type: 'int' },
],
})
expect(computeContentHash(config1)).toBe(computeContentHash(config2))
})
it('changes when desiredParameters change', () => {
const config1 = makeConfig({
desiredParameters: [{ name: 'A.Param', value: 1, type: 'int' }],
})
const config2 = makeConfig({
desiredParameters: [{ name: 'A.Param', value: 2, type: 'int' }],
})
expect(computeContentHash(config1)).not.toBe(computeContentHash(config2))
})
it('changes when firmwareTarget changes', () => {
const config1 = makeConfig({
firmwareTarget: { productClass: 'BM632w', targetVersion: '2.0.0' },
})
const config2 = makeConfig({
firmwareTarget: { productClass: 'BM632w', targetVersion: '3.0.0' },
})
expect(computeContentHash(config1)).not.toBe(computeContentHash(config2))
})
})
describe('buildProvisioningCode', () => {
it('returns the correct format', () => {
const config = makeConfig()
const code = buildProvisioningCode(config)
expect(code).toMatch(/^BM632w-standard\(v1\.0\.1\)-[a-f0-9]{12}$/)
})
it('matches the regex pattern from the spec', () => {
const config = makeConfig()
const code = buildProvisioningCode(config)
expect(code).toMatch(/^(.+)\(v(.+)\)-([a-f0-9]{12})$/)
})
})
describe('parseProvisioningCode', () => {
it('parses a valid provisioning code', () => {
const result = parseProvisioningCode('BM632w-standard(v1.0.1)-a1b2c3d4e5f6')
expect(result).toEqual({
identifier: 'BM632w-standard',
version: '1.0.1',
hash: 'a1b2c3d4e5f6',
})
})
it('returns null for invalid format', () => {
expect(parseProvisioningCode('invalid')).toBeNull()
expect(parseProvisioningCode('')).toBeNull()
expect(parseProvisioningCode('id(v1.0)-short')).toBeNull() // hash too short
})
it('round-trips with buildProvisioningCode', () => {
const config = makeConfig()
const code = buildProvisioningCode(config)
const parsed = parseProvisioningCode(code)
expect(parsed).not.toBeNull()
expect(parsed!.identifier).toBe('BM632w-standard')
expect(parsed!.version).toBe('1.0.1')
expect(parsed!.hash).toMatch(/^[a-f0-9]{12}$/)
})
})
+100
View File
@@ -0,0 +1,100 @@
import { describe, it, expect } from 'vitest'
import { isWithinSchedule } from '../../../app/utils/schedule_window.js'
import type { ScheduleWindow } from '../../../app/domain/configuration/configuration.js'
describe('isWithinSchedule', () => {
it('returns true when schedule is undefined', () => {
expect(isWithinSchedule(undefined)).toBe(true)
})
it('returns true when within a normal window (non-midnight-spanning)', () => {
const schedule: ScheduleWindow = {
daysOfWeek: [1, 2, 3, 4, 5], // Mon-Fri
startHour: 2,
endHour: 6,
timezone: 'UTC',
}
// Wednesday at 03:00 UTC
const now = new Date('2025-01-08T03:00:00Z')
expect(isWithinSchedule(schedule, now)).toBe(true)
})
it('returns false when outside a normal window', () => {
const schedule: ScheduleWindow = {
daysOfWeek: [1, 2, 3, 4, 5],
startHour: 2,
endHour: 6,
timezone: 'UTC',
}
// Wednesday at 10:00 UTC
const now = new Date('2025-01-08T10:00:00Z')
expect(isWithinSchedule(schedule, now)).toBe(false)
})
it('returns false when at the endHour boundary (exclusive)', () => {
const schedule: ScheduleWindow = {
daysOfWeek: [1, 2, 3, 4, 5],
startHour: 2,
endHour: 6,
timezone: 'UTC',
}
// Wednesday at 06:00 UTC (endHour is exclusive)
const now = new Date('2025-01-08T06:00:00Z')
expect(isWithinSchedule(schedule, now)).toBe(false)
})
it('handles midnight-spanning window (startHour > endHour)', () => {
const schedule: ScheduleWindow = {
daysOfWeek: [0, 1, 2, 3, 4, 5, 6],
startHour: 22,
endHour: 6,
timezone: 'UTC',
}
// At 23:00 UTC - should be within
expect(isWithinSchedule(schedule, new Date('2025-01-08T23:00:00Z'))).toBe(true)
// At 03:00 UTC - should be within
expect(isWithinSchedule(schedule, new Date('2025-01-08T03:00:00Z'))).toBe(true)
// At 10:00 UTC - should be outside
expect(isWithinSchedule(schedule, new Date('2025-01-08T10:00:00Z'))).toBe(false)
})
it('filters by day of week', () => {
const schedule: ScheduleWindow = {
daysOfWeek: [1, 2, 3, 4, 5], // Mon-Fri only
startHour: 0,
endHour: 23,
timezone: 'UTC',
}
// Saturday 2025-01-11 at 10:00 UTC
const saturday = new Date('2025-01-11T10:00:00Z')
expect(isWithinSchedule(schedule, saturday)).toBe(false)
// Monday 2025-01-06 at 10:00 UTC
const monday = new Date('2025-01-06T10:00:00Z')
expect(isWithinSchedule(schedule, monday)).toBe(true)
})
it('handles IANA timezone conversion', () => {
const schedule: ScheduleWindow = {
daysOfWeek: [0, 1, 2, 3, 4, 5, 6],
startHour: 2,
endHour: 6,
timezone: 'Europe/Paris', // UTC+1 in winter
}
// 03:00 UTC = 04:00 Paris time → within [2, 6)
expect(isWithinSchedule(schedule, new Date('2025-01-08T03:00:00Z'))).toBe(true)
// 00:00 UTC = 01:00 Paris time → outside [2, 6)
expect(isWithinSchedule(schedule, new Date('2025-01-08T00:00:00Z'))).toBe(false)
})
it('returns true when daysOfWeek is empty (no day restriction)', () => {
const schedule: ScheduleWindow = {
daysOfWeek: [],
startHour: 2,
endHour: 6,
timezone: 'UTC',
}
// Any day at 03:00 UTC
expect(isWithinSchedule(schedule, new Date('2025-01-08T03:00:00Z'))).toBe(true)
})
})