Skip to content

Commit

Permalink
Added endpoint to rerun bank transactions (#516)
Browse files Browse the repository at this point in the history
* added endpoint to rerun bank transaction sync

* fixed tests complaining for missing enviornment variables

* added tests and fixed logger
  • Loading branch information
quantum-grit authored Jul 15, 2023
1 parent 1660bd6 commit 360245f
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ import { ConfigService } from '@nestjs/config'
import { CampaignService } from '../campaign/campaign.service'
import { PersonService } from '../person/person.service'
import { VaultService } from '../vault/vault.service'
import { IrisTasks } from '../tasks/bank-import/import-transactions.task'
import { SchedulerRegistry } from '@nestjs/schedule'
import { EmailService } from '../email/email.service'
import { TemplateService } from '../email/template.service'

const bankTransactionsMock = [
{
Expand Down Expand Up @@ -103,9 +107,16 @@ const stripeMock = {
checkout: { sessions: { create: jest.fn() } },
}

// Mock the IrisTask check for environment variables
jest
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.spyOn(IrisTasks.prototype as any, 'checkForRequiredVariables')
.mockImplementation(() => true)

describe('BankTransactionsController', () => {
let controller: BankTransactionsController
let prismaService: PrismaService
let irisService: IrisTasks

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
Expand All @@ -129,10 +140,15 @@ describe('BankTransactionsController', () => {
CampaignService,
VaultService,
PersonService,
IrisTasks,
SchedulerRegistry,
EmailService,
TemplateService,
],
}).compile()

controller = module.get<BankTransactionsController>(BankTransactionsController)
irisService = module.get<IrisTasks>(IrisTasks)

prismaService = prismaMock
})
Expand Down Expand Up @@ -193,4 +209,16 @@ describe('BankTransactionsController', () => {
expect(prismaService.$transaction).toHaveBeenCalled()
})
})

describe('rerunBankSync ', () => {
it('should rerun bank transactions for specific dates', async () => {
jest.spyOn(irisService, 'importBankTransactionsTASK').mockImplementation()

await controller.rerunBankTransactionsForDate({
startDate: '2022-12-01',
endDate: '2022-12-04',
})
expect(irisService.importBankTransactionsTASK).toHaveBeenCalledTimes(4)
})
})
})
27 changes: 27 additions & 0 deletions apps/api/src/bank-transactions/bank-transactions.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import {
Body,
Controller,
Get,
Logger,
NotFoundException,
Param,
Post,
Put,
Query,
Res,
Expand Down Expand Up @@ -107,4 +109,29 @@ export class BankTransactionsController {
status: BankDonationStatus.reImported,
}
}

/** Manually rerun bank transactions for date interval */
@Post('/rerun-dates')
@Roles({
roles: [RealmViewSupporters.role, ViewSupporters.role],
mode: RoleMatchingMode.ANY,
})
async rerunBankTransactionsForDate(@Body() body: { startDate: string; endDate: string }) {
Logger.debug('rerunBankTransactionsForDate startDate: ', body.startDate)
if (!body.startDate) throw new BadRequestException('Missing startDate in Request')
if (!body.endDate) throw new BadRequestException('Missing endDate in Request')

const startDate = new Date(body.startDate.split('T')[0])
const endDate = new Date(body.endDate.split('T')[0])

//rerun transactions iterating from startDate to endDate
for (
const dateToCheck = startDate;
dateToCheck <= endDate;
dateToCheck.setDate(dateToCheck.getDate() + 1)
) {
Logger.debug('Getting transactions for date: ' + dateToCheck.toISOString().split('T')[0])
await this.bankTransactionsService.rerunBankTransactionsForDate(dateToCheck)
}
}
}
11 changes: 8 additions & 3 deletions apps/api/src/bank-transactions/bank-transactions.module.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { Module } from '@nestjs/common'
import { CampaignModule } from '../campaign/campaign.module'
import { DonationsModule } from '../donations/donations.module'
import { ExportService } from '../export/export.service'
import { PrismaService } from '../prisma/prisma.service'
import { BankTransactionsController } from './bank-transactions.controller'
import { BankTransactionsService } from './bank-transactions.service'
import { ConfigModule } from '@nestjs/config'
import { ExportModule } from '../export/export.module'
import { IrisTasks } from '../tasks/bank-import/import-transactions.task'
import { HttpModule } from '@nestjs/axios'
import { EmailService } from '../email/email.service'
import { TemplateService } from '../email/template.service'

