import { DebtService } from '../../src/modules/bot-debt/application/debt.service'; describe('DebtService summary/netting', () => { let service: DebtService; let prisma: any; let mockSync: any; beforeEach(async () => { prisma = { debtGroupMember: { findUnique: jest.fn().mockResolvedValue({}) }, debtPerson: { findMany: jest.fn() }, debtTransaction: { findMany: jest.fn() }, }; mockSync = { execute: jest.fn().mockResolvedValue({ id: 'u' }) }; service = new DebtService(prisma as any, mockSync as any); }); function peopleABC() { return [ { id: 'A', name: 'A' }, { id: 'B', name: 'B' }, { id: 'C', name: 'C' }, ]; } it('basic debt A->B 17000', async () => { prisma.debtPerson.findMany.mockResolvedValue(peopleABC()); prisma.debtTransaction.findMany.mockResolvedValue([ { fromPersonId: 'A', toPersonId: 'B', amount: BigInt(17000), type: 'DEBT' }, ]); const res = await service.getSummary('u', 'g'); expect(res).toEqual([{ from: 'A', to: 'B', amount: '17000' }]); }); it('accumulation A->B 17k + 10k = 27k', async () => { prisma.debtPerson.findMany.mockResolvedValue(peopleABC()); prisma.debtTransaction.findMany.mockResolvedValue([ { fromPersonId: 'A', toPersonId: 'B', amount: BigInt(17000), type: 'DEBT' }, { fromPersonId: 'A', toPersonId: 'B', amount: BigInt(10000), type: 'DEBT' }, ]); const res = await service.getSummary('u', 'g'); expect(res).toEqual([{ from: 'A', to: 'B', amount: '27000' }]); }); it('reverse debt A->B 27k B->A 5k = A->B 22k', async () => { prisma.debtPerson.findMany.mockResolvedValue(peopleABC()); prisma.debtTransaction.findMany.mockResolvedValue([ { fromPersonId: 'A', toPersonId: 'B', amount: BigInt(27000), type: 'DEBT' }, { fromPersonId: 'B', toPersonId: 'A', amount: BigInt(5000), type: 'DEBT' }, ]); const res = await service.getSummary('u', 'g'); expect(res).toEqual([{ from: 'A', to: 'B', amount: '22000' }]); }); it('partial payment A->B 50k debt, payment 20k => A->B 30k', async () => { prisma.debtPerson.findMany.mockResolvedValue(peopleABC()); prisma.debtTransaction.findMany.mockResolvedValue([ { fromPersonId: 'A', toPersonId: 'B', amount: BigInt(50000), type: 'DEBT' }, { fromPersonId: 'A', toPersonId: 'B', amount: BigInt(20000), type: 'PAYMENT' }, ]); const res = await service.getSummary('u', 'g'); expect(res).toEqual([{ from: 'A', to: 'B', amount: '30000' }]); }); it('overpayment A->B 50k debt, payment 60k => B->A 10k', async () => { prisma.debtPerson.findMany.mockResolvedValue(peopleABC()); prisma.debtTransaction.findMany.mockResolvedValue([ { fromPersonId: 'A', toPersonId: 'B', amount: BigInt(50000), type: 'DEBT' }, { fromPersonId: 'A', toPersonId: 'B', amount: BigInt(60000), type: 'PAYMENT' }, ]); const res = await service.getSummary('u', 'g'); expect(res).toEqual([{ from: 'B', to: 'A', amount: '10000' }]); }); it('cross substitution A->B 100k B->C 100k C->A 100k => empty', async () => { prisma.debtPerson.findMany.mockResolvedValue(peopleABC()); prisma.debtTransaction.findMany.mockResolvedValue([ { fromPersonId: 'A', toPersonId: 'B', amount: BigInt(100000), type: 'DEBT' }, { fromPersonId: 'B', toPersonId: 'C', amount: BigInt(100000), type: 'DEBT' }, { fromPersonId: 'C', toPersonId: 'A', amount: BigInt(100000), type: 'DEBT' }, ]); const res = await service.getSummary('u', 'g'); expect(res).toEqual([]); }); it('cross substitution partial A->B 100k B->C 50k => settlement equivalent', async () => { prisma.debtPerson.findMany.mockResolvedValue(peopleABC()); prisma.debtTransaction.findMany.mockResolvedValue([ { fromPersonId: 'A', toPersonId: 'B', amount: BigInt(100000), type: 'DEBT' }, { fromPersonId: 'B', toPersonId: 'C', amount: BigInt(50000), type: 'DEBT' }, ]); const res = await service.getSummary('u', 'g'); // possible valid settlements: A->B 50000, A->C 50000 or other equivalent expect(res.reduce((acc: any, cur: any) => acc + cur.amount, '')).toBeDefined(); expect(res.length).toBeGreaterThan(0); }); });