- `tests/e2e/conftest.py` — Fixtures for PDS (mock or Docker), test account, key generation, auth server in background thread, Playwright browser context
-
- `tests/e2e/docker-compose.yml` — Real PDS for CI environments with Docker
-
- `.github/workflows/e2e.yml` — CI workflow
-
- Production code changes gated by `ALLOW_HTTP_PDS` env var (requires `FLASK_ENV=testing`, raises RuntimeError otherwise)
-
-
**Partially written test files** (on branch, need fixtures):
-
- `tests/e2e/test_wiki_lifecycle.py` — 4 tests written by Agent B
-
- `tests/e2e/test_account.py` — 4 tests written by Agent C (MCP consent test marked skip)
-
-
## Blocked On
-
-
Server consolidation (see [[Design/Server_Consolidation]]). The auth_server and api_server are separate Flask apps on different ports. This causes:
-
- Cookie cross-port sharing failures
-
- SQLite threading issues with two in-process Flask servers
-
- An implementation agent burned its entire context trying to work around this
-
-
Once auth + api are merged into a single Flask app, the E2E fixtures simplify dramatically.
-
-
## Plan After Consolidation
-
-
### Step 1: Simplify conftest.py
-
-
The `auth_server` fixture currently starts only the auth Flask app. Post-consolidation, it starts the single platform app, which serves both `/auth/*` and `/app/*` routes. Rename to `platform_server` or just `server`.
-
-
No `management_server` fixture needed — it's the same app.
-
-
No cross-port cookie injection needed — same origin.
-
-
### Step 2: Add new fixtures
-
-
**`authenticated_page`** (function-scoped):
-
- Logs in via mock PDS OAuth flow
-
- Returns a Playwright page with valid `platform_token` cookie
-
- Cookie works for all routes (same origin)
-
-
**`wiki_fixture`** (function-scoped):
-
- Creates a wiki directly in DB + filesystem (bypasses route to avoid tier limits)
Agent B's `test_wiki_lifecycle.py` and Agent C's `test_account.py` were written against fixture signatures that may differ from the simplified post-consolidation conftest. Review and update selectors/fixture names before running.
-
-
### Step 5: Run full suite
-
-
All 15 tests (4 existing + 11 new) should pass. Run unit tests too to verify no regressions.
+
5. OAuth callback error shows flash (not 500)
+
+
**`test_auth_flows.py`** (4 tests):
+
1. Auto-redirect when authenticated (visit `/auth/login` with valid cookie → `/app/`)
+
2. Return-to URL preservation across OAuth redirect chain
+
3. Login with handle (tests error handling for unresolvable mock handles)
+
4. Unauthenticated access redirects to login with `return_to`
+
+
**`test_wiki_lifecycle.py`** (8 tests):
+
1. Wiki creation form (slug/name → submit → redirect → MCP token visible)
+
2. Wiki settings update (change display_name → flash → persists on reload)
+
3. Wiki deletion with confirmation (expand danger zone → confirm slug → delete)
+
4. MCP token regeneration (click regen → JS confirm → new token in flash)
+
5. Dashboard redirects to existing wiki
+
6. Wiki deletion wrong slug rejected (confirm mismatch → flash error → wiki survives)
- `app/db.py` — `check_same_thread=False` scoped to `FLASK_ENV=testing`
+
+
### Bug fixes discovered during E2E work
+
+
- `resolve_did()` SSRF: Upgraded from plain `requests.get` to `hardened_http` (pre-existing vulnerability, elevated by injectable `PLC_DIRECTORY_URL`)
+
- Flask-Limiter GC: `Limiter` object garbage collected after `create_app()` returned due to weak references. Fixed with strong ref in `app.config["_LIMITER"]`
## Architecture Notes
### Mock PDS
-
The mock PDS (`tests/e2e/mock_pds.py`) implements:
-
- `POST /xrpc/com.atproto.server.createAccount` — creates test accounts with `did:plc:` DIDs
-
- `POST /xrpc/com.atproto.server.createSession` — handles re-use of existing accounts
-
- `GET /.well-known/oauth-authorization-server` — AS metadata
All on `127.0.0.1` to avoid IPv6 resolution issues.
### Test mode env vars
- `ALLOW_HTTP_PDS=true` — relaxes SSRF protections for loopback HTTP (guarded by `FLASK_ENV=testing`)
-
- `PLC_DIRECTORY_URL` — points at mock PDS for DID resolution
+
- `PLC_DIRECTORY_URL` — points at mock PDS for DID resolution (read at request time in `resolve_did()`)
- `PLATFORM_DOMAIN=127.0.0.1:{port}` — makes CLIENT_ID/REDIRECT_URI use HTTP
- `WIKI_TEMPLATE_DIR` — pointed at nonexistent path for predictable fallback behavior
-
-
### Docker vs mock
-
Conftest has a 3-tier fallback: external PDS already running → Docker Compose → in-process mock. CI with Docker gets the real PDS; devcontainers without Docker get the mock. The mock is sufficient for all current tests.