Write automated tests for the e-signature app in this repo, using the acceptance criteria below as the spec. Use Vitest for unit and integration tests, and Playwright (including a mobile emulation project) for signer flows. Each test file gets its own on-disk temp SQLite database (`mkdtemp` a directory, set `DATA_DIR` to it, run the migrations, delete it after), so the real triggers, `job_locks`, `./data/files`, and `./data/outbox` are exercised. Mock only the two allowed outside services: the nodemailer transport (when `SMTP_URL` is set) and the webhook target. Use `pdf-lib` and `pdf-parse` (or `pdfjs-dist`) in tests to inspect produced PDFs.

## Acceptance criteria to cover

1. Uploading `fixtures/three-pages.pdf` creates a draft with `page_count = 3`, correct `page_sizes`, and `original_sha256` equal to the file's hash.
2. A field placed at the visual center of page 2 is stored within 2 pt of the expected bottom-left PDF coordinates. Test the conversion function directly with a Letter page and an A4 landscape page.
3. Sequential send emails only the order-1 signer; after they sign, the order-2 signer is emailed. Parallel send emails all signers at once.
4. Opening `/sign/[token]` writes a `viewed` event with the IP from `x-forwarded-for` and the user agent.
5. Consent must be checked before fields are enabled; checking it writes `consented`.
6. Submitting with a required field empty returns 422 and writes no `signed` event and no field values.
7. A drawn signature on Playwright's mobile project results in a non-empty PNG under `DATA_DIR/files/signers/<id>/` referenced by the signer.
8. After the final signer signs, the signed PDF has at least `page_count + 1` pages, contains an image XObject on page 2 near the field's coordinates, and its SHA-256 matches `signed_sha256`.
9. The certificate page text contains every signer's name, email, IP, and `signed` timestamp, and the audit table lists events in chronological order.
10. A voided document's signing link renders the rejection page and writes no `viewed` event.
11. Daily job: call `runDaily()` directly with a frozen clock. A reminder is sent 3 days after `sent`, not again the next day; a document past `expires_at` becomes `expired` and the owner is emailed once.
12. `UPDATE` and `DELETE` on `audit_events` throw at the database level (the SQLite `RAISE(ABORT)` triggers).
13. The completion webhook body carries a valid HMAC-SHA256 header and is retried when the endpoint returns 500 then 200.
14. Using a template with two roles produces a draft with fields assigned to the correct signers.
15. Starting the app with an empty `DATA_DIR` and no env vars serves the first-run form at `/`, and submitting it creates the admin owner and a session cookie.
16. `docker compose config` parses and lists `app`, `caddy`, and `backup`; the Caddyfile references `DOMAIN`. (The real HTTPS check is manual.)
17. Job lock: with the `daily` lock row held by another process id and `locked_until` in the future, `runDaily()` returns without sending anything. With `locked_until` in the past, it takes the lock, runs, and clears it. Killing the run after the first document's reminder commits and calling `runDaily()` again reminds only the remaining documents.
18. Run `scripts/backup.sh` against a seeded database, delete `DATA_DIR/app.db`, restore the newest backup, and confirm every row count and every file under `DATA_DIR/files` matches.
19. With `SMTP_URL` unset, sending a document writes one `.eml` per signer into `DATA_DIR/outbox/` containing the signing link, and nothing is sent through nodemailer.
20. A signing token for a signer who already signed shows "already signed" and cannot re-submit.

## Layout

- `tests/unit/`: coordinate conversion, HMAC signing, signed download URL minting and expiry, certificate text builder, reminder-due logic (criteria 2, 11 logic, 13 signature).
- `tests/integration/`: first run, upload, send, sign, advance, complete, daily job, job lock, audit triggers, webhook, outbox, backup (criteria 1, 3, 4, 6, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20).
- `tests/e2e/`: Playwright desktop and mobile projects for 5 and 7, plus a full happy path from first-run form to download.

## Rules

- Name tests after criteria: `test("AC8: completed PDF hash matches signed_sha256")`.
- Put fixture PDFs in `fixtures/` (generate them with pdf-lib in a setup script if they don't exist: a 3-page Letter PDF and an A4 landscape PDF).
- Freeze time with fake timers for the daily job tests; never wait on the real croner schedule.
- Add `pnpm test` and a GitHub Actions workflow. No services needed: SQLite and the outbox are on disk, so the workflow is checkout, `pnpm install`, `pnpm test`.
- Run everything. Fix the app where it is wrong and the tests where they are wrong. Report per-criterion pass/fail and every change made.
