import { AutoSyncService } from "../src/transactions/auto-sync.service"; const createService = () => { const prisma = { account: { findMany: jest.fn(), }, }; const plaid = { syncTransactionsForUser: jest.fn(), }; return { service: new AutoSyncService(prisma as any, plaid as any), prisma, plaid }; }; describe("AutoSyncService", () => { const originalEnv = process.env; beforeEach(() => { process.env = { ...originalEnv, AUTO_SYNC_INTERVAL_MINUTES: "15", AUTO_SYNC_STALE_MINUTES: "15", AUTO_SYNC_LOOKBACK_DAYS: "7", AUTO_SYNC_MAX_USERS_PER_RUN: "25", }; }); afterEach(() => { process.env = originalEnv; jest.restoreAllMocks(); }); it("syncs only stale Plaid users selected by the query", async () => { const { service, prisma, plaid } = createService(); prisma.account.findMany.mockResolvedValue([{ userId: "user_1" }, { userId: "user_2" }]); plaid.syncTransactionsForUser.mockResolvedValue({ created: 3 }); const result = await service.runOnce(); expect(prisma.account.findMany).toHaveBeenCalledWith(expect.objectContaining({ where: expect.objectContaining({ isActive: true, plaidAccessToken: { not: null }, plaidAccountId: { not: null }, syncStatus: { not: "syncing" }, }), distinct: ["userId"], take: 25, })); expect(plaid.syncTransactionsForUser).toHaveBeenCalledTimes(2); expect(result).toEqual({ usersScanned: 2, usersSynced: 2, created: 6, failed: 0 }); }); it("skips overlapping runs", async () => { const { service, prisma, plaid } = createService(); prisma.account.findMany.mockResolvedValue([{ userId: "user_1" }]); let release!: () => void; plaid.syncTransactionsForUser.mockReturnValue(new Promise((resolve) => { release = () => resolve({ created: 1 }); })); const firstRun = service.runOnce(); const secondRun = await service.runOnce(); release(); await firstRun; expect(secondRun).toEqual({ skipped: true, reason: "already_running" }); expect(plaid.syncTransactionsForUser).toHaveBeenCalledTimes(1); }); });