项目文件夹

文件
2026-07-13 13:39:12 +08:00

217 行
10 KiB
TypeScript

此文件含有不可见的 Unicode 字符
此文件含有人类无法区分的不可见的 Unicode 字符,但可以由计算机进行不同的处理。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
import { test } from "node:test";
import assert from "node:assert/strict";
const { validateJsonl } = await import("../../../../src/lib/batches/validateJsonl.ts");
const ENDPOINT = "/v1/chat/completions" as const;
// ── Helpers ──────────────────────────────────────────────────────────────────
function makeLine(
customId: string,
url: string = ENDPOINT,
method: string = "POST",
body: unknown = { model: "gpt-4o", messages: [{ role: "user", content: "hi" }] }
) {
return JSON.stringify({ custom_id: customId, method, url, body });
}
function makeJsonl(lines: string[]) {
return lines.join("\n") + "\n";
}
// ── Empty / trivial ────────────────────────────────────────────────────────────
test("validateJsonl: empty string → ok=false, totalLines=0", () => {
const result = validateJsonl("", { endpoint: ENDPOINT });
assert.equal(result.ok, true, "empty JSONL has no errors — but totalLines=0");
assert.equal(result.totalLines, 0);
assert.equal(result.sampledLines, 0);
assert.equal(result.errors.length, 0);
});
test("validateJsonl: whitespace-only content → no lines", () => {
const result = validateJsonl(" \n\n \n", { endpoint: ENDPOINT });
assert.equal(result.totalLines, 0);
});
// ── Valid lines ────────────────────────────────────────────────────────────────
test("validateJsonl: 1 valid OpenAI-shape line → ok=true, 1 uniqueCustomId", () => {
const jsonl = makeJsonl([makeLine("req-1")]);
const result = validateJsonl(jsonl, { endpoint: ENDPOINT });
assert.ok(result.ok, "valid line should produce ok=true");
assert.equal(result.totalLines, 1);
assert.equal(result.sampledLines, 1);
assert.equal(result.uniqueCustomIds, 1);
assert.equal(result.errors.length, 0);
assert.equal(result.duplicateCustomIds.length, 0);
assert.equal(result.preview.length, 1);
});
test("validateJsonl: Anthropic params shape → ok=true (no url/method/body required)", () => {
const line = JSON.stringify({
custom_id: "anthropic-req-1",
params: { model: "claude-3-5-sonnet-20241022", messages: [{ role: "user", content: "hi" }] },
});
const result = validateJsonl(line + "\n", { endpoint: ENDPOINT });
assert.ok(result.ok);
assert.equal(result.uniqueCustomIds, 1);
});
test("validateJsonl: multiple valid lines → uniqueCustomIds matches count", () => {
const lines = Array.from({ length: 5 }, (_, i) => makeLine(`req-${i + 1}`));
const result = validateJsonl(makeJsonl(lines), { endpoint: ENDPOINT });
assert.ok(result.ok);
assert.equal(result.uniqueCustomIds, 5);
assert.equal(result.totalLines, 5);
});
// ── Errors: invalid JSON ───────────────────────────────────────────────────────
test("validateJsonl: invalid JSON line → error with 'invalid JSON' reason", () => {
const jsonl = "this is not json\n";
const result = validateJsonl(jsonl, { endpoint: ENDPOINT });
assert.equal(result.ok, false);
assert.ok(result.errors.some((e) => e.reason.toLowerCase().includes("invalid json")));
});
// ── Errors: missing custom_id ─────────────────────────────────────────────────
test("validateJsonl: missing custom_id → error reported with field=custom_id", () => {
const line = JSON.stringify({ method: "POST", url: ENDPOINT, body: {} });
const result = validateJsonl(line + "\n", { endpoint: ENDPOINT });
assert.equal(result.ok, false);
const err = result.errors.find((e) => e.field === "custom_id");
assert.ok(err, "should have a custom_id error");
});
test("validateJsonl: empty custom_id string → error", () => {
const line = JSON.stringify({ custom_id: "", method: "POST", url: ENDPOINT, body: {} });
const result = validateJsonl(line + "\n", { endpoint: ENDPOINT });
assert.equal(result.ok, false);
assert.ok(result.errors.some((e) => e.field === "custom_id"));
});
// ── Errors: duplicate custom_id ───────────────────────────────────────────────
test("validateJsonl: duplicate custom_id → ok=false, duplicateCustomIds populated", () => {
const lines = [makeLine("dup-id"), makeLine("dup-id"), makeLine("unique-id")];
const result = validateJsonl(makeJsonl(lines), { endpoint: ENDPOINT });
assert.equal(result.ok, false);
assert.ok(result.duplicateCustomIds.includes("dup-id"));
assert.equal(result.uniqueCustomIds, 2, "dup-id + unique-id = 2 unique ids");
});
// ── Errors: wrong url / method ─────────────────────────────────────────────────
test("validateJsonl: url differs from endpoint → error on field=url", () => {
const line = JSON.stringify({
custom_id: "req-1",
method: "POST",
url: "/v1/embeddings",
body: {},
});
const result = validateJsonl(line + "\n", { endpoint: ENDPOINT });
assert.equal(result.ok, false);
assert.ok(result.errors.some((e) => e.field === "url"));
});
test("validateJsonl: completely unsupported url → error on field=url", () => {
const line = JSON.stringify({ custom_id: "req-1", method: "POST", url: "/v1/unknown", body: {} });
const result = validateJsonl(line + "\n", { endpoint: ENDPOINT });
assert.equal(result.ok, false);
assert.ok(result.errors.some((e) => e.field === "url"));
});
test("validateJsonl: method is GET instead of POST → error on field=method", () => {
const line = JSON.stringify({ custom_id: "req-1", method: "GET", url: ENDPOINT, body: {} });
const result = validateJsonl(line + "\n", { endpoint: ENDPOINT });
assert.equal(result.ok, false);
assert.ok(result.errors.some((e) => e.field === "method"));
});
// ── Sampling ──────────────────────────────────────────────────────────────────
test("validateJsonl: sampling — 1500 lines with maxLinesToInspect=1000, tailLinesToInspect=100", () => {
const lines = Array.from({ length: 1500 }, (_, i) => makeLine(`req-${i}`));
const result = validateJsonl(makeJsonl(lines), {
endpoint: ENDPOINT,
maxLinesToInspect: 1000,
tailLinesToInspect: 100,
});
assert.equal(result.totalLines, 1500);
// sampledLines = head 1000 + tail 100 (but head already covers 1000..1399, so tail adds 100 extra beyond 1000)
assert.ok(result.sampledLines >= 1000, "should have sampled at least 1000 lines");
assert.ok(result.sampledLines <= 1100, "should not exceed head+tail");
});
test("validateJsonl: no sampling — all lines inspected when maxLinesToInspect >= totalLines", () => {
const lines = Array.from({ length: 10 }, (_, i) => makeLine(`req-${i}`));
const result = validateJsonl(makeJsonl(lines), {
endpoint: ENDPOINT,
maxLinesToInspect: 10000,
});
assert.equal(result.sampledLines, 10);
assert.equal(result.totalLines, 10);
});
// ── Preview ────────────────────────────────────────────────────────────────────
test("validateJsonl: preview contains at most 5 items", () => {
const lines = Array.from({ length: 10 }, (_, i) => makeLine(`req-${i}`));
const result = validateJsonl(makeJsonl(lines), { endpoint: ENDPOINT });
assert.ok(result.preview.length <= 5, `preview should be ≤5, got ${result.preview.length}`);
});
test("validateJsonl: invalid lines are not included in preview", () => {
const jsonl = "not-json\n" + makeLine("req-1") + "\n";
const result = validateJsonl(jsonl, { endpoint: ENDPOINT });
// preview should only contain the valid line
assert.ok(result.preview.length <= 1);
});
// ── byteSize ──────────────────────────────────────────────────────────────────
test("validateJsonl: byteSize matches UTF-8 byte length of input", () => {
const content = makeJsonl([makeLine("req-1")]);
const result = validateJsonl(content, { endpoint: ENDPOINT });
const expected = new TextEncoder().encode(content).length;
assert.equal(result.byteSize, expected);
});
// ── Errors cap ────────────────────────────────────────────────────────────────
test("validateJsonl: errors capped at 50 even with many invalid lines", () => {
const lines = Array.from({ length: 100 }, (_, i) => `{"custom_id":"req-${i}","method":"GET","url":"${ENDPOINT}","body":{}}`);
const result = validateJsonl(makeJsonl(lines), { endpoint: ENDPOINT });
assert.ok(result.errors.length <= 50, `errors should be capped at 50, got ${result.errors.length}`);
});
// ── body must be object (not array) — Array.isArray guard ─────────────────────
test("validateJsonl: rejects body that is an array (typeof []==='object' guard)", () => {
const line = JSON.stringify({ custom_id: "req-1", method: "POST", url: ENDPOINT, body: [] });
const result = validateJsonl(line, { endpoint: ENDPOINT });
assert.ok(!result.ok, "should be invalid");
assert.ok(
result.errors.some((e) => e.field === "body"),
`should flag body field; errors=${JSON.stringify(result.errors)}`,
);
});
// ── BOM stripping (Windows-saved files) ───────────────────────────────────────
test("validateJsonl: strips UTF-8 BOM before parsing first line", () => {
const line = JSON.stringify({
custom_id: "req-1",
method: "POST",
url: ENDPOINT,
body: { model: "gpt-4o", messages: [{ role: "user", content: "hi" }] },
});
const result = validateJsonl("" + line, { endpoint: ENDPOINT });
assert.ok(result.ok, `BOM should be stripped; got errors=${JSON.stringify(result.errors)}`);
assert.equal(result.totalLines, 1);
assert.equal(result.uniqueCustomIds, 1);
});