import { NotFoundException } from "@nestjs/common"; import { AuthService } from "../src/auth/auth.service"; const model = (order: string[]) => ({ findMany: jest.fn(), findUnique: jest.fn(), create: jest.fn(), update: jest.fn(), updateMany: jest.fn(), upsert: jest.fn(), delete: jest.fn(async () => { order.push("user.delete"); }), deleteMany: jest.fn(async () => undefined), }); const createService = () => { const order: string[] = []; const tx = { account: model(order), transactionRaw: model(order), transactionDerived: model(order), rule: model(order), ruleExecution: model(order), exportLog: model(order), auditLog: model(order), googleConnection: model(order), emailVerificationToken: model(order), passwordResetToken: model(order), refreshToken: model(order), session: model(order), socialAccount: model(order), subscription: model(order), abuseEvent: model(order), taxReturn: model(order), taxDocument: model(order), user: model(order), }; tx.taxDocument.deleteMany.mockImplementation(async () => { order.push("taxDocument.deleteMany"); }); tx.ruleExecution.deleteMany.mockImplementation(async () => { order.push("ruleExecution.deleteMany"); }); tx.transactionDerived.deleteMany.mockImplementation(async () => { order.push("transactionDerived.deleteMany"); }); tx.transactionRaw.deleteMany.mockImplementation(async () => { order.push("transactionRaw.deleteMany"); }); tx.account.deleteMany.mockImplementation(async () => { order.push("account.deleteMany"); }); tx.rule.deleteMany.mockImplementation(async () => { order.push("rule.deleteMany"); }); const prisma = { user: { findUnique: jest.fn() }, $transaction: jest.fn((callback: (client: typeof tx) => Promise) => callback(tx)), }; const service = new AuthService( prisma as any, { sign: jest.fn(), verify: jest.fn() } as any, { sendVerificationEmail: jest.fn(), sendPasswordResetEmail: jest.fn() } as any, { decrypt: jest.fn() } as any, ); return { service, prisma, tx, order }; }; describe("AuthService account deletion", () => { it("deletes dependent personal data before deleting the user", async () => { const { service, prisma, tx, order } = createService(); prisma.user.findUnique.mockResolvedValue({ id: "user_1" }); tx.account.findMany.mockResolvedValue([{ id: "acct_1" }]); tx.transactionRaw.findMany.mockResolvedValue([{ id: "tx_1" }]); tx.rule.findMany.mockResolvedValue([{ id: "rule_1" }]); tx.taxReturn.findMany.mockResolvedValue([{ id: "return_1" }]); await expect(service.deleteAccount("user_1")).resolves.toEqual({ message: "Account and associated personal data deleted.", }); expect(tx.ruleExecution.deleteMany).toHaveBeenCalledWith({ where: { OR: [ { ruleId: { in: ["rule_1"] } }, { transactionId: { in: ["tx_1"] } }, ], }, }); expect(tx.user.delete).toHaveBeenCalledWith({ where: { id: "user_1" } }); expect(order.slice(0, 6)).toEqual([ "taxDocument.deleteMany", "ruleExecution.deleteMany", "transactionDerived.deleteMany", "transactionRaw.deleteMany", "account.deleteMany", "rule.deleteMany", ]); expect(order[order.length - 1]).toBe("user.delete"); }); it("throws when the user no longer exists", async () => { const { service, prisma } = createService(); prisma.user.findUnique.mockResolvedValue(null); await expect(service.deleteAccount("missing_user")).rejects.toBeInstanceOf(NotFoundException); expect(prisma.$transaction).not.toHaveBeenCalled(); }); }); describe("AuthService session-bound tokens", () => { it("creates a server-side session and binds tokens to it", async () => { const { service, prisma } = createService(); (prisma as any).session = { create: jest.fn().mockResolvedValue({ id: "session_1" }), }; (prisma as any).refreshToken = { create: jest.fn().mockResolvedValue({ id: "refresh_1" }), }; const jwt = (service as any).jwtService; jwt.sign.mockReturnValue("access_token"); const result = await service.issueTokensForUser("user_1", { ipAddress: "127.0.0.1", userAgent: "jest", }); expect(result).toEqual({ accessToken: "access_token", refreshToken: expect.any(String) }); expect((prisma as any).session.create).toHaveBeenCalledWith({ data: expect.objectContaining({ userId: "user_1", ipHash: expect.any(String), userAgentHash: expect.any(String), }), }); expect(jwt.sign).toHaveBeenCalledWith({ sub: "user_1", sid: "session_1" }); expect((prisma as any).refreshToken.create).toHaveBeenCalledWith({ data: expect.objectContaining({ userId: "user_1", sessionId: "session_1" }), }); }); });