- 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>
186 lines
6.2 KiB
TypeScript
186 lines
6.2 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
import * as fc from 'fast-check'
|
|
import ConfigurationService, { ValidationError } from '../../app/services/configuration_service.js'
|
|
import ConfigurationModel from '../../app/models/configuration.js'
|
|
import type { Configuration, ConfigurationCriteria } from '../../app/domain/configuration/configuration.js'
|
|
import { PolicyType } from '../../app/domain/configuration/configuration.js'
|
|
|
|
/**
|
|
* **Validates: Requirements 3.4**
|
|
*
|
|
* Property 7: Same-priority overlap rejected
|
|
* Two configurations with the same priority and overlapping criteria are always rejected with 409.
|
|
*/
|
|
|
|
vi.mock('../../app/models/configuration.js', () => ({
|
|
default: {
|
|
findAllEnabled: vi.fn(),
|
|
},
|
|
}))
|
|
|
|
// Arbitrary: generates a positive integer priority
|
|
const priorityArb = fc.integer({ min: 1, max: 10000 })
|
|
|
|
// Arbitrary: generates a non-empty string suitable for criteria fields (no glob chars for exact overlap)
|
|
const criteriaValueArb = fc
|
|
.array(
|
|
fc.constantFrom(
|
|
...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'.split('')
|
|
),
|
|
{ minLength: 1, maxLength: 20 }
|
|
)
|
|
.map((chars) => chars.join(''))
|
|
|
|
// Arbitrary: generates a valid configuration identifier
|
|
const identifierArb = fc
|
|
.array(
|
|
fc.constantFrom(
|
|
...'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_'.split('')
|
|
),
|
|
{ minLength: 1, maxLength: 20 }
|
|
)
|
|
.map((chars) => {
|
|
const first = chars[0].replace(/[-_]/, 'A')
|
|
return first + chars.slice(1).join('')
|
|
})
|
|
|
|
// Arbitrary: generates overlapping criteria (same productClass and same manufacturer)
|
|
const overlappingCriteriaArb = fc.record({
|
|
manufacturer: criteriaValueArb,
|
|
productClass: criteriaValueArb,
|
|
})
|
|
|
|
// Build a minimal Configuration object for mocking findAllEnabled results
|
|
function buildExistingConfig(
|
|
identifier: string,
|
|
priority: number,
|
|
criteria: ConfigurationCriteria
|
|
): Configuration {
|
|
return {
|
|
identifier,
|
|
name: `Config ${identifier}`,
|
|
version: '1.0.0',
|
|
priority,
|
|
policy: PolicyType.MAINTAIN,
|
|
enabled: true,
|
|
tags: [],
|
|
criteria,
|
|
inventory: [],
|
|
desiredParameters: [],
|
|
createdAt: new Date(),
|
|
updatedAt: new Date(),
|
|
}
|
|
}
|
|
|
|
describe('Property 7: Same-priority overlap rejected', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
it('detectOverlap throws ValidationError with statusCode 409 when same priority and overlapping criteria', async () => {
|
|
await fc.assert(
|
|
fc.asyncProperty(
|
|
priorityArb,
|
|
overlappingCriteriaArb,
|
|
identifierArb,
|
|
async (priority, criteria, existingId) => {
|
|
vi.clearAllMocks()
|
|
// Mock findAllEnabled to return an existing config with the same priority and overlapping criteria
|
|
const existingConfig = buildExistingConfig(existingId, priority, criteria)
|
|
vi.mocked(ConfigurationModel.findAllEnabled).mockResolvedValue([existingConfig])
|
|
|
|
const service = new ConfigurationService()
|
|
|
|
// Call detectOverlap with the same criteria and same priority
|
|
try {
|
|
await service.detectOverlap(criteria, priority)
|
|
// If no error thrown, the property is violated
|
|
expect.fail('Expected ValidationError to be thrown')
|
|
} catch (error) {
|
|
expect(error).toBeInstanceOf(ValidationError)
|
|
expect((error as ValidationError).statusCode).toBe(409)
|
|
}
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
|
|
it('detectOverlap throws 409 with conflicting identifier listed in the error message', async () => {
|
|
await fc.assert(
|
|
fc.asyncProperty(
|
|
priorityArb,
|
|
overlappingCriteriaArb,
|
|
identifierArb,
|
|
async (priority, criteria, existingId) => {
|
|
vi.clearAllMocks()
|
|
const existingConfig = buildExistingConfig(existingId, priority, criteria)
|
|
vi.mocked(ConfigurationModel.findAllEnabled).mockResolvedValue([existingConfig])
|
|
|
|
const service = new ConfigurationService()
|
|
|
|
try {
|
|
await service.detectOverlap(criteria, priority)
|
|
expect.fail('Expected ValidationError to be thrown')
|
|
} catch (error) {
|
|
expect(error).toBeInstanceOf(ValidationError)
|
|
const validationError = error as ValidationError
|
|
expect(validationError.statusCode).toBe(409)
|
|
expect(validationError.message).toContain(existingId)
|
|
}
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
|
|
it('detectOverlap does NOT throw 409 when same criteria but different priority', async () => {
|
|
await fc.assert(
|
|
fc.asyncProperty(
|
|
priorityArb,
|
|
overlappingCriteriaArb,
|
|
identifierArb,
|
|
fc.integer({ min: 1, max: 5000 }),
|
|
async (priority, criteria, existingId, offset) => {
|
|
vi.clearAllMocks()
|
|
// Ensure different priority by adding an offset
|
|
const differentPriority = priority + offset + 1
|
|
const existingConfig = buildExistingConfig(existingId, differentPriority, criteria)
|
|
vi.mocked(ConfigurationModel.findAllEnabled).mockResolvedValue([existingConfig])
|
|
|
|
const service = new ConfigurationService()
|
|
|
|
// Should NOT throw — returns a warning instead
|
|
const result = await service.detectOverlap(criteria, priority)
|
|
expect(result).not.toBeNull()
|
|
expect(result!.type).toBe('overlap_different_priority')
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
|
|
it('detectOverlap excludes the configuration being updated (excludeId)', async () => {
|
|
await fc.assert(
|
|
fc.asyncProperty(
|
|
priorityArb,
|
|
overlappingCriteriaArb,
|
|
identifierArb,
|
|
async (priority, criteria, configId) => {
|
|
vi.clearAllMocks()
|
|
// The existing config has the same id as the one being updated
|
|
const existingConfig = buildExistingConfig(configId, priority, criteria)
|
|
vi.mocked(ConfigurationModel.findAllEnabled).mockResolvedValue([existingConfig])
|
|
|
|
const service = new ConfigurationService()
|
|
|
|
// Should NOT throw because we exclude the config being updated
|
|
const result = await service.detectOverlap(criteria, priority, configId)
|
|
expect(result).toBeNull()
|
|
}
|
|
),
|
|
{ numRuns: 100 }
|
|
)
|
|
})
|
|
})
|