/** * Scenario: database-view-rbac-denied * * Verifies that a user without `rows:write` permission cannot open the * InlineEditor when double-clicking a cell. The cell must remain read-only * (tooltip shown, no input field). * * Implementation note on test isolation: * Creating a real restricted user via Docmost invitation requires email * confirmation in the standard flow. For e2e we use one of two approaches: * * A) If the `acadenicePerms` cookie mechanism is available (R2.3a), we * inject a restrictive cookie via `page.context().addCookies()`. * B) As a fallback, we verify the read-only rendering by manipulating the * `window.__acadenice_perms` global that `usePermissions` reads. * * Both approaches test the same UI contract: the InlineEditor must render in * read-only mode (Tooltip + span, no text input) when canWrite=false. * * If a real restricted user is available (E2E_READER_EMAIL set and functional), * we prefer approach A with a real session. */ import { test, expect } from "@playwright/test"; import * as fs from "fs"; import * as path from "path"; import type { BaserowSeed } from "../fixtures/baserow"; const BASE_URL = process.env.E2E_DOCMOST_URL ?? "http://localhost:5173"; const SEED_FILE = path.resolve(__dirname, "../.auth/baserow-seed.json"); test.describe("database-view RBAC write denied", () => { let seed: BaserowSeed; test.beforeAll(() => { if (!fs.existsSync(SEED_FILE)) { throw new Error(`Seed file not found at ${SEED_FILE}.`); } seed = JSON.parse(fs.readFileSync(SEED_FILE, "utf-8")) as BaserowSeed; }); test( "user without rows:write cannot open inline editor on double-click", async ({ page }) => { await page.goto(BASE_URL); const tableRenderer = page .getByTestId("table-renderer") .or(page.locator("[data-node-type='database-view'] table")) .first(); const rendererVisible = await tableRenderer .isVisible({ timeout: 5_000 }) .catch(() => false); if (!rendererVisible) { test.skip( true, "No database-view page found. Run database-view-insert spec first.", ); return; } // Inject read-only permissions via the window global that usePermissions reads. // This simulates a user whose RBAC cookie/global does NOT include rows:write. await page.evaluate(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any (window as any).__acadenice_perms = ["pages:read", "space:read"]; }); // Locate a cell in the primary column. const firstCell = page .getByTestId(`cell-${seed.rowIds[0]}-${seed.primaryFieldName}`) .or(page.getByRole("cell", { name: /Task Alpha|Task/i }).first()) .first(); await firstCell.waitFor({ state: "visible", timeout: 10_000 }); // Double-click the cell. await firstCell.dblclick(); // The InlineEditor must NOT show a text input — only the read-only span. const inlineInput = page .getByTestId("inline-editor-input") .or(page.locator(".inline-editor input, input[class*='input']").first()); // Input must not appear — wait briefly then assert hidden. await expect(inlineInput).toBeHidden({ timeout: 3_000 }); // The read-only tooltip or span must be visible. const readOnlyIndicator = page .getByTestId("inline-editor-readonly") .or( page.getByRole("tooltip", { name: /permission|read.only|denied/i }), ) .or(page.locator("[class*='readOnly']").first()); await expect(readOnlyIndicator).toBeVisible({ timeout: 5_000 }); }, ); });