// Unit test with `@voltro/testing` — no database, no running server, no search // backend. The `articles.create` executor is a tenant-guarded insert: on commit // the search plugin's ChangeEvent tap mirrors the row into the index, but that // tap is dispatch-time infrastructure (Typesense/Meili/Algolia in prod, the // in-memory backend in dev) and is NOT wired into the unit store — so we do not // fake it. We cover the DB write + the cross-tenant write guard that gates it, // and pin the descriptor's typed error. Run with `voltro test` (vitest). import { describe, it, expect } from 'vitest' import { makeTestContext, mockStore } from '@voltro/testing' import { TenantMismatch } from '@voltro/plugin-multitenancy/guard' import { database } from '../database/schema' // registers articles / actors / tenants import { createArticle } from '../mutations/articles.create.mutation' import createArticleHandler from '../mutations/articles.create.mutation.server' describe('articles.create', () => { it('inserts an article for the caller\'s own tenant', async () => { const ctx = makeTestContext({ subject: { type: 'user', id: 'user_1', tenantId: 'acme' }, store: mockStore({ articles: [] }), }) const row = await createArticleHandler( { tenantId: 'acme', title: 'Hello', body: 'World', tag: 'news' }, ctx, ) expect(row['title']).toBe('Hello') expect(row['tag']).toBe('news') expect(row['tenantId']).toBe('acme') expect(row['id'] as string).toMatch(/^art_/) }) it('rejects a cross-tenant write with a typed TenantMismatch (the spoof guard)', async () => { const ctx = makeTestContext({ subject: { type: 'user', id: 'user_1', tenantId: 'acme' }, store: mockStore({ articles: [] }), }) // Input claims a DIFFERENT tenant than the subject → assertOwnTenant throws. // The executor is async, so the throw surfaces as a rejected promise. await expect( createArticleHandler( { tenantId: 'evil', title: 'x', body: '', tag: '' }, ctx, ), ).rejects.toThrow(TenantMismatch) // Nothing was written. const rows = await ctx.store.query(database.articles.descriptor) expect(rows).toHaveLength(0) }) it('isolates tenants — t2 never sees t1 articles', async () => { const ctx = makeTestContext({ subject: { type: 'user', id: 'u1', tenantId: 't1' }, store: mockStore({ articles: [] }), }) await createArticleHandler({ tenantId: 't1', title: 'secret', body: '', tag: '' }, ctx) const seenByT2 = await ctx.withTenant('t2', (c) => c.store.query(database.articles.descriptor)) expect(seenByT2).toHaveLength(0) }) it('pins the descriptor error contract — articles.create declares TenantMismatch', () => { // The descriptor is value-imported into the browser rpcGroup, so the client // decodes this typed failure without a manual error union. expect(createArticle.name).toBe('articles.create') expect(createArticle.error).toBe(TenantMismatch) }) })