mirror of
https://github.com/ipnet-mesh/meshcore-hub.git
synced 2026-07-29 04:52:48 +02:00
83a936cd33
Operators can now create, edit, and delete routes (previously admin-only). A user may never scope a route above their own role tier: an operator creating/editing an admin-visibility route is rejected (403 on the visibility value, 404 on touching an existing higher-visibility route), preventing them from creating routes they could then never see or modify. - routes.py: RequireAdmin -> RequireOperatorOrAdmin on create/update/delete; add visibility-cap enforcement helpers reusing the existing resolve_user_role / VISIBILITY_LEVELS ladder - web/app.py: proxy access map admits operator for routes POST/PUT/DELETE - Routes.tsx: canManage gate (admin||operator) on Add/Edit/Delete; visibility <select> filters options by caller tier so operators never see 'admin' - tests: operator-tier coverage (create/update/delete at/below/above level), proxy access-map assertion, vitest role-gating + filtered select - e2e: mint operator session + routes-operator spec - docs: routes.md + auth.md operator/visibility-cap notes
41 lines
1.1 KiB
TypeScript
41 lines
1.1 KiB
TypeScript
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { expect, type Page } from "@playwright/test";
|
|
|
|
const AUTH_DIR = path.resolve(
|
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
"..",
|
|
".auth",
|
|
);
|
|
export const ADMIN_STATE = path.join(AUTH_DIR, "admin.json");
|
|
export const MEMBER_STATE = path.join(AUTH_DIR, "member.json");
|
|
export const OPERATOR_STATE = path.join(AUTH_DIR, "operator.json");
|
|
|
|
export async function expectListLoaded(page: Page): Promise<void> {
|
|
await expect(page.getByTestId("list-row").first()).toBeVisible();
|
|
}
|
|
|
|
export async function openFilters(page: Page): Promise<void> {
|
|
const toggle = page.locator("#filter-toggle");
|
|
if (!(await toggle.isChecked())) {
|
|
await toggle.click();
|
|
}
|
|
}
|
|
|
|
export async function countApiCalls(
|
|
page: Page,
|
|
urlFragment: string,
|
|
durationMs: number,
|
|
): Promise<number> {
|
|
let count = 0;
|
|
const onRequest = (request: { url: () => string }): void => {
|
|
if (request.url().includes(urlFragment)) {
|
|
count += 1;
|
|
}
|
|
};
|
|
page.on("request", onRequest);
|
|
await page.waitForTimeout(durationMs);
|
|
page.off("request", onRequest);
|
|
return count;
|
|
}
|