162 lines
5.7 KiB
TypeScript
162 lines
5.7 KiB
TypeScript
/**
|
|
* Characterization + API-surface test: callLogs.ts god-file decomposition.
|
|
*
|
|
* The 10 pure formatting/sanitization helpers were extracted verbatim from
|
|
* src/lib/usage/callLogs.ts into the pure leaf src/lib/usage/callLogs/format.ts
|
|
* (no DB, no fs). The DB CRUD / disk-artifact / rotation code stays in the host.
|
|
*
|
|
* Verifies that:
|
|
* 1. The pure helpers behave correctly (DB-free).
|
|
* 2. The host callLogs.ts still exposes the FULL public API (10 functions).
|
|
* 3. The format leaf exports its helpers directly.
|
|
*
|
|
* NOTE: helpers that call sanitizePII are exercised with non-PII text, since PII
|
|
* redaction is opt-in / off by default (Hard Rule #20), so sanitizePII is a
|
|
* pass-through here — the test pins the formatting logic, not PII behaviour.
|
|
*/
|
|
|
|
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
import {
|
|
asRecord,
|
|
toNumber,
|
|
toStringOrNull,
|
|
truncateText,
|
|
parseInlineError,
|
|
normalizeDetailState,
|
|
toStoredErrorSummary,
|
|
buildRequestSummary,
|
|
} from "../../src/lib/usage/callLogs/format.ts";
|
|
|
|
describe("callLogs/format — coercers", () => {
|
|
it("asRecord keeps plain objects, rejects arrays/primitives/null", () => {
|
|
const o = { a: 1 };
|
|
assert.equal(asRecord(o), o);
|
|
assert.deepEqual(asRecord([1, 2]), {});
|
|
assert.deepEqual(asRecord("x"), {});
|
|
assert.deepEqual(asRecord(null), {});
|
|
});
|
|
|
|
it("toNumber parses finite numbers and numeric strings, else 0", () => {
|
|
assert.equal(toNumber(5), 5);
|
|
assert.equal(toNumber("42"), 42);
|
|
assert.equal(toNumber(" 7 "), 7);
|
|
assert.equal(toNumber("abc"), 0);
|
|
assert.equal(toNumber(Infinity), 0);
|
|
assert.equal(toNumber(null), 0);
|
|
});
|
|
|
|
it("toStringOrNull keeps non-blank strings, else null", () => {
|
|
assert.equal(toStringOrNull("hi"), "hi");
|
|
assert.equal(toStringOrNull(" "), null);
|
|
assert.equal(toStringOrNull(5), null);
|
|
});
|
|
|
|
it("truncateText slices only when over the limit", () => {
|
|
assert.equal(truncateText("hello", 10), "hello");
|
|
assert.equal(truncateText("hello", 3), "hel");
|
|
});
|
|
});
|
|
|
|
describe("callLogs/format — parseInlineError", () => {
|
|
it("returns null for non-string / blank", () => {
|
|
assert.equal(parseInlineError(null), null);
|
|
assert.equal(parseInlineError(" "), null);
|
|
});
|
|
it("parses valid JSON, falls back to the raw string on invalid JSON", () => {
|
|
assert.deepEqual(parseInlineError('{"code":429}'), { code: 429 });
|
|
assert.equal(parseInlineError("not json"), "not json");
|
|
});
|
|
});
|
|
|
|
describe("callLogs/format — normalizeDetailState", () => {
|
|
it("passes through the four known states", () => {
|
|
for (const s of ["ready", "missing", "corrupt", "legacy-inline"]) {
|
|
assert.equal(normalizeDetailState(s), s);
|
|
}
|
|
});
|
|
it("maps anything else to 'none'", () => {
|
|
assert.equal(normalizeDetailState("bogus"), "none");
|
|
assert.equal(normalizeDetailState(undefined), "none");
|
|
});
|
|
});
|
|
|
|
describe("callLogs/format — toStoredErrorSummary", () => {
|
|
it("returns null for nullish error", () => {
|
|
assert.equal(toStoredErrorSummary(null), null);
|
|
assert.equal(toStoredErrorSummary(undefined), null);
|
|
});
|
|
it("stringifies a plain-text error (PII pass-through by default)", () => {
|
|
assert.equal(toStoredErrorSummary("boom"), "boom");
|
|
});
|
|
it("JSON-stringifies an Error object's sanitized shape", () => {
|
|
const out = toStoredErrorSummary(new Error("kaboom"));
|
|
assert.equal(typeof out, "string");
|
|
assert.ok(out.includes("kaboom"));
|
|
assert.ok(out.includes("message"));
|
|
});
|
|
});
|
|
|
|
describe("callLogs/format — buildRequestSummary", () => {
|
|
it("returns null for non-search request types", () => {
|
|
assert.equal(buildRequestSummary("chat", { query: "x" }), null);
|
|
assert.equal(buildRequestSummary(null, { query: "x" }), null);
|
|
});
|
|
it("summarizes a search request's query + non-query/provider filters", () => {
|
|
const out = buildRequestSummary("search", { query: "cats", provider: "p", topK: 5 });
|
|
assert.equal(typeof out, "string");
|
|
const parsed = JSON.parse(out);
|
|
assert.equal(parsed.query, "cats");
|
|
assert.deepEqual(parsed.filters, { topK: 5 });
|
|
assert.equal("provider" in (parsed.filters ?? {}), false);
|
|
});
|
|
it("returns null when a search request carries no summarizable fields", () => {
|
|
assert.equal(buildRequestSummary("search", {}), null);
|
|
assert.equal(buildRequestSummary("search", { provider: "only" }), null);
|
|
});
|
|
});
|
|
|
|
// ── host public API surface ──────────────────────────────────────────────────
|
|
|
|
const host = await import("../../src/lib/usage/callLogs.ts");
|
|
|
|
describe("callLogs.ts public API surface (10 functions)", () => {
|
|
const expected = [
|
|
"cleanupOrphanCallLogFiles",
|
|
"cleanupOverflowCallLogFiles",
|
|
"deleteCallLogsBefore",
|
|
"exportCallLogsSince",
|
|
"getCallLogById",
|
|
"getCallLogs",
|
|
"rotateCallLogs",
|
|
"saveCallLog",
|
|
"scheduleCallLogRotation",
|
|
"trimCallLogsToMaxRows",
|
|
];
|
|
for (const name of expected) {
|
|
it(`exposes ${name} as a function`, () => {
|
|
assert.equal(typeof host[name], "function", `${name} must be a function on the host`);
|
|
});
|
|
}
|
|
it("loses no public function in the split", () => {
|
|
const missing = expected.filter((n) => typeof host[n] !== "function");
|
|
assert.deepEqual(missing, [], `missing: ${missing.join(", ")}`);
|
|
});
|
|
});
|
|
|
|
describe("format.ts exports its helpers directly", () => {
|
|
it("the moved helpers are functions on the leaf", async () => {
|
|
const fmt = await import("../../src/lib/usage/callLogs/format.ts");
|
|
for (const fn of [
|
|
"asRecord",
|
|
"toNumber",
|
|
"sanitizeErrorForLog",
|
|
"protectPipelinePayloads",
|
|
"buildRequestSummary",
|
|
]) {
|
|
assert.equal(typeof fmt[fn], "function", fn);
|
|
}
|
|
});
|
|
});
|