/** * Parity-tests с backend `tests/test_rbac.py::test_is_path_allowed_*`. * * Если кто-то меняет глоб-семантику backend'а — должен синхронно поменять JS * порт здесь, иначе UI начнёт врать про доступы (показывать ссылки, которые * backend заблокирует, или наоборот скрывать разрешённые). * * Pairs мирорят `auth/roles.yaml` (обновлено 2026-05-26 — pilot scope сужен * до /trade-in/** only): * admin: paths=["/**"] deny=[] * pilot: paths=["/trade-in/**", "/trade-in/api/v1/**"] * deny=["/admin/**", "/api/v1/admin/**", "/trade-in/api/v1/admin/**"] */ import { isPathAllowed } from "../isPathAllowed"; const ADMIN_PATHS = ["/**"] as const; const ADMIN_DENY = [] as const; const PILOT_PATHS = ["/trade-in/**", "/trade-in/api/v1/**"] as const; const PILOT_DENY = [ "/admin/**", "/api/v1/admin/**", "/trade-in/api/v1/admin/**", ] as const; describe("isPathAllowed — admin everywhere", () => { const cases: string[] = [ "/", "/admin", "/admin/scrape/runs", "/api/v1/admin/scrape/status", "/trade-in/api/v1/admin/scrape", "/concept/123", "/analytics/dashboard", ]; it.each(cases)("allows %s", (path) => { expect(isPathAllowed(ADMIN_PATHS, ADMIN_DENY, path)).toBe(true); }); }); describe("isPathAllowed — pilot allowed paths (только trade-in)", () => { const allowed: string[] = [ "/trade-in", "/trade-in/", "/trade-in/123", "/trade-in/api/v1/search", "/trade-in/api/v1/me", ]; it.each(allowed)("allows %s", (path) => { expect(isPathAllowed(PILOT_PATHS, PILOT_DENY, path)).toBe(true); }); }); describe("isPathAllowed — pilot denied paths (landing + admin + остальное)", () => { const denied: string[] = [ // Landing + non-tradein разделы — теперь denied (decision 2026-05-26) "/", "/analytics/dashboard", "/site-finder/123", "/concept/abc", "/api/v1/parcels/123", "/api/v1/analytics/dashboard", // Admin paths (explicit deny + not in allowed) "/admin", "/admin/jobs", "/admin/scrape/runs/42", "/api/v1/admin/scrape/status", "/api/v1/admin/jobs", "/trade-in/api/v1/admin/scrape", ]; it.each(denied)("denies %s", (path) => { expect(isPathAllowed(PILOT_PATHS, PILOT_DENY, path)).toBe(false); }); }); describe("isPathAllowed — edge cases", () => { it("denies path not matching any rule", () => { expect(isPathAllowed(PILOT_PATHS, PILOT_DENY, "/unknown")).toBe(false); }); it("matches bare /trade-in via /trade-in/** glob (no trailing /)", () => { // Backend glob `/foo/**` после подстановки `(?:/.*)?` матчит и bare `/foo`. expect(isPathAllowed(PILOT_PATHS, PILOT_DENY, "/trade-in")).toBe(true); }); });