Repository navigation
feat(e2e): add tests for real-time updates and token lifecycle - #621
Conversation
- Implement end-to-end tests for real-time updates in the UI, ensuring tasks appear, update, and disappear without page reloads. - Add tests for token lifecycle management, verifying that sockets handle token expiration and reauthentication correctly. - Enhance the project real-time hook to reset cached data when access is lost, preventing stale data from displaying.
There was a problem hiding this comment.
ℹ️ No critical issues — one tradeoff worth a look.
Reviewed changes — the new realtime E2E area plus a one-line production fix in the project realtime hook.
- Realtime probe helpers — new
tests/helpers/realtime.tsopens real socket.io connections as signed-in members, mints short-lived JWTs with the stack'sJWT_SECRET, and records events/reauth/disconnects for the specs. - Event-authorization specs — 38 cases covering handshake refusals, per-role room joins, and per-resource Allow/Deny (sprint/folder/view conditions, role unions, ordering).
- IAM-change specs — 20 cases keeping one socket open across role edits, attach/detach, and member/user/project deletion, asserting
permissions:changedand room re-sync. - Token-lifecycle specs —
reauth~60s before expiry and disconnect at expiry on minted short-TTL tokens. - Live-UI specs — real-browser checks for live create/rename/delete, role-edit reflection, the
reauthround trip, and offline catch-up. - Web hook fix —
use-project-realtime.tsnowresetQueriesthe project when a namespace is lost, so a refetch the server 403s does not leave refused data on screen. Verified against TanStack Query v5 (resetQueriesresets to initial state then refetches active matches) andquery-client.ts(shouldRetryQueryskips 4xx), so the fix behaves as described.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
ℹ️ No critical issues — the prior reset-scope concern is addressed; one completeness note on the new mapping.
Reviewed changes — the delta since the prior pullfrog review (6baefe5): the narrowed lost-namespace reset plus new gained-namespace handling in the project realtime hook and socket client.
- Scoped the lost-namespace reset —
use-project-realtime.tsnow resets through aholdsDataOfpredicate driven by a newNAMESPACE_QUERY_ROOTStable, so only the lost namespace's data is cleared and the just-invalidatedmembers/permissions queries are spared. - Added a gained-namespace refetch —
NamespacesChangenow carriesadded(empty on the first report for a project); the hook invalidates a regained namespace's queries, which may be stuck in the error state from a prior rejection. - Refined
recordNamespaces— the socket client computes gained vs. lost namespaces against the previous grant and reports both. - Expanded unit tests — new hook cases exercising the predicate (per-namespace, task data under
sprints/views, unknown namespaces, permissions spared) and a socket-client case foradded.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
The new table deliberately errs safe for a namespace it does not know, but for the seven known namespaces it uses a fixed list, and that list misses a few ["projects", projectId, …] caches gated by the namespace's own read action: task-types/task-statuses/custom-fields (tasks:read) and automation-plugin-node-types (workflows:read). After losing tasks:read/workflows:read those caches are neither reset nor cleared, whereas the broad reset this replaced did clear them. Low impact — the screens that render them are gated by the same permission — but worth listing for completeness.
Technical details
# `NAMESPACE_QUERY_ROOTS` omits role-gated schema queries
## Affected sites
- `apps/web/src/hooks/use-project-realtime.ts:92` — `tasks: ["tasks", "agentActivities"]` omits `task-types`, `task-statuses`, `custom-fields`.
- `apps/web/src/hooks/use-project-realtime.ts:96` — `workflows: ["workflows", "automations", "automation-dependency-map"]` omits `automation-plugin-node-types`.
## Evidence
- `task-types` / `task-statuses` / `custom-fields` keys: `apps/web/src/lib/project-api.ts:679`, `:685`, `:691`. Their GET routes require `iam.ActionTasksRead`: `services/api/internal/transport/http/router/router.go:539`, `:555`, `:672`.
- `automation-plugin-node-types` key: `apps/web/src/lib/automation-api.ts:704`. Its GET route requires `iam.ActionWorkflowsRead`: `router.go:590`.
- With `removed = ["tasks"]`, `holdsDataOf(["projects", id, "task-types"], id, ["tasks"])` sees `root = "task-types"`, which is not in the `tasks` roots and does not hit the sprints/views special case, so it returns `false`.
- The `"workflows"` entry matches no query in `apps/web/src` (automation list/detail queries live under `automations`), so that root is inert today.
## Required outcome
- A namespace's role-gated schema/config caches count as that namespace's data, so losing the namespace clears them too (add the missing roots to the respective arrays).
## Open questions for the human
- Whether "namespace data" is intended to include task/automation schema, or only the rows behind each namespace's room.There was a problem hiding this comment.
ℹ️ No new issues in this delta. These commits touch only the e2e helpers and unit tests, plus comments — no production behavior change. The one prior note on
NAMESPACE_QUERY_ROOTScompleteness is untouched by these commits and still open, so this is not an approval.
Reviewed changes — the delta since the prior pullfrog review (5a57238): realtime test-helper hardening and new hook unit tests.
- Guarded token minting —
mintTokennow calls a newassertE2eStack()that refuses to mint unlessBASE_URLis loopback (localhost/127.0.0.1/[::1]) orE2E_JWT_SECRETwas set explicitly. - Tightened event matching —
Probe.eventsAboutnow matches the id as a whole JSON string value (JSON.stringify(id)) rather than a bare substring, so it cannot match inside another value. - Awaitable cleanup —
closeAllis nowasyncand awaits each trackedclose(); the three socket specsawaitit inafterEach. - Expanded unit tests — new cases for queries outside the lost namespace's roots, the real
viewTaskPositionsQueryOptionskey matching the predicate, and the gained-namespace refetch path. - Comment refreshes —
eventsAbout/waitForEventJSDoc and the realtime hook's header/reset comments now match the scoped-reset behavior.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏


Summary
Adds a
realtimeE2E area covering the per-event authorization work merged in #620, and fixes one UI gap those tests found.The area has 68 cases in four specs (each with its
.featurefile, following the repo's convention). The specs open real socket.io connections as signed-in members (the protocol the web app speaks), change data as the admin over the API, and assert on what each socket receives. A negative assertion always waits for a socket that IS allowed to receive the same event first, so "nothing arrived" never means "nothing has arrived yet".event-authorizationfixme)views:readvssprints:read.iam-changestoken-lifecyclereauthabout a minute before expiry and disconnect at expiry, using tokens minted with the stack's JWT secret. Two lifetimes run concurrently (about 80s).live-updates-uireauthround trip with real websocket frames; catch-up after going offline.Shared helpers are in
tests/helpers/realtime.ts(socket probe, token minter, cleanup tracking).socket.io-clientis added as a dev dependency ofapps/e2e, and the README lists the new area.Fix found by the tests
After a member lost
tasks:read, the page kept showing the task: the refetch is refused with a 403, and React Query keeps the previous data on error.use-project-realtime.tsnow callsresetQueriesinstead ofinvalidateQuerieswhen a namespace is lost, so refused data does not stay on screen. Granting or changing permissions without losing a namespace still only invalidates. Two unit tests cover both branches.Placeholders
Five cases are
test.fixme:Type of Change
Testing
deploy/docker-compose.e2e.yml, Chromium, 3 workers, no retries): 63 passed, 5 skipped, 0 failed.tsc -b, biome and the related vitest files pass.e2e-tests.ymlis manual-trigger only.Checklist
🤖 Generated with Claude Code