Files
adam-acs/tests/property/configuration_crud.property.spec.ts
T
Kevin L'HereecandCopilot d1c65dbf1e 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>
2026-05-29 16:46:37 +02:00

196 lines
6.8 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
import fc from 'fast-check'
import ConfigurationService from '../../app/services/configuration_service.js'
import type { CreateConfigurationDto } from '../../app/services/configuration_service.js'
import { PolicyType } from '../../app/domain/configuration/configuration.js'
import type { Configuration } from '../../app/domain/configuration/configuration.js'
// Mock the models (same pattern as existing unit tests)
vi.mock('#models/configuration', () => {
return {
default: {
createIfNotExists: vi.fn(),
findByIdentifier: vi.fn(),
findAllEnabled: vi.fn(),
paginateConfigurations: vi.fn(),
deleteByIdentifier: vi.fn(),
updateOne: vi.fn(),
},
}
})
vi.mock('#models/configuration_version', () => {
return {
default: {
deleteByConfigurationIdentifier: vi.fn(),
getLatestVersionNumber: vi.fn(),
create: vi.fn(),
paginateVersions: vi.fn(),
},
}
})
import ConfigurationModel from '#models/configuration'
/**
* Property 1: Configuration CRUD round-trip
* Validates: Requirements 1.1, 1.3
*
* For any valid Configuration, create + findById returns identical fields.
*/
describe('Property 1: Configuration CRUD round-trip', () => {
let service: ConfigurationService
beforeEach(() => {
service = new ConfigurationService()
vi.clearAllMocks()
// No overlap by default
vi.mocked(ConfigurationModel.findAllEnabled).mockResolvedValue([])
})
// ─── Arbitraries ─────────────────────────────────────────────────────────────
/**
* Generate a valid identifier: starts with alphanumeric, followed by alphanumeric/hyphens/underscores
*/
const arbIdentifier = fc.stringMatching(/^[A-Za-z0-9][A-Za-z0-9_-]{0,20}$/)
/**
* Generate a valid semver version string (e.g. "1.0.0")
*/
const arbVersion = fc.stringMatching(/^[0-9]+\.[0-9]+\.[0-9]+$/)
/**
* Generate a positive integer priority
*/
const arbPriority = fc.integer({ min: 1, max: 10000 })
/**
* Generate a valid policy type
*/
const arbPolicy = fc.constantFrom(PolicyType.ONCE, PolicyType.MAINTAIN)
/**
* Generate a valid TR-069 inventory path (ending with ".")
*/
const arbInventoryPath = fc.stringMatching(/^[A-Za-z][A-Za-z0-9]{1,10}\.[A-Za-z][A-Za-z0-9]{1,10}\.$/)
.map((path) => ({ path }))
/**
* Generate valid criteria restricted to DeviceId fields
*/
const arbCriteria = fc.record(
{
manufacturer: fc.option(fc.string({ minLength: 1, maxLength: 15 }), { nil: undefined }),
productClass: fc.option(fc.string({ minLength: 1, maxLength: 15 }), { nil: undefined }),
},
{ requiredKeys: [] }
)
/**
* Generate tags (array of simple strings without whitespace)
*/
const arbTags = fc.array(
fc.string({ minLength: 1, maxLength: 15 }).map((s) => s.replace(/\s/g, 'x')),
{ minLength: 0, maxLength: 5 }
)
/**
* Generate a valid name
*/
const arbName = fc.string({ minLength: 1, maxLength: 50 })
/**
* Generate a complete valid CreateConfigurationDto.
* Desired parameters are generated to be covered by the inventory path.
*/
const arbCreateConfigurationDto: fc.Arbitrary<CreateConfigurationDto> = arbInventoryPath.chain(
(inventoryPath) => {
// Generate desired parameters that are covered by the inventory path
const arbDesiredParam = fc
.stringMatching(/^[A-Za-z][A-Za-z0-9]{1,15}$/)
.map((leafName) => ({
name: inventoryPath.path + leafName,
value: 3600 as string | number | boolean,
type: 'unsignedInt' as const,
}))
return fc.record({
identifier: arbIdentifier,
name: arbName,
version: arbVersion,
priority: arbPriority,
policy: arbPolicy,
enabled: fc.boolean(),
tags: arbTags,
criteria: arbCriteria,
inventory: fc.constant([inventoryPath]),
desiredParameters: fc.array(arbDesiredParam, { minLength: 0, maxLength: 3 }),
})
}
)
// ─── Property Test ───────────────────────────────────────────────────────────
it('create + findById returns identical fields for any valid Configuration', () => {
return fc.assert(
fc.asyncProperty(arbCreateConfigurationDto, async (dto) => {
vi.clearAllMocks()
vi.mocked(ConfigurationModel.findAllEnabled).mockResolvedValue([])
const now = new Date()
// Simulate what the model would persist and return
const persisted: Configuration = {
identifier: dto.identifier,
name: dto.name,
version: dto.version,
priority: dto.priority,
policy: dto.policy as PolicyType,
enabled: dto.enabled ?? true,
tags: dto.tags ?? [],
criteria: dto.criteria,
inventory: dto.inventory,
desiredParameters: dto.desiredParameters,
firmwareTarget: dto.firmwareTarget,
schedule: dto.schedule,
createdAt: now,
updatedAt: now,
}
// Mock createIfNotExists to return the persisted configuration
vi.mocked(ConfigurationModel.createIfNotExists).mockResolvedValue(persisted)
// Mock findByIdentifier to return the same persisted configuration
vi.mocked(ConfigurationModel.findByIdentifier).mockResolvedValue(persisted)
// Act: create the configuration
const created = await service.create(dto)
// Act: retrieve by identifier
const found = await service.findById(dto.identifier)
// Assert: findById was called with the correct identifier
expect(ConfigurationModel.findByIdentifier).toHaveBeenCalledWith(dto.identifier)
// Assert: found configuration matches created configuration on all fields
expect(found).not.toBeNull()
expect(found!.identifier).toBe(created.identifier)
expect(found!.name).toBe(created.name)
expect(found!.version).toBe(created.version)
expect(found!.priority).toBe(created.priority)
expect(found!.policy).toBe(created.policy)
expect(found!.enabled).toBe(created.enabled)
expect(found!.tags).toEqual(created.tags)
expect(found!.criteria).toEqual(created.criteria)
expect(found!.inventory).toEqual(created.inventory)
expect(found!.desiredParameters).toEqual(created.desiredParameters)
expect(found!.firmwareTarget).toEqual(created.firmwareTarget)
expect(found!.schedule).toEqual(created.schedule)
expect(found!.createdAt).toEqual(created.createdAt)
expect(found!.updatedAt).toEqual(created.updatedAt)
}),
{ numRuns: 100 }
)
})
})