c67686463a
Registers schedule URL serializer in parity harness with 18-entry fixture corpus and fast-check fuzz. 10 URL round-trip integration tests, 6 SEO/ JSON-LD integration tests. Updates barrel and MF expose from stub to real.
161 lines
4.8 KiB
TypeScript
161 lines
4.8 KiB
TypeScript
/**
|
|
* Schedule URL parity tests.
|
|
*
|
|
* Registers the Schedule URL serializer against the generic URL parity
|
|
* harness. Tests fixture corpus + fast-check fuzz roundtrip.
|
|
*
|
|
* @module
|
|
*/
|
|
|
|
import * as fc from "fast-check";
|
|
import { parseScheduleUrl, buildScheduleUrl } from "@/features/schedule/url.js";
|
|
import type { ScheduleParams } from "@/features/schedule/url.js";
|
|
import { defineUrlParityTests } from "./harness.js";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// fast-check arbitraries for ScheduleParams
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const ALPHA_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("") as [string, ...string[]];
|
|
|
|
const alphaCharArb: fc.Arbitrary<string> = fc.constantFrom(...ALPHA_CHARS);
|
|
|
|
/** 2-char uppercase carrier code */
|
|
const carrierArb: fc.Arbitrary<string> = fc
|
|
.tuple(alphaCharArb, alphaCharArb)
|
|
.map(([a, b]) => `${a}${b}`);
|
|
|
|
/** 3-char uppercase IATA station code */
|
|
const stationArb: fc.Arbitrary<string> = fc
|
|
.tuple(alphaCharArb, alphaCharArb, alphaCharArb)
|
|
.map(([a, b, c]) => `${a}${b}${c}`);
|
|
|
|
/** Valid yyyyMMdd date string */
|
|
const dateArb = fc
|
|
.record({
|
|
year: fc.integer({ min: 2020, max: 2030 }),
|
|
month: fc.integer({ min: 1, max: 12 }),
|
|
day: fc.integer({ min: 1, max: 28 }),
|
|
})
|
|
.map(({ year, month, day }) => {
|
|
const m = String(month).padStart(2, "0");
|
|
const d = String(day).padStart(2, "0");
|
|
return `${year}${m}${d}`;
|
|
});
|
|
|
|
/** 4-digit zero-padded flight number */
|
|
const flightNumberArb = fc
|
|
.integer({ min: 1, max: 9999 })
|
|
.map((n) => String(n).padStart(4, "0"));
|
|
|
|
/** Optional time range: 4-digit HHmm strings */
|
|
const timeArb = fc
|
|
.record({
|
|
hour: fc.integer({ min: 0, max: 23 }),
|
|
minute: fc.integer({ min: 0, max: 59 }),
|
|
})
|
|
.map(({ hour, minute }) =>
|
|
`${String(hour).padStart(2, "0")}${String(minute).padStart(2, "0")}`,
|
|
);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Route direction params arbitrary
|
|
// ---------------------------------------------------------------------------
|
|
|
|
import type { IScheduleRouteDirectionParams } from "@/features/schedule/types.js";
|
|
|
|
const directionBaseArb: fc.Arbitrary<IScheduleRouteDirectionParams> = fc.record({
|
|
departure: stationArb,
|
|
arrival: stationArb,
|
|
dateFrom: dateArb,
|
|
dateTo: dateArb,
|
|
});
|
|
|
|
const directionWithTimeArb: fc.Arbitrary<IScheduleRouteDirectionParams> = fc.record({
|
|
departure: stationArb,
|
|
arrival: stationArb,
|
|
dateFrom: dateArb,
|
|
dateTo: dateArb,
|
|
timeFrom: timeArb,
|
|
timeTo: timeArb,
|
|
});
|
|
|
|
const directionWithConnArb: fc.Arbitrary<IScheduleRouteDirectionParams> = fc.record({
|
|
departure: stationArb,
|
|
arrival: stationArb,
|
|
dateFrom: dateArb,
|
|
dateTo: dateArb,
|
|
connections: fc.integer({ min: 0, max: 5 }),
|
|
});
|
|
|
|
const directionWithAllArb: fc.Arbitrary<IScheduleRouteDirectionParams> = fc.record({
|
|
departure: stationArb,
|
|
arrival: stationArb,
|
|
dateFrom: dateArb,
|
|
dateTo: dateArb,
|
|
timeFrom: timeArb,
|
|
timeTo: timeArb,
|
|
connections: fc.integer({ min: 0, max: 5 }),
|
|
});
|
|
|
|
const directionArb: fc.Arbitrary<IScheduleRouteDirectionParams> = fc.oneof(
|
|
directionBaseArb,
|
|
directionWithTimeArb,
|
|
directionWithConnArb,
|
|
directionWithAllArb,
|
|
);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Discriminated union arbitraries
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const startArb: fc.Arbitrary<ScheduleParams> = fc.constant({ type: "start" as const });
|
|
|
|
const routeArb: fc.Arbitrary<ScheduleParams> = directionArb.map((d) => ({
|
|
type: "route" as const,
|
|
outbound: d,
|
|
}));
|
|
|
|
const roundtripArb: fc.Arbitrary<ScheduleParams> = fc
|
|
.tuple(directionArb, directionArb)
|
|
.map(([outbound, inbound]) => ({
|
|
type: "roundtrip" as const,
|
|
outbound,
|
|
inbound,
|
|
}));
|
|
|
|
import type { IScheduleFlightId } from "@/features/schedule/types.js";
|
|
|
|
const flightIdArb: fc.Arbitrary<IScheduleFlightId> = fc.record({
|
|
carrier: carrierArb,
|
|
flightNumber: flightNumberArb,
|
|
date: dateArb,
|
|
});
|
|
|
|
const detailsArb: fc.Arbitrary<ScheduleParams> = fc
|
|
.array(flightIdArb, { minLength: 1, maxLength: 4 })
|
|
.map((flights) => ({
|
|
type: "details" as const,
|
|
flights,
|
|
}));
|
|
|
|
/** Combined arbitrary covering all ScheduleParams discriminants */
|
|
const scheduleParamsArb: fc.Arbitrary<ScheduleParams> = fc.oneof(
|
|
{ weight: 1, arbitrary: startArb },
|
|
{ weight: 3, arbitrary: routeArb },
|
|
{ weight: 3, arbitrary: roundtripArb },
|
|
{ weight: 3, arbitrary: detailsArb },
|
|
);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Register against harness
|
|
// ---------------------------------------------------------------------------
|
|
|
|
defineUrlParityTests<ScheduleParams>({
|
|
feature: "Schedule",
|
|
fixturePath: "tests/fixtures/phase-3/url-corpus/schedule.json",
|
|
parse: parseScheduleUrl,
|
|
build: buildScheduleUrl,
|
|
fuzzArbitrary: scheduleParamsArb,
|
|
});
|