@arcote.tech/arc-testing
v0.8.25
Published
In-process test harness for Arc — write use-case-shaped tests on bun:test with the same scope API the client uses
Downloads
2,495
Readme
@arcote.tech/arc-testing
In-process test harness for Arc. Write use-case-shaped tests on bun:test
using the same scope API the client uses — no port, no mocks of the framework,
full type inference from your context.
import { createHarness } from "@arcote.tech/arc-testing";
const h = await createHarness(myContext);
// declare current state
await h.seed.rows("plan", [{ _id: "plan_pro", price: 4900 }]);
// an authenticated actor — the SAME scope API as the app
const creator = h.scope(userToken, { projectId });
await creator.mutation().startCheckout({ projectId, planId: "plan_pro" });
const order = await creator.query().paymentOrder.findOne({ _id: orderId });
// a webhook / route, in-process (real route boundary, no port)
await h.route("payuWebhook", { orderId, status: "COMPLETED" });
// drain the async listener cascade deterministically (no setTimeout)
await h.settle();
// assert events + state with native bun:test expect
expect(h.events.ofType("subscriptionActivated")).toHaveLength(1);
expect(await creator.query().subscription.findOne({ _id: projectId }))
.toMatchObject({ status: "active" });API
| | |
|---|---|
| createHarness(context) | builds the harness (in-memory SQLite, declarative seeds run, listeners active) |
| h.scope(token, params) | an authenticated scope — .mutation() (= client useMutation), .query() (= client useQuery, awaited rows) |
| h.anonymous() | a scope with no identity (public caller) |
| h.seed.rows(store, rows) | raw insert into a view/aggregate store (like withSeed); rows need _id |
| h.seed.event(type, payload, { as }) | publish through real projection handlers (and listeners) |
| h.route(name, body, { headers, token }) | invoke a route/webhook in-process (real verification + protection gate) |
| h.settle() | resolve once the async listener cascade has fully drained |
| h.events | typed log: .ofType(name), .last(name), .count(name), .all() |
mutation() / query() are the exact client ScopeAPI accessors (core's
MutationExecutor / QueryAccessor) — element names, params, payloads and rows
are all inferred from your context. No new naming layer, no any.
Recipe: testing the real billing buy-subscription-card (platform)
Prerequisites (one-time, user-gated):
- Publish
@arcote.tech/arc,@arcote.tech/arc-host,@arcote.tech/arc-testingto npm. - Add
@arcote.tech/arc-testingtopackages/billingdevDependencies (npm, notlink:—link:breaks the Docker build).
PayU-free reaction test (avoids the external gateway by injecting the domain event, then asserting the real billing listener cascade — grant → activation):
import { describe, it, expect, beforeEach } from "bun:test";
import { createHarness, type Harness } from "@arcote.tech/arc-testing";
import { getBillingContext } from "@ndt/billing";
import { userToken } from "@ndt/auth";
describe("buy-subscription-card (billing reaction)", () => {
let h: Harness<ReturnType<typeof getBillingContext>>;
const accountId = "acc_test";
beforeEach(async () => { h = await createHarness(getBillingContext()); });
it("paid order → grant → subscription activated", async () => {
const user = h.scope(userToken, { accountId, role: "user" });
// inject the 'payment completed' domain event with the caller's identity,
// then let the real billingPaymentGrant → billingSubscriptionActivation chain run
await h.seed.event(
"paymentOrderCompleted",
{ _id: "order_1", userId: accountId, amountPLNGrosz: 4900, kind: "subscription" },
{ as: { tokenName: "user", params: { accountId, role: "user" } } },
);
await h.settle();
expect(h.events.ofType("subscriptionActivated")).toHaveLength(1);
const sub = await user.query().subscription.findOne({ _id: accountId });
expect(sub).toMatchObject({ /* active subscription shape */ });
});
});For the full checkout-through-webhook flow (startCheckoutCardAsUser →
payuWebhook), stub the PayU client the checkout/route modules call, then drive
it with creator.mutation().startCheckoutCardAsUser(...) and
h.route("payuWebhook", payuBody) exactly as the self-proof does
(tests/buy-subscription-like.test.ts).
