Files
OmniRoute/tests/unit/combos-duplicate-route.test.ts
Diego Rodrigues de Sa e Souza 93265eede3 test(infra): retry recursive temp-dir removal on main (main twin of #11968) (#12246)
* test(infra): retry recursive temp-dir removal on main (main twin of #11968)

`main` has been red since b342c1a361 on the vitest and integration gates:

  ✖ tests/unit/autoCombo/provider-family-combos.test.ts > auto/<family>
  ✖ chat pipeline applies Codex OAuth fingerprint and priority tier inside combos

Both call resetStorage() from beforeEach, which does an fs.rmSync(TEST_DATA_DIR,
{recursive: true, force: true}) with no retry, and intermittently loses the race
with a not-yet-released SQLite handle (ENOTEMPTY).

release/v3.8.51 fixed this in #11968 with a mechanical codemod adding
maxRetries/retryDelay to every recursive rm/rmSync/rmdirSync under tests/, but
that PR landed only on the release branch. Because main only receives work at
the release squash, it stayed broken for the whole cycle — and repo-wide gates
then turn every open PR into main red on checks unrelated to their diff.

This is the --base main twin: re-runs the same codemod that already shipped on
the release branch (scripts/ad-hoc/codemod-rm-maxretries.mjs), so the two
branches converge on identical test-teardown semantics. Test-only; no product
logic is touched.

The remaining three failures reported on #12133 (unit full suite exceeding its
4800s ceiling, package-artifact exceeding 1200s, and the boot-smoke that is
skipped as a consequence) are runner-contention timeouts, not code defects —
validate-release-green.mjs runs those heavy gates concurrently on one shared
hosted runner. There is no fix to port for those.

* chore(scripts): carry the rm-maxretries codemod onto main alongside its output

The codemod that generated the previous commit lives in the repo on
release/v3.8.51 (added by #11968) but was never on main. Bringing it over keeps
the tool next to the change it produced, so the transformation stays
reproducible and auditable from either branch.
2026-09-01 01:48:00 -03:00

288 lines
10 KiB
TypeScript

/**
* Unit tests for POST /api/combos/duplicate (Rule #18).
*
* Coverage: auth gate, input validation (400), success response shape (201)
* including weight distribution and naming convention, config fields,
* and error sanitization (no stack traces in responses).
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "os";
import path from "node:path";
// ── DB / auth setup ───────────────────────────────────────────────────────────
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-combos-duplicate-"));
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.API_KEY_SECRET = process.env.API_KEY_SECRET ?? "combos-duplicate-test-secret";
const core = await import("../../src/lib/db/core.ts");
const settingsDb = await import("../../src/lib/db/settings.ts");
const apiKeysDb = await import("../../src/lib/db/apiKeys.ts");
// Route loaded AFTER env is set
const duplicateRoute = await import("../../src/app/api/combos/duplicate/route.ts");
function makePostRequest(url: string, body: unknown, apiKey?: string): Request {
return new Request(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
...(apiKey ? { authorization: `Bearer ${apiKey}` } : {}),
},
body: JSON.stringify(body),
});
}
test.after(() => {
core.resetDbInstance();
try {
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
} catch {
// best-effort cleanup
}
});
// ── Auth gate ─────────────────────────────────────────────────────────────────
test("POST /api/combos/duplicate returns 401/403 when auth is required and no token", async () => {
await settingsDb.updateSettings({ requireLogin: true });
process.env.INITIAL_PASSWORD = "test-password-dup";
const req = makePostRequest("http://localhost/api/combos/duplicate", {
name: "auto/best-coding",
});
const res = await duplicateRoute.POST(req as never);
assert.ok(
res.status === 401 || res.status === 403,
`Expected 401 or 403 without auth, got ${res.status}`
);
await settingsDb.updateSettings({ requireLogin: false });
delete process.env.INITIAL_PASSWORD;
});
test("POST /api/combos/duplicate passes auth with valid management API key", async () => {
await settingsDb.updateSettings({ requireLogin: true });
process.env.INITIAL_PASSWORD = "test-password-dup-key";
const { key } = await apiKeysDb.createApiKey("dup-test", "machine-dup", ["manage"]);
// Invalid body (empty name) — auth should pass, business logic rejects
const req = makePostRequest("http://localhost/api/combos/duplicate", { name: "" }, key);
const res = await duplicateRoute.POST(req as never);
assert.ok(
!(res.status === 401 || res.status === 403),
`Auth should have passed with valid key, got ${res.status}`
);
assert.equal(res.status, 400);
await settingsDb.updateSettings({ requireLogin: false });
delete process.env.INITIAL_PASSWORD;
});
// ── Input validation ────────────────────────────────────────────────────────
test("POST /api/combos/duplicate returns 400 when name is missing", async () => {
await settingsDb.updateSettings({ requireLogin: false });
const req = makePostRequest("http://localhost/api/combos/duplicate", {});
const res = await duplicateRoute.POST(req as never);
const body = await res.json();
assert.equal(res.status, 400);
assert.ok(
typeof body.error === "string" && body.error.length > 0,
"should return an error message"
);
});
test("POST /api/combos/duplicate returns 400 when name is not a string", async () => {
await settingsDb.updateSettings({ requireLogin: false });
const req = makePostRequest("http://localhost/api/combos/duplicate", {
name: 123,
});
const res = await duplicateRoute.POST(req as never);
assert.equal(res.status, 400);
});
test("POST /api/combos/duplicate returns 400 when name is empty string", async () => {
await settingsDb.updateSettings({ requireLogin: false });
const req = makePostRequest("http://localhost/api/combos/duplicate", {
name: "",
});
const res = await duplicateRoute.POST(req as never);
assert.equal(res.status, 400);
});
// ── Success response shape (when models match) ───────────────────────────────
test("POST /api/combos/duplicate returns valid combo with correct naming and weights on success", async () => {
await settingsDb.updateSettings({ requireLogin: false });
const req = makePostRequest("http://localhost/api/combos/duplicate", {
name: "auto/best-coding",
});
const res = await duplicateRoute.POST(req as never);
const body = await res.json();
if (res.status === 201) {
// --- Naming convention: starts with static- and ends with "copy" (or "copy N") ---
assert.ok(
typeof body.name === "string" && body.name.length > 0,
"response should contain combo name"
);
assert.ok(
body.name.startsWith("static-"),
`Combo name should start with 'static-', got: ${body.name}`
);
assert.ok(
/^static-[\w-]+(\s+\d+)?$/.test(body.name),
`Combo name should be 'static-<template>' optionally followed by a numeric suffix, got: ${body.name}`
);
// --- Default strategy is "priority" ---
assert.equal(body.strategy, "priority", "default strategy should be priority");
// --- Models array present and weights sum to exactly 100 ---
assert.ok(Array.isArray(body.models), "response should include models array");
const totalWeight = body.models.reduce(
(sum: number, m: { weight?: number }) => sum + (m.weight ?? 0),
0
);
assert.equal(totalWeight, 100, `model weights must sum to exactly 100, got ${totalWeight}`);
// Every model has a positive weight
for (const m of body.models) {
assert.ok(m.weight > 0, `each model weight must be positive, got ${m.weight}`);
}
} else if (res.status === 422) {
// No models matched — still valid behavior in a clean test DB
ok();
} else {
throw new Error(`Unexpected status: ${res.status}`);
}
function ok() {}
});
test("POST /api/combos/duplicate uses custom strategy when provided", async () => {
await settingsDb.updateSettings({ requireLogin: false });
const req = makePostRequest("http://localhost/api/combos/duplicate", {
name: "auto/best-coding",
strategy: "round-robin",
});
const res = await duplicateRoute.POST(req as never);
const body = await res.json();
if (res.status === 201) {
assert.equal(body.strategy, "round-robin");
} else if (res.status === 422) {
ok();
} else {
throw new Error(`Unexpected status: ${res.status}`);
}
function ok() {}
});
test("POST /api/combos/duplicate created combo has sourceAutoCombo, weightPack in config and version 2", async () => {
await settingsDb.updateSettings({ requireLogin: false });
const req = makePostRequest("http://localhost/api/combos/duplicate", {
name: "auto/best-coding",
});
const res = await duplicateRoute.POST(req as never);
const body = await res.json();
if (res.status === 201) {
assert.ok(
body.config && typeof body.config.sourceAutoCombo === "string",
"config should contain sourceAutoCombo reference"
);
assert.equal(body.config.sourceAutoCombo, "auto/best-coding");
assert.equal(body.version, 2);
// Description includes template name + ISO timestamp (e.g. "auto/best-coding @ 2026-...")
assert.ok(
typeof body.description === "string" && body.description.startsWith("auto/best-coding @ "),
`description should be 'templateName @ timestamp', got: ${body.description}`
);
// weightPack is captured from the virtual combo's scoring config at creation time
assert.ok(
typeof body.config.weightPack === "object" && body.config.weightPack !== null,
"config should include weightPack"
);
const wp = body.config.weightPack;
assert.ok(
typeof wp.taskFit === "number" || typeof wp.health === "number",
"weightPack should contain scoring coefficients"
);
} else if (res.status === 422) {
ok();
}
function ok() {}
});
test("POST /api/combos/duplicate generates unique name when duplicate exists", async () => {
await settingsDb.updateSettings({ requireLogin: false });
// Create two duplicates — second should have a different name (numeric suffix)
const req1 = makePostRequest("http://localhost/api/combos/duplicate", {
name: "auto/best-coding",
});
const res1 = await duplicateRoute.POST(req1 as never);
if (res1.status !== 201) ok();
else {
const body1 = await res1.json();
const req2 = makePostRequest("http://localhost/api/combos/duplicate", {
name: "auto/best-coding",
});
const res2 = await duplicateRoute.POST(req2 as never);
const body2 = await res2.json();
if (res2.status === 201) {
assert.ok(
body1.name !== body2.name,
`duplicate combo should get a unique name. Both got: ${body1.name}`
);
// Second one ends with a numeric suffix (e.g. " 2") to disambiguate
assert.ok(
/\s+[\d]+$/.test(body2.name),
`second duplicate should end with a numeric suffix, got: ${body2.name}`
);
} else if (res2.status === 422) {
ok(); // no models matched for second call either
} else {
throw new Error(`Unexpected status on second call: ${res2.status}`);
}
}
function ok() {}
});
// ── Error response does not leak stack traces ───────────────────────────────
test("POST /api/combos/duplicate error responses do not contain stack traces", async () => {
await settingsDb.updateSettings({ requireLogin: false });
const req = makePostRequest("http://localhost/api/combos/duplicate", {
name: "auto/nonexistent-template-xyz",
});
const res = await duplicateRoute.POST(req as never);
const body = await res.json();
assert.ok(!JSON.stringify(body).includes("at "), "error response must not leak stack traces");
});