Files
helios/server/test/llm.test.ts
marcuspaico e81d07975a fix(labs): strict numeric parsing, atomic confirm guard, test integrity
- normalize.ts num(): parseFloat truncated at the first comma, so "1,200"
  silently became 1 (1000x error) and "5,5" became 5. Now strictly matches
  either US thousands-grouping or a plain number spanning the whole string;
  anything else (incl. ambiguous "5,5") returns null instead of a wrong value.
- labs.ts confirm handler: the pending-status check ran before the request
  body was read, so two concurrent confirms could both pass it and
  double-insert. Added a guarded UPDATE ... WHERE status = 'pending' as the
  first statement inside the existing synchronous transaction; zero rows
  affected throws and the route returns 409, with the fast-path check kept
  for the common case.
- Added missing `await` on two rejects.toThrow assertions (llm.test.ts,
  extract.test.ts) that were previously resolving before the assertion
  settled.
- Bumped the 11th-failed-login rate-limit test to a 30s timeout — 10
  sequential argon2id verifies can exceed bun:test's 5s default under load.
2026-08-17 16:17:03 -07:00

52 lines
2.3 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { openDb } from "../src/db";
import { settings } from "../src/db/schema";
import { encrypt, loadOrCreateKey } from "../src/lib/crypto";
import { chatJSON } from "../src/lib/llm";
function deps(fetchImpl: typeof fetch) {
const dir = mkdtempSync(join(tmpdir(), "helios-"));
const db = openDb(dir);
const key = loadOrCreateKey(dir);
return { db, key, fetchImpl };
}
describe("chatJSON", () => {
test("posts OpenAI-compatible request with bearer key and parses JSON content", async () => {
let captured: { url: string; init: RequestInit } | null = null;
const mock = (async (url: any, init: any) => {
captured = { url: String(url), init };
return new Response(JSON.stringify({ choices: [{ message: { content: '{"ok":1}' } }] }), { status: 200 });
}) as typeof fetch;
const d = deps(mock);
await d.db.insert(settings).values({ key: "llm_key", value: encrypt(d.key, "sk-test-1234") });
const out = await chatJSON(d, { system: "sys", user: "usr" });
expect(out).toEqual({ ok: 1 });
expect(captured!.url).toBe("https://openrouter.ai/api/v1/chat/completions");
const body = JSON.parse(String(captured!.init.body));
expect(body.response_format).toEqual({ type: "json_object" });
expect(body.messages).toEqual([{ role: "system", content: "sys" }, { role: "user", content: "usr" }]);
expect((captured!.init.headers as Record<string, string>).authorization).toBe("Bearer sk-test-1234");
});
test("no key → no auth header (Ollama)", async () => {
let headers: Record<string, string> = {};
const mock = (async (_: any, init: any) => {
headers = init.headers;
return new Response(JSON.stringify({ choices: [{ message: { content: "```json\n{\"a\":2}\n```" } }] }), { status: 200 });
}) as typeof fetch;
const out = await chatJSON(deps(mock), { system: "s", user: "u" });
expect(out).toEqual({ a: 2 }); // fenced JSON stripped
expect(headers.authorization).toBeUndefined();
});
test("non-2xx throws llm_error", async () => {
const mock = (async () => new Response("nope", { status: 401 })) as unknown as typeof fetch;
await expect(chatJSON(deps(mock), { system: "s", user: "u" })).rejects.toThrow(/llm_error/);
});
});