Run Perplexity search and auth requests through pi's Node runtime instead of shelling out to Bun. Move development and CI commands to npm, add a Node test build path, and keep OTP auth fail-fast when Set-Cookie headers are not available.
117 lines
3.2 KiB
TypeScript
117 lines
3.2 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, test } from "../test-helpers.js";
|
|
|
|
import { formatForLLM } from "../../src/search/format.js";
|
|
|
|
const NOW = Date.UTC(2026, 1, 16, 12, 0, 0);
|
|
|
|
describe("formatForLLM", () => {
|
|
const originalNow = Date.now;
|
|
|
|
beforeEach(() => {
|
|
Date.now = () => NOW;
|
|
});
|
|
|
|
afterEach(() => {
|
|
Date.now = originalNow;
|
|
});
|
|
|
|
test("renders required sections and deterministic source ordering", () => {
|
|
const output = formatForLLM({
|
|
answer: "Answer body",
|
|
sources: [
|
|
{
|
|
name: "Source 1",
|
|
url: "https://example.com/1",
|
|
snippet: "Snippet 1",
|
|
timestamp: new Date(NOW - 3 * 60 * 60 * 1000).toISOString(),
|
|
},
|
|
{
|
|
name: "Source 2",
|
|
url: "https://example.com/2",
|
|
snippet: "Snippet 2",
|
|
timestamp: new Date(NOW - 2 * 24 * 60 * 60 * 1000).toISOString(),
|
|
},
|
|
],
|
|
displayModel: "pplx_pro_upgraded",
|
|
uuid: "req-123",
|
|
});
|
|
|
|
expect(output).toContain("## Answer");
|
|
expect(output).toContain("## Sources");
|
|
expect(output).toContain("## Meta");
|
|
expect(output.indexOf("[1] Source 1")).toBeLessThan(output.indexOf("[2] Source 2"));
|
|
expect(output).toContain("Provider: perplexity (oauth)");
|
|
expect(output).toContain("Model: pplx_pro_upgraded");
|
|
expect(output).toContain("Request ID: req-123");
|
|
});
|
|
|
|
test("humanizes source ages", () => {
|
|
const output = formatForLLM({
|
|
answer: "Age test",
|
|
sources: [
|
|
{
|
|
name: "Recent",
|
|
url: "https://example.com/recent",
|
|
snippet: "recent snippet",
|
|
timestamp: new Date(NOW - 30 * 1000).toISOString(),
|
|
},
|
|
{
|
|
name: "Minutes",
|
|
url: "https://example.com/minutes",
|
|
snippet: "minutes snippet",
|
|
timestamp: new Date(NOW - 12 * 60 * 1000).toISOString(),
|
|
},
|
|
{
|
|
name: "Hours",
|
|
url: "https://example.com/hours",
|
|
snippet: "hours snippet",
|
|
timestamp: new Date(NOW - 5 * 60 * 60 * 1000).toISOString(),
|
|
},
|
|
{
|
|
name: "Days",
|
|
url: "https://example.com/days",
|
|
snippet: "days snippet",
|
|
timestamp: new Date(NOW - 3 * 24 * 60 * 60 * 1000).toISOString(),
|
|
},
|
|
],
|
|
});
|
|
|
|
expect(output).toContain("Recent (just now)");
|
|
expect(output).toContain("Minutes (12m ago)");
|
|
expect(output).toContain("Hours (5h ago)");
|
|
expect(output).toContain("Days (3d ago)");
|
|
});
|
|
|
|
test("truncates snippets to 240 chars", () => {
|
|
const longSnippet = "x".repeat(300);
|
|
|
|
const output = formatForLLM({
|
|
answer: "Snippet test",
|
|
sources: [
|
|
{
|
|
name: "Long snippet",
|
|
url: "https://example.com/long",
|
|
snippet: longSnippet,
|
|
},
|
|
],
|
|
});
|
|
|
|
const snippetLine = output
|
|
.split("\n")
|
|
.find((line) => line.startsWith(" ") && line.includes("…"));
|
|
|
|
expect(snippetLine).toBeDefined();
|
|
expect(snippetLine!.trim().length).toBe(240);
|
|
});
|
|
|
|
test("handles empty source list", () => {
|
|
const output = formatForLLM({
|
|
answer: "No sources",
|
|
sources: [],
|
|
});
|
|
|
|
expect(output).toContain("0 sources");
|
|
expect(output).toContain("(no sources returned)");
|
|
});
|
|
});
|