Commit graph

4 commits

Author SHA1 Message Date
86fb946e33 Add password reset, logged-in change password, and Mailpit email dev/E2E.
All checks were successful
CI / Lint, type check, unit tests, coverage (push) Successful in 2m2s
CI / E2E browser tests (push) Successful in 1m55s
Operators can fix prod admin passwords without email via Byt lösenord;
end users can use forgot-password when SMTP is configured. Local and CI
use Mailpit to capture outbound mail and verify reset links end-to-end.

- Backend: V8 password_reset_tokens, PasswordResetService, EmailService,
  POST /api/auth/forgot-password, reset-password, change-password
- Optional testToken in forgot-password response (docker profile only, for E2E)
- Frontend: ForgotPasswordPage, ResetPasswordPage, ChangePasswordPage,
  routes, login link, header Byt lösenord
- Mailpit (ghcr.io/axllent/mailpit:v1.28) in docker-compose + e2e stack
- E2E: password-reset.spec.ts + Mailpit API helper tests SMTP delivery
- Separate dev/e2e Docker image names to avoid overwriting bilhej-frontend
- Docs: README email section, production-email-checklist, .env.example
- Unit/integration tests for reset, change password, and Vitest page specs

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-21 18:05:15 +02:00
828dd82dd3 fix: use tmpfs for postgres data in E2E compose to prevent Flyway checksum mismatches
Some checks failed
CI / E2E browser tests (push) Has been cancelled
CI / Lint, type check, unit tests, coverage (push) Has been cancelled
The postgres:16 image declares a VOLUME for /var/lib/postgresql/data.
Docker Compose creates an anonymous volume that persists across CI runs.
When a Flyway migration file is modified, the next run sees a checksum
mismatch because the old migration is already recorded in the schema_history
table in the stale volume.

- Add tmpfs: [/var/lib/postgresql/data] to the postgres service
- This keeps data in RAM only, guaranteeing a completely fresh database
  on every E2E run with no persistent state between invocations

Result: eliminates FlywayValidateException caused by migration checksum
mismatches in CI.
2026-05-19 19:57:58 +02:00
5abb5bc2e9 fix: use host Docker socket with isolated E2E network
Some checks failed
CI / Lint, type check, unit tests, coverage (push) Successful in 11m41s
CI / E2E browser tests (push) Failing after 45s
The per-job DinD approach failed because Forgejo Runner's service container
DNS resolution does not work when the runner itself uses DinD
(container.docker_host: tcp://dind:2375). The job container could not resolve
the 'dind' service hostname, causing docker compose to fail immediately.

New approach:

- Runner now uses container.docker_host: 'automount' which mounts the host
  Docker socket into job containers. The runner runs as root (user: 0:0)
  to access /var/run/docker.sock.

- E2E job no longer uses a 'dind' service. docker compose runs directly
  against the host Docker daemon inside the job container.

- docker-compose.e2e.yml gets a custom 'e2e' bridge network. All E2E
  containers (postgres, backend, frontend, playwright) attach only to this
  network, isolating them from other host containers (Nextcloud, Jellyfin,
  etc.). They can still reach the internet for vehicle lookup and npm.

Tradeoff: job containers can see other containers via docker ps, but they
are on an isolated network. For a single-user home server, this is the
simplest reliable configuration.
2026-05-19 18:17:01 +02:00
1f1016a775 feat: add isolated E2E browser test pipeline for Forgejo Actions
Some checks failed
CI / Lint, type check, unit tests, coverage (push) Successful in 1m53s
CI / E2E browser tests (push) Failing after 11s
Implement per-job Docker-in-Docker (DinD) for E2E tests, giving each
job a completely isolated Docker daemon and network. This prevents
leakage to the host Docker or other containers.

The previous E2E approach failed because:
1. The Forgejo runner's container.docker_host was not set, causing
   the runner itself to try unix:///var/run/docker.sock and crash-loop.
2. The host DinD daemon had isolated networking — job containers
   running docker compose could not resolve 'dind' hostname or access
   host filesystem bind mounts (e.g. .:/app).

New approach — zero bind mounts, all COPY-based images:

- docker/backend.e2e.Dockerfile: multi-stage build from repo root.
  Copies gradlew + settings.gradle + backend/build.gradle to download
  dependencies in a cacheable layer, then copies backend/src and builds
  the bootJar. Runs the JAR directly on startup.

- docker/frontend.e2e.Dockerfile: multi-stage Node build → nginx.
  Reuses existing docker/nginx.conf for /api proxy to backend service.
  No volume mounts, fully self-contained.

- docker/playwright.e2e.Dockerfile: extends official Playwright image.
  Installs deps from package-lock.json, copies e2e tests + config.

- docker-compose.e2e.yml: zero bind mounts. Services depend on each
  other in order: postgres (healthy) → backend → frontend → playwright.
  Playwright waits for backend and frontend via curl loops before
  running tests.

- .forgejo/workflows/ci.yml: E2E job adds a 'dind' service container
  (docker:28-dind, privileged, no TLS). The job sets DOCKER_HOST to
  tcp://dind:2375 so the docker CLI inside the job talks to the
  per-job DinD daemon. The compose file is docker-compose.e2e.yml.

- Runner fix on tocke: added container.docker_host: 'tcp://dind:2375'
  to runner-config.yaml so the runner's own Docker client connects to
  the host DinD container, stopping the crash loop.

Key properties:
- Network isolation: each E2E job gets its own DinD with its own
  container network. No host container visibility.
- No bind mount leakage: all images use COPY instead of volume mounts.
  The per-job DinD has its own filesystem and can't see host paths.
- Deterministic: builds start from clean state every time. Image cache
  exists only within the per-job DinD lifetime.
- Lint-and-test job is untouched and remains green.
2026-05-19 18:07:12 +02:00