@Module({
imports: [CampaignModule, DonationsModule],
imports: [CampaignModule, DonationsModule, ConfigModule, ExportModule, HttpModule],
controllers: [BankTransactionsController],
providers: [BankTransactionsService, PrismaService, ExportService],
providers: [BankTransactionsService, PrismaService, IrisTasks, EmailService, TemplateService], //TODO: Create Email module to not need to import each service
exports: [BankTransactionsService],
})
export class BankTransactionsModule {}
14 changes: 14 additions & 0 deletions apps/api/src/bank-transactions/bank-transactions.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,21 @@ import { MockPrismaService } from '../prisma/prisma-client.mock'
import { NotificationModule } from '../sockets/notifications/notification.module'
import { VaultService } from '../vault/vault.service'
import { BankTransactionsService } from './bank-transactions.service'
import { IrisTasks } from '../tasks/bank-import/import-transactions.task'
import { SchedulerRegistry } from '@nestjs/schedule'
import { EmailService } from '../email/email.service'
import { TemplateService } from '../email/template.service'

const stripeMock = {
checkout: { sessions: { create: jest.fn() } },
}

// Mock the IrisTask check for environment variables
jest
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.spyOn(IrisTasks.prototype as any, 'checkForRequiredVariables')
.mockImplementation(() => true)

