kundencenter/apps/api/test/tickets.test.ts

58 lines
4.7 KiB
TypeScript
Raw Normal View History

import { beforeAll, describe, expect, it } from 'vitest';
import type { FastifyInstance } from 'fastify';
import { buildApp } from '../src/server.js';
import { call, code, login, makeUser } from './helpers.js';
let app: FastifyInstance;
beforeAll(async () => { app = await buildApp(); await app.ready(); });
async function staff(email: string, role: string) {
await makeUser({ email, kind: 'staff', staffRole: role }); const { client } = await login(app, email);
const s = (await call(app, client, 'POST', '/auth/mfa/setup')).json(); await call(app, client, 'POST', '/auth/mfa/confirm', { code: code(s.secret) }); return client;
}
async function customer(admin: any, name: string, mail: string) {
const c = (await call(app, admin, 'POST', '/admin/customers', { type: 'business', name, owner: { email: mail, name } })).json();
await app.inject({ method: 'POST', url: '/v1/auth/invite/accept', payload: { token: new URL(c.inviteLink).searchParams.get('token'), password: 'passwort-kunde-123', repeat: 'passwort-kunde-123' } });
return { org: c.id as string, client: (await login(app, mail, 'passwort-kunde-123')).client };
}
describe('Support-Tickets', () => {
it('Kunde eröffnet, Personal antwortet, interne Notiz bleibt verborgen, Zuweisung, Schließen, Mandantentrennung', async () => {
const admin = await staff('tix-admin@example.com', 'admin'); const support = await staff('tix-support@example.com', 'support'); const accounting = await staff('tix-acc@example.com', 'accounting');
const A = await customer(admin, 'Firma A', 'tix-a@example.com'); const B = await customer(admin, 'Firma B', 'tix-b@example.com');
const create = await call(app, A.client, 'POST', '/tickets', { orgId: A.org, subject: 'Server langsam', body: 'Seit heute Morgen sehr langsam.' });
expect(create.statusCode).toBe(200); const id = create.json().id;
// Kunde kann keine hohe Priorität setzen und nicht für einen fremden Kunden
expect((await call(app, A.client, 'POST', '/tickets', { orgId: A.org, subject: 'Test', body: 'y', priority: 'urgent' })).statusCode).toBe(403);
expect((await call(app, A.client, 'POST', '/tickets', { orgId: B.org, subject: 'Test', body: 'y' })).statusCode).toBe(404);
expect((await call(app, accounting, 'PATCH', `/tickets/${id}`, { priority: 'high' })).statusCode).toBe(403); // Buchhaltung nur lesend
const get1 = (await call(app, support, 'GET', `/tickets/${id}`)).json(); expect(get1.status).toBe('pending_staff'); expect(get1.messages).toHaveLength(1);
expect((await call(app, support, 'POST', `/tickets/${id}/messages`, { body: 'Interne Notiz: Ticket klingt nach Netzwerkproblem', internalNote: true })).statusCode).toBe(200);
expect((await call(app, support, 'POST', `/tickets/${id}/messages`, { body: 'Wir prüfen das und melden uns.' })).statusCode).toBe(200);
expect((await call(app, admin, 'PATCH', `/tickets/${id}`, { assignedTo: null })).statusCode).toBe(200);
// Kunde sieht die interne Notiz nicht, aber die Antwort; Status ist jetzt "wartet auf Kunde"
const seenByCustomer = (await call(app, A.client, 'GET', `/tickets/${id}`)).json();
expect(seenByCustomer.status).toBe('pending_customer'); expect(seenByCustomer.messages).toHaveLength(2);
expect(seenByCustomer.messages.some((m: any) => m.body.includes('Interne Notiz'))).toBe(false);
expect((await call(app, B.client, 'GET', `/tickets/${id}`)).statusCode).toBe(404); // fremder Kunde
// Kunde antwortet erneut → wartet auf Personal
await call(app, A.client, 'POST', `/tickets/${id}/messages`, { body: 'Danke, bitte kurzfristig.' });
expect((await call(app, support, 'GET', `/tickets/${id}`)).json().status).toBe('pending_staff');
// Zuweisen, lösen, schließen
expect((await call(app, admin, 'PATCH', `/tickets/${id}`, { status: 'resolved' })).statusCode).toBe(200);
expect((await call(app, A.client, 'POST', `/tickets/${id}/close`)).statusCode).toBe(200);
expect((await call(app, A.client, 'POST', `/tickets/${id}/close`)).statusCode).toBe(409); // schon geschlossen
expect((await call(app, A.client, 'POST', `/tickets/${id}/messages`, { body: 'noch was' })).statusCode).toBe(409);
// Listen: Kunde sieht nur eigene, Personal sieht alle offenen
const openB = (await call(app, admin, 'POST', '/tickets', { orgId: B.org, subject: 'Frage', body: 'Wie funktioniert X?' })).json();
expect((await call(app, B.client, 'GET', '/tickets')).json()).toHaveLength(1);
const staffOpen = (await call(app, support, 'GET', '/tickets?status=open')).json(); expect(staffOpen.map((t: any) => t.id)).toContain(openB.id); expect(staffOpen.map((t: any) => t.id)).not.toContain(id);
expect((await call(app, support, 'GET', '/tickets?status=closed')).json().map((t: any) => t.id)).toEqual([id]);
});
});