- 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>
132 lines
3.7 KiB
TypeScript
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)
|
|
}
|
|
}
|
|
)
|
|
)
|
|
})
|
|
})
|