mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 07:12:12 +03:00
Every finding from the first full ci.yml run on the release PR, fixed or justified together so a single re-push clears the board. Lint / check:route-validation:t06 — three routes read request.json() with no visible Zod validation. The two proxy-subscriptions routes validated with a hand-rolled parsePayload(); they now use real Zod schemas (src/lib/proxySubscription/schema.ts) reproducing the same acceptance rules, error strings and status codes. chat/completions is the proxy's hottest path and parses the body ONCE on purpose (#4380 OOM crash-loop), so it now safeParses the ALREADY-PARSED object against a deliberately permissive structural schema — proven not to change behavior: absent model and model:null still pass through, role "developer" still reaches 200, a ~300 KB payload is accepted, and the body is still read exactly once. 25 new tests. i18n UI value drift — 13 English strings rewritten during the cycle left stale translations in up to 41 locales (317 pairs). Eleven are genuine rewrites and now carry the pipeline's __MISSING__:<english> marker so the runtime serves corrected English until translation catches up; vi forbids that marker by test, so it got a real translation. PR Test Policy — 33 files flagged. Each was verified against the SOURCE, not the diff: 26 assert reductions are legitimate (mostly the #7866 Qwen OAuth provider removal and the #8013 Antigravity refactor deleting the surface under test) and are allowlisted with the PR and the evidence; 5 deleted files have verified replacements. One was NOT legitimate: #7528's GraphQL->WebSocket migration dropped four muse-spark continuation scenarios whose logic is still live — connection isolation, cache eviction after a failed turn (the commit itself says "was missing"), parallel-chat cache collision, and the empty-content guard. All four are restored against the new transport and each was verified to fail when the corresponding production mechanism is broken. Quality Ratchet / openapiCoverage — 36.6% against a baseline of 38: the cycle added routes faster than the spec. Eight real endpoints are now documented from their route.ts (usage cache-health and model-latency-stats, the two OIDC endpoints, and the five proxy-subscriptions paths), bringing it to 38.1%. Quality Gates (Extended) / zizmor — the runner measures 190 where the devbox measures 189 on the same commit, a delta already recorded in this baseline's history. Baselined to the runner's number. Also: the driverFactory better-sqlite3 guard moved from a mid-body t.skip() to a declared { skip: <condition> } test option. Same behavior for the optional native dependency, but the skip now shows up in the report and is distinguishable from a test.skip() that silences a test outright. Verified under both runners: 15/15 on Node, 14/14 on Bun. SonarCloud Code Analysis stays red and is not a blocker: sonar.qualitygate.wait=false since #7038 makes the job informative, the built-in gate cannot be swapped on the FREE plan, and main has no branch protection.
346 lines
14 KiB
TypeScript
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 });
|
|
|
|
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");
|
|
});
|