describe('BankTransactionsService', () => {
let service: BankTransactionsService

Expand All @@ -39,6 +49,10 @@ describe('BankTransactionsService', () => {
CampaignService,
VaultService,
PersonService,
IrisTasks,
SchedulerRegistry,
EmailService,
TemplateService,
],
}).compile()

Expand Down
6 changes: 6 additions & 0 deletions apps/api/src/bank-transactions/bank-transactions.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ import { PrismaService } from '../prisma/prisma.service'
import { Response } from 'express'
import { CreateBankPaymentDto } from '../donations/dto/create-bank-payment.dto'
import { DonationsService } from '../donations/donations.service'
import { IrisTasks } from '../tasks/bank-import/import-transactions.task'

@Injectable()
export class BankTransactionsService {
constructor(
private prisma: PrismaService,
private exportService: ExportService,
private donationService: DonationsService,
private irisBankImport: IrisTasks,
) {}

/**
Expand Down Expand Up @@ -149,4 +151,8 @@ export class BankTransactionsService {
await this.donationService.createUpdateBankPayment(bankPayment)
})
}

async rerunBankTransactionsForDate(transactionsDate: Date) {
this.irisBankImport.importBankTransactionsTASK(transactionsDate)
}
}
21 changes: 12 additions & 9 deletions apps/api/src/tasks/bank-import/import-transactions.task.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,21 +266,23 @@ describe('ImportTransactionsTask', () => {
trx.creditDebitIndicator !== 'DEBIT',
)

const transactionsDate = new Date()

// Run task
await irisTasks.importBankTransactionsTASK()
await irisTasks.importBankTransactionsTASK(transactionsDate)

// 1. Should get IRIS iban account
expect(getIBANSpy).toHaveBeenCalled()
// 2. Should get IBAN transactions from IRIS
expect(getTrxSpy).toHaveBeenCalledWith(irisIBANAccountMock)
expect(getTrxSpy).toHaveBeenCalledWith(irisIBANAccountMock, transactionsDate)
// 3. Should check if transactions are up-to-date
expect(checkTrxsSpy).toHaveBeenCalledWith(mockIrisTransactions)
expect(checkTrxsSpy).toHaveBeenCalledWith(mockIrisTransactions, transactionsDate)
expect(prismaMock.bankTransaction.count).toHaveBeenCalledWith(
expect.objectContaining({
where: {
transactionDate: {
gte: new Date(DateTime.now().toFormat('yyyy-MM-dd')),
lte: new Date(DateTime.now().toFormat('yyyy-MM-dd')),
gte: new Date(transactionsDate.toISOString().split('T')[0]),
lte: new Date(transactionsDate.toISOString().split('T')[0]),
},
},
}),
Expand Down Expand Up @@ -446,15 +448,16 @@ describe('ImportTransactionsTask', () => {
jest.spyOn(prismaMock, '$transaction').mockResolvedValue('SUCCESS')
jest.spyOn(prismaMock.campaign, 'findMany').mockResolvedValue(mockDonatedCampaigns)

const transactionsDate = new Date()
// Run task
await irisTasks.importBankTransactionsTASK()
await irisTasks.importBankTransactionsTASK(transactionsDate)

// 1. Should get IRIS iban account
expect(getIBANSpy).toHaveBeenCalled()
// 2. Should get IBAN transactions from IRIS
expect(getTrxSpy).toHaveBeenCalledWith(irisIBANAccountMock)
expect(getTrxSpy).toHaveBeenCalledWith(irisIBANAccountMock, transactionsDate)
// 3. Should check if transactions are up-to-date
expect(checkTrxsSpy).toHaveBeenCalledWith(mockIrisTransactions)
expect(checkTrxsSpy).toHaveBeenCalledWith(mockIrisTransactions, transactionsDate)
// The rest of the flow should not have been executed
// 4. Should not be run
expect(prepareBankTrxSpy).not.toHaveBeenCalled()
Expand Down Expand Up @@ -484,7 +487,7 @@ describe('ImportTransactionsTask', () => {
)

// Run task
await irisTasks.importBankTransactionsTASK()
await irisTasks.importBankTransactionsTASK(new Date())

expect(prepareBankTrxSpy).not.toHaveBeenCalled()
})
Expand Down
27 changes: 16 additions & 11 deletions apps/api/src/tasks/bank-import/import-transactions.task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,14 @@ export class IrisTasks {
}
}

async importBankTransactionsTASK() {
async importBankTransactionsTASK(transactionsDate: Date) {
// De-register the task, so that it doesn't waste server resources
if (!this.canRun) {
this.deregisterTask('import-bank-transactions')
return
}

Logger.debug('RUNNING TASK - import-bank-transactions')
Logger.debug('RUNNING TASK - import-bank-transactions for date: ' + transactionsDate)

// 1. Get IRIS IBAN Account Info
let ibanAccount: IrisIbanAccountInfo
Expand All @@ -142,7 +142,7 @@ export class IrisTasks {
// 2. Get transactions from IRIS
let transactions: IrisTransactionInfo[]
try {
transactions = await this.getTransactions(ibanAccount)
transactions = await this.getTransactions(ibanAccount, transactionsDate)
// No transactions for the day yet
if (!transactions.length) return
} catch (e) {
Expand All @@ -151,7 +151,7 @@ export class IrisTasks {

// 3. Check if the cron should actually run
try {
const isUpToDate = await this.hasNewOrNonImportedTransactions(transactions)
const isUpToDate = await this.hasNewOrNonImportedTransactions(transactions, transactionsDate)

/**
Should we let it run every time, (giving it a chance to import some previously failed donation for example, because DB was down for 0.5 sec).
Expand Down Expand Up @@ -233,14 +233,16 @@ export class IrisTasks {
return account
}

private async getTransactions(ibanAccount: IrisIbanAccountInfo) {
private async getTransactions(ibanAccount: IrisIbanAccountInfo, transactionsDate: Date) {
const endpoint = this.config.get<string>('iris.transactionsEndPoint', '')

const today = DateTime.now().toFormat('yyyy-MM-dd')
const dateToCheck = transactionsDate.toISOString().split('T')[0]

Logger.debug('Getting transactions for date:' + dateToCheck)

const response = (
await this.httpService.axiosRef.get<GetIrisTransactionInfoResponse>(
endpoint + `/${ibanAccount.id}` + `?dateFrom=${today}&dateTo=${today}`,
endpoint + `/${ibanAccount.id}` + `?dateFrom=${dateToCheck}&dateTo=${dateToCheck}`,
{
headers: {
'x-user-hash': this.userHash,
Expand All @@ -254,14 +256,17 @@ export class IrisTasks {
}

// Checks to see if all transactions have been processed already
private async hasNewOrNonImportedTransactions(transactions: IrisTransactionInfo[]) {
const today = new Date(DateTime.now().toFormat('yyyy-MM-dd'))
private async hasNewOrNonImportedTransactions(
transactions: IrisTransactionInfo[],
transactionsDate: Date,
) {
const dateToCheck = new Date(transactionsDate.toISOString().split('T')[0])

const count = await this.prisma.bankTransaction.count({
where: {
transactionDate: {
gte: today,
lte: today,
gte: dateToCheck,
lte: dateToCheck,
},
},
})
Expand Down
2 changes: 1 addition & 1 deletion apps/api/src/tasks/tasks-initializer.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('ImportTransactionsTask', () => {
checkout: { sessions: { create: jest.fn() } },
}

// Mock this before instantiating service - else it failss
// Mock the IrisTask check for environment variables
jest
// eslint-disable-next-line @typescript-eslint/no-explicit-any
.spyOn(IrisTasks.prototype as any, 'checkForRequiredVariables')
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/tasks/tasks-initializer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common'
import { IrisTasks } from './bank-import/import-transactions.task'
import { ConfigService } from '@nestjs/config'
import { Cron, SchedulerRegistry } from '@nestjs/schedule'
import { DateTime } from 'luxon'

// Schedules all background tasks
@Injectable()
Expand Down Expand Up @@ -29,7 +30,7 @@ export class TasksInitializer {

const callback = async () => {
try {
await this.irisTasks.importBankTransactionsTASK()
await this.irisTasks.importBankTransactionsTASK(new Date())
} catch (e) {
Logger.error('An error occured while executing importBankTransactions \n', e)
}
Expand Down
5 changes: 4 additions & 1 deletion apps/api/src/tasks/tasks.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import { IrisTasks } from './bank-import/import-transactions.task'
import { EmailService } from '../email/email.service'
import { TemplateService } from '../email/template.service'
import { TasksInitializer } from './tasks-initializer.service'
import { ConfigModule } from '@nestjs/config'
import { ScheduleModule } from '@nestjs/schedule'

@Module({
imports: [HttpModule, DonationsModule],
imports: [HttpModule, DonationsModule, ConfigModule, ScheduleModule],
providers: [IrisTasks, PrismaService, EmailService, TemplateService, TasksInitializer],
exports: [IrisTasks],
})
export class TasksModule {}

0 comments on commit 360245f

Please sign in to comment.