Files
OmniRoute/tests/unit/migration-147-api-keys-model-access-mode.test.ts
Xiangzhe def5334768 feat(api-manager): add provider-level model permissions (#9313)
* feat(api-manager): add provider-level model permissions

Persist canonical provider wildcards alongside exact model grants and
preserve explicit restricted-empty deny-all semantics across API, SQLite,
JSON import, sync, runtime policy, and the dashboard.

Invalidate filtered model catalogs on permission changes and guard against
stale in-flight catalog builders repopulating invalidated cache entries.

* fix(api-manager): show provider and model counts separately in summary

Provider wildcard selections (provider/*) are no longer counted as
individual models in the Selected Models Summary. The header now shows
"N providers · M models" when both are present, or just the non-empty
category when only one type is selected.

* fix(api-manager): separate provider and model permission displays

* fix(api-manager): separate provider wildcard permissions in UI
2026-08-11 10:29:28 -03:00

135 lines
4.1 KiB
TypeScript

/**
* Acceptance: migration 147 — api_keys.model_access_mode
*
* Confirmed backfill:
* - Adds model_access_mode column (public shape: "all" | "restricted")
* - Legacy empty allowed_models rows → "all"
* - Legacy non-empty allowed_models rows → "restricted"
*
* The existence assertion keeps the migration artifact part of the accepted contract.
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import Database from "better-sqlite3";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const MIGRATION_147_PATH = path.join(
__dirname,
"../../src/lib/db/migrations/147_api_keys_model_access_mode.sql"
);
interface TestDb {
exec: (sql: string) => unknown;
prepare: (sql: string) => {
run: (...p: unknown[]) => unknown;
get: (...p: unknown[]) => Record<string, unknown> | undefined;
all: (...p: unknown[]) => Record<string, unknown>[];
};
close: () => void;
}
function makeLegacyApiKeysDb(): TestDb {
const db = new Database(":memory:") as unknown as TestDb;
db.exec(`
CREATE TABLE api_keys (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
key TEXT NOT NULL,
allowed_models TEXT DEFAULT '[]'
);
`);
return db;
}
function insertKey(db: TestDb, id: string, name: string, allowedModelsJson: string | null): void {
db.prepare("INSERT INTO api_keys (id, name, key, allowed_models) VALUES (?, ?, ?, ?)").run(
id,
name,
`omni_${id}`,
allowedModelsJson
);
}
function modeOf(db: TestDb, id: string): string | null {
const row = db.prepare("SELECT model_access_mode AS mode FROM api_keys WHERE id = ?").get(id);
return (row?.mode as string | undefined) ?? null;
}
function hasModelAccessModeColumn(db: TestDb): boolean {
const cols = db.prepare("PRAGMA table_info(api_keys)").all();
return cols.some((col) => col.name === "model_access_mode");
}
test("R-migration: 147_api_keys_model_access_mode.sql must exist", () => {
assert.ok(
fs.existsSync(MIGRATION_147_PATH),
"expected src/lib/db/migrations/147_api_keys_model_access_mode.sql"
);
});
test("R-migration: 147 adds model_access_mode and backfills all/restricted from allowed_models", () => {
assert.ok(
fs.existsSync(MIGRATION_147_PATH),
"expected src/lib/db/migrations/147_api_keys_model_access_mode.sql"
);
const sql = fs.readFileSync(MIGRATION_147_PATH, "utf-8");
const db = makeLegacyApiKeysDb();
insertKey(db, "legacy-all", "Legacy Allow All", "[]");
insertKey(db, "legacy-spaced-all", "Legacy Spaced Allow All", "[ ]");
insertKey(db, "legacy-nullish", "Legacy Nullish", "null");
insertKey(db, "legacy-sql-null", "Legacy SQL Null", null);
insertKey(db, "legacy-restricted", "Legacy Restricted", '["ollama-cloud/*","openai/gpt-4.1"]');
insertKey(db, "legacy-exact", "Legacy Exact", '["openai/gpt-4.1"]');
insertKey(db, "legacy-scalar", "Legacy Scalar", "true");
insertKey(db, "legacy-malformed", "Legacy Malformed", "not-json");
db.exec(sql);
assert.equal(hasModelAccessModeColumn(db), true, "must add model_access_mode column");
assert.equal(
modeOf(db, "legacy-all"),
"all",
"legacy empty allowed_models must backfill to model_access_mode=all"
);
assert.equal(
modeOf(db, "legacy-spaced-all"),
"all",
"valid empty JSON arrays remain allow-all regardless of whitespace"
);
assert.equal(
modeOf(db, "legacy-nullish"),
"all",
"JSON null allowed_models must backfill to all"
);
assert.equal(modeOf(db, "legacy-sql-null"), "all", "SQL NULL must backfill to all");
assert.equal(
modeOf(db, "legacy-restricted"),
"restricted",
"legacy non-empty allowed_models must backfill to restricted"
);
assert.equal(
modeOf(db, "legacy-exact"),
"restricted",
"legacy exact-model allow-list must backfill to restricted"
);
assert.equal(
modeOf(db, "legacy-scalar"),
"restricted",
"non-array JSON values other than null must fail closed"
);
assert.equal(
modeOf(db, "legacy-malformed"),
"restricted",
"malformed legacy values must fail closed"
);
db.close();
});