All checks were successful
CI / Lint, typecheck, test (push) Successful in 3m7s
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m14s
CD / Deploy to Test (push) Successful in 12s
CD / Smoke tests against Test (push) Successful in 1m16s
CD / Promote to Int (push) Successful in 9s
CI / Auth e2e pack (push) Successful in 5m9s
CI / Import/export fidelity gate (push) Successful in 45s
Imprint and privacy policy are two new Markdown instance settings (legal.imprint, legal.privacyPolicy), edited by Site Admins in a new "Legal pages" admin section with a toggleable rendered preview. The preview uses the same shared pipeline the server renders with (markdown → schema doc → escaped HTML), so stored markup can never smuggle script to visitors. The pages render publicly at /legal/imprint and /legal/privacy — as an SPA route plus, like #56, a self-contained server-rendered HTML document under /api/v1/legal/:kind. The endpoints are setup-exempt: legal information stays reachable even while the first-run wizard is pending. Unconfigured pages show a localized notice instead of 404ing, and Site Admins additionally get a warning banner linking to the settings. A new footer with both links appears on every SPA view (editor, auth screens, public pages) and in the server-rendered documents, whose shared shell moved to public/html-shell.ts and now renders its chrome in the instance default locale (ADR 0012). docs/self-hosting/legal-template.md ships imprint and privacy-policy templates in English and German whose sections mirror Dorfteich's actual processing activities (accounts, sessions, rate-limit IPs, proxy logs, transactional mail, content, export, deletion, no third-party requests), with a review checklist tied to security.md §Privacy. New `legal` i18n namespace (de+en); api and web e2e coverage including a new CI legal pack (footer navigation, notice vs. admin banner, and the admin form publishing a text end to end). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
75 lines
3.2 KiB
TypeScript
75 lines
3.2 KiB
TypeScript
import { expect, test } from '@playwright/test';
|
|
|
|
import { contextForUser } from './helpers';
|
|
|
|
const BASE_URL = process.env.E2E_BASE_URL ?? 'http://localhost:5173';
|
|
|
|
const IMPRINT_TEXT = 'E2E Legal Operator, Example Lane 1';
|
|
|
|
/**
|
|
* Instance legal pages (issue #82): a Site Admin's Markdown renders publicly,
|
|
* footer links reach it from everywhere (including auth screens), and an
|
|
* unconfigured page shows a notice — plus a warning banner for Site Admins.
|
|
* The state is set explicitly up front, so the pack is repeatable.
|
|
*/
|
|
test.beforeAll(async ({ browser }) => {
|
|
const admin = await contextForUser(browser, BASE_URL, 'fixture-admin');
|
|
const response = await admin.request.patch('/api/v1/admin/settings', {
|
|
data: { 'legal.imprint': `## Operator\n\n${IMPRINT_TEXT}`, 'legal.privacyPolicy': '' },
|
|
});
|
|
expect(response.ok()).toBe(true);
|
|
await admin.close();
|
|
});
|
|
|
|
test('footer links lead anonymous visitors to the configured imprint', async ({ page }) => {
|
|
// The auth screen carries the footer too (AC: links appear everywhere).
|
|
await page.goto('/login');
|
|
await page
|
|
.locator('.app-footer')
|
|
.getByRole('link', { name: /imprint|impressum/i })
|
|
.click();
|
|
await expect(page).toHaveURL(/\/legal\/imprint$/);
|
|
await expect(page.getByRole('heading', { name: /imprint|impressum/i }).first()).toBeVisible();
|
|
await expect(page.getByText(IMPRINT_TEXT)).toBeVisible();
|
|
});
|
|
|
|
test('an unconfigured page shows a notice to visitors, not a 404', async ({ page }) => {
|
|
await page.goto('/legal/privacy');
|
|
await expect(page.getByText(/not provided this text yet|noch nicht hinterlegt/i)).toBeVisible();
|
|
// Anonymous visitors get the neutral notice only — no admin warning.
|
|
await expect(page.getByRole('alert')).toHaveCount(0);
|
|
});
|
|
|
|
test('site admins see a warning banner on unconfigured pages', async ({ browser }) => {
|
|
const admin = await contextForUser(browser, BASE_URL, 'fixture-admin');
|
|
const page = await admin.newPage();
|
|
await page.goto('/legal/privacy');
|
|
const banner = page.getByRole('alert');
|
|
await expect(banner).toBeVisible();
|
|
await banner.getByRole('link').click();
|
|
await expect(page).toHaveURL(/\/admin$/);
|
|
await admin.close();
|
|
});
|
|
|
|
test('the admin form previews and publishes the privacy policy', async ({ browser }) => {
|
|
const admin = await contextForUser(browser, BASE_URL, 'fixture-admin');
|
|
const page = await admin.newPage();
|
|
await page.goto('/admin');
|
|
|
|
// Second legal editor = the privacy policy (imprint comes first).
|
|
const editor = page.locator('.legal-editor').nth(1);
|
|
await editor.locator('textarea').fill('We process **only what is needed**.');
|
|
await editor.getByRole('button', { name: /preview|vorschau/i }).click();
|
|
await expect(editor.locator('.legal-editor__preview strong')).toHaveText('only what is needed');
|
|
|
|
await page.getByRole('button', { name: /save legal pages|rechtsseiten speichern/i }).click();
|
|
await expect(page.getByRole('status')).toBeVisible();
|
|
await admin.close();
|
|
|
|
const anonymous = await browser.newContext({ baseURL: BASE_URL });
|
|
const anonymousPage = await anonymous.newPage();
|
|
await anonymousPage.goto('/legal/privacy');
|
|
await expect(anonymousPage.getByText('only what is needed')).toBeVisible();
|
|
await anonymous.close();
|
|
});
|