Skip to content

expand the end-to-end suite to cover every authenticated route - #1477

Merged
joshunrau merged 7 commits into
DouglasNeuroInformatics:mainfrom
joshunrau:e2e-tests
Jul 29, 2026
Merged

expand the end-to-end suite to cover every authenticated route#1477
joshunrau merged 7 commits into
DouglasNeuroInformatics:mainfrom
joshunrau:e2e-tests

Conversation

@joshunrau

Copy link
Copy Markdown
Collaborator

Expands the Playwright suite from the handful of flows it covered to every authenticated route in apps/web, and adds a server-side authorization block that drives the API directly.

New coverage

Spec Covers
static-pages /about release info (web, API, gateway healthcheck), /contact validation and its mailto link, unauthenticated redirects for both
user-profile Profile fields, updating one without a reload, password strength/confirmation rules, changing a password and logging back in with it
subject-detail Graph tab plotting a measure, assignments tab listing and cancelling a remote assignment, opening a record from the table
upload Uploading a valid CSV and finding the subject it created, and the validation-error + retry path
group-manage Previewing an accessible instrument, and saving instrument selection + group settings so they survive navigation
admin-settings Toggling the uploader feature through to the sidebar, and the group-switcher position preference
admin-branding Navigating to the login-page editor and persisting a branding change
admin-instrument-repos The repos page and client-side URL validation (a successful import would clone from GitHub, so it is out of scope)
admin-audit-logs The table, filtering to a login it triggers itself, and the JSON download
accessible-instruments Search filtering the instrument showcase
authorization Extended — see below

admin-management, auth, dashboard, datahub, disclaimer, gateway-assignment, instrument-completion, remote-assignment and start-session all gained cases as well.

Server-side authorization

_app/route.tsx's beforeLoad checks that a token exists, not what role holds it, and no route under /admin adds a check of its own — so a non-admin who navigates there directly gets the real screen (#1470). What actually protects those screens is the API, so authorization.spec.ts now asserts that half directly rather than through the UI:

  • Every privileged request those screens fire (POST/PATCH/DELETE /v1/users, PATCH /v1/setup, POST /v1/instrument-repos, GET /v1/audit/logs) returns 403 for both GROUP_MANAGER and STANDARD.
  • A group manager cannot rename or delete a group they do not belong to — asserted by effect, since @RouteAccess sees only the subject type there and the row scoping in GroupsService is the whole check.
  • Read scoping: GET /v1/users returns only the acting user's own group for a GROUP_MANAGER, and only their own account for a STANDARD user. This is what makes a populated table on /admin/users acceptable rather than a leak.

