Skip to content

Testing Strategy

How VelaPay tests its 15 repos. The strategy is layered — each tier catches a specific class of failure, runs at a specific cost, and runs at a specific cadence. Tests at the wrong tier (a unit test that wants a real network, or an E2E test that should be a unit test) waste time and miss bugs.

Test Ladder

TierWhat it assertsToolingWhen it runs
1. Rust unit (in-process VM)Single-instruction correctness; account-validation paths; PDA seed derivation; numeric edge casescargo test + litesvm crateEvery commit (CI), local cargo test
2. TS integration (in-process VM)Multi-instruction flows; SDK ↔ program contracts; cross-instruction invariantsbun test + litesvm (npm) + anchor-litesvmEvery commit, local bun test
3. App unit/componentDashboard / portal / checkout / admin internal logic, hooks, componentsbun test (built-in)Every commit
4. App E2E (browser)Critical user flows in a real browser; auth, plan creation, checkout, portal cancel, plan switchPlaywright 1.59.x (bunx playwright test)Pre-merge for PRs touching the surface; nightly full sweep
5. Webhook simulationVerifier signature handling; dispatch retry; DLQbun test over vela-webhook/test-fixtures/Every commit
6. Mail smokeEvery @velapay/mail template renders + dispatches via real CF Emailvela-mail-smoke workerPre-deploy of any release that touches vela-mail
7. Synthetic monitorFull subscribe → pull → cancel funnel against deployed devnet stagingvela-synthetic Cron WorkerEvery 15 minutes, continuously

A bug should be caught at the lowest tier where it can be reproduced. If it's caught at tier 7 (synthetic), it should have a regression test added at tier 1, 2, or 4.

Tier 1 — Rust Unit (LiteSVM)

Where: vela-protocol/programs/*/tests/*.rs and vela-protocol/tests/.

LiteSVM is an in-process Solana VM — orders of magnitude faster than solana-test-validator. Each test boots a fresh VM, deploys the program from the workspace artifact, and exercises one or a few instructions against it.

rust
#[test]
fn execute_pull_blocks_when_amount_exceeds_plan() {
    let mut svm = LiteSVM::new();
    // ... seed plan, mandate, accounts
    let ix = build_execute_pull(/* amount = plan.amount + 1 */);
    let err = svm.send_transaction(tx).expect_err("must fail");
    assert_matches!(err, TransactionError::InstructionError(_, _));
}

Use Tier 1 for: numeric edge cases (u64::MAX, off-by-one, accrued-rate overflow), account-validation guards, instruction discriminator dispatch, transfer-hook validation paths.

Tier 2 — TS Integration (anchor-litesvm)

Where: vela-protocol/tests/integration/*.test.ts, vela-sdk/tests/*.test.ts.

anchor-litesvm 0.2.1 provides a LitesvmProvider that drops into Anchor's TS client wherever you'd otherwise pass an AnchorProvider. The result: real Anchor IDL deserialization, real instruction byte construction, real account-state inspection — without booting a validator.

ts
import { LitesvmProvider } from "anchor-litesvm";
import { LiteSVM } from "litesvm";
import { Program } from "@coral-xyz/anchor";

const svm = new LiteSVM();
const provider = new LitesvmProvider(svm, wallet);
const program = new Program<VelaProtocol>(IDL, provider);

const tx = await program.methods.executePull(...).rpc();

Use Tier 2 for: SDK API contracts (does subscribe() produce the right instruction bytes?), multi-instruction sequences (subscribe → pull → cancel), accruedNow() arithmetic, error translation (translate.ts).

Tier 3 — App Unit / Component

Where: each surface's tests/ directory using bun:test.

Pure logic — no network, no real DOM beyond what the surface needs. Things like billing-amount formatters, date math, route guards, custom hooks, schema validators.

ts
import { test, expect } from "bun:test";
import { formatAmount } from "@vela/sdk/token";

test("formatAmount honors TokenConfig decimals", () => {
  expect(formatAmount(1_000_000n, { decimals: 6 })).toBe("1.000000");
});

Use Tier 3 for: hooks, formatters, route resolution, Zod schema fixtures, brand-token presence checks.

Tier 4 — App E2E (Playwright)

Where: vela-dashboard/tests/e2e/*.e2e.ts, similar in vela-portal, vela-checkout, vela-admin.

The dashboard ships per-phase Playwright configs (playwright.phase31.config.ts, playwright.phase50.config.ts, …) so a feature owner can run only their feature's E2Es during PR work and the full sweep happens nightly.

ts
import { test, expect } from "@playwright/test";

test("merchant can create a plan and see it appear", async ({ page }) => {
  await loginAsTestMerchant(page);
  await page.goto("/plans/new");
  await page.fill('[name=name]', "Pro");
  await page.fill('[name=amount]', "9.99");
  await page.click('text="Create plan"');
  await expect(page.locator("text=Pro")).toBeVisible();
});

Use Tier 4 for: auth flows (magic-link, SIWS, Google SSO), plan creation, checkout flow, portal cancel + switch-plan, webhook endpoint CRUD.

Don't put protocol invariants here — Tier 1/2 is faster and more deterministic for those.

Tier 5 — Webhook Simulation

Where: vela-webhook/tests/*.test.ts over test-fixtures/.

Pre-signed payloads exercise the verifier and dispatch logic without any network or real on-chain state. Catches signature regressions, schema-evolution mistakes, retry/backoff bugs.

Tier 6 — Mail Smoke

Where: vela-mail-smoke Worker.

A throwaway Worker dispatches every @velapay/mail template via real Cloudflare Email Service to a single test inbox. Deploy → curl each template → visually confirm → tear down. Run before any release that touches vela-mail.

Tier 7 — Synthetic Monitor

Where: vela-synthetic Cron Worker.

Every 15 minutes, against deployed devnet staging:

  1. Subscribe a synthetic subscriber to a synthetic plan.
  2. Wait for the next pull window.
  3. Execute a pull.
  4. Verify the resulting webhook event lands in D1.
  5. Cancel.

Failures page on-call with the typed error class name (PullTooEarlyError, MandateExpiredError, …) so triage is fast.

What to Run When

SituationCommand
Touched a Rust file in vela-protocolcargo test (and bun test if it touches IDL surface)
Touched a TS file in vela-sdkbun test in vela-sdk
Touched dashboard logicbun test && bun run test:e2e:phaseNN for the feature area
Touched vela-webhook schemasbun test in vela-webhook + dashboard worker tests
Touched vela-mail templatesbun test in vela-mail then deploy vela-mail-smoke and curl each template
Pre-release across all reposPer-repo test suite + nightly full Playwright sweep + pre-deploy mail smoke

Coverage Bars

There is no enforced numeric coverage threshold. The coverage bar is behavioral: every protocol invariant has a Tier 1 or 2 test, every typed error class has a triggering test, every E2E user flow that a merchant or subscriber would care about has a Tier 4 test.

Anti-patterns

  • Mocking vela-protocol in dashboard tests — this is what Tier 1/2 is for.
  • Testing UI in vela-protocol — use vela-demo for visual sanity.
  • Letting Playwright failures sit "flaky" — flake is real signal; chase the root cause.
  • Skipping mail smoke before a vela-mail release — Mailpit doesn't catch CF Email rejection.
  • Adding tests that require a deployed devnet — push them to Tier 7 (synthetic) instead.

Internal knowledge base for the Vela Labs workspace.