@archal/vitest
v0.1.2
Published
Hosted Archal route-mode support for Vitest
Downloads
431
Readme
@archal/vitest
Hosted route-mode bootstrap for Vitest.
This package is hosted-only. It does not boot local twins. It configures Vitest to start an Archal hosted session, install route-mode request rewriting, and expose the resolved hosted runtime state to tests.
Two Usage Patterns
Pick whichever matches your existing Vitest setup.
1. Single vitest.config.ts (recommended for most projects)
import { defineConfig } from 'vitest/config';
import { archalVitestConfig } from '@archal/vitest';
export default defineConfig({
test: archalVitestConfig({
services: {
stripe: { mode: 'route', seed: 'small-business' },
github: { mode: 'route', seed: 'small-project' },
},
}),
});archalVitestConfig() returns a complete Vitest test config — wire it into defineConfig({ test: ... }) directly.
2. vitest.workspace.ts (for multi-project monorepos)
import { archalVitestProject } from '@archal/vitest';
export default [
archalVitestProject(
{
name: 'github-hosted-live',
services: {
github: { mode: 'route', seed: 'small-project' },
stripe: { mode: 'route' },
},
},
{
include: ['__tests__/hosted-live.test.ts'],
},
),
];archalVitestProject() returns a workspace project definition — use it inside the array exported from vitest.workspace.ts. Do not wrap it in defineConfig({ test: archalVitestProject(...) }) — that pattern silently drops the setup files and lets requests hit real SaaS APIs. Use archalVitestConfig() instead for the single-config pattern.
Defaults Applied Automatically
Both helpers apply the same defaults so a developer's first run works without extra tuning:
hookTimeout: 120_000andtestTimeout: 120_000— ECS Fargate cold-start for twins can take 30s+, which exceeds Vitest's 10s/5s defaults.exclude: ['**/node_modules/**', ...]— prevents Vitest from walking into bundled Archal tests when installed as an npm dependency.setupFiles: [<route-mode bootstrap>]— wires the hosted session provisioning and request interception.
Pass your own hookTimeout/testTimeout/exclude/include in the second argument to override these.
Reset Twin State Between Tests
By default, twin state accumulates across tests in a run. Use resetArchalTwins() in beforeEach to get a fresh world:
import { beforeEach } from 'vitest';
import { resetArchalTwins } from '@archal/vitest';
beforeEach(async () => {
await resetArchalTwins();
});This restores each twin to the post-seed state that was captured during session bootstrap — no re-provisioning, no cold start, just a clean state snapshot pushed back to each twin in parallel. Typical cost is a few hundred milliseconds.
resetArchalTwins() also drains the twins' pending webhook queues so each test starts with no leftover deliveries from the previous one.
Webhook Testing
Test your webhook handlers without needing a tunnel, public URL, or mock server. The hosted twin runs in AWS ECS and can't reach your localhost, so instead of the twin POSTing to you, your test pulls queued deliveries over the same session and invokes your handler directly.
import { waitForArchalWebhook, listArchalWebhooks, clearArchalWebhooks } from '@archal/vitest';
it('records subscription via webhook handler', async () => {
// 1. Register an endpoint so the twin knows to queue events
await stripe.webhookEndpoints.create({
url: 'http://test.local/wh',
enabled_events: ['customer.subscription.created'],
});
// 2. Fire the event via normal SDK calls
const customer = await stripe.customers.create({ email: '[email protected]' });
const sub = await stripe.subscriptions.create({ customer: customer.id, items: [...] });
// 3. Pull the queued delivery; default timeout 2000ms, default consume:true
const event = await waitForArchalWebhook('stripe', 'customer.subscription.created');
// 4. Invoke your handler with the exact payload the twin queued
await handleStripeWebhook(event.body, event.headers['Stripe-Signature'], secret);
});API:
waitForArchalWebhook(service, eventTypeOrMatcher, options?)— polls the twin, returns first match withintimeout(default 2000ms). Withconsume: true(default), drops the service's queue after matching so it can't be re-returned. Passconsume: falsefor sequence assertions.waitForArchalWebhook(service, { eventType, where, timeout, consume })— the full options form.whereis a predicate(delivery) => booleanapplied aftereventType.listArchalWebhooks(service)— one-shot snapshot of currently queued deliveries. Does not consume.clearArchalWebhooks(service)— manually drop the service's queue.
Supported services:
| Service | Webhook helper support |
|---|---|
| Stripe, GitHub, Slack | ✅ Full — signatures computed at queue time |
| Jira, Linear | ✅ Via history buffer — survives their inline flush() |
| Supabase | ❌ Database-triggered webhooks; test against real Postgres |
| Google Workspace | ❌ Uses GCP Pub/Sub, not webhooks |
Signature verification: delivery.body is the exact JSON string the twin would have POSTed; use it with delivery.headers[SIGNATURE_HEADER] and the endpoint's secret. Do NOT re-serialize delivery.payload — key order/spacing changes will break HMAC verification.
Parallel worker safety: per-worker twin state isolation (shipped alongside this helper) gives each vitest worker its own webhook queue automatically. Worker A cannot consume worker B's deliveries when both workers use isolation-enabled twins. Twins without isolation (Supabase, Google Workspace, Telegram, Ramp, Browser) still share a queue across workers — for those, set testIsolation: 'serial' if you depend on event ordering.
Slack caveat: Slack verifies signatures at the receiver (timestamp + body, not a sender-side header). delivery.headers from a Slack twin delivery won't include X-Slack-Signature; stub verification in tests or compute the header yourself.
Silence: the webhook queue-drain that runs inside resetArchalTwins() is best-effort and doesn't log.
Session Reuse Across Runs
The integration computes a stable session key from (projectName, services, seeds) so that repeated vitest run invocations with the same configuration reuse an already-provisioned hosted session. First run pays the 30s cold-start cost; subsequent runs finish in ~2s until the session's 30-minute idle TTL expires or the configuration changes.
Inspecting Resolved Runtime
Use getInstalledArchalVitestSession() inside a test to see what the backend actually resolved:
import { getInstalledArchalVitestSession } from '@archal/vitest';
const session = getInstalledArchalVitestSession();
console.log(session?.resolvedRuntime.resolvedServices);
console.log(session?.resolvedRuntime.resolvedSeeds);
console.log(session?.resolvedRuntime.manifestVersions);
console.log(session?.resolvedRuntime.capabilityVersion);
console.log(session?.resolvedRuntime.runtimeVersion);That makes seed resolution and backend/runtime drift visible in CI failures instead of hidden in client defaults.
The session object returned here is a redacted snapshot — it deliberately does not expose routed-request auth headers or other credentials. The route runtime injects auth internally when forwarding test requests to twins; userland code should never need the raw token.
Authentication
The integration needs an Archal token to provision twin sessions. In priority order:
ARCHAL_VITEST_TOKENenv varARCHAL_TOKENenv var- Stored credentials from
archal loginin~/.archal/credentials.json
When stored credentials are used, the adapter refreshes them automatically before hosted-session API calls and keeps the routed twin auth header current during long watch runs.
Credential Sandbox Under Tests
When running under Vitest, Jest, or node --test, the credential store automatically redirects to a per-worker sandbox in the OS temp directory. On first access it copies your real ~/.archal/credentials.json into the sandbox, so reads see valid credentials. Any writes stay isolated from your real home directory — tests cannot clobber your login.
Set ARCHAL_HOME explicitly to opt out of the sandbox and point the credential store at a specific directory.
Environment
ARCHAL_VITEST_TOKENorARCHAL_TOKENARCHAL_HOME— explicit credential directory (skips test sandbox)ARCHAL_VITEST_API_URL— override hosted API endpointARCHAL_VITEST_SESSION_READY_TIMEOUT_MS— override 5min default ready timeout
Packaging Status
The published artifact bundles its internal Archal runtime and auth helpers, so consumers do not need the Archal monorepo to install or use it.
