-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add a separate endpoint for tx linking
- Loading branch information
Showing
17 changed files
with
420 additions
and
113 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 2 additions & 0 deletions
2
src/controllers/transactions.controller/transfer-linking/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './link-transactions'; | ||
export * from './unlink-transfer-transactions'; |
185 changes: 185 additions & 0 deletions
185
src/controllers/transactions.controller/transfer-linking/link-transactions.e2e.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
import { TRANSACTION_TRANSFER_NATURE, TRANSACTION_TYPES } from 'shared-types'; | ||
import * as helpers from '@tests/helpers'; | ||
import { ERROR_CODES } from '@js/errors'; | ||
|
||
describe('link transactions between each other', () => { | ||
it('link two valid transactions', async () => { | ||
// Create 2 income and 2 expense to check that multiple updation is possible | ||
const accountA = await helpers.createAccount({ raw: true }); | ||
const accountB = await helpers.createAccount({ raw: true }); | ||
|
||
const [incomeA] = await helpers.createTransaction({ | ||
payload: helpers.buildTransactionPayload({ | ||
accountId: accountA.id, | ||
transactionType: TRANSACTION_TYPES.income, | ||
}), | ||
raw: true, | ||
}); | ||
const [incomeB] = await helpers.createTransaction({ | ||
payload: helpers.buildTransactionPayload({ | ||
accountId: accountB.id, | ||
transactionType: TRANSACTION_TYPES.income, | ||
}), | ||
raw: true, | ||
}); | ||
const [expenseA] = await helpers.createTransaction({ | ||
payload: helpers.buildTransactionPayload({ | ||
accountId: accountA.id, | ||
transactionType: TRANSACTION_TYPES.expense, | ||
}), | ||
raw: true, | ||
}); | ||
const [expenseB] = await helpers.createTransaction({ | ||
payload: helpers.buildTransactionPayload({ | ||
accountId: accountB.id, | ||
transactionType: TRANSACTION_TYPES.expense, | ||
}), | ||
raw: true, | ||
}); | ||
|
||
const linkingResult = await helpers.linkTransactions({ | ||
payload: { | ||
ids: [ | ||
[incomeA.id, expenseB.id], | ||
[incomeB.id, expenseA.id], | ||
], | ||
}, | ||
raw: true, | ||
}); | ||
|
||
// Check that linkind response is coorect | ||
[incomeA, incomeB, expenseA, expenseB].forEach((tx) => { | ||
const txAfter = linkingResult.flat().find((t) => t.id === tx.id); | ||
// Expect that only transferNature and transferId were changed | ||
expect({ ...tx }).toEqual({ | ||
...txAfter, | ||
transferNature: expect.toBeAnythingOrNull(), | ||
transferId: expect.toBeAnythingOrNull(), | ||
}); | ||
|
||
expect(txAfter.transferNature).toBe( | ||
TRANSACTION_TRANSFER_NATURE.common_transfer, | ||
); | ||
expect(txAfter.transferId).toEqual(expect.any(String)); | ||
}); | ||
|
||
// Check that transactions fetching also returns correct result | ||
const txsAfterUpdation = await helpers.getTransactions({ raw: true }); | ||
[incomeA, incomeB, expenseA, expenseB].forEach((tx) => { | ||
const txAfter = txsAfterUpdation.find((t) => t.id === tx.id); | ||
// Expect that only transferNature and transferId were changed | ||
expect({ ...tx }).toEqual({ | ||
...txAfter, | ||
transferNature: expect.toBeAnythingOrNull(), | ||
transferId: expect.toBeAnythingOrNull(), | ||
}); | ||
|
||
expect(txAfter.transferNature).toBe( | ||
TRANSACTION_TRANSFER_NATURE.common_transfer, | ||
); | ||
expect(txAfter.transferId).toEqual(expect.any(String)); | ||
}); | ||
|
||
expect(incomeA.transferId).toBe(expenseB.transferId); | ||
expect(incomeB.transferId).toBe(expenseA.transferId); | ||
}); | ||
|
||
it('throws an error when trying to link tx from the same account', async () => { | ||
await helpers.monobank.pair(); | ||
const { transactions } = await helpers.monobank.mockTransactions(); | ||
|
||
const tx1 = transactions.find( | ||
(item) => item.transactionType === TRANSACTION_TYPES.expense, | ||
); | ||
const tx2 = transactions.find( | ||
(item) => item.transactionType === TRANSACTION_TYPES.income, | ||
); | ||
|
||
const result = await helpers.linkTransactions({ | ||
payload: { | ||
ids: [[tx1.id, tx2.id]], | ||
}, | ||
}); | ||
expect(result.statusCode).toBe(ERROR_CODES.ValidationError); | ||
}); | ||
|
||
it.each([[TRANSACTION_TYPES.expense], [TRANSACTION_TYPES.income]])( | ||
'throws an error when trying to link tx with same transactionType. test %s type', | ||
async (txType) => { | ||
const accountA = await helpers.createAccount({ raw: true }); | ||
const accountB = await helpers.createAccount({ raw: true }); | ||
|
||
const [tx1] = await helpers.createTransaction({ | ||
payload: helpers.buildTransactionPayload({ | ||
accountId: accountA.id, | ||
transactionType: txType, | ||
}), | ||
raw: true, | ||
}); | ||
const [tx2] = await helpers.createTransaction({ | ||
payload: helpers.buildTransactionPayload({ | ||
accountId: accountB.id, | ||
transactionType: txType, | ||
}), | ||
raw: true, | ||
}); | ||
|
||
const result = await helpers.linkTransactions({ | ||
payload: { | ||
ids: [[tx1.id, tx2.id]], | ||
}, | ||
}); | ||
|
||
expect(result.statusCode).toBe(ERROR_CODES.ValidationError); | ||
}, | ||
); | ||
|
||
it.each([[TRANSACTION_TYPES.expense], [TRANSACTION_TYPES.income]])( | ||
'throws an error when trying to link to the transaction that is already a transfer. test %s type', | ||
async (txType) => { | ||
const accountA = await helpers.createAccount({ raw: true }); | ||
const accountB = await helpers.createAccount({ raw: true }); | ||
const accountC = await helpers.createAccount({ raw: true }); | ||
|
||
const [tx1] = await helpers.createTransaction({ | ||
payload: helpers.buildTransactionPayload({ | ||
accountId: accountA.id, | ||
transactionType: txType, | ||
}), | ||
raw: true, | ||
}); | ||
const transactions = await helpers.createTransaction({ | ||
payload: { | ||
...helpers.buildTransactionPayload({ | ||
accountId: accountB.id, | ||
amount: 10, | ||
transferNature: TRANSACTION_TRANSFER_NATURE.common_transfer, | ||
destinationAmount: 20, | ||
destinationAccountId: accountC.id, | ||
}), | ||
}, | ||
raw: true, | ||
}); | ||
|
||
const expenseTx = transactions.find( | ||
(t) => t.transactionType === TRANSACTION_TYPES.expense, | ||
); | ||
const incomeTx = transactions.find( | ||
(t) => t.transactionType === TRANSACTION_TYPES.income, | ||
); | ||
|
||
const result = await helpers.linkTransactions({ | ||
payload: { | ||
ids: [ | ||
[ | ||
tx1.id, | ||
txType === TRANSACTION_TYPES.income ? expenseTx.id : incomeTx.id, | ||
], | ||
], | ||
}, | ||
}); | ||
|
||
expect(result.statusCode).toBe(ERROR_CODES.ValidationError); | ||
}, | ||
); | ||
}); |
29 changes: 29 additions & 0 deletions
29
src/controllers/transactions.controller/transfer-linking/link-transactions.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { API_RESPONSE_STATUS, endpointsTypes } from 'shared-types'; | ||
import { CustomResponse } from '@common/types'; | ||
import { errorHandler } from '@controllers/helpers'; | ||
import * as transactionsService from '@services/transactions'; | ||
import { ValidationError } from '@js/errors'; | ||
|
||
export const linkTransactions = async (req, res: CustomResponse) => { | ||
try { | ||
const { ids }: endpointsTypes.LinkTransactionsBody = req.body; | ||
const { id: userId } = req.user; | ||
|
||
if (!ids || !Array.isArray(ids)) { | ||
throw new ValidationError({ | ||
message: '"ids" field is missing or invalid.', | ||
}); | ||
} | ||
|
||
const data = await transactionsService.linkTransactions({ | ||
userId, | ||
ids, | ||
}); | ||
|
||
return res | ||
.status(200) | ||
.json({ status: API_RESPONSE_STATUS.success, response: data }); | ||
} catch (err) { | ||
errorHandler(res, err); | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,19 @@ | ||
import * as Transactions from '@models/Transactions.model'; | ||
import { GenericSequelizeModelAttributes } from '@common/types'; | ||
import { type GetTransactionsParams } from '@models/transactions'; | ||
import type { GetTransactionsParams } from '@models/transactions'; | ||
|
||
export const getTransactions = async ( | ||
params: GetTransactionsParams, | ||
attributes: GenericSequelizeModelAttributes = {}, | ||
params: GetTransactionsParams, | ||
attributes: GenericSequelizeModelAttributes = {}, | ||
) => { | ||
// eslint-disable-next-line no-useless-catch | ||
try { | ||
const data = await Transactions.getTransactions(params, { transaction: attributes.transaction }); | ||
|
||
const data = await Transactions.getTransactions(params, { | ||
transaction: attributes.transaction, | ||
}); | ||
|
||
return data; | ||
} catch(err) { | ||
throw new err; | ||
} catch (err) { | ||
throw err; | ||
} | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.