Skip to content

Commit 360245f

Browse files
authored
Added endpoint to rerun bank transactions (#516)
* added endpoint to rerun bank transaction sync * fixed tests complaining for missing enviornment variables * added tests and fixed logger
1 parent 1660bd6 commit 360245f

10 files changed

+118
-26
lines changed

apps/api/src/bank-transactions/bank-transactions.controller.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ import { ConfigService } from '@nestjs/config'
2020
import { CampaignService } from '../campaign/campaign.service'
2121
import { PersonService } from '../person/person.service'
2222
import { VaultService } from '../vault/vault.service'
23+
import { IrisTasks } from '../tasks/bank-import/import-transactions.task'
24+
import { SchedulerRegistry } from '@nestjs/schedule'
25+
import { EmailService } from '../email/email.service'
26+
import { TemplateService } from '../email/template.service'
2327

2428
const bankTransactionsMock = [
2529
{
@@ -103,9 +107,16 @@ const stripeMock = {
103107
checkout: { sessions: { create: jest.fn() } },
104108
}
105109

110+
// Mock the IrisTask check for environment variables
111+
jest
112+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
113+
.spyOn(IrisTasks.prototype as any, 'checkForRequiredVariables')
114+
.mockImplementation(() => true)
115+
106116
describe('BankTransactionsController', () => {
107117
let controller: BankTransactionsController
108118
let prismaService: PrismaService
119+
let irisService: IrisTasks
109120

110121
beforeEach(async () => {
111122
const module: TestingModule = await Test.createTestingModule({
@@ -129,10 +140,15 @@ describe('BankTransactionsController', () => {
129140
CampaignService,
130141
VaultService,
131142
PersonService,
143+
IrisTasks,
144+
SchedulerRegistry,
145+
EmailService,
146+
TemplateService,
132147
],
133148
}).compile()
134149

135150
controller = module.get<BankTransactionsController>(BankTransactionsController)
151+
irisService = module.get<IrisTasks>(IrisTasks)
136152

137153
prismaService = prismaMock
138154
})
@@ -193,4 +209,16 @@ describe('BankTransactionsController', () => {
193209
expect(prismaService.$transaction).toHaveBeenCalled()
194210
})
195211
})
212+
213+
describe('rerunBankSync ', () => {
214+
it('should rerun bank transactions for specific dates', async () => {
215+
jest.spyOn(irisService, 'importBankTransactionsTASK').mockImplementation()
216+
217+
await controller.rerunBankTransactionsForDate({
218+
startDate: '2022-12-01',
219+
endDate: '2022-12-04',
220+
})
221+
expect(irisService.importBankTransactionsTASK).toHaveBeenCalledTimes(4)
222+
})
223+
})
196224
})

apps/api/src/bank-transactions/bank-transactions.controller.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import {
44
Body,
55
Controller,
66
Get,
7+
Logger,
78
NotFoundException,
89
Param,
10+
Post,
911
Put,
1012
Query,
1113
Res,
@@ -107,4 +109,29 @@ export class BankTransactionsController {
107109
status: BankDonationStatus.reImported,
108110
}
109111
}
112+
113+
/** Manually rerun bank transactions for date interval */
114+
@Post('/rerun-dates')
115+
@Roles({
116+
roles: [RealmViewSupporters.role, ViewSupporters.role],
117+
mode: RoleMatchingMode.ANY,
118+
})
119+
async rerunBankTransactionsForDate(@Body() body: { startDate: string; endDate: string }) {
120+
Logger.debug('rerunBankTransactionsForDate startDate: ', body.startDate)
121+
if (!body.startDate) throw new BadRequestException('Missing startDate in Request')
122+
if (!body.endDate) throw new BadRequestException('Missing endDate in Request')
123+
124+
const startDate = new Date(body.startDate.split('T')[0])
125+
const endDate = new Date(body.endDate.split('T')[0])
126+
127+
//rerun transactions iterating from startDate to endDate
128+
for (
129+
const dateToCheck = startDate;
130+
dateToCheck <= endDate;
131+
dateToCheck.setDate(dateToCheck.getDate() + 1)
132+
) {
133+
Logger.debug('Getting transactions for date: ' + dateToCheck.toISOString().split('T')[0])
134+
await this.bankTransactionsService.rerunBankTransactionsForDate(dateToCheck)
135+
}
136+
}
110137
}
Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
import { Module } from '@nestjs/common'
22
import { CampaignModule } from '../campaign/campaign.module'
33
import { DonationsModule } from '../donations/donations.module'
4-
import { ExportService } from '../export/export.service'
54
import { PrismaService } from '../prisma/prisma.service'
65
import { BankTransactionsController } from './bank-transactions.controller'
76
import { BankTransactionsService } from './bank-transactions.service'
7+
import { ConfigModule } from '@nestjs/config'
8+
import { ExportModule } from '../export/export.module'
9+
import { IrisTasks } from '../tasks/bank-import/import-transactions.task'
10+
import { HttpModule } from '@nestjs/axios'
11+
import { EmailService } from '../email/email.service'
12+
import { TemplateService } from '../email/template.service'
813

914
@Module({
10-
imports: [CampaignModule, DonationsModule],
15+
imports: [CampaignModule, DonationsModule, ConfigModule, ExportModule, HttpModule],
1116
controllers: [BankTransactionsController],
12-
providers: [BankTransactionsService, PrismaService, ExportService],
17+
providers: [BankTransactionsService, PrismaService, IrisTasks, EmailService, TemplateService], //TODO: Create Email module to not need to import each service
1318
exports: [BankTransactionsService],
1419
})
1520
export class BankTransactionsModule {}

apps/api/src/bank-transactions/bank-transactions.service.spec.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,21 @@ import { MockPrismaService } from '../prisma/prisma-client.mock'
1010
import { NotificationModule } from '../sockets/notifications/notification.module'
1111
import { VaultService } from '../vault/vault.service'
1212
import { BankTransactionsService } from './bank-transactions.service'
13+
import { IrisTasks } from '../tasks/bank-import/import-transactions.task'
14+
import { SchedulerRegistry } from '@nestjs/schedule'
15+
import { EmailService } from '../email/email.service'
16+
import { TemplateService } from '../email/template.service'
1317

1418
const stripeMock = {
1519
checkout: { sessions: { create: jest.fn() } },
1620
}
1721

22+
// Mock the IrisTask check for environment variables
23+
jest
24+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
25+
.spyOn(IrisTasks.prototype as any, 'checkForRequiredVariables')
26+
.mockImplementation(() => true)
27+
1828
describe('BankTransactionsService', () => {
1929
let service: BankTransactionsService
2030

@@ -39,6 +49,10 @@ describe('BankTransactionsService', () => {
3949
CampaignService,
4050
VaultService,
4151
PersonService,
52+
IrisTasks,
53+
SchedulerRegistry,
54+
EmailService,
55+
TemplateService,
4256
],
4357
}).compile()
4458

apps/api/src/bank-transactions/bank-transactions.service.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@ import { PrismaService } from '../prisma/prisma.service'
1515
import { Response } from 'express'
1616
import { CreateBankPaymentDto } from '../donations/dto/create-bank-payment.dto'
1717
import { DonationsService } from '../donations/donations.service'
18+
import { IrisTasks } from '../tasks/bank-import/import-transactions.task'
1819

1920
@Injectable()
2021
export class BankTransactionsService {
2122
constructor(
2223
private prisma: PrismaService,
2324
private exportService: ExportService,
2425
private donationService: DonationsService,
26+
private irisBankImport: IrisTasks,
2527
) {}
2628

2729
/**
@@ -149,4 +151,8 @@ export class BankTransactionsService {
149151
await this.donationService.createUpdateBankPayment(bankPayment)
150152
})
151153
}
154+
155+
async rerunBankTransactionsForDate(transactionsDate: Date) {
156+
this.irisBankImport.importBankTransactionsTASK(transactionsDate)
157+
}
152158
}

apps/api/src/tasks/bank-import/import-transactions.task.spec.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -266,21 +266,23 @@ describe('ImportTransactionsTask', () => {
266266
trx.creditDebitIndicator !== 'DEBIT',
267267
)
268268

269+
const transactionsDate = new Date()
270+
269271
// Run task
270-
await irisTasks.importBankTransactionsTASK()
272+
await irisTasks.importBankTransactionsTASK(transactionsDate)
271273

272274
// 1. Should get IRIS iban account
273275
expect(getIBANSpy).toHaveBeenCalled()
274276
// 2. Should get IBAN transactions from IRIS
275-
expect(getTrxSpy).toHaveBeenCalledWith(irisIBANAccountMock)
277+
expect(getTrxSpy).toHaveBeenCalledWith(irisIBANAccountMock, transactionsDate)
276278
// 3. Should check if transactions are up-to-date
277-
expect(checkTrxsSpy).toHaveBeenCalledWith(mockIrisTransactions)
279+
expect(checkTrxsSpy).toHaveBeenCalledWith(mockIrisTransactions, transactionsDate)
278280
expect(prismaMock.bankTransaction.count).toHaveBeenCalledWith(
279281
expect.objectContaining({
280282
where: {
281283
transactionDate: {
282-
gte: new Date(DateTime.now().toFormat('yyyy-MM-dd')),
283-
lte: new Date(DateTime.now().toFormat('yyyy-MM-dd')),
284+
gte: new Date(transactionsDate.toISOString().split('T')[0]),
285+
lte: new Date(transactionsDate.toISOString().split('T')[0]),
284286
},
285287
},
286288
}),
@@ -446,15 +448,16 @@ describe('ImportTransactionsTask', () => {
446448
jest.spyOn(prismaMock, '$transaction').mockResolvedValue('SUCCESS')
447449
jest.spyOn(prismaMock.campaign, 'findMany').mockResolvedValue(mockDonatedCampaigns)
448450

451+
const transactionsDate = new Date()
449452
// Run task
450-
await irisTasks.importBankTransactionsTASK()
453+
await irisTasks.importBankTransactionsTASK(transactionsDate)
451454

452455
// 1. Should get IRIS iban account
453456
expect(getIBANSpy).toHaveBeenCalled()
454457
// 2. Should get IBAN transactions from IRIS
455-
expect(getTrxSpy).toHaveBeenCalledWith(irisIBANAccountMock)
458+
expect(getTrxSpy).toHaveBeenCalledWith(irisIBANAccountMock, transactionsDate)
456459
// 3. Should check if transactions are up-to-date
457-
expect(checkTrxsSpy).toHaveBeenCalledWith(mockIrisTransactions)
460+
expect(checkTrxsSpy).toHaveBeenCalledWith(mockIrisTransactions, transactionsDate)
458461
// The rest of the flow should not have been executed
459462
// 4. Should not be run
460463
expect(prepareBankTrxSpy).not.toHaveBeenCalled()
@@ -484,7 +487,7 @@ describe('ImportTransactionsTask', () => {
484487
)
485488

486489
// Run task
487-
await irisTasks.importBankTransactionsTASK()
490+
await irisTasks.importBankTransactionsTASK(new Date())
488491

489492
expect(prepareBankTrxSpy).not.toHaveBeenCalled()
490493
})

apps/api/src/tasks/bank-import/import-transactions.task.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -116,14 +116,14 @@ export class IrisTasks {
116116
}
117117
}
118118

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

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

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

152152
// 3. Check if the cron should actually run
153153
try {
154-
const isUpToDate = await this.hasNewOrNonImportedTransactions(transactions)
154+
const isUpToDate = await this.hasNewOrNonImportedTransactions(transactions, transactionsDate)
155155

156156
/**
157157
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).
@@ -233,14 +233,16 @@ export class IrisTasks {
233233
return account
234234
}
235235

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

239-
const today = DateTime.now().toFormat('yyyy-MM-dd')
239+
const dateToCheck = transactionsDate.toISOString().split('T')[0]
240+
241+
Logger.debug('Getting transactions for date:' + dateToCheck)
240242

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

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

260265
const count = await this.prisma.bankTransaction.count({
261266
where: {
262267
transactionDate: {
263-
gte: today,
264-
lte: today,
268+
gte: dateToCheck,
269+
lte: dateToCheck,
265270
},
266271
},
267272
})

apps/api/src/tasks/tasks-initializer.service.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ describe('ImportTransactionsTask', () => {
3030
checkout: { sessions: { create: jest.fn() } },
3131
}
3232

33-
// Mock this before instantiating service - else it failss
33+
// Mock the IrisTask check for environment variables
3434
jest
3535
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3636
.spyOn(IrisTasks.prototype as any, 'checkForRequiredVariables')

apps/api/src/tasks/tasks-initializer.service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Injectable, Logger } from '@nestjs/common'
22
import { IrisTasks } from './bank-import/import-transactions.task'
33
import { ConfigService } from '@nestjs/config'
44
import { Cron, SchedulerRegistry } from '@nestjs/schedule'
5+
import { DateTime } from 'luxon'
56

67
// Schedules all background tasks
78
@Injectable()
@@ -29,7 +30,7 @@ export class TasksInitializer {
2930

3031
const callback = async () => {
3132
try {
32-
await this.irisTasks.importBankTransactionsTASK()
33+
await this.irisTasks.importBankTransactionsTASK(new Date())
3334
} catch (e) {
3435
Logger.error('An error occured while executing importBankTransactions \n', e)
3536
}

apps/api/src/tasks/tasks.module.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ import { IrisTasks } from './bank-import/import-transactions.task'
66
import { EmailService } from '../email/email.service'
77
import { TemplateService } from '../email/template.service'
88
import { TasksInitializer } from './tasks-initializer.service'
9+
import { ConfigModule } from '@nestjs/config'
10+
import { ScheduleModule } from '@nestjs/schedule'
911

1012
@Module({
11-
imports: [HttpModule, DonationsModule],
13+
imports: [HttpModule, DonationsModule, ConfigModule, ScheduleModule],
1214
providers: [IrisTasks, PrismaService, EmailService, TemplateService, TasksInitializer],
15+
exports: [IrisTasks],
1316
})
1417
export class TasksModule {}

0 commit comments

Comments
 (0)