How This Project Is Tested
This page explains, in everyday language, how we check that the Headless Auth Platform actually works and stays secure — and then lists every individual test in an appendix at the end.
If some words are unfamiliar, the Getting started guide has a plain- language glossary (tenant, token, OIDC, MFA, passkey, and so on).
Why testing matters here
This software logs people in and decides who they are. If it gets that wrong, real harm follows — accounts get broken into, data leaks. So the project is tested heavily, and the tests focus on the security-critical behaviours: passwords, tokens, sessions, multi-factor codes, passkeys, and keeping each customer's data separate.
A useful way to think about it: every test sets up a small, realistic situation, performs an action, and then checks the result is exactly what it should be — including the cases where the system must say "no" (a wrong password, a replayed code, a tampered message).
How we test (in plain terms)
- Against the real database, not fakes. When the tests run, they automatically start a throwaway copy of the real database (PostgreSQL) and cache (Redis) inside containers, run against them, and throw them away afterwards. This means the tests exercise the actual storage rules — not a simplified stand-in that might hide bugs.
- Each test is isolated. Every test creates its own fresh tenant/users, so tests don't interfere with each other and results are repeatable.
- "Try to break it" tests. Many tests deliberately do the wrong thing — submit a bad code, reuse a one-time token, tamper with a signed message, log in as the wrong tenant — and confirm the platform rejects it. Catching the failure cases is the whole point of security testing.
- End-to-end test. One test plays the role of a real application from start to finish: set up an organisation, register a user, turn on a second factor, log in, and collect a verified token — proving the whole journey works together, not just the pieces.
The four kinds of checks we run
Beyond the test suite, the project runs three more automated checks. All four must pass.
- Automated test suite — 318 tests covering every feature area (details in the appendix). They run on a real database and cache.
- Code style check — an automated linter/formatter (
ruff) keeps the code consistent and flags risky patterns. - Type checking — a strict type checker (
mypy --strict) catches whole classes of mistakes before the code ever runs, by verifying that data is used consistently. - Database migration check — the database setup is applied and then fully reversed, to confirm the schema can be created and torn down cleanly. The machine-readable API description is also regenerated and compared, so the docs can never silently drift from the code.
Current status
- 318 automated tests — all passing.
- Style, type checking, and migration checks: all clean.
- You can reproduce all of this yourself; see Running the tests below.
Running the tests
From inside the target/ folder:
uv run pytest # the full 318-test suite (starts its own DB/cache)
uv run ruff check . && uv run ruff format --check . # style
uv run mypy --strict src/hap # type checking
uv run alembic upgrade head && uv run alembic downgrade base # migration up/down(You need Docker running, plus uv — see the Getting started guide.)
Appendix: the individual tests
Every test is listed here, grouped by the area it covers, with a plain-words note on what each one proves. The groups run from the low-level building blocks through each feature in the order it was built — core sign-in and tokens, then Hosted Login and the apps that connect to it, self-registration restrictions, invitations, personal/CLI tokens, the tenant admin console & groups, unique usernames, SSH keys for git access, and UI appearance & branding — ending with the full end-to-end journey.
The runner reports 318 individual cases; a handful of the tests below are parametrised (run with several inputs), so the count of named lines is slightly lower than 318.
Building blocks: settings & cryptography (14 tests)
These check the low-level pieces everything else depends on.
- test_settings_load_typed — configuration is read correctly from the environment, and secret values are hidden when printed.
- test_settings_missing_database_url — refuses to start with a clear error if the database address is missing.
- test_settings_smtp_mode_requires_host — switching email into real-sending mode without an SMTP host is refused at startup.
- test_settings_derive_from_platform_env — connection settings can be derived from the shared platform environment variables when not given explicitly.
- test_settings_explicit_url_overrides_platform_env — an explicit database/cache address always overrides the platform-derived default.
- test_settings_redis_tls_uses_rediss_scheme — a TLS cache connection uses the secure
rediss://scheme. - test_jwt_sign_verify_roundtrip — a token the platform signs can be verified as genuine.
- test_jwt_tampered_signature_rejected — a token whose contents were altered is rejected.
- test_jwt_none_algorithm_not_allowed — refuses the infamous "no signature" trick that has broken other systems.
- test_jwt_expired_rejected — an expired token is no longer accepted.
- test_password_hash_and_verify — passwords are stored scrambled (hashed) and verified correctly; the right password matches, a wrong one doesn't.
- test_legacy_bcrypt_verifies_and_needs_rehash — older-style password hashes still work and are flagged to be upgraded.
- test_encryption_roundtrip_and_tamper — secrets encrypted at rest can be decrypted, and tampering is detected.
- test_build_jwks_kid_matches — the public "key set" the platform publishes matches the key it actually signs with.
Supporting infrastructure & email delivery (11 tests)
The shared plumbing the rest of the platform relies on: cache key isolation, database URLs, the encryption-key store, and sending email.
- test_namespace_prefix_normalisation — the cache-key prefix is normalised consistently.
- test_first_key_is_prefixed — every cache key is written under the configured prefix, so one deployment can't read another's cache.
- test_no_prefix_when_namespace_unset — with no prefix configured, keys are used as-is.
- test_plain_local_url — an ordinary local database URL is parsed correctly.
- test_rds_ssl_param_translated — a managed-database (RDS) URL's SSL option is translated to the form the driver expects.
- test_db_key_provider_roundtrip_and_ciphertext_only — the database-backed encryption-key store encrypts and decrypts correctly and never stores anything in the clear.
- test_db_key_provider_missing_key_raises — asking for a key that isn't there fails loudly rather than silently returning nothing.
- test_console_sender_logs_the_code — in development, the email "sender" logs the code so you can test without a real mailbox.
- test_smtp_sender_sends_with_starttls_and_login — the real email sender connects over encrypted STARTTLS and authenticates before sending.
- test_smtp_failure_is_swallowed_and_code_not_logged — if email sending fails, the error is contained and the secret code is never written to the logs.
- test_get_email_sender_selects_by_mode — the right email sender (console vs SMTP) is chosen from configuration.
Health & database rules (8 tests)
- test_healthz — the "am I alive?" check responds.
- test_readyz_ok — the "am I ready?" check confirms the database and cache are connected.
- test_check_constraint_rejects_bad_credential_type — the database itself refuses nonsensical data (an unknown credential type).
- test_unique_tenant_email — the same email can't be registered twice within one tenant.
- test_login_attempt_routes_to_partition — login-attempt records land in the correct time-based storage section (used for big-volume security logs).
- test_record_audit_writes_row — security-relevant actions are written to the audit log.
- test_unknown_client_id_returns_invalid_client — an unknown application is rejected with the correct error.
- test_suspended_tenant_rejected — a suspended organisation can't be used.
Accounts, passwords & verification (9 tests)
- test_identity_crud_cycle — an admin can create, read, update, and delete a user; the response never leaks password data.
- test_duplicate_email_conflict — creating a second user with the same email is refused.
- test_admin_requires_valid_key — admin actions need a valid admin key.
- test_cross_tenant_isolation — one organisation cannot see another's users.
- test_password_policy_rejected — a password that's too weak for the org's rules is refused, with reasons.
- test_lockout_after_failures_and_reset — too many wrong passwords locks the account; a correct login resets the counter.
- test_legacy_bcrypt_upgraded_on_login — an imported old-style password is transparently upgraded on the next successful login.
- test_password_reset_no_enumeration — a password-reset request looks the same whether or not the email exists, so attackers can't fish for valid accounts.
- test_email_verification_flow — email verification works, and a verification link can't be reused.
Logging in & issuing tokens — OAuth 2.0 / OIDC (8 tests)
- test_discovery_and_jwks — an app can auto-discover the login settings and the public keys; insecure options aren't advertised.
- test_authorize_requires_pkce_and_exact_redirect — login requires the PKCE safety step and an exactly-matching return address; otherwise it's refused.
- test_authorize_login_and_consent_required — the platform correctly asks the user to log in, and then to consent, before granting access.
- test_full_login_to_token_roundtrip — the complete login → token → "who am I" sequence works and the token verifies.
- test_pkce_and_code_reuse — a wrong PKCE value is rejected, and a one-time login code can't be used twice.
- test_refresh_rotation_and_replay — "stay logged in" tokens are rotated on use, and reusing an old one is detected and shuts the whole session down.
- test_introspect_and_revoke — an app can check whether a token is active, and revoking a token immediately makes it inactive.
- test_client_credentials_grant — app-to-app (no human) login issues a token correctly.
Sessions & step-up (4 tests)
- test_list_and_revoke_sessions — a user can see their active sessions and sign one out.
- test_logout_everywhere — "log out of all devices" revokes every session.
- test_stepup_required_then_elevated — a sensitive action that needs stronger proof asks for it, and once provided, the same session is upgraded without a fresh login.
- test_cannot_revoke_another_users_session — you can't sign out someone else's session.
Multi-factor authentication (4 tests)
- test_totp_enrol_confirm_verify_and_replay — setting up an authenticator app works, and the same 6-digit code can't be reused.
- test_totp_invalid_code_rejected — a wrong authenticator code is refused.
- test_email_otp_send_verify_and_stepup — an emailed one-time code can be verified and raises the session's assurance level.
- test_otp_rate_limit — requesting too many one-time codes too quickly is throttled.
Passkeys / WebAuthn (2 tests)
- test_passkey_register_and_usernameless_login — a passkey can be registered and then used to log in without even typing a username.
- test_clone_detection_on_nonincreasing_sign_count — a copied/cloned security key is detected and rejected.
Federation: social login, SSO, SAML (6 tests)
- test_oidc_login_jit_provisions — logging in via an external provider (e.g., "Sign in with …") creates the account on first use.
- test_oidc_state_mismatch_is_csrf — a forged/mismatched login callback is rejected as an attack.
- test_oidc_jit_disabled_requires_registration — if auto-creation is off, an unknown user is asked to register instead.
- test_saml_acs_jit_and_replay — an enterprise SSO (SAML) login is accepted when valid, and the same assertion can't be replayed.
- test_saml_tampered_and_unverified_domain — a tampered SSO message is rejected, and accounts are only auto-created for verified company domains.
- test_saml_outbound_issue — the platform can also act as the SSO provider, issuing a correctly signed assertion to another application.
AI-native risk & breached passwords (6 tests)
- test_credential_stuffing_detected — many failed logins from one source are recognised as an attack pattern.
- test_impossible_travel_detected — a login from a far-away location moments after another is flagged as suspicious.
- test_scorer_thresholds — the risk score correctly maps to allow / challenge / block decisions.
- test_breached_password_only_prefix_sent — checking a password against known-breached lists never sends the actual password — only a partial fingerprint.
- test_set_breached_password_rejected — a known-breached password is refused.
- test_ai_suggestions_lifecycle — security recommendations can be listed and accepted.
Operations: webhooks, SCIM, audit streaming (6 tests)
- test_webhook_signed_delivery_and_verify — event notifications are delivered with a signature the receiver can verify.
- test_webhook_retry_and_failure_count — failed deliveries are retried and tracked.
- test_scim_user_lifecycle — a company directory can create, deactivate, and delete users automatically (SCIM).
- test_scim_requires_valid_bearer — directory sync requires a valid access token.
- test_audit_streaming_once_with_cursor — audit events stream to an external system exactly once, without gaps or repeats.
- test_partition_maintenance_creates_next_month — the high-volume log storage prepares next month's section automatically.
Permissions, admin & hardening (4 tests)
- test_platform_admin_creates_tenant_and_app_and_logs_in — an administrator can set up a new organisation and application end to end.
- test_rbac_permissions_in_token — a user's roles/permissions are correctly embedded in their token.
- test_security_headers_present — responses include standard protective HTTP headers.
- test_token_endpoint_rate_limited — hammering the token endpoint is rate-limited.
Application-scoped access control & ecosystem SSO (29 tests)
These cover feature 005: deciding which applications a logged-in user may use (separate from proving who they are), with a clear "you're signed in, but not allowed into this app" outcome.
The access gate (test_app_access_gate)
- test_restricted_denies_unentitled_with_distinct_redirect — a fully logged-in user who isn't entitled to a restricted app is turned away with a distinct "no access to this application" outcome, not a misleading login error.
- test_grant_allows_and_issues_code — once granted access, the same user gets in normally.
- test_open_application_unchanged — ordinary ("open") apps behave exactly as before.
- test_auth_failure_is_not_conflated_with_access_denied — a genuine login failure is never mistaken for the "no access" outcome (and vice versa).
Managing who may use an app (test_entitlements_admin)
- test_grant_list_revoke_and_identity_apps — the control plane can grant, list, and revoke who may use an app, and list which apps a user may use; changes are audited.
- test_unknown_ids_are_404 — granting access for a non-existent app or user is rejected.
- test_read_scope_required_for_listing — these management endpoints require a valid admin key.
Revocation (test_access_revocation)
- test_revocation_blocks_refresh_and_authorize_but_not_live_token — after access is revoked, the next login and "stay logged in" refresh are blocked, while an already-issued short-lived token simply expires on its own (sessions can be revoked to cut access immediately).
Real-time decision hook (test_access_decision_hook)
- test_hook_allow_issues_code — when an app delegates the decision to an external control plane, an "allow" lets the user in.
- test_hook_deny_refuses — a "deny" turns the user away with the distinct refusal.
- test_hook_timeout_fail_closed_then_fail_open — if the control plane can't be reached, the configured policy decides (deny by default, or allow if set to fail-open).
- test_hook_wins_over_stored_grant — when both a stored grant and a live hook exist, the hook's answer wins.
Decision logic, in isolation (test_access_decision)
- test_open_allows — open apps always pass the gate.
- test_restricted_with_no_source_denies_fail_closed — a restricted app with no access source configured denies by default.
- test_hook_allow_signs_request — the outbound decision request is signed so the control plane can trust it; an "allow" is honoured.
- test_hook_deny_carries_reason — a "deny" carries the control plane's reason.
- test_hook_timeout_fail_closed — an unreachable control plane denies when fail-closed.
- test_hook_timeout_fail_open — an unreachable control plane allows when fail-open.
Cross-app silent SSO (test_silent_sso)
- test_prompt_none_with_session_issues_code_at_second_app — a user signed in to one app can reach a second app in the same org without re-entering credentials.
- test_prompt_none_without_session_is_login_required — silent sign-in with no existing session correctly asks the user to log in.
- test_prompt_none_still_enforces_access_gate — silent sign-in still respects the per-app access gate.
- test_prompt_none_still_enforces_step_up — silent sign-in still triggers step-up when a higher assurance level is required.
Auth-endpoint rate limiting (test_auth_rate_limiting)
- test_login_throttled_and_audited — too many login attempts are throttled per the org's policy and the event is audited.
- test_within_limit_login_succeeds — ordinary login within the limits is unaffected.
Operational hardening (test_admin_keys_and_ops)
- test_scoped_key_limited_to_entitlement_management — a least-privilege admin key can manage access entitlements and nothing else.
- test_admin_key_list_and_revoke — scoped keys can be listed and revoked (a revoked key stops working); secrets aren't leaked.
- test_audit_partitions_age_out — old audit-log storage sections are aged out per the retention policy.
- test_emit_enqueues_when_async — with async delivery on, webhook notifications are queued rather than sent inline.
- test_deliver_webhook_task_is_registered — the background delivery and audit age-out tasks are registered with the worker.
Passwordless login & identity-lifecycle webhooks (16 tests)
These cover feature 006: signing in without a password, and notifying an external control plane when a user's state changes.
One-time-code login (test_passwordless_otp)
- test_email_otp_login_success — a user signs in with an emailed one-time code (no password) and the resulting token works like any other.
- test_sms_otp_login_success — the same works over SMS.
- test_wrong_and_reused_code_rejected — a wrong code is refused and a correct code can't be used twice.
- test_no_enumeration_unknown_identifier — requesting a code for an unknown address looks identical to a known one, so attackers can't fish for accounts.
- test_rate_limited — too many code requests are throttled.
- test_access_gate_still_applies — a passwordless login to a restricted app is still subject to the feature-005 access gate.
Just-in-time provisioning (test_passwordless_jit)
- test_jit_off_sends_nothing_for_unknown — by default, an unknown address gets no code (no silent auto-signup).
- test_jit_on_provisions_and_logs_in — when an org opts in, a first passwordless sign-in creates the account (and audits it).
Magic link (test_magic_link)
- test_magic_link_login_success — a one-click email link signs the user in (redeemed by the app) and is single-use.
- test_unregistered_redirect_uri_rejected — a link may only point at the app's registered address (prevents open-redirect abuse).
- test_unknown_email_no_enumeration — requesting a link for an unknown address reveals nothing.
Identity-lifecycle webhooks (test_identity_webhooks, test_webhook_event_envelope)
- test_lifecycle_events_emitted_with_signed_envelope — creating, updating, email-changing, suspending, reinstating, and deleting a user each send a signed event carrying an idempotency key and a timestamp.
- test_subscription_filtering — a subscriber receives only the event types it asked for.
- test_emission_is_best_effort — a webhook delivery problem never blocks the underlying user change.
- test_build_event_envelope_shape — each event carries a unique id, timestamp, user id, and email.
- test_new_event_types_registered — the new lifecycle event types are available to subscribe to.
Hosted Login — platform-rendered sign-in (25 tests)
These cover feature 007: an optional sign-in screen the platform itself renders (so apps don't have to build one), backed by a secure browser session cookie that enables single sign-on across apps in the same organisation.
The session cookie (test_hosted_session_cookie)
- test_set_session_cookie_has_security_attributes — the session cookie is set with the protective flags (HttpOnly, SameSite, etc.).
- test_cookie_secure_follows_issuer_scheme — the cookie is marked "secure" whenever the site is served over HTTPS.
- test_read_session_cookie_round_trip — a cookie that was set can be read back correctly.
- test_read_session_cookie_absent_or_empty_is_none — a missing or empty cookie is treated as "no session", not an error.
- test_clear_session_cookie_expires_it — signing out expires the cookie immediately.
Turning it on (test_hosted_login_config, test_admin_hosted_login_config)
- test_hosted_login_disabled_by_default — the hosted screen is off until an organisation opts in (no behaviour change for existing tenants).
- test_patch_tenant_enables_hosted_login — an administrator enables hosted login for an org.
- test_patch_tenant_unknown_id_404 — configuring an unknown org is rejected.
- test_list_applications_returns_ids_for_patch — the apps that can be configured are listable.
- test_patch_application_registers_post_logout — an app can register where users land after logging out.
The login page & authorize flow (test_hosted_login_page, test_hosted_authorize)
- test_page_renders_email_step — the hosted page renders its first (email) step.
- test_404_when_hosted_disabled — the page doesn't exist for orgs that haven't enabled it.
- test_invalid_return_to_rejected — a tampered/again unregistered "return to" address is refused.
- test_full_flow_sets_cookie_and_resumes — completing the hosted login sets the session cookie and resumes the original sign-in the user started.
- test_wrong_code_re_renders_with_error — a wrong code re-shows the page with an error, not a crash.
- test_cookie_session_issues_code — a user who already has a session cookie is signed in without re-entering credentials (single sign-on).
- test_prompt_none_without_session_redirects_login_required — a silent sign-in with no session correctly reports "login required".
- test_no_session_redirects_to_hosted_login_page — with no session, the user is sent to the hosted login page.
- test_headless_unchanged_when_hosted_disabled — with hosted login off, the original headless behaviour is unchanged.
Logout & discovery (test_hosted_logout)
- test_logout_clears_cookie_and_revokes_session — logging out clears the cookie and ends the server-side session.
- test_logout_redirects_to_registered_post_logout_uri — after logout the user is returned to the app's registered address.
- test_logout_unregistered_post_logout_uri_renders_page — an unregistered return address is not honoured (open-redirect protection); a page is shown instead.
- test_logout_404_when_hosted_disabled — logout doesn't exist for orgs without hosted login.
- test_discovery_advertises_end_session_when_hosted / test_discovery_omits_end_session_when_disabled — the public discovery document advertises the logout endpoint only when hosted login is on.
Connecting apps to Hosted Login (4 tests)
These cover feature 008: the platform-side setup that lets relying-party apps delegate their login to Hosted Login.
- test_create_tenant_pins_id_and_sets_hosted_flags — provisioning an org can pin its id and turn on the hosted-login flags in one step.
- test_create_tenant_flags_default_off — those flags default to off when not requested.
- test_create_application_registers_post_logout_and_grants — registering an app records its post-logout address and access grants together.
- test_create_application_grants_default_when_omitted — sensible default grants are applied when none are specified.
Self-registration restrictions (20 tests)
These cover feature 009: per-organisation control over who may sign themselves up — by email-domain rules, disposable-email blocking, a sign-up window, and/or an external decision webhook — while admin/API provisioning is never restricted.
The rules, in isolation (test_registration_rules)
- test_email_domain — the email domain is extracted correctly for rule-matching.
- test_allowlist_admits_only_listed_domains — with an allowlist, only listed domains may sign up.
- test_empty_allowlist_is_no_constraint — an empty allowlist imposes no restriction.
- test_denylist_blocks_listed_domains — a denylist blocks the listed domains.
- test_denylist_takes_precedence_over_allowlist — if both lists apply, the denylist wins.
- test_disposable_block — known disposable/throwaway email domains can be blocked.
- test_registration_window — sign-up can be limited to a configured time window.
- test_case_insensitive_matching — domain matching ignores letter case.
Enforced end to end (test_registration_policy_modes)
- test_no_policy_keeps_signup_open — with no policy set, sign-up stays open (legacy behaviour).
- test_disabled_refuses_signup_but_api_provisioning_works — "disabled" blocks self-sign-up yet still lets an admin/API create the user.
- test_domain_allowlist — the allowlist is enforced on the real sign-up path.
- test_disposable_blocked — a disposable-email sign-up is refused.
- test_non_enumeration_blocked_domain — a blocked sign-up looks the same as any other refusal, so attackers learn nothing.
- test_audit_records_decisions — allow/deny decisions are written to the audit log.
The decision webhook (test_registration_webhook)
- test_webhook_allow / test_webhook_deny — an external decision service can allow or deny a sign-up.
- test_webhook_failclosed_on_error / test_webhook_failopen_on_error — if that service can't be reached, the configured policy decides (deny by default, or allow if set to fail-open).
- test_webhook_signature_is_verifiable — the request to the decision service is signed so it can trust the caller.
- test_rules_evaluated_before_webhook — the cheap local rules run first; the webhook is only asked if the rules pass.
User invitations (22 tests)
These cover feature 010: an application invites someone by email; the platform creates a pending account and a single-use link, and the recipient accepts (or rejects) on a hosted welcome page.
Creating an invitation (test_invitations_api, test_invitation_create)
- test_create_invitation_new_email — an app invites a brand-new email address.
- test_create_invitation_requires_return_url — an invitation must say where to send the user back.
- test_create_invitation_bad_secret / test_create_invitation_no_auth — an app must authenticate with valid credentials to invite.
- test_create_makes_pending_identity_and_token — creating an invitation makes a pending account and a single-use token.
- test_create_for_existing_active_identity — inviting someone who already has an active account is handled gracefully.
- test_reinvite_supersedes_outstanding — re-inviting the same person replaces the earlier outstanding invitation.
Already-registered & guard (test_invitation_already_registered, test_invitation_pending_login_guard)
- test_active_identity_auto_accepts — an already-active user's invitation is auto-accepted.
- test_suspended_identity_is_ineligible — a suspended user can't be invited back in this way.
- test_pending_identity_cannot_password_login — a not-yet-accepted (pending) account cannot log in with a password until it's activated.
Redeeming the link (test_invitation_redeem, test_invitation_result_token)
- test_welcome_then_accept — opening the link shows a welcome page and accepting activates the account and grants app access.
- test_reject — the recipient can decline the invitation.
- test_expired_link — an expired link no longer works.
- test_replay_after_consumed — a link can't be used twice.
- test_unknown_token — an unknown/forged token is refused.
- test_accepted_result_token_verifies — the signed "result" handed back to the app verifies as genuine.
- test_tampered_result_token_fails — a tampered result token is rejected.
Email sending & notifications (test_invitation_email_send, test_invitation_webhooks)
- test_platform_sends_and_substitutes — when asked, the platform sends the invitation email and substitutes the link/email placeholders.
- test_send_email_requires_bodies — sending requires the app to supply the message bodies.
- test_send_failure_preserves_invitation — an email-sending failure doesn't lose the invitation itself.
- test_accept_emits_webhook / test_reject_emits_webhook — accepting or rejecting notifies the inviting app via a webhook.
Personal / CLI access tokens (16 tests)
These cover feature 011: long-lived bearer tokens a user (or a git/CLI tool acting for them) can
present, validated by introspection, with suspension/deletion cutting them off automatically.
Issuing (test_personal_tokens_api, test_pat_issue)
- test_issue_requires_write_scope — minting a token requires a properly scoped admin key.
- test_issue_for_non_active_identity_conflicts — a token can't be minted for a suspended/deleted user.
- test_issue_stores_only_hash — only a hash of the token is stored; the secret itself is shown once.
- test_expiry_clamped_to_max — a requested lifetime is capped at the configured maximum.
Introspecting (test_personal_tokens_api, test_pat_introspect)
- test_introspect_returns_identity — checking a token returns whose it is (the stable user id).
- test_introspect_active_updates_last_used — a successful check records when the token was last used.
- test_scope_and_audience_round_trip — a token's scope and intended audience are stored and returned.
- test_default_scope_is_empty — a token with no scope requested has an empty scope.
- test_expired_token_inactive — an expired token reports inactive.
- test_personal_token_rejected_by_oauth_path — a personal token can't be mistaken for an OAuth token.
Revoking & lifecycle cascade (test_pat_revoke, test_pat_identity_cascade, test_pat_extended_cascade)
- test_list_metadata_only — listing a user's tokens reveals metadata only, never the secret.
- test_revoke_is_irreversible — a revoked token stays revoked.
- test_suspend_then_reinstate — suspending a user immediately stops their tokens; reinstating restores them.
- test_delete_invalidates_tokens — deleting a user invalidates their tokens.
- test_flag_on_revokes_sessions_and_oauth — with the opt-in flag on, suspending a user also cuts their live sessions and OAuth tokens.
- test_flag_off_leaves_sessions_and_oauth_untouched — with the flag off, existing behaviour is unchanged.
Tenant admin console & user-owned groups (34 tests)
These cover feature 012: an administrator managing a tenant's users, and any user creating and running a group (an organisation/club/collective) with owner, admin, and member roles. The rules that stop a group — or the whole tenant — from being left ungovernable are tested from every angle.
Group lifecycle & roles (test_groups_lifecycle)
- test_create_group_makes_creator_sole_owner — creating a group makes you its single owner (and an admin).
- test_create_group_forbidden_when_policy_disabled — if the org hasn't enabled group creation, the attempt is refused.
- test_capability_mode_requires_grant — in "by permission" mode, only users who've been granted the right may create groups.
- test_add_member_idempotent_and_existing_only — you can add existing members of the org; adding the same person twice is harmless, and a stranger can't be added.
- test_disabled_user_may_be_added_as_member — a suspended user can be added (but gains nothing until re-enabled).
- test_non_admin_cannot_add_member — only an owner or admin may manage membership.
- test_grant_and_revoke_admin — an admin can promote a member and demote them again.
- test_owner_is_always_admin — the owner's admin status can never be removed.
- test_transfer_ownership_keeps_one_owner — ownership moves to another admin, and there is still exactly one owner afterwards.
- test_transfer_to_non_admin_refused — ownership can only pass to an existing admin.
- test_owner_cannot_leave_without_transfer — an owner must hand over the group before leaving.
- test_member_can_leave — an ordinary member can leave on their own.
- test_only_owner_removes_an_admin_member — a fellow admin can't remove another admin; only the owner can.
Access & lockout protection (test_group_access_and_lockout)
- test_group_membership_grants_application_access — putting an app on a group grants every member access to it, and removing it takes the access away.
- test_disabled_member_gets_no_group_access — a suspended member gets none of the group's app access.
- test_cannot_disable_last_tenant_admin / test_cannot_revoke_last_tenant_admin — the last administrator of a tenant can never be disabled or demoted (so no one is ever locked out).
- test_second_admin_unblocks_disable — once a second admin exists, the first can be disabled.
- test_sole_group_owner_cannot_be_disabled_until_reassigned — the sole owner of a group can't be disabled until an administrator reassigns the group to someone else.
The administrator & self-service API (test_groups_api)
- test_self_service_create_and_membership — a signed-in user creates a group and adds a member through the API.
- test_self_service_requires_session — the self-service endpoints reject anonymous callers.
- test_group_creation_forbidden_when_policy_off — the API refuses group creation when the org hasn't enabled it.
- test_admin_oversight_and_reassign — an administrator lists all groups and performs an emergency owner reassignment.
- test_tenant_admin_grant_and_last_admin_block — granting the administrator role works, and revoking the last one is refused.
- test_patch_tenant_group_creation_policy — an administrator sets the group-creation policy, and an invalid value is rejected.
The Hosted Admin Console pages (test_hosted_admin_pages)
- test_admin_console_404_when_hosted_disabled — the console doesn't exist for orgs that haven't turned on the hosted UI.
- test_admin_console_requires_signin / test_admin_console_forbidden_for_non_admin — the console requires signing in, and only administrators may open it.
- test_admin_console_renders_for_tenant_admin — an administrator sees the Users and Groups screens.
- test_my_groups_page_create_flow — a user creates a group from the hosted page and sees it listed.
- test_admin_console_owner_email_and_user_id_tooltip — the console shows a group owner's email and the user-id tooltip so admins can identify people.
- test_admin_console_shows_username_in_rows — each user row shows the person's username.
- test_group_add_member_by_email_and_search — an admin adds a group member by email, with search.
- test_admin_reassign_owner_by_email — an admin performs an emergency owner reassignment by email.
Unique usernames & sign-in by username (14 tests)
These cover feature 013: every user gets a unique, human-readable username within their organisation, can sign in with it, and a changed/released name is briefly quarantined so it can't be immediately claimed by someone else.
- test_default_username_assigned_on_create — a new user is given a username automatically.
- test_default_username_is_unique — auto-assigned usernames don't collide.
- test_explicit_username_and_case_insensitive_uniqueness — a chosen username is accepted, and uniqueness ignores letter case (no "Bob" vs "bob").
- test_format_and_reserved_validation — badly-formatted or reserved usernames are refused.
- test_different_tenants_may_share_a_username — the same username may exist in different organisations, which stay separate.
- test_change_quarantines_old_name_others_cannot_claim — changing your username puts the old one in quarantine so no one else can grab it straight away.
- test_expired_quarantine_is_claimable — once the quarantine period passes, the old name is free again.
- test_release_on_delete_quarantines — deleting a user quarantines their freed username.
- test_self_service_change_rate_limited — a user can't change their username too frequently.
- test_admin_api_username_lifecycle — an admin can set and change usernames through the API.
- test_self_service_endpoint_and_rate_limit — the self-service rename endpoint works and is rate-limited.
- test_login_by_username — a user can log in with their username instead of their email.
- test_otp_login_by_username — one-time-code login also works by username.
- test_otp_unknown_username_sends_nothing — requesting a code for an unknown username sends nothing and reveals nothing.
SSH keys for git access (24 tests)
The SSH counterpart to personal/CLI tokens: the platform stores a user's SSH public key and a git gateway looks a presented key up to find out whose it is.
Registering & validating keys (test_ssh_key_register, test_ssh_key_validation)
- test_register_stores_public_key_and_fingerprint — a registered key is stored with its fingerprint, and no private material is ever kept or returned.
- test_rsa2048_accepted — a modern RSA key (2048 bits or more) is accepted.
- test_ecdsa_rejected / test_short_rsa_rejected / test_malformed_key_rejected — weak, unsupported, or garbled keys are turned away with a clear reason.
- test_duplicate_key_rejected_same_tenant — the same key can't be registered twice in one organisation (so a key always points at exactly one person).
- test_same_key_allowed_in_different_tenant — but the same key may exist under a different organisation, which is kept entirely separate.
- test_reregister_after_removal_allowed — once a key is removed, it can be added again later.
- test_scope_and_audience_round_trip — a key's optional scope and audience are stored and echoed back.
- test_expiry_is_set_when_requested — an optional expiry is recorded when the caller asks for one.
Authorisation on the endpoints (test_ssh_keys_api)
- test_register_requires_write_scope — registering a key on someone's behalf needs a write-scoped admin key.
- test_lookup_requires_read_scope — the gateway lookup needs a read-scoped key.
- test_register_unknown_identity_404 — registering a key for a non-existent user is rejected.
Looking a key up (test_ssh_key_lookup)
- test_lookup_by_fingerprint_and_by_key — a gateway can resolve a key either by its fingerprint or by the full key, and gets back the owning identity.
- test_unknown_key_inactive_and_nonenumerating — an unknown key returns "not usable" and reveals nothing else.
- test_lookup_updates_last_used — a successful lookup records when the key was last used.
- test_expired_key_inactive — a key past its optional expiry stops resolving.
- test_lookup_is_tenant_isolated — a key registered in one organisation never resolves for another.
Managing keys & instant revocation (test_ssh_key_manage, test_ssh_key_identity_cascade)
- test_list_and_remove_admin — an administrator lists a user's keys and removes one; the removed key immediately stops resolving.
- test_self_service_register_list_remove — a signed-in user manages their own keys directly.
- test_self_service_cannot_touch_another_identitys_key — a user can only manage their own keys, never someone else's.
- test_remove_unknown_key_404 — removing a key that doesn't exist is reported cleanly.
- test_suspend_then_reinstate — suspending a user makes all their SSH keys stop working at once (no separate revocation step); reinstating them restores access.
- test_delete_cascades — deleting a user makes their SSH keys stop working.
UI appearance & branding (24 tests)
These cover feature 015: a per-tenant appearance (palette, font, logo, favicon, product name) applied consistently across every platform-rendered screen, configurable by both a platform operator (admin API) and a tenant admin (self-service Hosted page).
The branding rules, in isolation (test_branding_domain)
- test_valid_full_config_is_normalized — a complete, valid appearance is accepted and stored in a normalised form.
- test_empty_payload_clears_and_has_no_warnings — an empty config clears branding back to the platform defaults.
- test_invalid_colour_rejected / test_unknown_font_rejected / test_non_https_asset_rejected / test_unknown_field_rejected / test_overlong_product_name_rejected — unsafe or unknown input is refused: bad colours, non-allowlisted fonts, non-HTTPS logo/favicon URLs, stray fields, and over-long names.
- test_low_contrast_warns_but_does_not_block / test_good_contrast_no_warning / test_contrast_ratio_extremes — low colour contrast produces a warning but still saves; sufficient contrast produces none.
- test_resolve_applies_defaults_when_empty / test_resolve_builds_font_link_and_helpers /
test_legacy_accent_and_logo_still_resolve — resolving an appearance fills in defaults, builds
the safe web-font link and HTML/CSS helpers, and keeps legacy
accent_color/logo_url-only tenants rendering unchanged.
The admin API (test_branding_admin_api)
- test_patch_sets_branding — an operator sets a tenant's branding through the admin API.
- test_patch_invalid_color_rejected / test_patch_non_https_logo_rejected / test_patch_unsupported_font_rejected — the same validation applies on the admin path.
- test_patch_low_contrast_warns_but_succeeds — low contrast is returned as a non-blocking warning rather than refused.
The platform-rendered screens & self-service page (test_branding_screens)
- test_hosted_login_reflects_branding — a tenant's appearance shows up on the Hosted Login screen.
- test_unbranded_tenant_renders_defaults — a tenant with no branding renders the defaults.
- test_branding_page_requires_tenant_admin — the self-service branding page is only open to a tenant administrator.
- test_branding_page_save_and_apply — a tenant admin saves an appearance and sees it applied.
- test_branding_page_warns_on_low_contrast — the page surfaces a non-blocking contrast warning.
- test_branding_page_rejects_bad_value — the page refuses an unsafe value with a clear reason.
Full end-to-end journey (1 test)
- test_full_journey — the whole story in one test: an admin provisions an organisation and app; a user registers, logs in, sets up an authenticator (reaching a higher assurance level), then completes the login flow and retrieves a verified token confirming the higher assurance level.