OmniRoute/tests/unit/codexAuthImportBulk.test.ts
diegosouzapw 25f3fe1ac5 feat(codex): bulk import Codex auth.json — multi-file, paste, ZIP
Adds three input modes for importing multiple Codex accounts at once,
all feeding a single partial-failure backend endpoint.

- `codexAuthZipExtract.ts`: safe ZIP extraction via fflate — rejects
  path traversal (../ and absolute paths), per-file 256 KB cap, 10 MB
  total cap, max 50 .json entries
- `POST /api/providers/codex-auth/zip-extract`: server-side ZIP
  extraction returning [{name, json, parseError}] to the client
- `POST /api/providers/codex-auth/import-bulk`: iterates entries,
  partial-failure semantics (always 200), per-entry audit log
  `provider.credentials.imported` + summary `bulk_imported`
- `importCodexAuthBulkSchema`: max 50 entries, email validation
- `<ImportCodexAuthModal>`: Single/Bulk top tabs; Bulk has Upload
  files, Paste list (JSON array or --- separator), ZIP sub-modes;
  live entry preview list; overwrite checkbox; result panel
- Install `fflate@0.8.3` (pure TypeScript, zero native deps)
- 29 unit tests: 12 ZIP safety cases + 17 schema/parser/shape cases
2026-05-17 16:52:43 -03:00

187 lines
6.1 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { z } from "zod";
// Local copy of importCodexAuthBulkSchema — avoids importing Next.js deps.
const importCodexAuthBulkSchema = z.object({
entries: z
.array(
z.object({
json: z.unknown(),
name: z.string().min(1).max(200).optional(),
email: z.string().email().optional(),
})
)
.min(1)
.max(50),
overwriteExisting: z.boolean().optional(),
});
function parse(body: unknown) {
return importCodexAuthBulkSchema.safeParse(body);
}
// ──── Schema tests ────────────────────────────────────────────────────────────
test("bulk schema: valid single entry passes", () => {
const result = parse({ entries: [{ json: { auth_mode: "chatgpt" } }] });
assert.ok(result.success);
});
test("bulk schema: valid multiple entries pass", () => {
const result = parse({
entries: [
{ json: {}, name: "Account A", email: "a@example.com" },
{ json: {}, name: "Account B" },
],
overwriteExisting: true,
});
assert.ok(result.success);
assert.equal(result.data.entries.length, 2);
assert.equal(result.data.overwriteExisting, true);
});
test("bulk schema: empty entries array fails", () => {
const result = parse({ entries: [] });
assert.ok(!result.success);
});
test("bulk schema: missing entries fails", () => {
const result = parse({});
assert.ok(!result.success);
});
test("bulk schema: 50 entries passes", () => {
const entries = Array.from({ length: 50 }, (_, i) => ({ json: { index: i } }));
const result = parse({ entries });
assert.ok(result.success);
});
test("bulk schema: 51 entries fails", () => {
const entries = Array.from({ length: 51 }, (_, i) => ({ json: { index: i } }));
const result = parse({ entries });
assert.ok(!result.success);
});
test("bulk schema: invalid email in entry fails", () => {
const result = parse({
entries: [{ json: {}, email: "not-an-email" }],
});
assert.ok(!result.success);
const emailIssue = result.error.issues.find((i) => i.path.some((p) => p === "email"));
assert.ok(emailIssue);
});
test("bulk schema: empty name in entry fails", () => {
const result = parse({ entries: [{ json: {}, name: "" }] });
assert.ok(!result.success);
});
test("bulk schema: overwriteExisting must be boolean", () => {
const result = parse({
entries: [{ json: {} }],
overwriteExisting: "yes",
});
assert.ok(!result.success);
});
// ──── Paste-list parser tests ─────────────────────────────────────────────────
// Client-side paste parser logic (mirrors what the UI does before submitting)
function parsePasteList(text: string): Array<{ json: unknown; parseError: string | null }> {
const trimmed = text.trim();
if (!trimmed) return [];
// Try as a JSON array first
try {
const parsed = JSON.parse(trimmed);
if (Array.isArray(parsed)) {
return parsed.map((item) => ({ json: item, parseError: null }));
}
// Single object
return [{ json: parsed, parseError: null }];
} catch {
// Fall back to --- separator
const parts = trimmed
.split(/^---$/m)
.map((s) => s.trim())
.filter(Boolean);
return parts.map((part) => {
try {
return { json: JSON.parse(part), parseError: null };
} catch {
return { json: null, parseError: "Invalid JSON" };
}
});
}
}
test("paste parser: JSON array returns all items", () => {
const text = JSON.stringify([{ auth_mode: "chatgpt" }, { auth_mode: "chatgpt" }]);
const result = parsePasteList(text);
assert.equal(result.length, 2);
assert.ok(result.every((r) => r.parseError === null));
});
test("paste parser: single JSON object returns one item", () => {
const text = JSON.stringify({ auth_mode: "chatgpt" });
const result = parsePasteList(text);
assert.equal(result.length, 1);
assert.equal(result[0].parseError, null);
});
test("paste parser: --- separator splits multiple JSONs", () => {
const a = JSON.stringify({ auth_mode: "chatgpt", index: 1 });
const b = JSON.stringify({ auth_mode: "chatgpt", index: 2 });
const text = `${a}\n---\n${b}`;
const result = parsePasteList(text);
assert.equal(result.length, 2);
assert.ok(result.every((r) => r.parseError === null));
});
test("paste parser: invalid JSON in --- section marked as error", () => {
const good = JSON.stringify({ auth_mode: "chatgpt" });
const text = `${good}\n---\nnot valid json`;
const result = parsePasteList(text);
assert.equal(result.length, 2);
assert.equal(result[0].parseError, null);
assert.ok(result[1].parseError !== null);
});
test("paste parser: empty string returns empty array", () => {
assert.equal(parsePasteList("").length, 0);
assert.equal(parsePasteList(" ").length, 0);
});
// ──── Partial-failure response shape ──────────────────────────────────────────
test("bulk response shape: partial success has correct structure", () => {
const response = {
success: 2,
failed: 1,
total: 3,
created: [{ id: "a" }, { id: "b" }],
errors: [{ index: 2, name: "entry 3", message: "duplicate_account" }],
};
assert.equal(response.success + response.failed, response.total);
assert.equal(response.created.length, response.success);
assert.equal(response.errors.length, response.failed);
assert.ok("index" in response.errors[0]);
assert.ok("name" in response.errors[0]);
assert.ok("message" in response.errors[0]);
});
test("bulk response shape: all-failure has status failed", () => {
const entries = 3;
const failed = 3;
const status = failed === entries ? "failure" : "success";
assert.equal(status, "failure");
});
test("bulk response shape: partial failure has status success", () => {
const entries = 3;
const failed = 1;
const status = failed === entries ? "failure" : "success";
assert.equal(status, "success");
});