Wiki/e2e/tests/database-view-rbac-denied.spec.ts
Corentin JOGUET e9695450ef
Some checks are pending
CI / Lint bridge (Biome) (push) Waiting to run
CI / Type-check bridge (push) Blocked by required conditions
CI / Tests unit bridge (push) Blocked by required conditions
CI / Tests integration bridge (push) Blocked by required conditions
CI / Security scan (push) Waiting to run
CI / Docker build + healthcheck (push) Blocked by required conditions
E2E Playwright / Playwright e2e (chromium) (push) Waiting to run
test(e2e): add Playwright cross-stack tests for R3.1.e database-view
7 scenarios covering the full bridge+DocAdenice+Baserow chain:
auth login, database-view insert, inline edit persistence, SSE realtime
update (no reload), RBAC write-denied, kanban drag-drop, calendar reschedule.

Includes docker-compose.e2e.yml (Postgres+Redis+Baserow+bridge+DocAdenice),
playwright.config.ts (3 projects: chromium/firefox/webkit), auth+baserow+cleanup
fixtures, global setup (API login + Baserow seed), and GitHub Actions e2e.yml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 00:37:23 +02:00

101 lines
3.6 KiB
TypeScript

/**
* 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 });
},
);
});