In development and tests
Magic links in jadpo dev
In jadpo dev, no email leaves your machine. It lands in the local inbox
(The dev inbox), and its link and code work
there. The link opens your frontend's page. An agent reads the token or
code with jadpo email links <id> or jadpo email show <id>, and posts
it to POST /auth/magic-link/confirm itself.
Magic links in tests
Most route tests don't sign in through a magic link. A request's as:
signs the test in as a user directly
(Acting as a user).
To test the sign-in itself, a route test asks for a link with
POST /auth/magic-link, reads the email from email.sent, and follows
its link or posts its code. It calls the generated routes with
request { }, as it calls your own. Each takes a built-in input, named
as the API record names it, which only a test, a fixture or a seed
builds:
| Route | input: |
Its fields | output |
|---|---|---|---|
POST /auth/magic-link |
MagicLinkAsk |
address: Email, return_to: Text?, locale: Locale? |
none |
POST /auth/magic-link/confirm |
MagicLinkConfirmation |
token: SignInToken?, address: Email?, code: SignInCode? |
MagicLinkConfirmed, its return_to: Text |
POST /auth/magic-link/redeem |
MagicLinkCode |
address: Email, code: SignInCode |
MagicLinkToken, its token: ApiToken |
- The redeem route is there only when you declare
api_token:. - Leave out any field that may be
none, as a request's body may. - Every request a test sends carries your app's own
Origin, as a browser's does, so the confirm route takes it. - The sign-in email lands in
email.sentwith its link's token insign_in_tokenand its code insign_in_code. Both arenoneon every other email, so a test never reads them out of the text. - Built anywhere but a test, a fixture or a seed, an input is
type.not_record.
test "an emailed link signs a new person in" { var asked: Response = request { method: HttpMethod.post path: "/auth/magic-link" input: MagicLinkAsk { address: "ada@notes.example" } } assert asked.status == 202 match list.first(email.sent) { none -> { assert list.count(email.sent) == 1 } some(sent) -> { assert sent.to == "ada@notes.example" var came_back: Response = request { method: HttpMethod.post path: "/auth/magic-link/confirm" input: MagicLinkConfirmation { token: sent.sign_in_token } } assert came_back.status == 200 } }}test "the emailed code signs a new person in" { var asked: Response = request { method: HttpMethod.post path: "/auth/magic-link" input: MagicLinkAsk { address: "ada@notes.example" } } assert asked.status == 202 match list.first(email.sent) { none -> { assert list.count(email.sent) == 1 } some(sent) -> { var came_back: Response = request { method: HttpMethod.post path: "/auth/magic-link/confirm" input: MagicLinkConfirmation { address: "ada@notes.example" code: sent.sign_in_code } } assert came_back.status == 200 } }}
To test on_first_sign_in:'s action, call it directly with a
FirstSignIn you build, giving every field. A subscriber's action takes a
sign-in event built the same way:
test "an unproved address is refused" { assert create_member(FirstSignIn { method: SignInMethod.magic_link address: "ada@example.com" address_verified: false subject: none name: none }) fails SignUpClosed}
Passkeys in jadpo dev
Browsers allow passkeys on http://localhost without TLS. So in
jadpo dev, a browser's own passkeys work against a browser_origin of
http://localhost:<port>. An agent uses the browser's virtual
authenticator, Chrome DevTools' WebAuthn panel or WebDriver's virtual
authenticator commands, rather than a real device.
Passkeys in tests
A route test has test_passkeys, a software authenticator that drives
the generated routes for real. Only tests can call it:
| Operation | Does |
|---|---|
test_passkeys.register(user: Principal.user, name: Text) -> TestPasskey |
Adds a passkey for user through POST /auth/passkey/register/options and POST /auth/passkey/register, signed in as as: signs in, and gives the device holding it |
test_passkeys.sign_in(passkey: TestPasskey) -> Response |
Signs in with it through the two sign-in routes and gives the second's Response |
Each TestPasskey is a device of its own holding one passkey, made by
the program running the tests, so only a test ever holds one. Called
anywhere else, test_passkeys is semantic.request_invalid.
If a route refuses register, or the first step of sign_in, the test
ends as a fault naming the route and its code, since the test meant to
have a passkey. A refusal of the sign-in itself comes back in its
Response, for the test to check.
test "a passkey signs Ada in" { var ada: Principal.user = member_called("ada") var phone: TestPasskey = test_passkeys.register(ada, "Phone") var response: Response = test_passkeys.sign_in(phone) assert response.status == 204}
Markdown for agents: /docs/in-development-and-tests.md.Built from Jadpo main at a707efb.