import { NotFoundException } from '@nestjs/common'; import { describe, expect, it, vi } from 'vitest'; // `forTenant` reicht den Klienten durch — Mandantenbindung selbst prueft // rls-access-inventory.spec.ts; hier zaehlt, dass je Methode (prisma, tenantId) // uebergeben wird. vi.mock('../prisma/prisma-tenant.extension', () => ({ forTenant: vi.fn((p: unknown) => p), })); import { forTenant } from '../prisma/prisma-tenant.extension'; import { CustomModulesService } from './custom-modules.service'; function makeFakePrisma() { const rows = new Map(); let seq = 0; const customModule = { create: vi.fn(async ({ data }: { data: any }) => { const id = `cm-${++seq}`; const row = { id, createdAt: new Date(), updatedAt: new Date(), ...data }; rows.set(id, row); return row; }), findMany: vi.fn(async ({ where, orderBy }: { where?: any; orderBy?: any } = {}) => { let list = [...rows.values()]; if (where?.tenantId) list = list.filter((r) => r.tenantId === where.tenantId); if (orderBy?.name === 'asc') list.sort((a, b) => a.name.localeCompare(b.name)); return list; }), findUnique: vi.fn(async ({ where }: { where: { id: string } }) => rows.get(where.id) ?? null), update: vi.fn(async ({ where, data }: { where: { id: string }; data: any }) => { const row = { ...rows.get(where.id), ...data }; rows.set(where.id, row); return row; }), delete: vi.fn(async ({ where }: { where: { id: string } }) => { rows.delete(where.id); }), }; return { customModule, rows }; } const dto = { name: 'Wiki', url: 'https://example.com', category: 'infrastructure' as const }; describe('CustomModulesService', () => { it('create speichert tenantId aus dem Argument, nie aus dem DTO', async () => { const prisma = makeFakePrisma(); const service = new CustomModulesService(prisma as any); await service.create('t1', { ...dto, tenantId: 'evil' } as any); expect(prisma.customModule.create).toHaveBeenCalledTimes(1); expect(prisma.customModule.create.mock.calls[0][0].data.tenantId).toBe('t1'); }); it('list liefert nur Zeilen des Mandanten, nach Name sortiert', async () => { const prisma = makeFakePrisma(); const service = new CustomModulesService(prisma as any); await service.create('t1', { ...dto, name: 'Zebra' }); await service.create('t1', { ...dto, name: 'Anker' }); await service.create('t2', { ...dto, name: 'Fremd' }); const result = await service.list('t1'); expect(result.map((r: any) => r.name)).toEqual(['Anker', 'Zebra']); expect(prisma.customModule.findMany.mock.calls[0]?.[0]?.where).toEqual({ tenantId: 't1' }); }); it('getOne liefert die Zeile ohne tenantId', async () => { const prisma = makeFakePrisma(); const service = new CustomModulesService(prisma as any); const created: any = await service.create('t1', dto); const row: any = await service.getOne('t1', created.id); expect(row.name).toBe('Wiki'); expect(row).not.toHaveProperty('tenantId'); }); it('getOne/update/remove mit unbekannter id -> NotFoundException', async () => { const service = new CustomModulesService(makeFakePrisma() as any); await expect(service.getOne('t1', 'nope')).rejects.toBeInstanceOf(NotFoundException); await expect(service.update('t1', 'nope', { name: 'x' })).rejects.toBeInstanceOf( NotFoundException, ); await expect(service.remove('t1', 'nope')).rejects.toBeInstanceOf(NotFoundException); }); it('getOne/update/remove mit Zeile eines anderen Mandanten -> NotFoundException', async () => { const prisma = makeFakePrisma(); const service = new CustomModulesService(prisma as any); const created: any = await service.create('t2', dto); await expect(service.getOne('t1', created.id)).rejects.toBeInstanceOf(NotFoundException); await expect(service.update('t1', created.id, { name: 'x' })).rejects.toBeInstanceOf( NotFoundException, ); await expect(service.remove('t1', created.id)).rejects.toBeInstanceOf(NotFoundException); expect(prisma.customModule.update).not.toHaveBeenCalled(); expect(prisma.customModule.delete).not.toHaveBeenCalled(); }); it('update aendert nur gesetzte Felder', async () => { const prisma = makeFakePrisma(); const service = new CustomModulesService(prisma as any); const created: any = await service.create('t1', dto); await service.update('t1', created.id, { name: 'Neu' }); expect(prisma.customModule.update.mock.calls[0][0].data).toEqual({ name: 'Neu' }); }); it('remove loescht und liefert { deleted: true }', async () => { const prisma = makeFakePrisma(); const service = new CustomModulesService(prisma as any); const created: any = await service.create('t1', dto); await expect(service.remove('t1', created.id)).resolves.toEqual({ deleted: true }); expect(prisma.rows.size).toBe(0); }); it('ruft forTenant je Methode mit (prisma, tenantId) auf', async () => { const prisma = makeFakePrisma(); const service = new CustomModulesService(prisma as any); vi.mocked(forTenant).mockClear(); const created: any = await service.create('t1', dto); await service.list('t1'); await service.getOne('t1', created.id); await service.update('t1', created.id, { name: 'a' }); await service.remove('t1', created.id); expect(forTenant).toHaveBeenCalledTimes(5); for (const call of vi.mocked(forTenant).mock.calls) { expect(call).toEqual([prisma, 't1']); } }); });