This directory contains all resources related to end-to-end tests. It starts a docker-compose environment with a running authgear server at port localhost:4000 and runs tests against it.
The end-to-end tests are run in CI/CD authgear-e2e job.
Simply run the following command:
make runThe authflow tests are located in the tests directory. Unlike normal golang tests, these tests are written in YAML.
An example test case is as follows:
# login/test.yaml
# Name of the test case
name: Email Login
authgear.yaml:
# Which authgear.yaml to extend on (Optional)
extend: base.yaml
# Override specific fields in the authgear.yaml
# (Merges on maps and replaces on arrays/scalars)
override: |
authentication:
identities:
- login_id
primary_authenticators:
- password
# Before steps to run before the test, used for creating fixtures
before:
# Uses user import API (/docs/specs/user-import.md) to import users from a JSON file
- type: user_import
user_import: users.json
# Executes a custom SQL script, useful for data that cannot be achieved through the user import API
# e.g. Password expiry
- type: customsql
customsql:
file: ./custom.sql
# Test steps, each step is an API call to Authflow API (/docs/specs/authentication-flow-api-reference.md)
steps:
# Use `action: create` to create a flow
- name: Start login
action: "create"
input: |
{
"type": "login",
"name": "default"
}
# `result` is the expected response from the API
output:
# [[string]] is a specific matcher pattern that matches any string, useful for dynamic values e.g. OTP code
result: |
{
"state_token": "[[string]]",
"type": "login",
"name": "default",
"action": {
"type": "identify",
"data": {
"type": "identification_data",
"options": "[[array]]"
}
}
}
# Use `action: input` to input data and proceed to the next step
- name: Choose to login with username
action: input
input: |
{
"identification": "username",
"login_id": "e2e_login"
}
output:
result: |
{
"action": {
"type": "authenticate"
}
}
# You can also use `error` to check for expected errors
- name: Enter incorrect password
action: input
input: |
{
"authentication": "primary_password",
"password": "incorrect_password"
}
output:
# `error` is the expected error response from the API
error: |
{
"reason": "InvalidCredentials"
}
# Finally, use `type: finish` to finish the flow
- name: Enter correct password
action: input
input: |
{
"authentication": "primary_password",
"password": "correct_password"
}
output:
result: |
{
"action": {
"type": "finished"
}
}Before steps are used to create fixtures for the test. The following before steps are available:
type: user_import: Uses user import API to import users from a JSON filetype: customsql: Executes a custom SQL script
- type: user_import
user_import: users.jsonThe format of users.json is documented in user-import.md
- type: customsql
customsql:
file: ./custom.sqlcustom.sql is preprocessed as a Go template with the following variables:
{{ .AppID }}: The database URL
Each step is an API call to the Authflow API. The following steps are available:
action: create: Creates a flowaction: input: Inputs data and proceeds to the next stepaction: oauth_redirect: Redirects to an OAuth provideraction: query: Query from databaseaction: http_request: Sends an HTTP request with the shared cookie jar
The oauth_redirect action is used to redirect to an OAuth provider. The result is prev.result.code that can be used in the next step to finish the identification.
- name: Redirect to Google
action: oauth_redirect
input: |
{
"provider_id": "google"
}
- action: input
input: |
{
"code": "{{ .prev.result.code }}"
}
output:
result: |
{
"action": {
"type": "finished"
}
}The query action is used to query from database. The results can be accessed by prev in next step.
- action: query
query: |
SELECT id
FROM _auth_user
WHERE app_id = '{{ .AppID }}'
AND standard_attributes ->> 'preferred_username' = 'my_username';
query_output:
rows: |
[
{
"id": "[[string]]"
}
]
- action: input
input: |
{
"identification": "id_token",
"id_token": "{{ generateIDToken (index .prev.result.rows 0).id }}"
}The http_request action can drive Auth UI pages at the HTTP/form level. It uses the same cookie jar across steps, rewrites project hosts the same way as other e2e helpers, and behaves like a same-origin browser client for unsafe methods by auto-sending Origin and Sec-Fetch-Site when they are not provided explicitly.
Real webapp CSRF protection is enabled in e2e, so POST requests to Auth UI pages should use this helper instead of ad hoc clients.
- action: http_request
http_request_method: POST
http_request_url: "{{ .steps.get_page.result.http_final_url }}"
http_request_follow_redirects: false
http_request_form_urlencoded_body:
x_password: password
http_output:
http_status: 302http_request_follow_redirects defaults to true. Set it to false to validate the immediate response, such as a 302 redirect, instead of the final response after following redirects.
http_output supports:
http_statusredirect_pathjson_bodysaml_elementhtml_xpath_exists: requires each XPath to exist in the response HTMLhtml_text_contains: requires each substring to appear in the raw response body
Each http_request step result also includes:
http_response_headershttp_json_bodyhttp_final_urlsaml_relay_state
The output field is used to assert the response or error from the API.
output:
result: |
{
"state_token": "[[string]]",
"type": "login",
"name": "default",
"action": {
"type": "identify",
"data": {
"type": "identification_data",
"options": "[[array]]"
}
}
}
error: |
{
"reason": "InvalidCredentials"
}The following matchers are available for assertions:
[[string]]: Matches any string[[array]]: Matches any array[[object]]: Matches any object[[number]]: Matches any number[[boolean]]: Matches any boolean[[null]]: Matches null[[never]]: Disallows the field, useful for blacklisting fields in maps["[[arrayof]]", "[[object]]"]: Matches an array of objects, 2nd element can be any matcher or a specific value["[[string]]", "some_constant"]: Matches a tuple. Extra or missing elements are reported as errors
The test cases are validated against a JSON schema located at schema.json.
For example, in VSCode, you can use the YAML extension to enable schema validation.
{
"yaml.schemas": {
"./e2e/schema.json": "e2e/**/*test.yaml"
}
}