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, 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" }; } 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 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 } ]); });