POST /v1/groups is deliberately absent from that table: a group manager is wrongly allowed it (#1468), and asserting the current behaviour would lock the defect in.

Supporting changes

  • data-testid attributes in apps/web for the elements the new specs select.
  • authenticateWithToken fixture, so a spec can act as a specific seeded user rather than a worker-cached role; authenticateAs now delegates to it, which removes a hand-rolled addInitScript block from group-manage.
  • ApiClient.findGroupById, used to check whether a refused write actually took effect.
  • testing/AGENTS.md fixture table updated.

Two defects fixed inline

Both blocked writing the tests at all, so they were fixed here rather than filed:

  • DisclaimerProvider used data-test-id (hyphenated) on all four of its selectors, so the disclaimer dialog was unreachable by Playwright's default testid convention.
  • UserDropup's trigger called setIsOpen(!isOpen) in an onClick on top of Radix's own controlled open/onOpenChange handling on the same DropdownMenu.Trigger. Both fire on one real click, and the second — driven by stale isOpen — intermittently flipped the menu straight back shut, so a normal click on the sidebar user menu sometimes silently did nothing.

Everything else found along the way

Filed rather than asserted, so no broken behaviour is pinned as correct: #1468, #1469, #1470, #1471, #1472, #1473, #1474, #1475, #1476, and DouglasNeuroInformatics/libui#108.

Test plan

  • pnpm lint — passes repo-wide (33/33).
  • pnpm test:e2e — runs in CI on this PR. It was not run locally: a dev stack was holding the API port, and Playwright's own servers cannot start alongside it (running against the dev stack is not an option, since global/teardown.spec.ts drops the database).

🤖 Generated with Claude Code

joshunrau and others added 7 commits July 27, 2026 18:17
Adds Playwright coverage for the routes that had none (static pages, user
profile, subject detail, upload, group management and all five admin screens),
plus a server-side authorization block that drives the API directly to pin the
access control the admin screens depend on.

Supporting changes:

- data-testid attributes in apps/web for the elements the new specs select
- authenticateWithToken fixture, so a spec can act as a specific seeded user
  instead of a worker-cached role; authenticateAs now delegates to it
- ApiClient.findGroupById, to check whether a refused write took effect

Two defects were fixed directly rather than filed, because they blocked writing
the tests at all:

- DisclaimerProvider used data-test-id (hyphenated) on all four of its
  selectors, so the dialog was unreachable by Playwright's testid convention
- UserDropup's trigger called setIsOpen on top of Radix's own controlled
  open/onOpenChange handling, so one real click fired both handlers and the
  stale-state toggle intermittently closed the menu again

Everything else found along the way is described in BUGS.md rather than
asserted as passing behavior.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every unfixed finding is now filed on the tracker (DouglasNeuroInformatics#1468-DouglasNeuroInformatics#1476, plus
DouglasNeuroInformatics/libui#108 for the one that belongs to that package
rather than this repo).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The file was a staging area for findings made while writing the suite. Each one
is now an issue (DouglasNeuroInformatics#1468-DouglasNeuroInformatics#1476, plus DouglasNeuroInformatics/libui#108), so the
spec comments that pointed at it cite the issue instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Each one came down to the suite depending on a value only a developer's .env
holds, since CI generates .env from .env.template.

Three specs log in through the real form -- deliberately, so an init script
cannot re-authenticate them -- and so never received the appState init script
that only ran inside authenticateWithToken. They met the app's in-code first-run
defaults, where the walkthrough overlay covers the sidebar and swallows every
click. That stays invisible locally because a dev .env sets
VITE_DEV_DISABLE_TUTORIAL=true where the template leaves it false. Seed appState
from an autouse fixture instead, for every test whatever its auth path, leaving
authenticateWithToken to inject only the token; the disclaimer helper this makes
redundant is deleted.

Read CONTACT_EMAIL through support/env.ts rather than asserting a literal that
only matches one machine.

Scope the graph assertion to its own series' dots: the dashed group-trend line
plots points carrying the same class once the linear-model query resolves, so an
unscoped locator matched one or two elements depending on timing. Retry an
instrument option that detaches mid-click, and give that test the budget its UI
seeding needs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@joshunrau
joshunrau merged commit c735442 into DouglasNeuroInformatics:main Jul 29, 2026
1 check passed
thomasbeaudry added a commit that referenced this pull request Jul 30, 2026
The test expected useState form values to persist across route navigation,
but nothing in the component saves form state on unmount. This test was
introduced by PR #1477 into split/mailer and has never passed in CI.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
thomasbeaudry added a commit that referenced this pull request Jul 30, 2026
Reconciles the SMTP-only refactor with the two mail fixes pushed to the
branch and with everything main gained since (#1441, #1444, #1477).

The branch had patched the HTTP transport rather than removed it, so the
conflicts were resolved toward the maintainer's decision 1: the HTTP client
and SigV4 signer go, which makes both patches unnecessary rather than wrong.
`isMailEnabled` no longer needs an HTTP branch, and the provider names come
back out of `eslint.config.js` since no JSX mentions them any more.

Kept from the other side:
- #1441's `getDefaultAssignmentExpiry` replaces the hardcoded one-year
  default in the create-assignment form.
- #1444's `$Email`/`$PhoneNumber`/`omittedIfBlank` validation helpers in the
  user-create form, replacing the old `PHONE_REGEX`.
- The deduplicated welcome-email notification in `useCreateUserMutation`,
  now also opted out of the router error boundary so the call site's own
  password-error handling is the only path.
- #1477's page objects and specs, unioned with the two added here.

`SaveStatus.tsx` is restored from main: #1442 has landed and `admin/settings`
now consumes it, so decision 4's "pick it back up once #1442 lands" applies.
`activeLanguages` and `utils/languages.ts` are still absent from main, so
those stay dropped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant