import { Test, TestingModule } from '@nestjs/testing'; import { MyService } from './my.service'; import { PrismaService } from '../../prisma/prisma.service'; import { AuditService } from '../audit/audit.service'; import { NotFoundException, BadRequestException } from '@nestjs/common'; describe('MyService', () => { let service: MyService; const mockPrisma: any = { towerUser: { findFirst: jest.fn(), delete: jest.fn() }, consentRecord: { findMany: jest.fn(), findFirst: jest.fn(), update: jest.fn(), updateMany: jest.fn(), create: jest.fn(), deleteMany: jest.fn(), }, group: { findFirst: jest.fn() }, memberOptOut: { create: jest.fn(), deleteMany: jest.fn() }, towerSession: { deleteMany: jest.fn() }, $transaction: jest.fn(), }; const mockAudit = { log: jest.fn() }; beforeEach(async () => { jest.clearAllMocks(); mockPrisma.$transaction.mockImplementation((cb: (tx: typeof mockPrisma) => Promise) => cb(mockPrisma)); const module: TestingModule = await Test.createTestingModule({ providers: [ MyService, { provide: PrismaService, useValue: mockPrisma }, { provide: AuditService, useValue: mockAudit }, ], }).compile(); service = module.get(MyService); }); describe('getProfile', () => { it('returns member profile', async () => { mockPrisma.towerUser.findFirst.mockResolvedValue({ id: 'u-1', tenantId: 'tnt-1', jid: '1234@s.whatsapp.net', displayName: 'Alice', createdAt: new Date('2026-01-01T00:00:00Z'), }); const res = await service.getProfile('u-1', 'tnt-1'); expect(res.id).toBe('u-1'); expect(res.displayName).toBe('Alice'); }); it('throws when not found', async () => { mockPrisma.towerUser.findFirst.mockResolvedValue(null); await expect(service.getProfile('u-x', 'tnt-1')).rejects.toThrow(NotFoundException); }); }); describe('listGroups', () => { it('returns groups with consent metadata', async () => { mockPrisma.consentRecord.findMany.mockResolvedValue([ { group: { id: 'g-1', name: 'UP Parivar' }, tenantId: 'tnt-1', groupId: 'g-1', scopes: ['INGEST', 'DISPLAY'], retentionDays: 90, policyVersion: 'v1', status: 'GRANTED', effectiveAt: new Date('2026-01-01T00:00:00Z'), }, ]); const res = await service.listGroups('u-1', 'tnt-1'); expect(res).toHaveLength(1); expect(res[0].name).toBe('UP Parivar'); expect(res[0].scopes).toContain('INGEST'); }); }); describe('optOut', () => { it('throws when no matching consent', async () => { mockPrisma.consentRecord.findMany.mockResolvedValue([]); await expect(service.optOut('u-1', 'tnt-1', { groupId: 'g-1' })).rejects.toThrow(NotFoundException); }); it('revokes consent and creates MemberOptOut', async () => { mockPrisma.consentRecord.findMany.mockResolvedValue([ { id: 'c-1', groupId: 'g-1', scopes: ['INGEST', 'DISPLAY'] }, ]); const res = await service.optOut('u-1', 'tnt-1', { groupId: 'g-1', reason: 'SELF_PORTAL' }); expect(res.revoked).toBe(1); expect(mockPrisma.consentRecord.update).toHaveBeenCalledWith( expect.objectContaining({ where: { id: 'c-1' }, data: expect.objectContaining({ status: 'REVOKED' }), }), ); expect(mockPrisma.memberOptOut.create).toHaveBeenCalledWith( expect.objectContaining({ data: expect.objectContaining({ reason: 'SELF_PORTAL' }) }), ); expect(mockAudit.log).toHaveBeenCalledWith( expect.objectContaining({ action: 'MEMBER_OPT_OUT' }), ); }); }); describe('optIn', () => { it('rejects empty scopes', async () => { await expect( service.optIn('u-1', 'tnt-1', { groupId: 'g-1', scopes: [] }), ).rejects.toThrow(BadRequestException); }); it('creates a new consent record', async () => { mockPrisma.group.findFirst.mockResolvedValue({ id: 'g-1', tenantId: 'tnt-1' }); mockPrisma.towerUser.findFirst.mockResolvedValue({ id: 'u-1', tenantId: 'tnt-1' }); mockPrisma.consentRecord.findFirst.mockResolvedValue(null); mockPrisma.consentRecord.create.mockResolvedValue({ id: 'c-new' }); const res = await service.optIn('u-1', 'tnt-1', { groupId: 'g-1', scopes: ['INGEST'] }); expect(res.ok).toBe(true); expect(res.consentId).toBe('c-new'); }); }); describe('deleteAccount', () => { it('cascades deletes and writes audit', async () => { const res = await service.deleteAccount('u-1', 'tnt-1'); expect(res.ok).toBe(true); expect(mockPrisma.consentRecord.deleteMany).toHaveBeenCalled(); expect(mockPrisma.towerSession.deleteMany).toHaveBeenCalled(); expect(mockAudit.log).toHaveBeenCalledWith( expect.objectContaining({ action: 'MEMBER_DELETED', resourceId: 'u-1' }), ); }); }); });