All checks were successful
CD / Build and push images (push) Successful in 4m2s
CI / Lint, typecheck, test (push) Successful in 2m46s
CI / Auth e2e pack (push) Successful in 3m45s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m18s
CD / Promote to Int (push) Successful in 12s
Extend uploads (#27, ADR 0011) beyond images to a configurable general attachment allowlist, plus the page attachments section and the Pond Admin file manager. Backend: - Two instance settings: `upload.allowedExtensions` (lowercase, dot-stripped, images always allowed regardless) and `upload.svgPolicy` (reject | sanitize). - FilesService.resolveUpload: raster images still decided by magic bytes; SVG is sanitized with DOMPurify (scripts, event handlers, foreignObject stripped) or rejected per policy; everything else is admitted only if its extension is on the allowlist. A sanitized SVG's stored bytes are re-accounted so pond_usage matches disk. - Downloads set `Content-Disposition: attachment` for every non-raster type (office files, PDFs, SVG) with `nosniff`, so they can never execute inline; raster images stay inline for page embeds. - New endpoints: `GET /ponds/:id/files` (pond_admin: all files + usage + orphan flag), `POST /pages/:id/files` and `GET /pages/:id/files` (page-write/read: the attachments section). New error code `upload_type_not_allowed` (de+en). Frontend: - Page attachments section (AttachmentsPanel): upload, list with type glyph, size, and uploader, insert-as-link into the document (an internal media link that downloads, never renders inline), and delete. Toggled in the editor. - Pond file manager (PondFileManager) in pond settings for Pond Admins: every file with its referencing page (or an orphan flag) and storage usage. - Admin uploads settings form (allowlist + SVG policy). New `files` i18n namespace (de+en). Tests: - files.e2e.db.test.ts: allowlisted non-image accepted and served as a download; disallowed extension rejected; renamed-.html-as-.png still fails; SVG sanitized (scripts/handlers stripped) and reject-mode rejects; page attachment listing; pond file manager usage/orphan; non-admin denied. - New e2e pack apps/web/e2e/attachments.spec.ts (+ CI step): upload → list → insert link (verified attachment disposition + nosniff), disallowed-type error, pond file manager usage/orphan. Local: typecheck, lint, i18n:check, build all green; api-db 184, shared 121, web 50; attachments pack 3/3, members 3/3, content 5/5. Adds dompurify + jsdom to the api for server-side SVG sanitization. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
122 lines
6.9 KiB
Markdown
122 lines
6.9 KiB
Markdown
# End-to-end tests
|
||
|
||
Playwright suites, most local-only — three run in CI/CD:
|
||
|
||
| Suite | Target | Where it runs |
|
||
| ----------------- | --------------------------------- | --------------------------------------------------------------------------- |
|
||
| `smoke.spec.ts` | any deployed stage | CD pipeline against `https://test.dorfteich.cloud` after every deploy |
|
||
| `auth.spec.ts` | full local stack **with Mailpit** | CI job `auth-e2e` on every PR/push; locally against the dev stack |
|
||
| `content.spec.ts` | full local stack | same CI job `auth-e2e` (a second step), right after the auth pack |
|
||
| everything else | full local stack | locally only — `editor`/`sidebar`/`image`/`link`/`markdown`/`trash`.spec.ts |
|
||
|
||
`content.spec.ts` is the M2 content regression pack (issue #32): page
|
||
lifecycle, editor basics, image paste, Markdown round-trip, and trash —
|
||
enough to catch a regression across the whole content model without
|
||
re-running every edge case the feature-specific packs above already cover.
|
||
Its Markdown round-trip test is a real regression pin, not just a smoke
|
||
check: it compares the seeded "Every Element" fixture page's exported
|
||
Markdown byte-for-byte against the checked-in `content-page.md` (see
|
||
"Content fixtures" below) — any schema/serializer change that alters how a
|
||
node round-trips fails it, once the seed has re-run against the changed
|
||
code (build → migrate → seed → test, exactly CI's order).
|
||
|
||
## Running locally
|
||
|
||
```sh
|
||
# 1. Stack: database + Mailpit, api (3001), web dev server (5173)
|
||
docker compose -f deploy/compose/docker-compose.yml -f deploy/compose/compose.dev.yml up -d db mailpit
|
||
DATABASE_URL=postgresql://dorfteich:dorfteich@localhost:5434/dorfteich pnpm --filter @dorfteich/api db:seed
|
||
DATABASE_URL=postgresql://dorfteich:dorfteich@localhost:5434/dorfteich PORT=3001 pnpm --filter @dorfteich/api start:dev &
|
||
pnpm --filter @dorfteich/web dev &
|
||
|
||
# 2. Tests
|
||
E2E_BASE_URL=http://localhost:5173 E2E_MAILPIT_URL=http://localhost:8025 pnpm --filter @dorfteich/web e2e
|
||
```
|
||
|
||
`auth.spec.ts` skips itself when `E2E_MAILPIT_URL` is unset, so the CD
|
||
smoke run never trips over it.
|
||
|
||
## Fixture matrix
|
||
|
||
Seeded by `pnpm --filter @dorfteich/api db:seed` (idempotent — re-running
|
||
never duplicates). Shared password: `fixture passwort 123`. Fixtures exist
|
||
only on dev machines and disposable CI/Test databases.
|
||
|
||
| Username | State | Purpose |
|
||
| ------------------ | ------------------- | ---------------------------------------------------------------------------------------- |
|
||
| `fixture-admin` | active, Site Admin | admin UI/permissions cases |
|
||
| `fixture-user` | active | regular journeys, settings, sessions |
|
||
| `fixture-editor` | active | second regular account for the collab-permissions pack (reader/editor of another's pond) |
|
||
| `fixture-viewer` | active | signed-in non-member for `authenticated`/`public` access-rule cases (issue #55) |
|
||
| `fixture-outsider` | active | the "foreign user" of the permission matrix — member of nothing (issue #60) |
|
||
| `fixture-pending` | e-mail not verified | unverified-login cases |
|
||
|
||
## Permission matrix (`permission-matrix.spec.ts`, issue #60)
|
||
|
||
The cross-feature permission hardening pack pins the security-relevant
|
||
**subject × surface** combinations against regressions. It is API-level (the
|
||
UI adds nothing over the resolved status code) and enforces the 404-vs-403
|
||
policy: an unauthorized _read_ is 404 (existence hidden), an unauthorized
|
||
_write_ on something readable is 403.
|
||
|
||
- **Subjects:** site admin (`fixture-admin`), pond admin / owner
|
||
(`fixture-user`), editor (`fixture-editor`), the same editor _label-restricted_
|
||
by a `secret`-label deny, reader (`fixture-viewer`), public (anonymous), and
|
||
the foreign user (`fixture-outsider`, a member of nothing).
|
||
- **Surfaces:** page read, edit (collab-token `rw`/`ro`), sidebar list, search,
|
||
versions (history = write), media, and the public HTML endpoint.
|
||
- **Extending it:** a new permission-touching feature adds a surface here (one
|
||
`expect` row per subject) rather than a bespoke test, so the matrix stays the
|
||
one place the policy is pinned. A weakened guard is caught here — verified by
|
||
temporarily loosening a route decorator and watching the pack go red.
|
||
|
||
## Attachments (`attachments.spec.ts`, issue #61)
|
||
|
||
Non-image attachments: a page's attachments section uploads an allowlisted
|
||
file, lists it, and inserts it into the document as a download link (verified
|
||
to serve with `Content-Disposition: attachment` + `nosniff`, never inline); a
|
||
disallowed extension is rejected with the localized allowlist error; the Pond
|
||
Admin file manager reports storage usage and flags an orphan (a pond-level
|
||
upload with no embedding page). The SVG sanitize/reject policy is covered at
|
||
the api level in `files.e2e.db.test.ts`.
|
||
|
||
## Content fixtures
|
||
|
||
`db:seed` also creates a **shared** pond `content-fixtures` (owned by
|
||
`fixture-user`) with two pages, for the content regression pack and manual
|
||
QA:
|
||
|
||
- **Every Element** (`every-element`) — every editor schema node and mark
|
||
(issue #24: headings 1–4, all list types, table, blockquote, code block,
|
||
horizontal rule, hard break, and all five marks). Loaded from the
|
||
checked-in `apps/api/prisma/fixtures/content-page.yjs`, a Yjs snapshot
|
||
generated from the human-readable `content-page.md` next to it —
|
||
`content-page.md` is the thing to read or edit; the `.yjs` file is a
|
||
build artifact of it, not source.
|
||
- **Fixture Image** (`fixture-image`) — one real, servable uploaded image
|
||
(the placeholder `fileId` inside the Markdown fixture above is not a
|
||
real attachment; this page's image is).
|
||
|
||
Regenerating after editing `content-page.md`:
|
||
|
||
```sh
|
||
pnpm --filter @dorfteich/api fixtures:regenerate
|
||
```
|
||
|
||
This is deterministic — re-running without editing the Markdown produces a
|
||
byte-identical `.yjs` file (the script pins the Yjs document's `clientID`,
|
||
which is otherwise randomized per `Y.Doc` instance) — and it refuses to
|
||
write a snapshot that isn't a fixed point of the Markdown round-trip
|
||
(`docToMarkdown(markdownToDoc(x)) === x`), so a stale fixture can't get
|
||
checked in silently.
|
||
|
||
## Conventions
|
||
|
||
- New feature packs get their own `<feature>.spec.ts` next to these and
|
||
extend the fixture matrix here (permission matrix arrives with M5,
|
||
issue #60).
|
||
- Use `contextForUser()` from `helpers.ts` for signed-in tests — it logs
|
||
in through the api and hands you a browser context with the session
|
||
cookie, no UI login repetition.
|
||
- Flaky tests are defects (ADR 0014): fix or quarantine immediately.
|