// Unit test with `@voltro/testing` — no database, no running HTTP server. A // `defineRestRoute` carries its logic in a plain `handler(input, ctx)`, so the // test calls that handler with a `RestRouteContext` built over the SAME // mixin-wrapped `ctx.store` a route gets at runtime, and asserts the wire shape // it returns. The REST routing + guard + idempotency layers are dispatch-time // concerns (integration), not exercised here. Run with `voltro test` (vitest). import { describe, it, expect } from 'vitest' import { makeTestContext, mockStore } from '@voltro/testing' import { anonymousSubject } from '@voltro/protocol' import type { RestRouteContext } from '@voltro/protocol/rest' import { database } from '../database/schema' // registers products / actors / tenants import createProduct from '../routes/v1/products.create.route' // A REST handler receives `{ subject, headers, store }` (not the full rpc // AppContext). Build that shape over the real test store so `store.insert` // runs the same mixin/validation path it does in production. `products` carries // no tenant() mixin, so the subject identity is irrelevant to the insert. const restCtx = (store: unknown): RestRouteContext => ({ subject: anonymousSubject(null), headers: {}, store, }) describe('POST /v1/products', () => { it('inserts a product and returns the wire shape', async () => { const ctx = makeTestContext({ store: mockStore({ products: [] }) }) const product = await createProduct.handler( { body: { name: 'Widget', priceCents: 1999 } }, restCtx(ctx.store), ) expect(product.name).toBe('Widget') expect(product.priceCents).toBe(1999) expect(product.id).toMatch(/^prod_/) // handler-minted id // `createdAt` is normalised to an ISO-8601 string by the row→wire mapper. expect(typeof product.createdAt).toBe('string') expect(Number.isNaN(Date.parse(product.createdAt))).toBe(false) }) it('persists the row to the store (readable back through the descriptor)', async () => { const ctx = makeTestContext({ store: mockStore({ products: [] }) }) const product = await createProduct.handler( { body: { name: 'Gadget', priceCents: 500 } }, restCtx(ctx.store), ) const rows = await ctx.store.query(database.products.descriptor) expect(rows).toHaveLength(1) expect(rows[0]!['id']).toBe(product.id) expect(rows[0]!['name']).toBe('Gadget') }) })