Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@
"test": "vitest run",
"test:drift": "vitest run --config vitest.config.drift.ts",
"test:exports": "publint && attw --pack .",
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p scripts/tsconfig.json --noEmit",
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit && tsc -p scripts/tsconfig.json --noEmit",
"lint": "eslint .",
"format:check": "prettier --check .",
"release": "pnpm format:check && pnpm typecheck && pnpm build && pnpm test && pnpm lint && pnpm test:exports && npm publish",
Expand Down
40 changes: 31 additions & 9 deletions src/__tests__/agui-mock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as http from "node:http";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import type { AGUIEvent, AGUIRunAgentInput, AGUITokenUsage } from "../agui-types.js";
import type { AGUIEvent, AGUIMessage, AGUIRunAgentInput, AGUITokenUsage } from "../agui-types.js";
import { AGUIMock } from "../agui-mock.js";
import {
buildTextResponse,
Expand Down Expand Up @@ -257,9 +257,9 @@ describe("AGUIMock core", () => {

it("6. messages snapshot", async () => {
agui = new AGUIMock({ port: 0 });
const msgs = [
{ role: "user", content: "hi" },
{ role: "assistant", content: "hello" },
const msgs: AGUIMessage[] = [
{ id: "m1", role: "user", content: "hi" },
{ id: "m2", role: "assistant", content: "hello" },
];
const events = buildMessagesSnapshot(msgs);
agui.addFixture({
Expand Down Expand Up @@ -482,7 +482,7 @@ describe("AGUIMock builders", () => {
expect(delta.map((e) => e.type)).toEqual(["RUN_STARTED", "STATE_DELTA", "RUN_FINISHED"]);

// buildMessagesSnapshot
const msgs = buildMessagesSnapshot([{ role: "user", content: "hi" }]);
const msgs = buildMessagesSnapshot([{ id: "m1", role: "user", content: "hi" }]);
expect(msgs.map((e) => e.type)).toEqual(["RUN_STARTED", "MESSAGES_SNAPSHOT", "RUN_FINISHED"]);

// buildErrorResponse
Expand Down Expand Up @@ -1477,6 +1477,8 @@ describe("extractLastUserMessage", () => {
it("returns plain string content verbatim", () => {
expect(
extractLastUserMessage({
threadId: "t1",
runId: "r1",
messages: [{ id: "1", role: "user", content: "hello" }],
}),
).toBe("hello");
Expand All @@ -1485,6 +1487,8 @@ describe("extractLastUserMessage", () => {
it("returns text from a single-part array", () => {
expect(
extractLastUserMessage({
threadId: "t1",
runId: "r1",
messages: [{ id: "1", role: "user", content: [{ type: "text", text: "hello" }] }],
}),
).toBe("hello");
Expand All @@ -1493,6 +1497,8 @@ describe("extractLastUserMessage", () => {
it("joins multiple text parts with a single space", () => {
expect(
extractLastUserMessage({
threadId: "t1",
runId: "r1",
messages: [
{
id: "1",
Expand All @@ -1510,6 +1516,8 @@ describe("extractLastUserMessage", () => {
it("extracts only text parts when mixed with non-text parts (e.g. file attachments)", () => {
expect(
extractLastUserMessage({
threadId: "t1",
runId: "r1",
messages: [
{
id: "1",
Expand All @@ -1530,6 +1538,8 @@ describe("extractLastUserMessage", () => {
it("returns empty string when content has no text parts", () => {
expect(
extractLastUserMessage({
threadId: "t1",
runId: "r1",
messages: [
{
id: "1",
Expand All @@ -1549,6 +1559,8 @@ describe("extractLastUserMessage", () => {
it("ignores non-text parts that happen to carry a 'text' field", () => {
expect(
extractLastUserMessage({
threadId: "t1",
runId: "r1",
messages: [
{
id: "1",
Expand All @@ -1563,6 +1575,8 @@ describe("extractLastUserMessage", () => {
it("returns the last user turn's text when multiple user turns exist", () => {
expect(
extractLastUserMessage({
threadId: "t1",
runId: "r1",
messages: [
{ id: "1", role: "user", content: "first" },
{ id: "2", role: "assistant", content: "ack" },
Expand All @@ -1575,6 +1589,8 @@ describe("extractLastUserMessage", () => {
it("skips non-user roles even when they have text content", () => {
expect(
extractLastUserMessage({
threadId: "t1",
runId: "r1",
messages: [
{ id: "1", role: "user", content: "real user message" },
{ id: "2", role: "assistant", content: "assistant turn" },
Expand All @@ -1584,13 +1600,15 @@ describe("extractLastUserMessage", () => {
});

it("returns empty string for empty or missing messages", () => {
expect(extractLastUserMessage({ messages: [] })).toBe("");
expect(extractLastUserMessage({ threadId: "t1", runId: "r1", messages: [] })).toBe("");
expect(extractLastUserMessage({} as AGUIRunAgentInput)).toBe("");
});

it("returns empty string when user message content is undefined", () => {
expect(
extractLastUserMessage({
threadId: "t1",
runId: "r1",
messages: [{ id: "1", role: "user" }],
}),
).toBe("");
Expand Down Expand Up @@ -1650,7 +1668,7 @@ describe("AGUIMock recorder — structured user content", () => {
],
},
],
} as AGUIRunAgentInput);
});
expect(resp.status).toBe(200);

const files = fs.readdirSync(tmpDir);
Expand All @@ -1670,6 +1688,8 @@ describe("AGUIMock recorder — structured user content", () => {
await agui.start();

const resp = await post(agui.url, {
threadId: "t1",
runId: "r1",
messages: [
{
id: "u1",
Expand All @@ -1682,13 +1702,15 @@ describe("AGUIMock recorder — structured user content", () => {
],
},
],
} as AGUIRunAgentInput);
});
expect(resp.status).toBe(200);

const files = fs.readdirSync(tmpDir);
expect(files.length).toBe(0);

const resp2 = await post(agui.url, {
threadId: "t2",
runId: "r2",
messages: [
{
id: "u2",
Expand All @@ -1701,7 +1723,7 @@ describe("AGUIMock recorder — structured user content", () => {
],
},
],
} as AGUIRunAgentInput);
});
expect(resp2.status).toBe(200);
});
});
Expand Down
10 changes: 6 additions & 4 deletions src/__tests__/api-key-auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,12 @@ describe("API key HTTP boundary", () => {
[{ match: { userMessage: "hello" }, response: { content: "ok" } }],
{ auth: { apiKeys: ["primary", "rotated"] } },
);
for (const headers of [
const rejectedHeaderSets: Record<string, string>[] = [
{},
{ Authorization: "Bearer wrong" },
{ Authorization: "Bearer primary", "X-Api-Key": "rotated" },
]) {
];
for (const headers of rejectedHeaderSets) {
const result = await request(`${instance.url}/v1/chat/completions`, headers);
expect(result.status).toBe(401);
expect(result.body).toBe(
Expand All @@ -124,7 +125,7 @@ describe("API key HTTP boundary", () => {
[{ match: { userMessage: "hello" }, response: { content: "ok" } }],
{ auth: { apiKeys: ["primary"] } },
);
for (const headers of [
const acceptedHeaderSets: Record<string, string>[] = [
{ Authorization: "Bearer primary" },
{ Authorization: "Key primary" },
{ Authorization: "bearer primary" },
Expand All @@ -133,7 +134,8 @@ describe("API key HTTP boundary", () => {
{ "X-Goog-Api-Key": "primary" },
{ "Api-Key": "primary" },
{ "Xi-Api-Key": "primary" },
])
];
for (const headers of acceptedHeaderSets)
expect((await request(`${instance.url}/v1/chat/completions`, headers)).status).toBe(200);
expect((await request(`${instance.url}/health`, {}, "GET")).status).toBe(200);
});
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/bedrock.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ describe("POST /model/{modelId}/invoke (journal)", () => {
expect(entry!.path).toBe("/model/anthropic.claude-3-5-sonnet-20241022-v2:0/invoke");
expect(entry!.response.status).toBe(200);
expect(entry!.response.fixture).toBe(textFixture);
expect(entry!.body.model).toBe("anthropic.claude-3-5-sonnet-20241022-v2:0");
expect(entry!.body!.model).toBe("anthropic.claude-3-5-sonnet-20241022-v2:0");
});
});

Expand Down
3 changes: 2 additions & 1 deletion src/__tests__/cohere.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -954,7 +954,7 @@ describe("POST /v2/chat (journal)", () => {
expect(entry!.path).toBe("/v2/chat");
expect(entry!.response.status).toBe(200);
expect(entry!.response.fixture).toBe(textFixture);
expect(entry!.body.model).toBe("command-r-plus");
expect(entry!.body!.model).toBe("command-r-plus");
});
});

Expand Down Expand Up @@ -1194,6 +1194,7 @@ function createDefaults(overrides: Partial<HandlerDefaults> = {}): HandlerDefaul
return {
latency: 0,
chunkSize: 100,
replaySpeed: 1,
logger: new Logger("silent"),
...overrides,
};
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/competitive-matrix.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
applyChanges,
COMPETITOR_MIGRATION_PAGES,
type DetectedChange,
} from "../../scripts/update-competitive-matrix.ts";
} from "../../scripts/update-competitive-matrix.js";

// Repo root: this file lives at <root>/src/__tests__/, so up two levels.
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
Expand Down
8 changes: 4 additions & 4 deletions src/__tests__/drift/models.drift.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,12 @@ describe("C4: checkDeprecatedFamiliesLive (infra-error short-circuit via isInfra

describe("C4: isFamilyStillReferenced (real source-tree ref-scan, zero-LLM)", () => {
it("finds a real reference (gpt-4 and gpt-4o are both referenced in src/server.ts)", () => {
expect(isFamilyStillReferenced("gpt-4", "openai")).toBe(true);
expect(isFamilyStillReferenced("gpt-4o", "openai")).toBe(true);
expect(isFamilyStillReferenced("gpt-4")).toBe(true);
expect(isFamilyStillReferenced("gpt-4o")).toBe(true);
});

it("reports zero-reference for a synthetic family that appears nowhere in src/", () => {
expect(isFamilyStillReferenced("zzz-totally-fictional-family-not-real", "openai")).toBe(false);
expect(isFamilyStillReferenced("zzz-totally-fictional-family-not-real")).toBe(false);
});

it("does not false-positive from being a strict substring of a longer live id", () => {
Expand All @@ -154,7 +154,7 @@ describe("C4: isFamilyStillReferenced (real source-tree ref-scan, zero-LLM)", ()
// not confuse the two. gpt-4 IS separately, exactly referenced in
// DEFAULT_MODELS (asserted above) — this confirms the match is a real
// boundary hit, not a substring artifact.
expect(isFamilyStillReferenced("gpt-4-turbo-nonexistent-suffix", "openai")).toBe(false);
expect(isFamilyStillReferenced("gpt-4-turbo-nonexistent-suffix")).toBe(false);
});
});

Expand Down
23 changes: 14 additions & 9 deletions src/__tests__/embeddings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -774,11 +774,16 @@ function createMockRes(): http.ServerResponse {
headers[name.toLowerCase()] = String(value);
return res;
};
res.writeHead = (statusCode: number, hdrs?: Record<string, string>) => {
res.writeHead = (
statusCode: number,
arg1?: string | http.OutgoingHttpHeaders | http.OutgoingHttpHeader[],
arg2?: http.OutgoingHttpHeaders | http.OutgoingHttpHeader[],
) => {
(res as { statusCode: number }).statusCode = statusCode;
if (hdrs) {
const hdrs = typeof arg1 === "string" ? arg2 : arg1;
if (hdrs && !Array.isArray(hdrs)) {
for (const [k, v] of Object.entries(hdrs)) {
headers[k.toLowerCase()] = v;
headers[k.toLowerCase()] = String(v);
}
}
return res;
Expand All @@ -802,7 +807,7 @@ describe("handleEmbeddings (direct call — ?? fallback branches)", () => {
it("uses fallback POST and /v1/embeddings when req.method and req.url are undefined", async () => {
const journal = new Journal();
const logger = new Logger("silent");
const defaults = { latency: 0, chunkSize: 10, logger };
const defaults = { latency: 0, chunkSize: 10, replaySpeed: 1, logger };

const mockReq = {
method: undefined,
Expand Down Expand Up @@ -834,7 +839,7 @@ describe("handleEmbeddings (direct call — ?? fallback branches)", () => {
it("uses fallback method/path on malformed JSON with undefined req fields", async () => {
const journal = new Journal();
const logger = new Logger("silent");
const defaults = { latency: 0, chunkSize: 10, logger };
const defaults = { latency: 0, chunkSize: 10, replaySpeed: 1, logger };

const mockReq = {
method: undefined,
Expand All @@ -855,7 +860,7 @@ describe("handleEmbeddings (direct call — ?? fallback branches)", () => {
it("uses fallback for strict mode with undefined req fields", async () => {
const journal = new Journal();
const logger = new Logger("silent");
const defaults = { latency: 0, chunkSize: 10, logger, strict: true };
const defaults = { latency: 0, chunkSize: 10, replaySpeed: 1, logger, strict: true };

const mockReq = {
method: undefined,
Expand Down Expand Up @@ -887,7 +892,7 @@ describe("handleEmbeddings (direct call — ?? fallback branches)", () => {
it("uses fallback for error fixture with undefined req fields", async () => {
const journal = new Journal();
const logger = new Logger("silent");
const defaults = { latency: 0, chunkSize: 10, logger };
const defaults = { latency: 0, chunkSize: 10, replaySpeed: 1, logger };

const errorFixture: Fixture = {
match: { inputText: "err" },
Expand Down Expand Up @@ -927,7 +932,7 @@ describe("handleEmbeddings (direct call — ?? fallback branches)", () => {
it("uses fallback for embedding fixture with undefined req fields", async () => {
const journal = new Journal();
const logger = new Logger("silent");
const defaults = { latency: 0, chunkSize: 10, logger };
const defaults = { latency: 0, chunkSize: 10, replaySpeed: 1, logger };

const embFixture: Fixture = {
match: { inputText: "embed" },
Expand Down Expand Up @@ -964,7 +969,7 @@ describe("handleEmbeddings (direct call — ?? fallback branches)", () => {
it("uses fallback for incompatible fixture response with undefined req fields", async () => {
const journal = new Journal();
const logger = new Logger("silent");
const defaults = { latency: 0, chunkSize: 10, logger };
const defaults = { latency: 0, chunkSize: 10, replaySpeed: 1, logger };

const badFixture: Fixture = {
match: { predicate: () => true },
Expand Down
2 changes: 1 addition & 1 deletion src/__tests__/fal-audio.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ describe("fal.ai audio queue — record walk (round 5)", () => {
// the synthesized envelope's headers have not been sent when
// persistFixture fails, so the failure can ride the response.
let selfUrl = "http://stub";
upstream = await new Promise((resolve, reject) => {
upstream = await new Promise<{ url: string; close: () => Promise<void> }>((resolve, reject) => {
const server = http.createServer((req, res) => {
const chunks: Buffer[] = [];
req.on("data", (c: Buffer) => chunks.push(c));
Expand Down
Loading
Loading