Files
flights_web/tests/parity/url/harness.ts
T
gnezim 9bd3697a17 Add URL and SEO parity harnesses with fast-check fuzz testing
Generic URL parity harness (table-driven + property-based roundtrip)
and SEO parity harness (shape/completeness validation) registered for
Online Board. 170 tests covering all 6 route types with 200-iteration
fast-check fuzz runs ensuring no serialization asymmetry.
2026-04-15 08:45:09 +03:00

127 lines
4.2 KiB
TypeScript

/**
* Generic URL parity test harness.
*
* Features register their URL serializer/parser against this harness.
* The harness runs:
* 1. Table-driven fixture tests (parse + build parity against corpus).
* 2. fast-check property-based fuzz tests (roundtrip: parse(build(x)) === x).
*
* Designed for reuse across Phase 3+ features.
*
* @module
*/
import { describe, it, expect } from "vitest";
import * as fc from "fast-check";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
// ---------------------------------------------------------------------------
// Public config contract
// ---------------------------------------------------------------------------
export interface UrlParityConfig<TQuery> {
/** Feature name (used in describe block labels). */
feature: string;
/** Path to the JSON fixture file relative to the project root. */
fixturePath: string;
/** Parse a raw URL string into the typed query object. Returns null on failure. */
parse(raw: string): TQuery | null;
/** Build a URL string from a typed query object. */
build(query: TQuery): string;
/** fast-check arbitrary for generating random valid query objects. */
fuzzArbitrary: fc.Arbitrary<TQuery>;
}
// ---------------------------------------------------------------------------
// Fixture shape
// ---------------------------------------------------------------------------
interface FixtureEntry<T> {
url: string;
expected: T;
}
// ---------------------------------------------------------------------------
// Harness entry point
// ---------------------------------------------------------------------------
/**
* Defines a full URL parity test suite for a feature.
*
* Call this from a `*.test.ts` file — it registers `describe` / `it` blocks
* via Vitest globals.
*/
export function defineUrlParityTests<T>(config: UrlParityConfig<T>): void {
const { feature, fixturePath, parse, build, fuzzArbitrary } = config;
// -------------------------------------------------------------------------
// Load fixtures
// -------------------------------------------------------------------------
const fixtureAbsPath = resolve(process.cwd(), fixturePath);
const raw = readFileSync(fixtureAbsPath, "utf-8");
const fixtures = JSON.parse(raw) as FixtureEntry<T>[];
// -------------------------------------------------------------------------
// 1. Table-driven fixture tests
// -------------------------------------------------------------------------
describe(`[${feature}] URL parity — fixture corpus (${fixtures.length} entries)`, () => {
for (const entry of fixtures) {
it(`parse("${entry.url}") matches expected`, () => {
const parsed = parse(entry.url);
expect(parsed).toEqual(entry.expected);
});
it(`build(expected) === "${entry.url}"`, () => {
const built = build(entry.expected);
expect(built).toBe(entry.url);
});
it(`roundtrip: build(parse("${entry.url}")) === "${entry.url}"`, () => {
const parsed = parse(entry.url);
expect(parsed).not.toBeNull();
if (parsed === null) return;
const rebuilt = build(parsed);
expect(rebuilt).toBe(entry.url);
});
}
});
// -------------------------------------------------------------------------
// 2. fast-check fuzz roundtrip tests
// -------------------------------------------------------------------------
describe(`[${feature}] URL parity — fast-check fuzz roundtrip`, () => {
it("parse(build(params)) deep-equals params for all generated inputs", () => {
fc.assert(
fc.property(fuzzArbitrary, (params) => {
const url = build(params);
const parsed = parse(url);
expect(parsed).toEqual(params);
}),
{ numRuns: 200 },
);
});
it("build(parse(build(params))) === build(params) for all generated inputs", () => {
fc.assert(
fc.property(fuzzArbitrary, (params) => {
const url = build(params);
const parsed = parse(url);
expect(parsed).not.toBeNull();
if (parsed === null) return;
const rebuilt = build(parsed);
expect(rebuilt).toBe(url);
}),
{ numRuns: 200 },
);
});
});
}