Files
stef-openclaw-skills/skills/flight-finder/tests/report-pdf.test.ts

274 lines
8.6 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import os from "node:os";
import path from "node:path";
import fs from "node:fs";
import {
describeQuoteBookingLinks,
formatArtifactsRoot,
renderFlightReportPdf,
ReportValidationError
} from "../src/report-pdf.js";
import type { FlightReportPayload } from "../src/types.js";
import {
DFW_BLQ_2026_PROMPT_DRAFT,
normalizeFlightReportRequest
} from "../src/request-normalizer.js";
function samplePayload(): FlightReportPayload {
return {
request: normalizeFlightReportRequest(DFW_BLQ_2026_PROMPT_DRAFT).request,
searchExecution: {
freshSearch: true,
startedAt: "2026-03-30T20:45:00Z",
completedAt: "2026-03-30T21:00:00Z",
artifactsRoot: "/Users/stefano/.openclaw/workspace/reports/dfw-blq-flight-report-2026-03-30-exec",
notes: ["Fresh bounded search executed for this report."]
},
sourceFindings: [
{
source: "kayak",
status: "viable",
checkedAt: "2026-03-30T21:00:00Z",
notes: ["Returned usable options."]
}
],
quotes: [
{
id: "quote-1",
source: "kayak",
legId: "outbound",
passengerGroupIds: ["pair", "solo"],
bookingLink: "https://example.com/quote-1",
itinerarySummary: "DFW -> BLQ via LHR",
totalPriceUsd: 2847,
displayPriceUsd: "$2,847"
}
],
rankedOptions: [
{
id: "primary",
title: "Best overall itinerary",
quoteIds: ["quote-1"],
totalPriceUsd: 2847,
rationale: "Best price-to-convenience tradeoff."
}
],
executiveSummary: ["Best fare currently found is $2,847 total for 3 adults."],
reportWarnings: [],
degradedReasons: [],
comparisonCurrency: "USD",
generatedAt: "2026-03-30T21:00:00Z",
lastCompletedPhase: "ranking"
};
}
function countPdfPages(outputPath: string): number {
return fs.readFileSync(outputPath, "latin1").split("/Type /Page").length - 1;
}
test("renderFlightReportPdf writes a non-empty PDF", async () => {
const outputPath = path.join(os.tmpdir(), `flight-finder-${Date.now()}.pdf`);
await renderFlightReportPdf(samplePayload(), outputPath);
assert.ok(fs.existsSync(outputPath));
assert.ok(fs.statSync(outputPath).size > 0);
});
test("renderFlightReportPdf embeds booking links and only includes direct-airline URLs when captured", async () => {
const outputPath = path.join(os.tmpdir(), `flight-finder-links-${Date.now()}.pdf`);
const payload = samplePayload();
payload.quotes = [
{
...payload.quotes[0],
bookingLink: "https://example.com/quote-1",
directBookingUrl: null
},
{
id: "quote-2",
source: "airline-direct",
legId: "return-pair",
passengerGroupIds: ["pair"],
bookingLink: "https://example.com/quote-2",
directBookingUrl: "https://www.britishairways.com/",
airlineName: "British Airways",
itinerarySummary: "BLQ -> DFW via LHR",
totalPriceUsd: 1100,
displayPriceUsd: "$1,100"
}
];
payload.rankedOptions = [
{
id: "primary",
title: "Best overall itinerary",
quoteIds: ["quote-1", "quote-2"],
totalPriceUsd: 3947,
rationale: "Best price-to-convenience tradeoff."
}
];
await renderFlightReportPdf(payload, outputPath);
const pdfContents = fs.readFileSync(outputPath, "latin1");
assert.match(pdfContents, /https:\/\/example\.com\/quote-1/);
assert.match(pdfContents, /https:\/\/example\.com\/quote-2/);
assert.match(pdfContents, /https:\/\/www\.britishairways\.com\//);
assert.doesNotMatch(pdfContents, /Direct airline booking: not captured in this run[\s\S]*https:\/\/example\.com\/quote-1/);
});
test("renderFlightReportPdf avoids raw absolute artifact paths and keeps page count bounded for a long report", async () => {
const outputPath = path.join(os.tmpdir(), `flight-finder-long-${Date.now()}.pdf`);
const payload = samplePayload();
payload.searchExecution.artifactsRoot =
"/Users/stefano/.openclaw/workspace/reports/dfw-blq-flight-report-2026-03-30-fresh-185742";
payload.quotes = [
{
...payload.quotes[0],
id: "quote-1",
notes: [
"Observed on fresh KAYAK sweep at $794 per traveler for 3 adults.",
"Skyscanner TH/USD corroborated the same Madrid pattern.",
"Expedia also found a nearby fare band."
]
},
{
id: "quote-2",
source: "kayak",
legId: "return-pair",
passengerGroupIds: ["pair"],
bookingLink: "https://example.com/quote-2",
itinerarySummary: "BLQ -> DFW via LHR",
totalPriceUsd: 1316,
displayPriceUsd: "$1,316 total ($658 pp x 2)",
notes: [
"Cheapest valid paired return captured in the fresh KAYAK sweep.",
"Skyscanner did not reproduce the exact low Jun 8 return cleanly.",
"Expedia hit a bot challenge on the Jun 8 paired-return check."
]
},
{
id: "quote-3",
source: "kayak",
legId: "return-solo",
passengerGroupIds: ["solo"],
bookingLink: "https://example.com/quote-3",
itinerarySummary: "BLQ -> DFW via MAD",
totalPriceUsd: 722,
displayPriceUsd: "$722 total",
notes: [
"Lowest valid solo-return fare in the fresh Jul window capture.",
"Skyscanner TH/USD showed the same Madrid connection at about $742.",
"Expedia surfaced a cheaper Turkish option, but it was excluded."
]
}
];
payload.rankedOptions = [
{
id: "primary",
title: "Best overall itinerary",
quoteIds: ["quote-1", "quote-2", "quote-3"],
totalPriceUsd: 4420,
rationale: "Best price-to-convenience tradeoff."
},
{
id: "backup",
title: "Backup itinerary",
quoteIds: ["quote-1", "quote-2", "quote-3"],
totalPriceUsd: 4515,
rationale: "Slightly higher price but still viable."
}
];
payload.executiveSummary = [
"Fresh Thailand-market sweeps shifted the outbound slightly earlier.",
"Lowest valid total observed was $4,420.",
"Cross-source corroboration is strongest for the outbound and solo return."
];
await renderFlightReportPdf(payload, outputPath);
const pdfContents = fs.readFileSync(outputPath, "latin1");
assert.doesNotMatch(pdfContents, /\/Users\/stefano\/\.openclaw\/workspace\/reports\//);
assert.ok(countPdfPages(outputPath) <= 4);
});
test("renderFlightReportPdf rejects incomplete report payloads", async () => {
const outputPath = path.join(os.tmpdir(), `flight-finder-incomplete-${Date.now()}.pdf`);
await assert.rejects(
() =>
renderFlightReportPdf(
{
...samplePayload(),
rankedOptions: [],
executiveSummary: []
},
outputPath
),
ReportValidationError
);
});
test("renderFlightReportPdf rejects payloads that are not marked as fresh-search output", async () => {
const outputPath = path.join(os.tmpdir(), `flight-finder-stale-${Date.now()}.pdf`);
await assert.rejects(
() =>
renderFlightReportPdf(
{
...samplePayload(),
searchExecution: {
...samplePayload().searchExecution,
freshSearch: false
}
},
outputPath
),
ReportValidationError
);
});
test("describeQuoteBookingLinks includes both aggregator and airline-direct links when captured", () => {
const payload = samplePayload();
const quote = {
...payload.quotes[0],
airlineName: "British Airways",
directBookingUrl: "https://www.britishairways.com/"
};
assert.deepEqual(describeQuoteBookingLinks(quote), [
{
label: "Search / book this fare",
text: "Search / book this fare: $2,847 via kayak",
url: "https://example.com/quote-1"
},
{
label: "Direct airline booking",
text: "Direct airline booking: British Airways",
url: "https://www.britishairways.com/"
}
]);
});
test("describeQuoteBookingLinks calls out missing direct-airline links explicitly", () => {
const payload = samplePayload();
assert.deepEqual(describeQuoteBookingLinks(payload.quotes[0]), [
{
label: "Search / book this fare",
text: "Search / book this fare: $2,847 via kayak",
url: "https://example.com/quote-1"
},
{
label: "Direct airline booking",
text: "Direct airline booking: not captured in this run",
url: null
}
]);
});
test("formatArtifactsRoot only shortens known workspace report paths", () => {
assert.equal(
formatArtifactsRoot("/Users/stefano/.openclaw/workspace/reports/dfw-blq-flight-report-2026-03-30-fresh-185742"),
"reports/dfw-blq-flight-report-2026-03-30-fresh-185742"
);
assert.equal(formatArtifactsRoot("/tmp/run-123"), "run-123");
});