test(smoke): authenticated production smoke report (t-paliad-034)
Logged-in smoke test against paliad.de as the seeded test admin (tester@hlc.de). Login flow + every gated route exercised; 17 screenshots and 10 prioritised bugs filed. Top finding: paliad.can_see_project() still references the renamed-away projekte/projekt_teams tables, which 500s every RLS-touching endpoint (/api/projects, /api/deadlines, /api/appointments and the project-detail page render). Two of today's three new shipments — project tree and reminder service — cannot be exercised end-to-end as a result. Team directory works modulo a stray /api/departments?include=members 4xx/5xx.
BIN
tests/screenshots-auth-2026-04-25/auth-01-dashboard.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 42 KiB |
BIN
tests/screenshots-auth-2026-04-25/auth-03-projects-tree-view.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 53 KiB |
BIN
tests/screenshots-auth-2026-04-25/auth-05-team-directory.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
tests/screenshots-auth-2026-04-25/auth-06-team-by-department.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
tests/screenshots-auth-2026-04-25/auth-07-project-detail.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
tests/screenshots-auth-2026-04-25/auth-08-project-deadlines.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
tests/screenshots-auth-2026-04-25/auth-09-fristenrechner.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
tests/screenshots-auth-2026-04-25/auth-10-kostenrechner.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
tests/screenshots-auth-2026-04-25/auth-11-glossary.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
tests/screenshots-auth-2026-04-25/auth-12-courts.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
tests/screenshots-auth-2026-04-25/auth-13-agenda.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
tests/screenshots-auth-2026-04-25/auth-14-changelog.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
BIN
tests/screenshots-auth-2026-04-25/auth-15-settings.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
tests/screenshots-auth-2026-04-25/auth-16-deadlines.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
tests/screenshots-auth-2026-04-25/auth-17-appointments.png
Normal file
|
After Width: | Height: | Size: 46 KiB |
181
tests/smoke-auth-2026-04-25.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# Paliad Authenticated Smoke Test — 2026-04-25
|
||||
|
||||
**Target:** https://paliad.de
|
||||
**Run by:** paliad/curie (t-paliad-034)
|
||||
**Method:** Playwright headless (Chromium 1.59.1, viewport 1280×900) via `/mai-tester`
|
||||
**Auth:** logged in as `tester@hlc.de` (UUID `591183c3-ca3a-4939-acac-5953c1dd08d8`, role=`admin`, office=`munich`, lang=`de`)
|
||||
**Screenshots:** `tests/screenshots-auth-2026-04-25/auth-NN-*.png`
|
||||
**Trigger:** follow-up to t-paliad-033 — exercise the gated surface, validate the three features that shipped today.
|
||||
|
||||
## Headline
|
||||
|
||||
Login works. **Critical regression: every endpoint that lists Projects/Deadlines/Appointments returns HTTP 500 for the test admin.** Knowledge tools, Team directory, Settings, Glossary, Courts and the public landing all render cleanly. Project tree (`/api/projects/tree`) works in isolation; flat `/api/projects` does not, breaking `/projects`, `/projects/new` (POST), `/deadlines`, `/appointments` and the project-detail page render. **Reminder service could not be exercised** — every prerequisite (create project, list deadlines) is broken end-to-end.
|
||||
|
||||
Two new features (Project tree, Team directory) ship in a partial state; reminder service is unverifiable from the UI.
|
||||
|
||||
## Setup note (resolved before the smoke run)
|
||||
|
||||
The first-attempt seed of the `tester@hlc.de` admin landed in the wrong Postgres (the Supabase MCP I had access to → `supa.flexsiebels.de`, not Paliad's actual auth DB at youpc `100.99.98.201:11833`). Login returned 401 for ~6 minutes while head re-seeded via psql against the correct DB. The original seeded UUID `b62cc337-1750-4599-8e42-10764d0b9a43` was deleted from the wrong DB; the working UUID is `591183c3-ca3a-4939-acac-5953c1dd08d8`. Worth noting because future smoke tests will hit the same trap: the youpc Postgres MCP is "broken" per `~/.claude/CLAUDE.md`, so seeding has to go through psql, not MCP.
|
||||
|
||||
## Per-URL Results
|
||||
|
||||
### Core app shell
|
||||
|
||||
| URL | HTTP | Console | Render | Notes |
|
||||
|-----|------|---------|--------|-------|
|
||||
| `/login` (form) | 200 | clean | ✅ | DE default, EN toggle works, HLC hint visible |
|
||||
| `POST /api/login` | 200 | clean | ✅ | Cookie set, redirects to `/dashboard` |
|
||||
| `/dashboard` | 200 | 0 errs (favicon 404 only) | ⚠️ | Renders; greeting/office correct (`Test Tester`, `München`); traffic-light shows `1 Überfällig`. Activity log leaks 2/3 raw i18n keys (see Bug 4) |
|
||||
| `/agenda` | 200 | clean | ✅ | Empty state for "Both / 30 days"; type/range chips work |
|
||||
| `/changelog` | 200 | clean | ✅ | Renders changelog list, "What's New" header, lime accents |
|
||||
| `/whatsnew` | **404** | err | ❌ | Brief expected this to work — it doesn't exist; sidebar uses `/changelog` |
|
||||
| `/search?q=test` | **404** | err | ❌ | Brief expected this to work — route does not exist |
|
||||
| `/settings` | 200 | clean | ✅ | Profile tab populated correctly, all 4 tabs (Profile/Notifications/CalDAV/Dezernat) render in the SPA |
|
||||
| `/settings/caldav` | 200 | clean | ✅ | Direct deep-link works |
|
||||
| `/settings/notifications`, `/settings/dezernat` | **404** | — | ⚠️ | Tab deep-links 404 (only `caldav` is routed); not blocking — in-page tabs work |
|
||||
|
||||
### The three new features
|
||||
|
||||
| Feature | URL | Verdict | Detail |
|
||||
|---------|-----|---------|--------|
|
||||
| Project tree (t-paliad-028) | `/projects` (flat **and** tree) | ❌ **broken** | Banner "Project management is currently unavailable — please contact an administrator." `/api/projects` and `/api/projects?view=tree` both return 500. The new `/api/projects/tree` endpoint **does** return 200 with valid JSON, but the page never reaches it because the initial flat-list load fails first. Tree-view dropdown is disabled implicitly by the error banner — no toggle UX possible. |
|
||||
| Team directory (t-paliad-029) | `/team` | ⚠️ **partial** | Shows `Test Tester` (admin/Munich) + `Matthias` (Counsel Knowledge Lawyer / Düsseldorf, real prod data). "By office" / "By department" toggle works; office filter pills work (`Munich` filter narrows to 1/2 correctly); search by name works (`Tester` → 1/2). **But:** `GET /api/departments?include=members` returns 404/500 silently — page still renders because the data falls back to `/api/users`. The fallback works; the dept include endpoint is bogus. |
|
||||
| Reminder service (t-paliad-032) | (UI prerequisite) | ❌ **unverifiable** | Cannot create a fresh deadline: `POST /api/projects` returns 500, so no project to attach a deadline to. The existing prod project (`Mandant vs Gegner`) is invisible in the project-detail page (Bug 2), so its deadline form is unreachable. SMTP / email side untested. |
|
||||
|
||||
### Knowledge platform (auth-gated, all green)
|
||||
|
||||
| URL | HTTP | Console | Visual |
|
||||
|-----|------|---------|--------|
|
||||
| `/tools/fristenrechner` | 200 | clean | ✅ Patent Deadline Calculator renders, UPC/German/EPO selectors visible |
|
||||
| `/tools/kostenrechner` | 200 | clean | ✅ Cost Calculator renders with default 1M EUR, totals computed live (51,924.92 €) |
|
||||
| `/tools/gebuehrentabellen` | 200 | clean | ✅ |
|
||||
| `/checklists` | 200 | clean | ✅ |
|
||||
| `/glossary` | 200 | clean | ✅ 89/89 terms; DE→EN toggle changes title to `Patentglossar` correctly |
|
||||
| `/courts` | 200 | clean | ✅ 41/41 courts; UPC/Germany/EPA/National + 13 country filters |
|
||||
| `/links` | 200 | clean | ✅ |
|
||||
| `/downloads` | 200 | clean | ✅ |
|
||||
|
||||
### Aktenverwaltung core (broken)
|
||||
|
||||
| URL | HTTP | Failed XHRs | Notes |
|
||||
|-----|------|-------------|-------|
|
||||
| `/projects` | 200 (page) | `/api/projects` → 500 | "Project management is currently unavailable" |
|
||||
| `/projects?view=tree` | 200 (page) | `/api/projects?view=tree` → 500 | Same banner; `view=tree` query param has no SSR effect |
|
||||
| `/projects/new` | 200 (page) | `POST /api/projects` → 500 | Form renders; submit displays `internal error` inline; no project created |
|
||||
| `/projects/{id}` | 200 (page) | n/a | "Project not found or no access." But `GET /api/projects/{id}` returns 200 — page-render visibility check is inconsistent with the API |
|
||||
| `/projects/{id}/deadlines` | 200 (page) | n/a | Same "not found or no access" — same root cause |
|
||||
| `/deadlines` | 200 (page) | `/api/deadlines?status=pending`, `/api/deadlines/summary`, `/api/projects` all 500 | "Deadline management currently unavailable" |
|
||||
| `/appointments` | 200 (page) | `/api/appointments?`, `/api/appointments/summary`, `/api/projects` all 500 | "Appointments unavailable — please contact your administrator." |
|
||||
|
||||
### DE/EN toggle smoke
|
||||
|
||||
| Page | DE | EN | Verdict |
|
||||
|------|----|----|---------|
|
||||
| `/dashboard` | "Guten Tag, Test Tester" | "My matters / Sign Out / Internal use only" | ✅ |
|
||||
| `/projects` | "Projekte" sidebar / "Akten" | "Projects / Internal use only" | ✅ (chrome) |
|
||||
| `/glossary` | "Patentglossar" / "Patentglossar — Paliad" title | "Patent Glossary" | ✅ |
|
||||
|
||||
## Bug-hunt findings (top 10 by severity)
|
||||
|
||||
### 🔴 Bug 1 — `/api/projects` (GET list, POST create) returns 500
|
||||
|
||||
**Severity:** showstopper for Aktenverwaltung. Reproduction: `curl https://paliad.de/api/projects` with the test admin's session cookie returns `{"error":"internal error"}`. Same pattern for `POST /api/projects` (project creation) and for `/api/deadlines`, `/api/appointments`, `/api/deadlines/summary`, `/api/appointments/summary`. Likely root cause: see Bug 3.
|
||||
|
||||
### 🔴 Bug 2 — `/projects/{id}` SSR shows "not found" while `/api/projects/{id}` returns 200
|
||||
|
||||
**Severity:** breaks every project-detail navigation. Repro: `curl https://paliad.de/api/projects/b0b39a87-c67e-4fa2-8935-4511ccce80dc` → 200 with full payload; visiting `https://paliad.de/projects/b0b39a87-c67e-4fa2-8935-4511ccce80dc` in the browser → page text "Project not found or no access." The page-render handler (`handleProjectsDetailPage`, registered for 9 sub-paths in `internal/handlers/handlers.go:219-227`) uses a different visibility check than the `/api/projects/{id}` handler. Almost certainly downstream of Bug 3.
|
||||
|
||||
### 🔴 Bug 3 — `paliad.can_see_project()` references renamed-away tables
|
||||
|
||||
**Severity:** root cause for Bugs 1 + 2 + the broken `/deadlines` and `/appointments`. The DB function still queries `paliad.projekte` and `paliad.projekt_teams` (German names dropped during the rename to English):
|
||||
|
||||
```sql
|
||||
-- excerpt from pg_get_functiondef('paliad.can_see_project')
|
||||
FROM paliad.projekte target
|
||||
JOIN paliad.projekt_teams pt
|
||||
ON pt.user_id = auth.uid()
|
||||
AND pt.projekt_id = ANY(string_to_array(target.path, '.')::uuid[])
|
||||
WHERE target.id = _projekt_id
|
||||
```
|
||||
|
||||
These tables no longer exist (`SELECT EXISTS(... 'projekte') AS has_projekte` → `f`). Calling the function fails: `ERROR: relation "paliad.projekte" does not exist`. The function is called by the RLS policies on `paliad.projects` (`projekte_select USING (paliad.can_see_project(id))`), so any code path that exercises RLS — page-render handlers, anything not running as `supabase_admin`/`postgres` — errors out. The Go tree handler probably works because it uses the service-side `visibilityPredicate` SQL embedded in its own query and runs as the connection's elevated role, bypassing RLS.
|
||||
|
||||
**Fix:** update the function body to reference `paliad.projects` and `paliad.project_teams`. There's likely a missing migration that renamed the tables but skipped the functions/policies.
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION paliad.can_see_project(_projekt_id uuid)
|
||||
RETURNS boolean LANGUAGE sql STABLE SECURITY DEFINER
|
||||
SET search_path TO 'paliad','public' AS $$
|
||||
SELECT EXISTS (SELECT 1 FROM paliad.users u WHERE u.id = auth.uid() AND u.role = 'admin')
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM paliad.projects target
|
||||
JOIN paliad.project_teams pt
|
||||
ON pt.user_id = auth.uid()
|
||||
AND pt.project_id = ANY(string_to_array(target.path,'.')::uuid[])
|
||||
WHERE target.id = _projekt_id);
|
||||
$$;
|
||||
```
|
||||
|
||||
The grep also turned up old-name fkey constraints (`projekte_*_fkey`, `akten_events_projekt_id_fkey`, `dokumente_projekt_id_fkey`, `fristen_projekt_id_fkey`, `notizen_projekt_id_fkey`, `parteien_projekt_id_fkey`, `projekt_teams_projekt_id_fkey`, `termine_projekt_id_fkey`) — cosmetic, but they suggest the rename migration was a `ALTER TABLE ... RENAME TO` only and never touched dependent objects. Worth a sweep.
|
||||
|
||||
### 🟠 Bug 4 — Dashboard activity log leaks raw i18n keys
|
||||
|
||||
**Severity:** cosmetic, but it's on the landing page — worst place for it. Visible strings on `/dashboard` "Letzte Aktivität":
|
||||
- `Matthias dashboard.action.notiz_created 000001.000001 Notiz hinzugefügt`
|
||||
- `Matthias dashboard.action.frist_created 000001.000001 Frist angelegt`
|
||||
- `Matthias legte Akte an 000001.000001 Akte created` ← the third entry mixes translated DE ("legte Akte an") with raw EN ("Akte created") — likely two different fallback paths.
|
||||
|
||||
The keys `dashboard.action.notiz_created` and `dashboard.action.frist_created` are not in the i18n bundle for DE (and presumably not EN either). The third entry's "Akte created" suffix looks like a default English title from the audit-event row that didn't go through translation. Three separate bugs in the same widget.
|
||||
|
||||
### 🟠 Bug 5 — Untranslated dropdown labels in `/deadlines` and `/appointments`
|
||||
|
||||
**Severity:** cosmetic. The Matter filter on `/deadlines` shows `fristen.filter.project.all` as its first option text; `/appointments` shows `termine.filter.project.all`. Both are raw i18n keys. (The pages themselves are also broken — Bug 1 — but the label leak is independent.)
|
||||
|
||||
### 🟠 Bug 6 — `/api/departments?include=members` returns 404 / 500 inconsistently
|
||||
|
||||
**Severity:** low (page renders via fallback), but worth tightening. First load on `/team` produced a 500; later fetches from the page return 404. Either the route shouldn't be hit at all (front-end should stop firing it) or it should be implemented. Both observed failures are silently swallowed by the team page — visible only in the console.
|
||||
|
||||
### 🟠 Bug 7 — `/whatsnew` and `/search` return 404
|
||||
|
||||
**Severity:** medium. The brief expected both routes to work; reality is `/changelog` (named "What's New" in the UI) and no `/search` page at all. Either:
|
||||
- the brief is stale (these routes were never added) — fix the brief,
|
||||
- or the routes were dropped in a rename — restore or redirect.
|
||||
|
||||
The sidebar correctly links to `/changelog`; users who type `/whatsnew` from memory will hit a bare "404 page not found" with no chrome at all (same for `/search`).
|
||||
|
||||
### 🟠 Bug 8 — Tab deep-links broken on `/settings`
|
||||
|
||||
**Severity:** low. Tabs visible in the UI: Profile / Notifications / CalDAV / Dezernat. Direct URL `/settings/caldav` → 200 ✅. `/settings/notifications`, `/settings/dezernat` → 404. The in-page click navigation works for all four — only the deep-link form is inconsistent.
|
||||
|
||||
### 🟡 Bug 9 — 404 pages have no chrome
|
||||
|
||||
**Severity:** polish. `/whatsnew`, `/search`, `/settings/notifications` all render bare `404 page not found` text — no sidebar, no header, no link back. Existing logged-in users hitting a typo get a jarring full-page break. `/projects/{nonexistent-uuid}` correctly renders the chrome + a friendly "not found" card; the 404 handler should match that style.
|
||||
|
||||
### 🟡 Bug 10 — Login form preserves session on failure
|
||||
|
||||
**Severity:** very minor. After a failed login attempt, the prior 401 console error stays attached to the session and is later replayed when console history is requested with `all=true`. Functional impact: zero. Surface this only if you're chasing a clean console for marketing screenshots.
|
||||
|
||||
---
|
||||
|
||||
## Acceptance check
|
||||
|
||||
- ✅ Login successful, cookie persisted, dashboard reached
|
||||
- ⚠️ Three new features exercised — Team directory ✅, Project tree ❌ (page broken), Reminder service ❌ (unverifiable, no path to create a deadline)
|
||||
- ✅ Report file written
|
||||
- ✅ MCP `mcp__mai__update_task(id="t-paliad-034", status="done")` will be called next
|
||||
- ⏳ Commit pending
|
||||
|
||||
## Cleanup
|
||||
|
||||
Per brief: test admin (`tester@hlc.de`, UUID `591183c3-ca3a-4939-acac-5953c1dd08d8`) **left intact** in youpc Postgres; password unchanged; no rows deleted.
|
||||
|
||||
Side-effect to flag: while debugging the seeding mismatch, I overwrote `auth.users.encrypted_password` for the **decoy** UUID `b62cc337-1750-4599-8e42-10764d0b9a43` in `supa.flexsiebels.de` Supabase (twice, with different bcrypt hashes for the same brief password). That DB is **not** Paliad's auth DB so there is no production impact, but if anything else points at that decoy it's now in an unspecified state. Head deleted the decoy UUID before reseeding — should be fully gone.
|
||||
|
||||
## Recommendation
|
||||
|
||||
1. **Ship a hotfix migration** that rewrites `paliad.can_see_project` to use the renamed tables (Bug 3). Without it the entire admin/lead surface for projects/deadlines/appointments is unusable. Real production users may also be affected — needs verification with a non-admin login.
|
||||
2. **Audit the rename migration** for other stale references: function bodies, fkey constraint names (cosmetic), trigger functions (`projekte_rewrite_subtree`, `projekte_sync_path` are still old-named — they sit on `paliad.projects` so they probably work via the trigger binding, but worth a sweep).
|
||||
3. **Reconcile the brief**: `/whatsnew` and `/search` either need to exist or stop being expected.
|
||||
4. **Fix the activity-log i18n keys** before the next user-facing demo (Bug 4).
|
||||
5. **Wire the 404 handler into the chrome** (Bug 9) — small change, big polish gain.
|
||||