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
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>
101 lines
3.6 KiB
TypeScript
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 });
|
|
},
|
|
);
|
|
});
|