import { eq } from "drizzle-orm"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { db, schema } from "../../../apps/api/src/db/index.js"; import { buildTestApp, loginAsAdmin, type TestApp } from "../test-server.js"; let testApp: TestApp; let adminToken: string; let editorToken: string; let userToken: string; beforeAll(async () => { testApp = await buildTestApp(); adminToken = await loginAsAdmin(testApp.app); // Create editor await testApp.app.inject({ method: "POST", url: "/api/auth/register", headers: { authorization: `Bearer ${adminToken}` }, payload: { username: "matrix_editor", password: "EditorPass1", role: "editor" }, }); await db .update(schema.users) .set({ mustChangePassword: false }) .where(eq(schema.users.username, "matrix_editor")); const editorLogin = await testApp.app.inject({ method: "POST", url: "/api/auth/login", payload: { username: "matrix_editor", password: "EditorPass1" }, }); editorToken = JSON.parse(editorLogin.body).token; // Create user await testApp.app.inject({ method: "POST", url: "/api/auth/register", headers: { authorization: `Bearer ${adminToken}` }, payload: { username: "matrix_user", password: "UserPass12", role: "user" }, }); await db .update(schema.users) .set({ mustChangePassword: false }) .where(eq(schema.users.username, "matrix_user")); const userLogin = await testApp.app.inject({ method: "POST", url: "/api/auth/login", payload: { username: "matrix_user", password: "UserPass12" }, }); userToken = JSON.parse(userLogin.body).token; }, 30_000); afterAll(async () => { await testApp.cleanup(); }, 10_000); interface RouteTest { method: "GET" | "POST" | "PUT" | "DELETE"; url: string; payload?: unknown; admin: number; editor: number; user: number; unauth: number; } const routes: RouteTest[] = [ // Settings { method: "GET", url: "/api/v1/settings", admin: 200, editor: 200, user: 200, unauth: 401 }, { method: "PUT", url: "/api/v1/settings", payload: { _test: "v" }, admin: 200, editor: 403, user: 403, unauth: 401, }, // Users management { method: "GET", url: "/api/auth/users", admin: 200, editor: 403, user: 403, unauth: 401 }, // Teams { method: "GET", url: "/api/v1/teams", admin: 200, editor: 403, user: 403, unauth: 401 }, // Audit log { method: "GET", url: "/api/v1/audit-log", admin: 200, editor: 403, user: 403, unauth: 401 }, // Admin health { method: "GET", url: "/api/v1/admin/health", admin: 200, editor: 403, user: 403, unauth: 401 }, // Files { method: "GET", url: "/api/v1/files", admin: 200, editor: 200, user: 200, unauth: 401 }, // Pipelines { method: "GET", url: "/api/v1/pipeline/list", admin: 200, editor: 200, user: 200, unauth: 401 }, // API keys { method: "GET", url: "/api/v1/api-keys", admin: 200, editor: 200, user: 200, unauth: 401 }, // Health (public) { method: "GET", url: "/api/v1/health", admin: 200, editor: 200, user: 200, unauth: 200 }, ]; describe("RBAC permission matrix", () => { for (const route of routes) { for (const [role, expectedStatus] of Object.entries({ admin: route.admin, editor: route.editor, user: route.user, unauth: route.unauth, })) { it(`${route.method} ${route.url} → ${role} = ${expectedStatus}`, async () => { const headers: Record = {}; const token = role === "admin" ? adminToken : role === "editor" ? editorToken : role === "user" ? userToken : undefined; if (token) { headers.authorization = `Bearer ${token}`; } const res = await testApp.app.inject({ method: route.method, url: route.url, headers, ...(route.payload ? { payload: route.payload } : {}), }); expect(res.statusCode).toBe(expectedStatus); }); } } });