// Unit test for `notes.create` — driven through `@voltro/testing`. // // The rate limit itself is enforced by the plugin's rpc INTERCEPTOR (a // dispatch-time middleware wired in app.config.ts), NOT by this executor — and // `invoke` reproduces only the input-Schema decode, not middleware (see its // header). So we test the executor's OWN observable behavior: the tenant-guarded // insert. `makeTestContext` hands the handler the SAME mixin-wrapped `ctx.store` // it gets in production, so tenant auto-scoping behaves as at runtime. // Run with `voltro test` (vitest). import { describe, it, expect } from 'vitest' import { makeTestContext, mockStore, invoke } from '@voltro/testing' import { database } from '../database/schema' // registers actors / tenants / notes import { createNote } from '../mutations/notes.create.mutation' import createNoteHandler from '../mutations/notes.create.mutation.server' import { TenantMismatch } from '@voltro/plugin-multitenancy/guard' const subject = { type: 'user', id: 'u1', tenantId: 'acme' } as const describe('notes.create', () => { it('inserts a note stamped with the caller tenant + a note_ TypeID', async () => { const ctx = makeTestContext({ subject, store: mockStore({ notes: [] }) }) const row = (await invoke( createNote, createNoteHandler, { tenantId: 'acme', title: 'Ship it', body: 'World' }, ctx, )) as { id: string; title: string; body: string; tenantId: string } expect(row.title).toBe('Ship it') expect(row.tenantId).toBe('acme') expect(row.id).toMatch(/^note_/) // The row is really in the store — the live tenant-scoped query sees it. const rows = await ctx.store.query(database.notes.descriptor) expect(rows).toHaveLength(1) }) it('rejects a cross-tenant write with a typed TenantMismatch — nothing written', async () => { const ctx = makeTestContext({ subject, store: mockStore({ notes: [] }) }) await expect( invoke(createNote, createNoteHandler, { tenantId: 'evil-corp', title: 'x', body: '' }, ctx), ).rejects.toBeInstanceOf(TenantMismatch) const rows = await ctx.store.select('notes').unscoped().all() expect(rows).toHaveLength(0) }) it('rejects an empty title at the input-schema decode (NonEmptyString)', async () => { const ctx = makeTestContext({ subject, store: mockStore({ notes: [] }) }) await expect( invoke(createNote, createNoteHandler, { tenantId: 'acme', title: '', body: '' }, ctx), ).rejects.toThrow() }) it('isolates tenants — a second tenant never sees the first tenant row', async () => { const ctx = makeTestContext({ subject: { type: 'user', id: 'u1', tenantId: 't1' }, store: mockStore({ notes: [] }), }) await invoke(createNote, createNoteHandler, { tenantId: 't1', title: 'secret', body: '' }, ctx) const seenByT2 = await ctx.withTenant('t2', (c) => c.store.query(database.notes.descriptor)) expect(seenByT2).toHaveLength(0) }) })