Test sign-in with test end users
Prompt:
Test my app's signed-in pages without me signing in.
Also works:
- "Sign a test user in to the development environment and check the account page."
- "Run the native app's sign-in, refresh, and sign-out with no person."
- "My local run needs a signed-in session to test
/me."
A test end user is an end user your tool creates on one of your application's realms. Its address is at the domain synthetic.turnzero.ai, which receives no mail. Your tool reads its sign-in code through a management action instead of a mailbox. It signs in through your application's own sign-in pages, so your application's code runs exactly as it does for a person.
What your tool does
- Reads your manifest. Where its
servicesarray declares noaccountsservice, it follows Add sign-in to your app first. - Picks the environment. Where the application has one environment, it asks you whether to call
create_environmentor to approveadmit_test_end_usersfor production (step 1). - Calls
create_test_end_userswith the labels you name, or labels it chooses, and reports each test end user's address and expiry. - Signs one in from a browser, or with
curland a cookie jar file. It reads the code withread_test_signin_codeand types it on the code page. - Keeps the session's value in a cookie jar file outside your project folder, never prints it, and deletes the file when the test ends.
- Tests the signed-in paths you ask for, then calls
delete_test_end_usersunless you ask to keep the test end users.
What you need
- An application whose manifest declares the
accountsservice, so each environment has a realm. - A development environment, or your approval of production's admission in the browser.
Before your AI starts
This section is for your AI tool: what it checks and gathers before it begins. You don't need to do these steps yourself.
- A connected, signed-in tool (Connect your tool), acting as the account that owns the application. No other account's credential reaches these actions, platform staff's included.
- For backend tests that need no live sign-in, the Account package's test double first (Test your application locally). Test end users are the live half: the sign-in pages, the session cookie, and the verification.
Steps
1. Choose the environment
A development realm accepts test end users from its creation. Most applications start with production alone, so call create_environment to add development (Turn on a development environment and promote), and test there.
To test on production, call admit_test_end_users with the application. It is a destructive action, so it needs a credential with the destructive class, creates a pending action, and waits for a person to approve it in the browser. No tool approves in your place.
Approving it means any credential of your account that reaches the application can sign a test end user in on production with no mailbox. Their sign-ins and verifications count as backend actions. The admission shows as test_end_users_admitted_at in read_realm, configure_realm, and export_account. withdraw_test_end_users ends it and removes every test end user of production.
An application whose manifest declares the workforce audience accepts no test end users in either environment, because only its tenant's users reach it. Test with a user of your own tenant.
2. Create the test end users
Call create_test_end_users with application, environment, and one to twenty labels. A label is a lowercase letter followed by lowercase letters, digits, and hyphens, three to forty characters. Each label becomes the verified address <label>@synthetic.turnzero.ai.
expires_in_days sets when the platform removes each new test end user, from 1 to 30; 7 when left out. The answer lists test_signin_page, and for each label its id, email, expires_at, and created.
Repeating the call is safe. A label an unexpired test end user already holds returns that user with created: false and creates nothing. A label whose test end user has expired is created again.
Test end users count as no user. They take no place in the development realm's ten end users, and the creation ceiling never counts them. read_realm returns their number as counts.test_users, and list_end_users marks each with test: true and its expires_at.
3. Sign one in from a browser
Open test_signin_page, which is /__account/signin/test on the environment's hostname. It shows the emailed-code form even where the realm offers only Google or other providers. Where the realm offers email, the ordinary sign-in page works too.
Enter the test end user's address. The code page appears, and the platform holds the code instead of sending it. Call read_test_signin_code with application, environment, and the user's end_user or email. Type the newest code on the code page.
The code lives ten minutes, allows five attempts, and confirms only in the browser that started the sign-in. Where several browsers started one, pass binding, the base64url SHA-256 of that browser's __Host-turnzero_cloud_signin_code cookie value, to read its code alone.
The confirmation opens a real session, as it does for any end user. GET /__account/me returns the user with the email route and the test address. No passkey is offered, and a test end user cannot register a passkey.
4. Sign one in with curl
A tool with no browser signs in with curl, keeping the cookies in a jar file. The jar holds a live session, so it goes in the system's temporary folder, never in your project folder, where it could be committed or uploaded. Send the application's own origin with each POST, because the sign-in pages accept same-origin requests alone.
HOST=https://<label>-dev.ai.host
JAR=$(mktemp)
curl -s -o /dev/null -c "$JAR" -b "$JAR" -H "Origin: $HOST" \
--data-urlencode "address=tester-one@synthetic.turnzero.ai" "$HOST/__account/email/start"
Read the code with read_test_signin_code, then confirm it with the same jar.
curl -s -o /dev/null -c "$JAR" -b "$JAR" -H "Origin: $HOST" \
--data-urlencode "code=<code>" "$HOST/__account/email/verify"
The jar now holds the session cookie __Host-turnzero_cloud_realm. Send later requests with -b "$JAR". Never print the jar or the cookie's value. When the test ends, delete the jar.
rm -f "$JAR"
5. Sign one in from a native app
A native app's sign-in opens the authorization endpoint in the system's browser sheet, as Sign in from a native app describes. The endpoint redirects to /__account/signin?authorization=<reference>. On a realm without email, open /__account/signin/test with the same authorization query instead.
Sign in there as in step 3. The sheet then returns to the app's redirect URI with a code, and the app's own code exchange, refresh, and revocation run unchanged.
6. Test a local run
A local run on your machine cannot open a session of its own: the browser never sends the realm's cookie to localhost. So sign in on the hosted realm the local run's development credential verifies against:
- Where the application has two environments, sign in on the development hostname,
<label>-dev.ai.host. - Where it has one, sign in on the production hostname after production's admission (step 1). A local run of such an application verifies production's sessions.
Sign in with curl as in step 4, then hand the session from the jar to the local run as the request's Cookie header, or as a bearer token where your backend reads one. The Account package's client finds no router header in a local run, so it verifies the session through the verification route under the development credential the run already holds. Test pages that need a browser on the development hostname instead.
7. Test the signed-in paths
Call your application's signed-in routes with the session. To test what a suspended user sees, call revoke_end_user with the test end user's id, and reinstate_end_user to restore it.
The sign-in limits apply to test end users as to anyone. A busy test suite can meet them: five code starts an hour for one address, and the realm's sign-in starts from one source address, thirty an hour by default. Reuse a session across tests, create more test end users, or raise limits.code_sends_per_hour or limits.signin_starts_per_hour with configure_realm.
8. Remove them
Call delete_test_end_users with application, environment, and either end_users, their identifiers, or all: true. It needs no approval, because a test end user holds no person's data.
Each removal ends the user's sessions on every router within about thirty seconds. It deletes the user's held codes and ends the sign-ins they could still confirm. A test end user also ends:
- within ten minutes after its
expires_at; - with
delete_end_user, or its own account deletion from the application; - with its realm, when you delete the environment or the application.
Expected result
create_test_end_users returns the realm, test_signin_page, and one entry per label with created: true. read_test_signin_code returns codes, newest first, each with code, issued_at, expires_at, binding, and attempts_remaining. After the confirmation, GET /__account/me returns the test end user, and list_end_users lists it with test: true. delete_test_end_users returns removed, the identifiers it removed.
Refusals
A refusal identifies the action, states the cause in its detail, and changes nothing.
| Refusal | Status | Cause | Remedy |
|---|---|---|---|
test_end_users_not_admitted |
409 | The production realm does not accept test end users, or the application declares the workforce audience. The detail names the remedy. |
Name the development environment, calling create_environment first where there is none, or approve admit_test_end_users. Under workforce, test with a user of your tenant. |
test_end_users_ceiling_reached |
409 | The call would pass twenty unexpired test end users on the realm, or fifty created by the application's realms within the hour. Nothing is created. | Remove test end users you no longer need, name fewer labels, or wait for the hour to pass. |
not_test_end_user |
409 | A label's address belongs to a real end user, or the named user is not an unexpired test end user of that realm. A deletion naming one is refused whole. | Use the test end users list_end_users marks test: true. Remove a real end user with delete_end_user. |
no_such_end_user |
404 | read_test_signin_code named an end_user no user of that realm has. |
Use an identifier create_test_end_users returned for the same environment. |
test_end_users_disabled |
403 | The platform's operator has switched test end users off. Reading, deleting, and withdrawing still work. | Ask the platform's operator, and meanwhile remove the test end users you no longer need. |
test_end_user_fixed |
403 | A sign-in tried to link another identity or a passkey to a test end user. | None: a test end user signs in by emailed code alone. |
fixture_address_reserved |
403 | A Google, Apple, or other provider sign-in with an address at synthetic.turnzero.ai reached your application. Nothing is created. |
Sign in with a real address, or sign a test end user in by emailed code. |
route_not_enabled |
400 | On a realm that does not offer email, an emailed-code start named an address outside synthetic.turnzero.ai. |
Use a test end user's address, or add email to the realm's sign_in_methods. |
signin_code_rate_limited |
429 | One address started more emailed-code sign-ins in the hour than the realm allows. | Reuse a session, create more test end users, or raise limits.code_sends_per_hour. |
signin_rate_limited |
429 | One source address started more sign-ins in the hour than the realm allows. | Reuse a session, or raise limits.signin_starts_per_hour. |
Related
- Manage end users lists, suspends, and deletes end users, and lists every realm setting.
- Add sign-in to your app turns on the accounts service and chooses the sign-in methods.
- Sign in from a native app runs a native app's sign-in, refresh, and revocation.
- Test your application locally tests the backend offline on the Account package's double.
- Verifying an end user explains how a backend verifies the session.
- create_test_end_users, read_test_signin_code, delete_test_end_users, admit_test_end_users, and withdraw_test_end_users in the generated reference give each action's arguments and result.