Files
adam-acs/tests/property/glob_match.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

132 lines
3.7 KiB
TypeScript

import { describe, it, expect } from 'vitest'
import fc from 'fast-check'
import { matchGlob } from '../../app/utils/glob_match.js'
/**
* Property 5: Glob matching correctness
* Validates: Requirements 2.3, 2.4
*
* Verifies that:
* - `*` matches any sequence of characters
* - `?` matches exactly one character
* - Exact strings always match themselves (when no glob chars present)
* - Matching is case-sensitive
* - Prefix glob patterns work correctly
* - A pattern of N `?` characters matches any string of length N
*/
describe('Property 5: Glob matching correctness', () => {
/**
* Arbitrary that generates strings without glob metacharacters (* and ?)
* to use as literal patterns/values.
*/
const nonGlobString = fc.string({ minLength: 0, maxLength: 50 }).map((s) =>
s.replace(/[*?]/g, 'x')
)
const nonEmptyNonGlobString = fc.string({ minLength: 1, maxLength: 50 }).map((s) =>
s.replace(/[*?]/g, 'x')
)
it('* matches any string', () => {
fc.assert(
fc.property(fc.string({ minLength: 0, maxLength: 100 }), (value) => {
expect(matchGlob('*', value)).toBe(true)
})
)
})
it('? matches exactly one character (single char → true)', () => {
fc.assert(
fc.property(
fc.string({ minLength: 1, maxLength: 1 }),
(singleChar) => {
expect(matchGlob('?', singleChar)).toBe(true)
}
)
)
})
it('? does not match strings of length != 1', () => {
fc.assert(
fc.property(
fc.string({ minLength: 2, maxLength: 50 }),
(multipleChars) => {
expect(matchGlob('?', multipleChars)).toBe(false)
}
)
)
// Also verify empty string
expect(matchGlob('?', '')).toBe(false)
})
it('exact match: matchGlob(str, str) is always true for non-glob strings', () => {
fc.assert(
fc.property(nonGlobString, (str) => {
expect(matchGlob(str, str)).toBe(true)
})
)
})
it('case sensitivity: different cases do not match', () => {
fc.assert(
fc.property(
nonEmptyNonGlobString.filter((s) => s.toLowerCase() !== s.toUpperCase() && s !== s.toUpperCase()),
(str) => {
// If str has at least one letter that differs in case, uppercasing should break the match
const upper = str.toUpperCase()
if (upper !== str) {
expect(matchGlob(str, upper)).toBe(false)
}
}
)
)
})
it('prefix glob: matchGlob(prefix + "*", prefix + anySuffix) is true', () => {
fc.assert(
fc.property(
nonEmptyNonGlobString,
nonGlobString,
(prefix, suffix) => {
expect(matchGlob(prefix + '*', prefix + suffix)).toBe(true)
}
)
)
})
it('pattern of N ? characters matches any string of length N', () => {
fc.assert(
fc.property(
fc.integer({ min: 1, max: 20 }),
(n) => {
const pattern = '?'.repeat(n)
// Generate a random string of exactly length n
const value = 'a'.repeat(n)
expect(matchGlob(pattern, value)).toBe(true)
}
)
)
})
it('pattern of N ? characters does not match strings of different length', () => {
fc.assert(
fc.property(
fc.integer({ min: 1, max: 20 }),
fc.integer({ min: 1, max: 10 }),
(n, offset) => {
const pattern = '?'.repeat(n)
// String longer than pattern
const longerValue = 'a'.repeat(n + offset)
expect(matchGlob(pattern, longerValue)).toBe(false)
// String shorter than pattern (if n > offset)
if (n > offset) {
const shorterValue = 'a'.repeat(n - offset)
expect(matchGlob(pattern, shorterValue)).toBe(false)
}
}
)
)
})
})