diff --git a/apps/workers/src/app/__tests__/queue.integration.spec.ts b/apps/workers/src/app/__tests__/queue.integration.spec.ts index 07882ee2..4074fd12 100644 --- a/apps/workers/src/app/__tests__/queue.integration.spec.ts +++ b/apps/workers/src/app/__tests__/queue.integration.spec.ts @@ -2,25 +2,19 @@ // Keep these imports above the rest to avoid errors // ===================================================== import type { SharedType } from '@maybe-finance/shared' -import type { AccountsGetResponse, TransactionsGetResponse } from 'plaid' +import { TellerGenerator } from 'tools/generators' import type { AccountConnection, User } from '@prisma/client' -import { TestUtil } from '@maybe-finance/shared' -import { PlaidTestData } from '../../../../../tools/test-data' -import { Prisma } from '@prisma/client' import prisma from '../lib/prisma' -import { default as _plaid } from '../lib/plaid' -import nock from 'nock' -import { DateTime } from 'luxon' +import { default as _teller } from '../lib/teller' import { resetUser } from './helpers/user.test-helper' // Import the workers process import '../../main' -import { queueService, securityPricingService } from '../lib/di' - -jest.mock('plaid') +import { queueService } from '../lib/di' // For TypeScript support -const plaid = jest.mocked(_plaid) +jest.mock('../lib/teller') +const teller = jest.mocked(_teller) let user: User | null let connection: AccountConnection @@ -30,25 +24,6 @@ if (process.env.IS_VSCODE_DEBUG === 'true') { jest.setTimeout(100000) } -beforeAll(() => { - nock.disableNetConnect() - - nock('https://api.polygon.io') - .get((uri) => uri.includes('v2/aggs/ticker/AAPL/range/1/day')) - .reply(200, PlaidTestData.AAPL) - .persist() - - nock('https://api.polygon.io') - .get((uri) => uri.includes('v2/aggs/ticker/WMT/range/1/day')) - .reply(200, PlaidTestData.WMT) - .persist() - - nock('https://api.polygon.io') - .get((uri) => uri.includes('v2/aggs/ticker/VOO/range/1/day')) - .reply(200, PlaidTestData.VOO) - .persist() -}) - beforeEach(async () => { jest.clearAllMocks() @@ -57,10 +32,10 @@ beforeEach(async () => { connection = await prisma.accountConnection.create({ data: { name: 'Chase Test', - type: 'plaid' as SharedType.AccountConnectionType, - plaidItemId: 'test-plaid-item-workers', - plaidInstitutionId: 'ins_3', - plaidAccessToken: + type: 'teller' as SharedType.AccountConnectionType, + tellerEnrollmentId: 'test-teller-item-workers', + tellerInstitutionId: 'chase_test', + tellerAccessToken: 'U2FsdGVkX1+WMq9lfTS9Zkbgrn41+XT1hvSK5ain/udRPujzjVCAx/lyPG7EumVZA+nVKXPauGwI+d7GZgtqTA9R3iCZNusU6LFPnmFOCE4=', // need correct encoding here userId: user.id, syncStatus: 'PENDING', @@ -84,7 +59,7 @@ describe('Message queue tests', () => { it('Should handle sync errors', async () => { const syncQueue = queueService.getQueue('sync-account-connection') - plaid.accountsGet.mockRejectedValueOnce('forced error for Jest tests') + teller.getAccounts.mockRejectedValueOnce(new Error('forced error for Jest tests')) await syncQueue.add('sync-connection', { accountConnectionId: connection.id }) @@ -92,7 +67,7 @@ describe('Message queue tests', () => { where: { id: connection.id }, }) - expect(plaid.accountsGet).toHaveBeenCalledTimes(1) + expect(teller.getAccounts).toHaveBeenCalledTimes(1) expect(updatedConnection?.status).toEqual('ERROR') }) @@ -117,28 +92,23 @@ describe('Message queue tests', () => { const syncQueue = queueService.getQueue('sync-account-connection') // Mock will return a basic banking checking account - plaid.accountsGet.mockResolvedValueOnce( - TestUtil.axiosSuccess({ - accounts: [PlaidTestData.checkingAccount], - item: PlaidTestData.item, - request_id: 'bkVE1BHWMAZ9Rnr', - }) as any - ) + const mockAccounts = TellerGenerator.generateAccountsWithBalances({ + count: 1, + institutionId: 'chase_test', + enrollmentId: 'test-teller-item-workers', + institutionName: 'Chase Test', + accountType: 'depository', + accountSubType: 'checking', + }) + teller.getAccounts.mockResolvedValueOnce(mockAccounts) - plaid.transactionsGet.mockResolvedValueOnce( - TestUtil.axiosSuccess({ - accounts: [PlaidTestData.checkingAccount], - transactions: PlaidTestData.checkingTransactions, - item: PlaidTestData.item, - total_transactions: PlaidTestData.checkingTransactions.length, - request_id: '45QSn', - }) as any - ) + const mockTransactions = TellerGenerator.generateTransactions(10, mockAccounts[0].id) + teller.getTransactions.mockResolvedValueOnce(mockTransactions) await syncQueue.add('sync-connection', { accountConnectionId: connection.id }) - expect(plaid.accountsGet).toHaveBeenCalledTimes(1) - expect(plaid.transactionsGet).toHaveBeenCalledTimes(1) + expect(teller.getAccounts).toHaveBeenCalledTimes(1) + expect(teller.getTransactions).toHaveBeenCalledTimes(1) const item = await prisma.accountConnection.findUniqueOrThrow({ where: { id: connection.id }, @@ -146,7 +116,7 @@ describe('Message queue tests', () => { accounts: { include: { balances: { - where: PlaidTestData.testDates.prismaWhereFilter, + where: TellerGenerator.testDates.prismaWhereFilter, orderBy: { date: 'asc' }, }, transactions: true, @@ -161,62 +131,15 @@ describe('Message queue tests', () => { expect(item.accounts).toHaveLength(1) const [account] = item.accounts - - expect(account.transactions).toHaveLength(PlaidTestData.checkingTransactions.length) - expect(account.balances.map((b) => b.balance)).toEqual( - [ - 3630, - 5125, - 5125, - 5125, - 5125, - 5125, - 5125, - 5125, - 5125, - 5125, - 5115, - 5115, - 5115, - 5089.45, - 5089.45, - PlaidTestData.checkingAccount.balances.current!, - ].map((v) => new Prisma.Decimal(v)) + const transactionBalance = mockTransactions.reduce( + (acc, t) => acc + t.amount, + mockAccounts[0].balance.available ) + + expect(account.transactions).toHaveLength(10) + expect(account.balances.map((b) => b.balance)).toEqual(transactionBalance) expect(account.holdings).toHaveLength(0) expect(account.valuations).toHaveLength(0) expect(account.investmentTransactions).toHaveLength(0) }) - - it('Should sync valid security prices', async () => { - const security = await prisma.security.create({ - data: { - name: 'Walmart Inc.', - symbol: 'WMT', - cusip: '93114210310', - pricingLastSyncedAt: new Date(), - }, - }) - - await securityPricingService.sync(security) - - const prices = await prisma.securityPricing.findMany({ - where: { securityId: security.id }, - orderBy: { date: 'asc' }, - }) - - expect(prices).toHaveLength(PlaidTestData.WMT.results.length) - - expect( - prices.map((p) => ({ - date: DateTime.fromJSDate(p.date, { zone: 'utc' }).toISODate(), - price: p.priceClose.toNumber(), - })) - ).toEqual( - PlaidTestData.WMT.results.map((p) => ({ - date: DateTime.fromMillis(p.t, { zone: 'utc' }).toISODate(), - price: p.c, - })) - ) - }) }) diff --git a/tools/generators/tellerGenerator.ts b/tools/generators/tellerGenerator.ts index 8ee131b4..c3f414b1 100644 --- a/tools/generators/tellerGenerator.ts +++ b/tools/generators/tellerGenerator.ts @@ -1,5 +1,7 @@ import { faker } from '@faker-js/faker' import type { TellerTypes } from '../../libs/teller-api/src' +import type { Prisma } from '@prisma/client' +import { DateTime } from 'luxon' function generateSubType( type: TellerTypes.AccountTypes @@ -23,6 +25,8 @@ type GenerateAccountsParams = { enrollmentId: string institutionName: string institutionId: string + accountType?: TellerTypes.AccountTypes + accountSubType?: TellerTypes.DepositorySubtypes | TellerTypes.CreditSubtype } export function generateAccounts({ @@ -30,12 +34,15 @@ export function generateAccounts({ enrollmentId, institutionName, institutionId, + accountType, + accountSubType, }: GenerateAccountsParams) { const accounts: TellerTypes.Account[] = [] for (let i = 0; i < count; i++) { const accountId = faker.string.uuid() const lastFour = faker.finance.creditCardNumber().slice(-4) - const type: TellerTypes.AccountTypes = faker.helpers.arrayElement(['depository', 'credit']) + const type: TellerTypes.AccountTypes = + accountType ?? faker.helpers.arrayElement(['depository', 'credit']) let subType: TellerTypes.DepositorySubtypes | TellerTypes.CreditSubtype subType = generateSubType(type) @@ -99,6 +106,8 @@ type GenerateAccountsWithBalancesParams = { enrollmentId: string institutionName: string institutionId: string + accountType?: TellerTypes.AccountTypes + accountSubType?: TellerTypes.DepositorySubtypes | TellerTypes.CreditSubtype } export function generateAccountsWithBalances({ @@ -106,7 +115,9 @@ export function generateAccountsWithBalances({ enrollmentId, institutionName, institutionId, -}: GenerateAccountsWithBalancesParams): TellerTypes.AccountWithBalances[] { + accountType, + accountSubType, +}: GenerateAccountsWithBalancesParams): TellerTypes.GetAccountsResponse { const accountsWithBalances: TellerTypes.AccountWithBalances[] = [] for (let i = 0; i < count; i++) { const account = generateAccounts({ @@ -114,6 +125,8 @@ export function generateAccountsWithBalances({ enrollmentId, institutionName, institutionId, + accountType, + accountSubType, })[0] const balance = generateBalance(account.id) accountsWithBalances.push({ @@ -170,7 +183,10 @@ export function generateTransactions(count: number, accountId: string): TellerTy running_balance: null, description: faker.word.words({ count: { min: 3, max: 10 } }), id: transactionId, - date: faker.date.recent({ days: 30 }).toISOString().split('T')[0], // recent date in 'YYYY-MM-DD' format + date: faker.date + .between({ from: lowerBound.toJSDate(), to: now.toJSDate() }) + .toISOString() + .split('T')[0], // recent date in 'YYYY-MM-DD' format account_id: accountId, links: { account: `https://api.teller.io/accounts/${accountId}`, @@ -246,3 +262,19 @@ export function generateConnection(): GenerateConnectionsResponse { transactions, } } + +const now = DateTime.fromISO('2022-01-03', { zone: 'utc' }) + +const lowerBound = DateTime.fromISO('2021-12-01', { zone: 'utc' }) + +export const testDates = { + now, + lowerBound, + totalDays: now.diff(lowerBound, 'days').days, + prismaWhereFilter: { + date: { + gte: lowerBound.toJSDate(), + lte: now.toJSDate(), + }, + } as Prisma.AccountBalanceWhereInput, +}