Testing
createTestRefreshFunctionIf you're testing a LumenizeClient-based application, you don't need test mode or testLoginWithMagicLink. Use createTestRefreshFunction from @lumenize/mesh instead — it mints JWTs locally with zero test infrastructure. Auth hooks verify them normally.
Test mode and testLoginWithMagicLink (documented below) are for testing @lumenize/auth endpoints themselves — magic link delivery, invite flows, delegation, and subject management.
Test Mode
Set LUMENIZE_AUTH_TEST_MODE to "true" to bypass email delivery and Turnstile validation. Append ?_test=true to get links directly in the response instead of sending emails. This works for both magic link and invite endpoints:
const response = await fetch('/auth/email-magic-link?_test=true', {
method: 'POST',
body: JSON.stringify({ email: 'test@example.com' })
});
// Returns: { message: "Magic link generated (test mode)", magic_link: "..." }
The invite endpoint supports the same pattern — POST {prefix}/invite?_test=true returns invite links in the response instead of sending emails. A dedicated test helper for invite flows may be added in a future release.
LUMENIZE_AUTH_TEST_MODE="true" must be set as an environment variable for test mode to work, but putting it in wrangler.jsonc risks accidentally deploying it — which would let anyone obtain magic links without email delivery. Instead, inject it via miniflare.bindings in your vitest.config.js, which only exists during test runs and is never deployed:
// vitest.config.js
import { defineConfig } from 'vitest/config';
import { cloudflareTest } from '@cloudflare/vitest-pool-workers';
export default defineConfig({
plugins: [
cloudflareTest({
wrangler: { configPath: './wrangler.jsonc' },
miniflare: {
bindings: { LUMENIZE_AUTH_TEST_MODE: 'true' },
},
}),
],
});
testLoginWithMagicLink
For testing with specific roles, use testLoginWithMagicLink. The browser parameter is required — it provides a cookie jar that preserves the refresh token across the multi-step login flow:
import { testLoginWithMagicLink } from '@lumenize/auth';
import { Browser } from '@lumenize/testing';
const browser = new Browser();
// Login as approved regular subject
const { accessToken, sub } = await testLoginWithMagicLink(browser, 'alice@test.com', {
subjectData: { adminApproved: true } // emailVerified is set automatically
});
// Login as admin
const adminBrowser = new Browser();
const { accessToken: adminToken } = await testLoginWithMagicLink(adminBrowser, 'admin@test.com', {
subjectData: { isAdmin: true } // Admins implicitly have adminApproved
});
// Login with delegation — alice is the principal, the actor acts on her behalf
const actorBrowser = new Browser();
const { accessToken: actorToken } = await testLoginWithMagicLink(actorBrowser, 'actor@test.com', {
subjectData: { adminApproved: true }
});
const aliceBrowser = new Browser();
const { accessToken: delegatedToken } = await testLoginWithMagicLink(aliceBrowser, 'alice@test.com', {
subjectData: { adminApproved: true },
actorAccessToken: actorToken // actor's access token for delegation
});
Delegation in Tests
The example above passes actorAccessToken directly — useful when you don't need to test the authorization setup. For integration tests that exercise the full flow, first authorize the actor via the Add Authorized Actor endpoint, then login the principal with the actor's access token:
import { testLoginWithMagicLink } from '@lumenize/auth';
import { Browser } from '@lumenize/testing';
const adminBrowser = new Browser();
const { accessToken: adminToken } = await testLoginWithMagicLink(adminBrowser, 'admin@test.com', {
subjectData: { isAdmin: true }
});
// Login the actor
const actorBrowser = new Browser();
const { accessToken: actorToken, sub: actorSub } = await testLoginWithMagicLink(
actorBrowser, 'actor@test.com', { subjectData: { adminApproved: true } }
);
// Authorize actorSub to act on behalf of targetSub
await fetch(`/auth/subject/${targetSub}/actors`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${adminToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ actorSub })
});
// Login alice with delegation — actor acts on her behalf
const aliceBrowser = new Browser();
const { accessToken, sub } = await testLoginWithMagicLink(aliceBrowser, 'alice@test.com', {
subjectData: { adminApproved: true },
actorAccessToken: actorToken // actor's access token for delegation
});
// sub = alice's sub, parseJwtUnsafe(accessToken).payload.act.sub = actorSub
Real Email Delivery in Tests
Most auth testing should use test mode — it bypasses email entirely and is deterministic, fast, and CI-safe. But if you specifically need to verify templates, domain configuration, or end-to-end delivery, you have two paths:
- Resend. Resend is a plain HTTPS API, so vitest-pool-workers' built-in
fetchhits it directly. No special configuration — the e2e test looks the same as any other integration test. - Cloudflare Email Sending. Miniflare's
send_emailbinding is a local stub by default — it accepts the call and writes to a temp file, but no email is actually sent. To exercise the real service from vitest, add"remote": trueto the binding:This proxies the binding call to the real Cloudflare service during test runs. Heads-up:"send_email": [
{ "name": "EMAIL", "remote": true }
]remote: truein CI means real sends (cost + credential wiring + potential flakiness when the proxy hiccups) — fine for local runs and pre-merge checks, worth considering carefully before enabling in your main CI pipeline.
Reference implementation: our own e2e suite at packages/auth/test/e2e-email/ exercises the full Cloudflare path end-to-end using this pattern.