import { DEFAULT_WELCOME_MAIL_TEXTS } from '@tessera/shared'; import { beforeEach, describe, expect, it, vi } from 'vitest'; import { WelcomeMailTemplateService } from './welcome-mail-template.service'; /** * WelcomeMailTemplateService — Speicherung der eigenen Vorlage je Mandant. * * Festgenagelt: jeder Zugriff gebunden an den uebergebenen Mandanten * (`forTenant`), Standardtexte ohne Vorlage, Speichern als upsert auf * `tenantId`, Zuruecksetzen loescht die Vorlage. */ vi.mock('../prisma/prisma-tenant.extension', () => ({ forTenant: vi.fn((prisma: any, tenantId: string) => prisma.__bound(tenantId)), })); type Row = { tenantId: string; subject: string; heading: string; intro: string; loginHintDirectory?: string | null; loginHintLocal?: string | null; closing: string; updatedBy: string | null; updatedAt: Date; }; function makePrisma(rows: Row[] = []) { const log: { tenantId: string; model: string; method: string; args: any }[] = []; return { __log: log, __rows: rows, __bound(tenantId: string) { return { welcomeMailTemplate: { findUnique: vi.fn(async (args: any) => { log.push({ tenantId, model: 'welcomeMailTemplate', method: 'findUnique', args }); // RLS-Nachbildung: nur Zeilen des gebundenen Mandanten sichtbar return ( rows.find((r) => r.tenantId === args.where.tenantId && r.tenantId === tenantId) ?? null ); }), upsert: vi.fn(async (args: any) => { log.push({ tenantId, model: 'welcomeMailTemplate', method: 'upsert', args }); const existing = rows.find((r) => r.tenantId === args.where.tenantId); if (existing) Object.assign(existing, args.update, { updatedAt: new Date() }); else rows.push({ ...args.create, updatedAt: new Date() }); return {}; }), deleteMany: vi.fn(async (args: any) => { log.push({ tenantId, model: 'welcomeMailTemplate', method: 'deleteMany', args }); const before = rows.length; for (let i = rows.length - 1; i >= 0; i--) { if (rows[i].tenantId === args.where.tenantId) rows.splice(i, 1); } return { count: before - rows.length }; }), }, tenant: { findUnique: vi.fn(async (args: any) => { log.push({ tenantId, model: 'tenant', method: 'findUnique', args }); return args.where.id === 't1' ? { name: 'Beispiel GmbH' } : null; }), }, }; }, }; } const texts = { subject: 'S {{name}}', heading: 'H', intro: 'I', loginHintDirectory: 'LD {{benutzername}}', loginHintLocal: 'LL', closing: 'C', }; let prisma: ReturnType; let service: WelcomeMailTemplateService; beforeEach(() => { prisma = makePrisma(); service = new WelcomeMailTemplateService(prisma as any); }); describe('WelcomeMailTemplateService', () => { it('ohne Vorlage: Standardtexte, custom=false, getCustomTexts=null', async () => { const state = await service.getState('t1'); expect(state.custom).toBe(false); expect(state.texts).toEqual(DEFAULT_WELCOME_MAIL_TEXTS); expect(state.defaults).toEqual(DEFAULT_WELCOME_MAIL_TEXTS); expect(await service.getCustomTexts('t1')).toBeNull(); expect(prisma.__log.every((c) => c.tenantId === 't1')).toBe(true); }); it('speichern: upsert auf tenantId, gebunden, mit Bearbeiter; danach custom=true', async () => { const state = await service.save('t1', texts, 'ada'); const upsert = prisma.__log.find((c) => c.method === 'upsert'); expect(upsert?.tenantId).toBe('t1'); expect(upsert?.args.where).toEqual({ tenantId: 't1' }); expect(upsert?.args.create).toEqual({ tenantId: 't1', ...texts, updatedBy: 'ada' }); expect(state.custom).toBe(true); expect(state.texts).toEqual(texts); expect(state.updatedBy).toBe('ada'); expect(await service.getCustomTexts('t1')).toEqual(texts); }); it('aeltere Vorlage ohne Anmeldehinweise (NULL) → Standardtexte fuer diese Felder, Rest bleibt eigen', async () => { prisma.__rows.push({ tenantId: 't1', subject: 'Alt', heading: 'H', intro: 'I', loginHintDirectory: null, loginHintLocal: null, closing: 'C', updatedBy: null, updatedAt: new Date(), }); const state = await service.getState('t1'); expect(state.custom).toBe(true); expect(state.texts.subject).toBe('Alt'); expect(state.texts.loginHintDirectory).toBe(DEFAULT_WELCOME_MAIL_TEXTS.loginHintDirectory); expect(state.texts.loginHintLocal).toBe(DEFAULT_WELCOME_MAIL_TEXTS.loginHintLocal); }); it('Mandantenbindung: Vorlage von t1 ist fuer t2 nicht sichtbar', async () => { await service.save('t1', texts, 'ada'); expect(await service.getCustomTexts('t2')).toBeNull(); expect(prisma.__log.filter((c) => c.method === 'findUnique').at(-1)?.tenantId).toBe('t2'); }); it('zuruecksetzen: loescht gebunden, danach wieder Standard', async () => { await service.save('t1', texts, 'ada'); const state = await service.reset('t1'); const del = prisma.__log.find((c) => c.method === 'deleteMany'); expect(del?.tenantId).toBe('t1'); expect(del?.args.where).toEqual({ tenantId: 't1' }); expect(state.custom).toBe(false); expect(state.texts).toEqual(DEFAULT_WELCOME_MAIL_TEXTS); }); it('Firmenname gebunden gelesen; unbekannter Mandant → leer', async () => { expect(await service.getTenantName('t1')).toBe('Beispiel GmbH'); expect(await service.getTenantName('tx')).toBe(''); expect(prisma.__log.filter((c) => c.model === 'tenant').map((c) => c.tenantId)).toEqual([ 't1', 'tx', ]); }); });