93 lines
3.3 KiB
TypeScript
93 lines
3.3 KiB
TypeScript
import { TransactionsService } from "../src/transactions/transactions.service";
|
|
import { createPrismaMock } from "./utils/mock-prisma";
|
|
|
|
const createService = () => {
|
|
const prisma = createPrismaMock();
|
|
const plaid = { syncTransactionsForUser: jest.fn() };
|
|
const service = new TransactionsService(prisma as any, plaid as any);
|
|
return { service, prisma, plaid };
|
|
};
|
|
|
|
describe("TransactionsService", () => {
|
|
it("calculates summary income/expense/net", async () => {
|
|
const { service, prisma } = createService();
|
|
prisma.transactionRaw.findMany.mockResolvedValue([
|
|
{ amount: -120.5, date: new Date("2025-01-01") },
|
|
{ amount: 40, date: new Date("2025-01-02") },
|
|
{ amount: -10, date: new Date("2025-01-03") }
|
|
]);
|
|
|
|
const result = await service.summary("user_1", "2025-01-01", "2025-01-31");
|
|
expect(result.total).toBe("-90.50");
|
|
expect(result.income).toBe("130.50");
|
|
expect(result.expense).toBe("40.00");
|
|
expect(result.net).toBe("90.50");
|
|
expect(result.count).toBe(3);
|
|
});
|
|
|
|
it("builds cashflow buckets for requested months", async () => {
|
|
const { service, prisma } = createService();
|
|
prisma.transactionRaw.findMany.mockResolvedValue([
|
|
{ amount: -200, date: new Date("2025-01-12") },
|
|
{ amount: 50, date: new Date("2025-02-03") }
|
|
]);
|
|
|
|
const result = await service.cashflow("user_1", 3);
|
|
expect(result).toHaveLength(3);
|
|
expect(result.some((row) => row.month.endsWith("-01"))).toBe(true);
|
|
});
|
|
|
|
it("returns merchant insights sorted by spend", async () => {
|
|
const { service, prisma } = createService();
|
|
prisma.transactionRaw.findMany.mockResolvedValue([
|
|
{ description: "Coffee Bar", amount: 12.5 },
|
|
{ description: "Coffee Bar", amount: 7.5 },
|
|
{ description: "Grocer", amount: 40 },
|
|
{ description: "Refund", amount: -10 }
|
|
]);
|
|
|
|
const result = await service.merchantInsights("user_1", 2);
|
|
expect(result[0].merchant).toBe("Grocer");
|
|
expect(result[0].total).toBe("40.00");
|
|
expect(result[1].merchant).toBe("Coffee Bar");
|
|
expect(result[1].count).toBe(2);
|
|
});
|
|
|
|
it("creates manual transaction and derived fields", async () => {
|
|
const { service, prisma } = createService();
|
|
prisma.account.findFirst.mockResolvedValue({ id: "acct_1", userId: "user_1" });
|
|
prisma.transactionRaw.create.mockResolvedValue({ id: "tx_1" });
|
|
prisma.transactionDerived.create.mockResolvedValue({ id: "derived_1" });
|
|
|
|
const result = await service.createManualTransaction({
|
|
userId: "user_1",
|
|
accountId: "acct_1",
|
|
date: "2025-01-15",
|
|
description: "Manual payment",
|
|
amount: 123.45,
|
|
category: "Operations",
|
|
note: "Test note",
|
|
hidden: false
|
|
});
|
|
|
|
expect(result).toEqual({ id: "tx_1" });
|
|
expect(prisma.transactionRaw.create).toHaveBeenCalled();
|
|
expect(prisma.transactionDerived.create).toHaveBeenCalled();
|
|
});
|
|
|
|
it("returns null when no account is available for manual transaction", async () => {
|
|
const { service, prisma } = createService();
|
|
prisma.account.findFirst.mockResolvedValue(null);
|
|
|
|
const result = await service.createManualTransaction({
|
|
userId: "user_1",
|
|
date: "2025-01-15",
|
|
description: "Manual payment",
|
|
amount: 10
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
expect(prisma.transactionRaw.create).not.toHaveBeenCalled();
|
|
});
|
|
});
|