@insureco/test-sdk
v0.1.0
Published
Mock clients and test utilities for Tawa platform services
Readme
@insureco/test-sdk
Mock clients for every Tawa platform service. Drop these into your unit tests so they never hit real infrastructure. Set TAWA_ENV=sandbox and the same test file runs against real sandbox services for integration testing.
Install
npm install --save-dev @insureco/test-sdkQuick start
import { mockRelay, mockWallet, mockSeptor, mockKoko, mockBioAuth } from '@insureco/test-sdk'
import { describe, it, beforeEach } from 'vitest'
describe('OrderService', () => {
const relay = mockRelay()
const wallet = mockWallet({ balance: 50000 })
const septor = mockSeptor()
beforeEach(() => {
relay.reset()
wallet.reset()
septor.reset()
})
it('charges gas and sends confirmation email', async () => {
const service = new OrderService({ relay, wallet, septor })
await service.placeOrder({ userId: 'u-1', amount: 99 })
wallet.expectDebit({ amount: 5 })
wallet.expectBalance(49995)
relay.expectEmailSent({ to: '[email protected]', subject: /order confirmed/i })
septor.expectEventEmitted('order.placed', { entityId: 'u-1' })
})
})Mock factories
mockRelay(options?)
Mock for @insureco/relay — email and SMS delivery.
const relay = mockRelay()
// Send email
await relay.sendEmail({
to: { email: '[email protected]', name: 'User' },
template: 'welcome',
data: { name: 'User' },
})
// Send SMS
await relay.sendSMS({
to: { phone: '+1234567890' },
content: { text: 'Your code is 1234' },
})Assertions:
| Method | Description |
|--------|-------------|
| expectEmailSent({ to?, subject? }) | At least one email matches. subject accepts string or RegExp. |
| expectSMSSent({ to?, text? }) | At least one SMS matches. text accepts string or RegExp. |
| expectNoEmailSent() | No emails were sent. |
| expectNoSMSSent() | No SMS were sent. |
Accessors: relay.sentEmails, relay.sentSMS
Options:
| Option | Default | Description |
|--------|---------|-------------|
| templates | ['welcome', 'invitation', 'password-reset', 'magic-link'] | Templates returned by listTemplates() |
mockWallet(options?)
Mock for @insureco/wallet — token balance, debits, credits, deploy gate.
const wallet = mockWallet({ balance: 50000 })
await wallet.debit('wallet-my-org', { amount: 100, reason: 'API call' })
wallet.expectBalance(49900)
await wallet.credit('wallet-my-org', { amount: 500, tokenType: 'purchased', reason: 'Top-up' })
wallet.expectBalance(50400)
// Deploy gate check
const reserve = await wallet.checkReserve('wallet-my-org', { podTier: 'nano' })
// reserve.sufficient === true (need 10800 tokens for 3 months of nano)Assertions:
| Method | Description |
|--------|-------------|
| expectDebit({ amount?, reason? }) | At least one debit matches. |
| expectCredit({ amount?, tokenType? }) | At least one credit matches. |
| expectBalance(n) | Current balance equals n. |
| expectNoDebit() | No debits occurred. |
Accessors: wallet.currentBalance, wallet.ledger
Options:
| Option | Default | Description |
|--------|---------|-------------|
| balance | 50000 | Starting token balance |
| tokenType | 'intro' | Default token type |
| walletId | 'wallet-mock-org' | Default wallet ID |
mockSeptor(options?)
Mock for @insureco/septor — immutable audit trail with hash chains.
const septor = mockSeptor({ namespace: 'my-service' })
await septor.emit('payment.processed', {
entityId: 'pay-123',
data: { amount: 100, currency: 'USD' },
metadata: { who: 'system', why: 'Auto-charge' },
})
septor.expectEventEmitted('payment.processed', { entityId: 'pay-123' })
septor.expectChainValid('pay-123')Assertions:
| Method | Description |
|--------|-------------|
| expectEventEmitted(type, { entityId?, data? }) | Event of given type was emitted matching filters. |
| expectNoEventsEmitted() | Nothing was emitted. |
| expectChainValid(entityId) | Hash chain for entity is intact. |
Accessors: septor.events
The mock maintains real hash chains — each event's previousHash links to the prior event's eventHash, and chainIndex increments per entity.
mockKoko(options?)
Mock for Koko service registry and namespace config.
const koko = mockKoko()
// Pre-configure a namespace
koko.setNamespace('my-org-sandbox', { transactionMultiplier: 0.1, rateLimit: 1000 })
// Register a service
await koko.register({
name: 'my-api',
namespace: 'my-org-sandbox',
url: 'http://my-api:3000',
routes: [{ path: '/api/users', methods: ['GET'], auth: 'required' }],
})
// Discover
const svc = await koko.discover('my-api')
koko.expectServiceRegistered('my-api')
koko.expectServiceDiscovered('my-api')Assertions:
| Method | Description |
|--------|-------------|
| expectServiceRegistered(name) | Service was registered via register(). |
| expectServiceDiscovered(name) | discover() was called for this service. |
Accessors: koko.registeredServices
Options:
| Option | Default | Description |
|--------|---------|-------------|
| services | [] | Pre-seeded services available for discovery |
mockBioAuth(options?)
Mock for @insureco/bio BioAuth — OAuth flows, token exchange, PKCE, introspection.
const auth = mockBioAuth({
user: { bioId: 'bio-user-1', email: '[email protected]', name: 'Alice', orgSlug: 'acme' },
})
// Authorization URL (PKCE)
const { url, state, codeVerifier } = auth.getAuthorizationUrl({
redirectUri: 'https://my-app.tawa.insureco.io/api/auth/callback',
scopes: ['openid', 'profile'],
})
// Exchange code for tokens
const tokens = await auth.exchangeCode('auth-code-123', codeVerifier, redirectUri)
auth.expectTokenExchanged({ code: 'auth-code-123' })
// Get user info
const user = await auth.getUserInfo(tokens.access_token)
// user.email === '[email protected]'
// Client credentials (service-to-service)
await auth.getClientCredentialsToken(['service:read'])
auth.expectClientCredentialsUsed({ scopes: ['service:read'] })
// Revocation + introspection
await auth.revokeToken(tokens.access_token)
const result = await auth.introspect(tokens.access_token)
// result.active === falseAssertions:
| Method | Description |
|--------|-------------|
| expectTokenExchanged({ code? }) | exchangeCode was called, optionally matching the auth code. |
| expectTokenRefreshed() | refreshToken was called. |
| expectTokenRevoked({ token? }) | A token was revoked. |
| expectClientCredentialsUsed({ scopes? }) | Client credentials flow was used. |
| expectIntrospected({ token? }) | introspect was called. |
Accessors: auth.issuedTokens, auth.revokedTokens
Mutation: auth.setUser({ name: 'New Name' }) — changes what getUserInfo returns.
mockBioAdmin(options?)
Mock for @insureco/bio BioAdmin — user management, departments, roles, OAuth clients.
const admin = mockBioAdmin({
users: [
{ bioId: 'bio-1', email: '[email protected]', name: 'Alice', roles: ['admin'], ... },
],
})
// User management
const alice = await admin.getUser('bio-1')
await admin.updateUser('bio-1', { name: 'Alice Smith' })
admin.expectUserUpdated('bio-1', { name: 'Alice Smith' })
// Create resources
await admin.createDepartment({ name: 'Engineering' })
await admin.createRole({ name: 'editor', permissions: ['read', 'write'] })
await admin.createClient({ name: 'my-app', redirectUris: ['https://app/callback'] })
admin.expectDepartmentCreated({ name: 'Engineering' })
admin.expectRoleCreated({ name: 'editor' })
admin.expectClientCreated({ name: 'my-app' })Assertions:
| Method | Description |
|--------|-------------|
| expectUserUpdated(bioId, data?) | User was updated, optionally matching fields. |
| expectDepartmentCreated({ name? }) | A department was created. |
| expectRoleCreated({ name? }) | A role was created. |
| expectClientCreated({ name? }) | An OAuth client was created. |
Accessors: admin.users, admin.departments, admin.roles, admin.oauthClients
Mutation: admin.addUser(user) — seed additional users at runtime.
Common assertions (all mocks)
Every mock inherits these from the shared tracker:
| Method | Description |
|--------|-------------|
| expectCalled(method, match?) | Assert method was called. match checks properties on the first arg. |
| expectNotCalled(method) | Assert method was never called. |
| getCalls(method?) | Returns CallRecord[] with { method, args, returnValue, timestamp }. |
| reset() | Clear all internal state and call history back to initial config. |
TAWA_ENV: unit tests vs integration tests
import { createClients } from '@insureco/test-sdk'
const { relay, wallet, septor, koko } = createClients()| TAWA_ENV | Behavior |
|------------|----------|
| (unset) or test | Returns mock clients. No network calls. |
| sandbox | Returns real SDK clients pointed at *.sandbox.tawa.insureco.io. Requires @insureco/relay, @insureco/wallet, @insureco/septor installed. |
| production | Returns real SDK clients pointed at *.tawa.insureco.io. |
This lets the same test file work as a fast unit test (default) or a real integration test:
# Unit tests (mocks)
npm test
# Integration tests (real sandbox)
TAWA_ENV=sandbox npm testDevelopment
npm install
npm test # vitest — 159 tests
npm run build # ESM + CJS dual output