Files
OmniRoute/tests/unit/proxy-subscriptions-route-validation.test.ts
diegosouzapw b3f6f697ef 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.
2026-09-01 00:16:48 -03:00

346 lines
14 KiB
TypeScript

/**
* Regression guard for the T06 route-validation gate on the proxy-subscriptions
* management routes.
*
* `POST /api/v1/management/proxy-subscriptions` and
* `PATCH /api/v1/management/proxy-subscriptions/:id` used to hand-roll their body
* parsing (a local `parsePayload()` / inline field-by-field checks) instead of a
* Zod schema, which `scripts/check/check-route-validation.mjs` flags as an
* unvalidated `request.json()` usage. The fix swaps both routes to
* `proxySubscriptionCreateSchema` / `proxySubscriptionUpdateSchema`
* (`src/lib/proxySubscription/schema.ts`) applied via `.safeParse()`.
*
* These tests pin the EXACT pre-existing acceptance/rejection rules, error
* messages, and `{ error: string }` envelope shape — the schema swap must be a
* pure refactor, not a behavior change.
*
* DB/auth setup mirrors tests/unit/api-malformed-json-400.test.ts: a temp
* DATA_DIR with no configured password means requireManagementAuth() is a
* no-op, so the handlers run unauthenticated.
*/
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-proxy-sub-route-"));
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
const ORIGINAL_API_KEY_SECRET = process.env.API_KEY_SECRET;
const ORIGINAL_INITIAL_PASSWORD = process.env.INITIAL_PASSWORD;
process.env.DATA_DIR = TEST_DATA_DIR;
process.env.API_KEY_SECRET = process.env.API_KEY_SECRET ?? "proxy-sub-route-test-secret";
delete process.env.INITIAL_PASSWORD; // ensure auth is NOT required
const core = await import("../../src/lib/db/core.ts");
const collectionRoute = await import(
"../../src/app/api/v1/management/proxy-subscriptions/route.ts"
);
const itemRoute = await import(
"../../src/app/api/v1/management/proxy-subscriptions/[id]/route.ts"
);
function jsonRequest(url: string, body: unknown, method = "POST"): Request {
return new Request(url, {
method,
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
});
}
function malformedJsonRequest(url: string, method = "POST"): Request {
return new Request(url, {
method,
headers: { "content-type": "application/json" },
body: "not-json",
});
}
async function createValidSubscription(name: string) {
const req = jsonRequest("http://localhost/api/v1/management/proxy-subscriptions", {
name,
url: `https://example.com/${name}`,
});
const res = await collectionRoute.POST(req);
assert.equal(res.status, 201, "fixture creation must succeed");
return (await res.json()) as { id: string };
}
test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 });
if (ORIGINAL_DATA_DIR === undefined) delete process.env.DATA_DIR;
else process.env.DATA_DIR = ORIGINAL_DATA_DIR;
if (ORIGINAL_API_KEY_SECRET === undefined) delete process.env.API_KEY_SECRET;
else process.env.API_KEY_SECRET = ORIGINAL_API_KEY_SECRET;
if (ORIGINAL_INITIAL_PASSWORD === undefined) delete process.env.INITIAL_PASSWORD;
else process.env.INITIAL_PASSWORD = ORIGINAL_INITIAL_PASSWORD;
});
// ═════════════════════════════════════════════════════════════════════════════
// POST /api/v1/management/proxy-subscriptions
// ═════════════════════════════════════════════════════════════════════════════
test("POST proxy-subscriptions — valid body still creates (201), unregressed happy path", async () => {
const req = jsonRequest("http://localhost/api/v1/management/proxy-subscriptions", {
name: "my-sub",
url: "https://example.com/sub.txt",
});
const res = await collectionRoute.POST(req);
assert.equal(res.status, 201);
const body = (await res.json()) as { name?: string; url?: string; mode?: string };
assert.equal(body.name, "my-sub");
assert.equal(body.url, "https://example.com/sub.txt");
assert.equal(body.mode, "global", "mode defaults to 'global' when omitted");
});
test("POST proxy-subscriptions — trims name/url and coerces unknown mode to 'global'", async () => {
const req = jsonRequest("http://localhost/api/v1/management/proxy-subscriptions", {
name: " padded-name ",
url: " https://example.com/padded ",
mode: "not-a-real-mode",
});
const res = await collectionRoute.POST(req);
assert.equal(res.status, 201);
const body = (await res.json()) as { name?: string; url?: string; mode?: string };
assert.equal(body.name, "padded-name");
assert.equal(body.url, "https://example.com/padded");
assert.equal(body.mode, "global");
});
test("POST proxy-subscriptions — malformed JSON body returns 400 with the original message", async () => {
const req = malformedJsonRequest("http://localhost/api/v1/management/proxy-subscriptions");
const res = await collectionRoute.POST(req);
assert.equal(res.status, 400);
const body = (await res.json()) as { error?: string };
assert.equal(body.error, "Invalid JSON body");
});
test("POST proxy-subscriptions — valid-JSON non-object (string) body returns 400 'Invalid JSON body'", async () => {
// A bare JSON array is still `typeof === "object"` in JS (matching the original
// `typeof body !== "object"` guard), so it falls through to the missing-name
// check instead — see the array-body test below for that path. A primitive
// (string/number/boolean) is the one JSON shape that actually trips this guard.
const req = jsonRequest("http://localhost/api/v1/management/proxy-subscriptions", "just-a-string");
const res = await collectionRoute.POST(req);
assert.equal(res.status, 400);
const body = (await res.json()) as { error?: string };
assert.equal(body.error, "Invalid JSON body");
});
test("POST proxy-subscriptions — a JSON array body is an 'object' in JS, so it hits 'name is required' (not 'Invalid JSON body')", async () => {
const req = jsonRequest("http://localhost/api/v1/management/proxy-subscriptions", [1, 2, 3]);
const res = await collectionRoute.POST(req);
assert.equal(res.status, 400);
const body = (await res.json()) as { error?: string };
assert.equal(body.error, "name is required");
});
test("POST proxy-subscriptions — missing name returns 400 'name is required'", async () => {
const req = jsonRequest("http://localhost/api/v1/management/proxy-subscriptions", {
url: "https://example.com/sub.txt",
});
const res = await collectionRoute.POST(req);
assert.equal(res.status, 400);
const body = (await res.json()) as { error?: string };
assert.equal(body.error, "name is required");
});
test("POST proxy-subscriptions — blank name (whitespace only) returns 400 'name is required'", async () => {
const req = jsonRequest("http://localhost/api/v1/management/proxy-subscriptions", {
name: " ",
url: "https://example.com/sub.txt",
});
const res = await collectionRoute.POST(req);
assert.equal(res.status, 400);
const body = (await res.json()) as { error?: string };
assert.equal(body.error, "name is required");
});
test("POST proxy-subscriptions — missing url returns 400 'url is required' (checked after name)", async () => {
const req = jsonRequest("http://localhost/api/v1/management/proxy-subscriptions", {
name: "my-sub",
});
const res = await collectionRoute.POST(req);
assert.equal(res.status, 400);
const body = (await res.json()) as { error?: string };
assert.equal(body.error, "url is required");
});
test("POST proxy-subscriptions — mode 'rule' without ruleProviders returns 400", async () => {
const req = jsonRequest("http://localhost/api/v1/management/proxy-subscriptions", {
name: "my-sub",
url: "https://example.com/sub.txt",
mode: "rule",
});
const res = await collectionRoute.POST(req);
assert.equal(res.status, 400);
const body = (await res.json()) as { error?: string };
assert.equal(body.error, "ruleProviders is required when mode is 'rule'");
});
test("POST proxy-subscriptions — mode 'rule' with an empty ruleProviders array returns 400", async () => {
const req = jsonRequest("http://localhost/api/v1/management/proxy-subscriptions", {
name: "my-sub",
url: "https://example.com/sub.txt",
mode: "rule",
ruleProviders: [],
});
const res = await collectionRoute.POST(req);
assert.equal(res.status, 400);
const body = (await res.json()) as { error?: string };
assert.equal(body.error, "ruleProviders is required when mode is 'rule'");
});
test("POST proxy-subscriptions — mode 'rule' with ruleProviders succeeds (201)", async () => {
const req = jsonRequest("http://localhost/api/v1/management/proxy-subscriptions", {
name: "rule-sub",
url: "https://example.com/rule-sub.txt",
mode: "rule",
ruleProviders: ["openai", 42, "anthropic"],
});
const res = await collectionRoute.POST(req);
assert.equal(res.status, 201);
const body = (await res.json()) as { mode?: string; ruleProviders?: string[] };
assert.equal(body.mode, "rule");
assert.deepEqual(
body.ruleProviders,
["openai", "anthropic"],
"non-string ruleProviders entries are filtered out, matching the original parser"
);
});
test("POST proxy-subscriptions — invalid updateIntervalMinutes silently falls back to 60", async () => {
const req = jsonRequest("http://localhost/api/v1/management/proxy-subscriptions", {
name: "interval-sub",
url: "https://example.com/interval-sub.txt",
updateIntervalMinutes: "not-a-number",
});
const res = await collectionRoute.POST(req);
assert.equal(res.status, 201);
const body = (await res.json()) as { updateIntervalMinutes?: number };
assert.equal(body.updateIntervalMinutes, 60);
});
test("POST proxy-subscriptions — enabled must be exactly `true`, not truthy", async () => {
const req = jsonRequest("http://localhost/api/v1/management/proxy-subscriptions", {
name: "enabled-sub",
url: "https://example.com/enabled-sub.txt",
enabled: "yes",
});
const res = await collectionRoute.POST(req);
assert.equal(res.status, 201);
const body = (await res.json()) as { enabled?: boolean };
assert.equal(body.enabled, false);
});
// ═════════════════════════════════════════════════════════════════════════════
// PATCH /api/v1/management/proxy-subscriptions/:id
// ═════════════════════════════════════════════════════════════════════════════
test("PATCH proxy-subscriptions/:id — valid partial body updates (200), unregressed happy path", async () => {
const fixture = await createValidSubscription("patch-target");
const req = jsonRequest(
`http://localhost/api/v1/management/proxy-subscriptions/${fixture.id}`,
{ name: "renamed" },
"PATCH"
);
const res = await itemRoute.PATCH(req, { params: Promise.resolve({ id: fixture.id }) });
assert.equal(res.status, 200);
const body = (await res.json()) as { name?: string };
assert.equal(body.name, "renamed");
});
test("PATCH proxy-subscriptions/:id — wrong-typed fields are ignored, not rejected", async () => {
const fixture = await createValidSubscription("patch-wrongtype");
const req = jsonRequest(
`http://localhost/api/v1/management/proxy-subscriptions/${fixture.id}`,
{ name: "kept", updateIntervalMinutes: "not-a-number", enabled: "yes" },
"PATCH"
);
const res = await itemRoute.PATCH(req, { params: Promise.resolve({ id: fixture.id }) });
assert.equal(res.status, 200, "unknown-typed fields must be silently dropped, not a 400");
const body = (await res.json()) as {
name?: string;
updateIntervalMinutes?: number;
enabled?: boolean;
};
assert.equal(body.name, "kept");
assert.equal(body.updateIntervalMinutes, 60, "untouched — non-number was ignored, not coerced");
assert.equal(body.enabled, false, "untouched — non-boolean was ignored");
});
test("PATCH proxy-subscriptions/:id — malformed JSON body returns 400 'Invalid JSON body'", async () => {
const fixture = await createValidSubscription("patch-malformed");
const req = malformedJsonRequest(
`http://localhost/api/v1/management/proxy-subscriptions/${fixture.id}`,
"PATCH"
);
const res = await itemRoute.PATCH(req, { params: Promise.resolve({ id: fixture.id }) });
assert.equal(res.status, 400);
const body = (await res.json()) as { error?: string };
assert.equal(body.error, "Invalid JSON body");
});
test("PATCH proxy-subscriptions/:id — valid-JSON non-object body returns 400 'Invalid JSON body'", async () => {
const fixture = await createValidSubscription("patch-nonobject");
const req = jsonRequest(
`http://localhost/api/v1/management/proxy-subscriptions/${fixture.id}`,
"just-a-string",
"PATCH"
);
const res = await itemRoute.PATCH(req, { params: Promise.resolve({ id: fixture.id }) });
assert.equal(res.status, 400);
const body = (await res.json()) as { error?: string };
assert.equal(body.error, "Invalid JSON body");
});
test("PATCH proxy-subscriptions/:id — a JSON array body is an 'object' in JS, so it's a no-op update (200), not 400", async () => {
const fixture = await createValidSubscription("patch-arraybody");
const req = jsonRequest(
`http://localhost/api/v1/management/proxy-subscriptions/${fixture.id}`,
[1, 2, 3],
"PATCH"
);
const res = await itemRoute.PATCH(req, { params: Promise.resolve({ id: fixture.id }) });
assert.equal(res.status, 200, "matches the original inline parser: no typed field matches, no error");
const body = (await res.json()) as { name?: string };
assert.equal(body.name, "patch-arraybody", "name is unchanged — the array had no usable fields");
});
test("PATCH proxy-subscriptions/:id — unknown id still 404s past body validation", async () => {
const req = jsonRequest(
"http://localhost/api/v1/management/proxy-subscriptions/does-not-exist",
{ name: "whatever" },
"PATCH"
);
const res = await itemRoute.PATCH(req, { params: Promise.resolve({ id: "does-not-exist" }) });
assert.equal(res.status, 404);
const body = (await res.json()) as { error?: string };
assert.equal(body.error, "Subscription not found");
});