test(e2e): read-only user reads a transcription, no edit affordances (#697)
Some checks failed
CI / Unit & Component Tests (pull_request) Failing after 2m35s
CI / OCR Service Tests (pull_request) Successful in 20s
CI / Backend Unit Tests (pull_request) Successful in 3m27s
CI / fail2ban Regex (pull_request) Successful in 43s
CI / Semgrep Security Scan (pull_request) Successful in 19s
CI / Compose Bucket Idempotency (pull_request) Successful in 1m2s

CI happy path: seed a PDF document with a transcription block as admin, then
as the READ_ALL "reader" open it — assert the "Transkription lesen" control,
the read text, a plain "Transkription" header, and the absence of the
Lesen/Bearbeiten tabs (panel cannot switch to edit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-05-31 12:21:39 +02:00
parent cef05abaaa
commit bee309e40c

View File

@@ -0,0 +1,98 @@
import { test, expect } from '@playwright/test';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs';
import { login } from './helpers/auth';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const PDF_FIXTURE = path.resolve(__dirname, 'fixtures/minimal.pdf');
/**
* E2E for issue #697 — read-only users can read an existing transcription.
*
* Setup runs as admin (default storage state): a PDF document with one
* transcription block, so hasTranscription is true. The assertions run in a
* fresh context logged in as the seeded READ_ALL-only "reader": they can open
* the read view but see no edit tab and no per-block edit controls, and the
* panel cannot be switched to edit.
*/
let docId: string;
let docHref: string;
test.describe.configure({ mode: 'serial' });
test.describe('Read-only user reads an existing transcription', () => {
test.beforeAll(async ({ request }) => {
const baseURL = process.env.E2E_BASE_URL ?? 'http://localhost:3000';
const uniqueSuffix = Date.now();
const docRes = await request.post('/api/documents', {
multipart: {
title: `E2E Read-only Transcription ${uniqueSuffix}`,
documentDate: '1945-05-08'
}
});
if (!docRes.ok()) throw new Error(`Create document failed: ${docRes.status()}`);
docId = (await docRes.json()).id;
docHref = `${baseURL}/documents/${docId}`;
await request.put(`/api/documents/${docId}`, {
multipart: {
title: `E2E Read-only Transcription ${uniqueSuffix}`,
documentDate: '1945-05-08',
file: {
name: 'minimal.pdf',
mimeType: 'application/pdf',
buffer: fs.readFileSync(PDF_FIXTURE)
}
}
});
const annRes = await request.post(`/api/documents/${docId}/annotations`, {
data: { pageNumber: 1, x: 0.1, y: 0.1, width: 0.5, height: 0.1, color: '#00C7B1' }
});
if (!annRes.ok()) throw new Error(`Create annotation failed: ${annRes.status()}`);
const blockRes = await request.post(`/api/documents/${docId}/transcription-blocks`, {
data: {
pageNumber: 1,
x: 0.1,
y: 0.1,
width: 0.5,
height: 0.1,
text: 'Liebe Mutter, viele Grüße vom Mai 1945',
label: null
}
});
if (!blockRes.ok()) throw new Error(`Create block failed: ${blockRes.status()}`);
});
test.afterAll(async ({ request }) => {
if (docId) await request.delete(`/api/documents/${docId}`);
});
test('reader opens the read view with no edit tab or edit controls', async ({ browser }) => {
const context = await browser.newContext({ storageState: { cookies: [], origins: [] } });
const page = await context.newPage();
try {
await login(page, 'reader', 'reader123');
await page.goto(docHref);
// Reader entry control is the read label, not "Transkribieren".
const readButton = page.getByRole('button', { name: /Transkription lesen/i });
await expect(readButton).toBeVisible({ timeout: 5000 });
await readButton.click();
// Read view shows the transcription text.
await expect(page.getByText(/Mai 1945/)).toBeVisible({ timeout: 5000 });
// Header is a plain "Transkription" label, not a Lesen/Bearbeiten toggle.
await expect(page.getByRole('heading', { name: /^Transkription$/i })).toBeVisible();
await expect(page.getByTestId('mode-edit')).toHaveCount(0);
await expect(page.getByTestId('mode-read')).toHaveCount(0);
} finally {
await context.close();
}
});
});