diff --git a/frontend/tests/professional-report-reference-route.test.ts b/frontend/tests/professional-report-reference-route.test.ts index fd8a061e..8ea73d2c 100644 --- a/frontend/tests/professional-report-reference-route.test.ts +++ b/frontend/tests/professional-report-reference-route.test.ts @@ -1,5 +1,7 @@ import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; import test from "node:test"; const routeSource = readFileSync( @@ -7,6 +9,106 @@ const routeSource = readFileSync( "utf8", ); +const frontendRoot = fileURLToPath(new URL("../", import.meta.url)); + +function executeReadyReportExport() { + const script = String.raw` + import { mock } from "node:test"; + import { pathToFileURL } from "node:url"; + import { Agent } from "@mastra/core/agent"; + + const moduleUrl = (path) => pathToFileURL(process.cwd() + "/" + path).href; + mock.module(moduleUrl("src/lib/personal-report-entitlement.ts"), { exports: { + checkSameOrigin: () => ({ ok: true }), + resolveAllowedReportOrigins: () => [], + }}); + mock.module(moduleUrl("src/lib/personal-report-service.ts"), { exports: { + createSupabasePersonalReportService: () => ({ + getOwnedById: async () => ({ status: "ready" }), + }), + }}); + mock.module(moduleUrl("src/lib/server-owned-birth-profile.ts"), { exports: { + ACCOUNT_BIRTH_SELECT: "birth-select", + globalBirthProfileFromAccountRow: () => ({ + date: "1990-01-02", + time: "03:04", + latitude: 39.9, + longitude: 116.4, + timezoneOffset: 8, + ayanamsa: "lahiri", + }), + }}); + mock.module(moduleUrl("src/lib/supabase/config.ts"), { exports: { + isSupabaseConfigurationError: () => false, + }}); + const profileQuery = { + select() { return this; }, + eq() { return this; }, + async maybeSingle() { return { data: {}, error: null }; }, + }; + mock.module(moduleUrl("src/lib/supabase/server.ts"), { exports: { + createServerSupabaseClient: async () => ({ + auth: { getUser: async () => ({ data: { user: { id: "user-1" } }, error: null }) }, + from: () => profileQuery, + }), + }}); + + let modelCalls = 0; + Agent.prototype.generate = async () => { + modelCalls += 1; + throw new Error("professional export must not call a model"); + }; + let telemetryEvents = 0; + console.info = (...args) => { + if (args[0] === "[personal-report-agent]") telemetryEvents += 1; + }; + const upstream = []; + globalThis.fetch = async (input, init) => { + upstream.push({ + url: String(input), + method: init?.method, + body: JSON.parse(String(init?.body)), + }); + return new Response(JSON.stringify({ format: "markdown", markdown: "# Professional reference" }), { + status: 200, + headers: { "content-type": "application/json" }, + }); + }; + + const { POST } = await import(moduleUrl("src/app/api/reports/[reportId]/professional-reference/route.ts")); + const response = await POST( + new Request("https://staging.jyotisha.chat/api/reports/123e4567-e89b-12d3-a456-426614174000/professional-reference", { + method: "POST", + headers: { origin: "https://staging.jyotisha.chat" }, + }), + { params: Promise.resolve({ reportId: "123e4567-e89b-12d3-a456-426614174000" }) }, + ); + console.log(JSON.stringify({ + status: response.status, + body: await response.json(), + modelCalls, + telemetryEvents, + upstream, + })); + `; + const result = spawnSync(process.execPath, [ + "--experimental-test-module-mocks", + "--import", + "tsx", + "--input-type=module", + "--eval", + script, + ], { cwd: frontendRoot, encoding: "utf8" }); + assert.equal(result.status, 0, result.stderr || result.stdout); + return JSON.parse(result.stdout.trim()) as { + status: number; + body: { format: string; markdown: string }; + modelCalls: number; + telemetryEvents: number; + upstream: Array<{ url: string; method: string; body: Record }>; + }; +} + test("professional reference route authenticates, checks origin, owner and ready status", () => { assert.match(routeSource, /createServerSupabaseClient/); assert.match(routeSource, /supabase\.auth\.getUser\(\)/); @@ -32,6 +134,30 @@ test("birth data stays server-owned and the route calls only the public Python e assert.doesNotMatch(routeSource, /\.insert\(|\.update\(|\.delete\(/); }); +test("ready report export calls the Python endpoint without model calls or writer telemetry", () => { + const result = executeReadyReportExport(); + assert.equal(result.status, 200); + assert.deepEqual(result.body, { format: "markdown", markdown: "# Professional reference" }); + assert.equal(result.modelCalls, 0); + assert.equal(result.telemetryEvents, 0); + assert.equal(result.upstream.length, 1); + assert.equal(result.upstream[0].url, "http://127.0.0.1:5200/api/professional_report_reference"); + assert.equal(result.upstream[0].method, "POST"); + assert.deepEqual(result.upstream[0].body, { + year: 1990, + month: 1, + day: 2, + hour: 3, + minute: 4, + lat: 39.9, + lon: 116.4, + tz: 8, + ayanamsa: "lahiri", + format: "markdown", + packs: ["full"], + }); +}); + test("busy upstream responses preserve 429 and Retry-After", () => { assert.match(routeSource, /upstream\.status/); assert.match(routeSource, /upstream\.headers\.get\("retry-after"\)/);