Skip to main content

write-e2e-test

Write end-to-end (e2e) tests for authgear-server. Use when the user asks to write, add, or create e2e tests. The tests live in e2e/tests/ and are YAML-driven.

Zur Installation springen

Quellinformationen

Repository
authgear/authgear-server
Letzte Quellaktivität
13. Mai 2026 um 07:45
Erkannte Sprache von SKILL.md
Englisch
Sterne
2.058
Forks
125

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
write-e2e-test
description
Write end-to-end (e2e) tests for authgear-server. Use when the user asks to write, add, or create e2e tests. The tests live in e2e/tests/ and are YAML-driven.
Follow this guide when writing e2e tests. ## E2E Environment Setup **CRITICAL: All `make` commands must be run from `e2e/` directory, not project root.** ### Standard Workflow When starting fresh or after code changes: ```bash cd e2e # ← MUST be in e2e directory make teardown && make setup # ← Tears down old containers, rebuilds binaries, applies migrations ``` This command: - Stops and removes Docker containers (Redis, PostgreSQL, Deno hook server) - Rebuilds authgear and authgear-portal binaries with latest code - Applies all database migrations - Starts services fresh ### Running Tests After setup completes, run tests from the `e2e` directory: ```bash cd e2e # ← Must be in e2e directory # Run a specific test go test ./pkg/testrunner/ -count 1 -v -timeout 10m -run "TestAuthflow/<folder>/<filename_without_extension>" # Run all tests go test ./pkg/testrunner/ -count 1 -v -timeout 10m ``` ### When Setup Fails If `make teardown && make setup` fails: 1. **Try again** - transient Docker issues are common: ```bash cd e2e && make teardown && make setup ``` 2. **Check if containers are stuck**: ```bash docker ps # Run from any directory to list containers ``` 3. **If still failing** - ask the user to manually intervene. Do NOT attempt low-level Docker operations (`docker-compose down -v`, etc.) as these can affect other projects and data. **Always use the Makefile (make teardown && make setup).** Do not skip to `./run.sh` or other workarounds. ### When to Re-setup Always run `make teardown && make setup` from `e2e/` directory when: - You modified server code (rebuilds binaries) - Database schema/migrations changed - Tests fail with DB schema errors - Starting fresh after a long break ## Overview E2e tests are YAML files placed under `e2e/tests/<feature>/`. The test runner auto-discovers all `*.test.yaml` files. Each file defines one test case. ## Test File Structure ```yaml name: Human-readable test name authgear.yaml: # Optional: config overrides extend: path/to/base.yaml # Optional base config override: | fraud_protection: enabled: true before: # Optional: setup hooks (run before steps) - type: custom_sql custom_sql: path: fixtures.sql steps: - name: step name # Optional but recommended action: create input: | { "type": "signup", "name": "default" } output: result: | { "action": { "type": "identify" } } ``` ## Step Actions ### `create` — Start a new auth flow ```yaml - action: create input: | { "type": "signup", "name": "default" } output: result: | { "action": { "type": "identify" } } ``` ### `input` — Provide input to the current flow step ```yaml - action: input input: | { "identification": "phone", "login_id": "+6591230001" } output: result: | { "action": { "type": "verify" } } ``` To expect an error response: ```yaml - action: input input: | { "channel": "sms" } output: error: | { "name": "TooManyRequest", "reason": "BlockedByFraudProtection", "code": 429 } ``` ### `query` — SELECT from the main app database ```yaml - action: query query: | SELECT id, email FROM _auth_user WHERE app_id = '{{ .AppID }}' ORDER BY created_at query_output: rows: | [ { "id": "[[string]]", "email": "user@example.com" } ] ``` ### `audit_query` — SELECT from the audit database ```yaml - action: audit_query audit_query: | SELECT activity_type, data->'payload'->'record'->>'decision' AS decision FROM _audit_log WHERE app_id = '{{ .AppID }}' AND activity_type = 'fraud_protection.decision_recorded' ORDER BY decision, created_at audit_query_output: rows: | [ { "activity_type": "fraud_protection.decision_recorded", "decision": "allowed" } ] ``` **Important:** The `_audit_log.data` column stores the full serialized `event.Event`. JSON paths must include the `payload` level: `data->'payload'->'field'`. **Ordering tip:** When multiple rows can land in the same second, `ORDER BY created_at` is non-deterministic. Add a secondary sort on a stable column (e.g., `ORDER BY decision, created_at`). ### `http_request` — Raw HTTP request ```yaml - action: http_request http_request_method: POST http_request_url: http://{{ .AppID }}.authgeare2e.localhost:4000/oauth2/token http_request_form_urlencoded_body: grant_type: client_credentials client_id: myclient client_secret: mysecret http_output: http_status: 200 json_body: | { "access_token": "[[string]]", "token_type": "Bearer" } ``` ### `admin_api_graphql` — Admin API GraphQL ```yaml - action: admin_api_graphql admin_api_request: query: | mutation { deleteUser(input: {userID: $userID}) { deletedUserID } } variables: | {"userID": "{{ nodeID "User" .steps.get_user.result.rows 0 .id }}"} admin_api_output: result: | { "data": { "deleteUser": { "deletedUserID": "[[string]]" } } } ``` ### `sleep` — Wait for async operations ```yaml - action: sleep sleep_for: 2s ``` ## Template Variables Available in all string fields (inputs, queries, outputs, SQL files): | Variable | Value | |---|---| | `{{ .AppID }}` | Unique 32-char hex ID for this test run | | `{{ .prev }}` | Result of the previous step | | `{{ .steps.<name> }}` | Result of a named step | Template functions available: all [Sprig functions](http://masterminds.github.io/sprig/) plus: - `{{ linkOTPCode "phone" "+6591230001" }}` — Get OTP code (test mode sends `111111`) - `{{ generateTOTPCode "secret" }}` — Generate TOTP code - `{{ nodeID "User" "some-uuid" }}` — Encode a relay global ID - `{{ printf "%s-suffix" .AppID }}` — String formatting ## Matcher Patterns Use these in `output.result`, `output.error`, `query_output.rows`, and `audit_query_output.rows`: | Pattern | Meaning | |---|---| | `[[string]]` | Any string value | | `[[number]]` | Any number | | `[[boolean]]` | Any boolean | | `[[object]]` | Any object | | `[[array]]` | Any array | | `[[null]]` | Must be null | | `[[ignore]]` | Skip this field | | `[[never]]` | Field must not exist | | `[["[[arrayof]]", "[[object]]"]]` | Array of any length containing objects | Extra fields in objects are allowed by default (partial matching). ## Before Hooks ```yaml before: # Run SQL fixtures against the main database - type: custom_sql custom_sql: path: fixtures.sql # relative to the test file # Run SQL fixtures against the audit database - type: custom_audit_sql custom_audit_sql: path: audit_fixtures.sql # Import users from JSON - type: user_import user_import: users.json ``` SQL fixture files also support template variables (`{{ .AppID }}`, `{{ uuidv4 }}`, etc.). ## authgear.yaml Overrides The `override` snippet is merged into the default config. Use it to enable features, add identity providers, or change policies: ```yaml authgear.yaml: override: | fraud_protection: enabled: true decision: action: deny_if_any_warning ``` To base the test on a different config file: ```yaml authgear.yaml: extend: ../base/authgear.yaml override: | authentication: primary_authenticators: - password ``` ## After Writing Tests After creating the test file(s), always run them to verify they pass. If the e2e environment may be stale (e.g. first run in this session, or migrations/server code changed), set it up first: ``` make teardown && make setup ``` Then run the new test(s): ``` cd e2e && go test ./pkg/testrunner/ -count 1 -v -timeout 10m -run "TestAuthflow/<folder>/<filename_without_extension>" ``` If the authgear/e2e daemons are started by `./run.sh setup` and do not survive shell exit, combine setup and test in one shell: ``` ./run.sh teardown ./run.sh setup go test ./pkg/testrunner -count 1 -v -timeout 10m -run "TestAuthflow/<folder>/<filename_without_extension>" ``` If a test fails, read the error output, fix the test file, and re-run. Do not report the tests as done until they pass. ## Common Mistakes ### ❌ Testing Only Endpoint Existence, Not Functionality **Wrong approach — just verify the endpoint works:** ```yaml steps: - name: query_endpoint action: admin_api_graphql admin_api_request: query: | query { user { accountLockout { isLocked } } } admin_api_output: result: | { "data": { "node": { "accountLockout": { "isLocked": "[[boolean]]" } } } } ``` This test only verifies the endpoint exists and returns data. It doesn't test whether the feature actually works. **Right approach — trigger real behavior, then verify state changed:** ```yaml steps: # 1. Trigger actual failed authentication attempts - name: failed_login_1 action: input input: | { "authentication": "primary_password", "password": "wrong" } output: error: | { "name": "InvalidCredentials" } - name: failed_login_2 action: input input: | { "authentication": "primary_password", "password": "wrong" } output: error: | { "reason": "TooManyRequest" } # 2. Now verify the feature actually changed state - name: verify_locked action: admin_api_graphql admin_api_request: query: | query { user { accountLockout { isLocked } } } admin_api_output: result: | { "data": { "node": { "accountLockout": { "isLocked": true } } } } ``` The right approach: 1. **Triggers actual functionality** via auth flow `create`/`input` actions 2. **Verifies state changed** via queries and mutations 3. **Tests the feature end-to-end**, not just the endpoint ### ❌ Using Go Tests Instead of YAML Tests **Wrong approach** — writing e2e tests in Go code: ```go // ❌ Don't write e2e tests in Go func TestAccountLockout(t *testing.T) { // Direct API calls, manual JSON marshaling, etc. } ``` **Right approach** — use YAML-driven e2e tests: ```yaml # ✅ Always use YAML for e2e tests name: Account lockout prevents further attempts steps: - action: input input: | {"authentication": "primary_password", "password": "wrong"} output: error: | {"name": "InvalidCredentials"} ``` **Why YAML?** - Declarative, human-readable format - Consistent with existing test suite in `e2e/tests/` - Automatic test discovery and execution - Built-in support for auth flows, queries, mutations, and fixtures - Clear separation of test data from test logic ## Tips 1. Give steps descriptive `name` fields — they make failures readable and enable `{{ .steps.<name> }}` references. 2. Use comment lines to group related steps (e.g., `# Flow 1 — signup`). 3. OTP codes in test mode are always `111111` — no need to fetch them dynamically for phone/email OTP. 4. Each test case gets a fully isolated app (unique `AppID`) — no cleanup needed. 5. When testing audit events, always check the JSON path includes `payload`: `data->'payload'->'...'`.
Auf GitHub ansehen
Diese SKILL.md ist sehr gross, daher zeigt SkillsMP hier nur den ersten Abschnitt. Auf GitHub ansehen