/opt/mawid/apps/api/src/patients
NameSizeModeActions
patients.controller.ts14620644editdlrm
patients.gdpr.spec.ts41390644editdlrm
patients.module.ts2690644editdlrm
patients.service.ts42660644editdlrm
Edit: /opt/mawid/apps/api/src/patients/patients.gdpr.spec.ts (4139B)
/** * KVKK/GDPR endpoints (PROJECT_PLAN Phase 7 task 4): export returns everything * we hold; delete-on-request removes every row keyed to the patient. * Requires docker-compose postgres. */ import { PrismaService } from '../prisma/prisma.service'; import { PatientsService } from './patients.service'; process.env.DATABASE_URL ??= 'postgresql://mawid:mawid@localhost:5432/mawid'; describe('Patients GDPR', () => { const prisma = new PrismaService(); const service = new PatientsService(prisma); let clinicId: string; let patientId: string; let appointmentId: string; let conversationId: string; beforeAll(async () => { const clinic = await prisma.clinic.create({ data: { name: `gdpr-e2e-${Date.now()}`, phone: '+900000000094' }, }); clinicId = clinic.id; const svc = await prisma.service.create({ data: { clinicId, name: { ar: 'x', tr: 'x', en: 'x' }, durationMinutes: 30 }, }); const staff = await prisma.staff.create({ data: { clinicId, name: 'Dr. G' } }); const patient = await prisma.patient.create({ data: { clinicId, waPhone: `+90593${Date.now().toString().slice(-7)}`, name: 'GDPR Patient', notes: 'sensitive note', }, }); patientId = patient.id; const startsAt = new Date(Date.now() + 48 * 3600_000); const appointment = await prisma.appointment.create({ data: { clinicId, patientId, staffId: staff.id, serviceId: svc.id, startsAt, endsAt: new Date(startsAt.getTime() + 30 * 60_000), status: 'pending', source: 'whatsapp', }, }); appointmentId = appointment.id; await prisma.reminderJob.create({ data: { appointmentId, kind: 'h24', scheduledFor: new Date(startsAt.getTime() - 24 * 3600_000) }, }); const entry = await prisma.waitlistEntry.create({ data: { clinicId, patientId, serviceId: svc.id, preferredWindow: {} }, }); await prisma.slotHold.create({ data: { clinicId, waitlistEntryId: entry.id, staffId: staff.id, serviceId: svc.id, startsAt, endsAt: new Date(startsAt.getTime() + 30 * 60_000), expiresAt: new Date(Date.now() + 20 * 60_000), }, }); const conversation = await prisma.conversation.create({ data: { clinicId, patientId } }); conversationId = conversation.id; await prisma.message.create({ data: { conversationId, direction: 'inbound', type: 'text', body: 'merhaba gdpr' }, }); }); afterAll(async () => { await prisma.clinic.delete({ where: { id: clinicId } }).catch(() => {}); await prisma.$disconnect(); }); it('export returns the patient with appointments, waitlist and full transcripts', async () => { const data = await service.exportData(clinicId, patientId); expect(data.patient.name).toBe('GDPR Patient'); expect(data.patient.notes).toBe('sensitive note'); expect(data.appointments).toHaveLength(1); expect(data.waitlistEntries).toHaveLength(1); expect(data.conversations).toHaveLength(1); expect(data.conversations[0].messages[0].body).toBe('merhaba gdpr'); }); it('delete-on-request removes every row keyed to the patient and audits without PII', async () => { const result = await service.deleteData(clinicId, patientId, 'owner:test'); expect(result.deleted).toBe(true); expect(await prisma.patient.findUnique({ where: { id: patientId } })).toBeNull(); expect(await prisma.appointment.count({ where: { patientId } })).toBe(0); expect(await prisma.reminderJob.count({ where: { appointmentId } })).toBe(0); expect(await prisma.waitlistEntry.count({ where: { patientId } })).toBe(0); expect(await prisma.conversation.count({ where: { patientId } })).toBe(0); expect(await prisma.message.count({ where: { conversationId } })).toBe(0); const audit = await prisma.auditLog.findFirst({ where: { clinicId, action: 'patient:data_deleted' }, }); expect(audit).not.toBeNull(); expect(JSON.stringify(audit!.meta)).not.toContain('GDPR Patient'); // no PII in audit }); });