/opt/mawid/apps/api/src/patients
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
});
});