- 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>
196 lines
6.8 KiB
TypeScript
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 }
|
|
)
|
|
})
|
|
})
|