identity-and-auth-testing

This is the auth-focused helper layer of the youteacher integration test suite. Its guiding idea is that authentication is only proven when it is exercised the way a real person exercises it — through the actual UI, against the actual backend services, and then confirmed against the state those services actually wrote. The auth services and their own stores are never mocked; the only stand-ins are the external email delivery and OAuth provider, which a test cannot drive for real. The helpers drive signup and login through the rendered pages, generate second-factor codes the way an authenticator app would, exercise passkeys through a real browser credential device, and finally read each service's own database to check that the expected rows exist or are gone.

Real UI flows, not shortcuts

The signup and login helpers click through the same dialogs and pages a visitor sees. They open the login dialog, fill the credential fields, submit, and then wait for the header's account control to report an authenticated state before continuing. The signup helper walks the full email-verification-then-password sequence: it fills the address, triggers the one-time-code email, retrieves that code from a mock mail service, submits it, sets the password, and creates the account.

Much of the code in these helpers is not the happy path itself but the timing discipline around it. The pages hydrate after their server-rendered HTML arrives, some lists auto-select a row and silently rewrite the URL, and gate controls stay disabled until a challenge token is ready. A click that lands a moment too early does nothing or detaches the field being typed into. So the helpers wait on concrete signals — an element's auth-state attribute, a specific network response, a settled URL — rather than on wall-clock delays. This is the recurring lesson: an e2e helper spends most of its length making the flow deterministic, and the reliability of the whole suite rests on those waits.

When the UI sign-out path fails, the helper falls back to clearing the session cookie directly so a stale session cannot leak into the next test. Test isolation is treated as a correctness property, not a nicety.

Second factors, generated not stubbed

Time-based one-time passwords are produced by a small standards-conformant generator (the RFC 6238 construction) that turns a shared secret into the same rolling code an authenticator app would show. The setup helper enrolls a second factor through the security page, reads back the secret the server issued, immediately computes a valid code from it, and submits that to confirm enrollment. Later logins feed the same generator to answer the second-factor prompt. Because the codes are computed rather than faked, the server's real verification path runs unchanged.

Passkeys are exercised through a virtual authenticator attached over the Chrome DevTools Protocol. This is not a stub of the credential API — it is a real WebAuthn device provided by the browser, so navigator.credentials behaves exactly as it does in production, only backed by a virtual device instead of physical hardware. The helper can add and remove the device, list its stored credentials, clear them, and toggle whether user verification succeeds, which lets a test cover both a successful biometric and a failed one.

Service-to-service calls that the test makes directly (rather than through the browser) carry a short-lived bearer token that proves the request comes from a trusted service. The token deliberately carries no user identity — user sessions live in the session cookie, and service trust is a separate concern. Signature and expiry validation of that token is left to backend unit and integration tests; the e2e layer only needs a valid one to reach admin and profile endpoints.

Verifying against the source of truth

Each backend service owns its own store, and the database helper reaches into each one to check outcomes: user accounts, sessions, second-factor secrets, passkeys and OAuth links in the auth service; talent, employer, recruiter and admin records in the profile service; postings and applications in the job service; search documents in the search index; and rate-limit and cache counters in Redis. The assertion helpers express expectations as state — this user exists with these properties, exactly one new session was created, only this session remains, all of a deleted user's rows are gone across every service — and fail with a message naming the mismatch.

This closes the loop that a UI-only assertion leaves open. A screen can say "signed in" while the backend wrote nothing; a delete can look successful while orphan rows survive in a sibling service. Reading the source of truth is what makes a green test mean the capability actually ran. Two habits stand out: session assertions compare a before-and-after snapshot so the check measures this action rather than accumulated state, and the deletion checks query each table by user id directly rather than through a join, so an orphan with no parent row is still caught.

Why it is built this way

The whole layer is an argument that in a multi-service system, identity is the one thing you cannot afford to fake in tests. Auth spans the browser, several services, and several stores; a mock at any seam hides exactly the integration bug that hurts most. By driving the real UI, generating real factors, using a real credential device, and reading the real stores, these helpers keep the test honest about the one flow every other test depends on.

See also

  • youteacher_integral — the integration test suite this layer belongs to
  • e2e-must-be-blackbox — why tests drive user actions and read visible state
  • guard-must-fail-on-the-bug — an assertion has value only when the bug can make it red
about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →