Skip to content

Commit

Permalink
creat§e, retrieve transaction & process trans. queue clean
Browse files Browse the repository at this point in the history
  • Loading branch information
lailien3 committed Nov 16, 2024
1 parent f9d8d60 commit 280f4e3
Show file tree
Hide file tree
Showing 6 changed files with 219 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
import { TRANSACTION_STAGING_TABLE } from '../../../config.js'
import { getPermissionCost } from '@defra-fish/business-rules-lib'
import { getReferenceDataForEntityAndId } from '../../reference-data.service.js'
import { DynamoDBDocumentClient, PutCommand, BatchWriteCommand } from '@aws-sdk/lib-dynamodb'
import { docClient } from '../../../../../connectors-lib/src/aws.js'
import { PutCommand, BatchWriteCommand } from '@aws-sdk/lib-dynamodb'

jest.mock('@defra-fish/business-rules-lib')
jest.mock('../../reference-data.service.js', () => ({
Expand All @@ -22,50 +23,57 @@ jest.mock('../../reference-data.service.js', () => ({
})
}))

jest.mock('@aws-sdk/lib-dynamodb', () => ({
DynamoDBDocumentClient: {
from: jest.fn().mockReturnValue({
send: jest.fn()
})
},
PutCommand: jest.fn(),
BatchWriteCommand: jest.fn()
jest.mock('../../../../../connectors-lib/src/aws.js', () => ({
docClient: {
send: jest.fn()
}
}))

jest.mock('@aws-sdk/lib-dynamodb', () => {
const originalModule = jest.requireActual('@aws-sdk/lib-dynamodb')
return {
...originalModule,
PutCommand: jest.fn(),
BatchWriteCommand: jest.fn()
}
})

describe('transaction service', () => {
let mockDynamoDb
beforeAll(() => {
TRANSACTION_STAGING_TABLE.TableName = 'TestTable'
getPermissionCost.mockReturnValue(54)
mockDynamoDb = DynamoDBDocumentClient.from()
})
beforeEach(jest.clearAllMocks)

describe('createTransaction', () => {
it('accepts a new transaction', async () => {
const mockPayload = mockTransactionPayload()
const expectedResult = Object.assign({}, mockPayload, {
const expectedResult = {
...mockPayload,
id: expect.stringMatching(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i),
expires: expect.any(Number),
cost: 54,
isRecurringPaymentSupported: true,
status: { id: 'STAGED' }
})
}

const result = await createTransaction(mockPayload)
expect(result).toMatchObject(expectedResult)
expect(PutCommand).toHaveBeenCalledWith({
TableName: TRANSACTION_STAGING_TABLE.TableName,
Item: expectedResult,
ConditionExpression: 'attribute_not_exists(id)'
})
await createTransaction(mockPayload)

expect(docClient.send).toHaveBeenCalledWith(expect.any(PutCommand))
expect(PutCommand).toHaveBeenCalledWith(
expect.objectContaining({
TableName: TRANSACTION_STAGING_TABLE.TableName,
Item: expectedResult,
ConditionExpression: 'attribute_not_exists(id)'
})
)
})

it.each([99, 115, 22, 87.99])('uses business rules lib to calculate price (%d)', async permitPrice => {
getPermissionCost.mockReturnValueOnce(permitPrice)
const mockPayload = mockTransactionPayload()
const { cost } = await createTransaction(mockPayload)
expect(cost).toBe(permitPrice)
const result = await createTransaction(mockPayload)
expect(result.cost).toBe(permitPrice)
})

it('passes startDate and permit to getPermissionCost', async () => {
Expand All @@ -84,35 +92,45 @@ describe('transaction service', () => {
})

it('throws exceptions back up the stack', async () => {
mockDynamoDb.send.mockRejectedValueOnce(new Error('Test error'))
docClient.send.mockRejectedValueOnce(new Error('Test error'))
await expect(createTransaction(mockTransactionPayload())).rejects.toThrow('Test error')
})

it('uses transaction id if supplied in payload', async () => {
const mockPayload = mockTransactionPayload()
mockPayload.transactionId = 'abc-123-def-456'
const result = await createTransaction(mockPayload)
expect(result.id).toBe(mockPayload.transactionId)
})
})

describe('createTransactions', () => {
it('accepts multiple transactions', async () => {
const mockPayload = mockTransactionPayload()
const expectedRecord = Object.assign({}, mockPayload, {
const expectedRecord = {
...mockPayload,
id: expect.stringMatching(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i),
expires: expect.any(Number),
cost: 54,
isRecurringPaymentSupported: true,
status: { id: 'STAGED' }
})
}

await createTransactions([mockPayload, mockPayload])

const result = await createTransactions([mockPayload, mockPayload])
expect(result).toEqual(expect.arrayContaining([expectedRecord, expectedRecord]))
expect(BatchWriteCommand).toHaveBeenCalledWith({
RequestItems: {
[TRANSACTION_STAGING_TABLE.TableName]: [{ PutRequest: { Item: expectedRecord } }, { PutRequest: { Item: expectedRecord } }]
}
})
expect(mockDynamoDb.send).toHaveBeenCalledWith(expect.any(BatchWriteCommand))
expect(docClient.send).toHaveBeenCalledWith(expect.any(BatchWriteCommand))
expect(BatchWriteCommand).toHaveBeenCalledWith(
expect.objectContaining({
RequestItems: {
[TRANSACTION_STAGING_TABLE.TableName]: [{ PutRequest: { Item: expectedRecord } }, { PutRequest: { Item: expectedRecord } }]
}
})
)
})

it('throws exceptions back up the stack', async () => {
mockDynamoDb.send.mockRejectedValueOnce(new Error('Test error'))
await expect(createTransaction(mockTransactionPayload())).rejects.toThrow('Test error')
docClient.send.mockRejectedValueOnce(new Error('Test error'))
await expect(createTransactions([mockTransactionPayload()])).rejects.toThrow('Test error')
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,7 @@ import {
import { TRANSACTION_STAGING_TABLE, TRANSACTION_STAGING_HISTORY_TABLE } from '../../../config.js'
import { POCL_DATA_SOURCE, DDE_DATA_SOURCE } from '@defra-fish/business-rules-lib'
import moment from 'moment'
import { DynamoDBDocument } from '@aws-sdk/lib-dynamodb'

jest.mock('@aws-sdk/lib-dynamodb', () => ({
DynamoDBDocument: {
from: jest.fn().mockReturnValue({
get: jest.fn(),
put: jest.fn(),
delete: jest.fn(),
update: jest.fn(),
query: jest.fn()
})
}
}))
import { docClient } from '../../../../../connectors-lib/src/aws.js'

jest.mock('../../reference-data.service.js', () => ({
...jest.requireActual('../../reference-data.service.js'),
Expand Down Expand Up @@ -76,14 +64,20 @@ jest.mock('@defra-fish/business-rules-lib', () => ({
START_AFTER_PAYMENT_MINUTES: 30
}))

jest.mock('../../../../../connectors-lib/src/aws.js', () => ({
docClient: {
send: jest.fn()
}
}))

describe('transaction service', () => {
let mockDynamoDb
beforeAll(() => {
TRANSACTION_STAGING_TABLE.TableName = 'TestTable'
mockDynamoDb = DynamoDBDocument.from()
})

beforeEach(jest.clearAllMocks)
beforeEach(() => {
jest.clearAllMocks()
})

describe('processQueue', () => {
describe('processes messages related to different licence types', () => {
Expand Down Expand Up @@ -166,37 +160,39 @@ describe('transaction service', () => {
]
])('handles %s', async (description, initialiseMockTransactionRecord, entityExpectations) => {
const mockRecord = initialiseMockTransactionRecord()
mockDynamoDb.get.mockResolvedValueOnce({ Item: mockRecord })
mockDynamoDb.delete.mockResolvedValueOnce({})
mockDynamoDb.put.mockResolvedValueOnce({})
docClient.send
.mockResolvedValueOnce({ Item: mockRecord }) // GetCommand response
.mockResolvedValueOnce({}) // DeleteCommand response
.mockResolvedValueOnce({}) // PutCommand response

const result = await processQueue({ id: mockRecord.id })
expect(result).toBeUndefined()
expect(persist).toBeCalledWith(entityExpectations, undefined)
expect(mockDynamoDb.get).toBeCalledWith(
expect.objectContaining({
TableName: TRANSACTION_STAGING_TABLE.TableName,
Key: { id: mockRecord.id },
ConsistentRead: true
})
)
expect(mockDynamoDb.delete).toBeCalledWith(
expect.objectContaining({
TableName: TRANSACTION_STAGING_TABLE.TableName,
Key: { id: mockRecord.id }
})
)
const expectedRecord = Object.assign(mockRecord, {

// verifies GetCommand parameters
expect(docClient.send.mock.calls[0][0].input).toEqual({
TableName: TRANSACTION_STAGING_TABLE.TableName,
Key: { id: mockRecord.id },
ConsistentRead: true
})

// verifies DeleteCommand parameters
expect(docClient.send.mock.calls[1][0].input).toEqual({
TableName: TRANSACTION_STAGING_TABLE.TableName,
Key: { id: mockRecord.id }
})

// verifies PutCommand parameters
const expectedRecord = Object.assign({}, mockRecord, {
id: expect.stringMatching(/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/i),
expires: expect.any(Number)
})

expect(mockDynamoDb.put).toBeCalledWith(
expect.objectContaining({
TableName: TRANSACTION_STAGING_HISTORY_TABLE.TableName,
Item: expectedRecord,
ConditionExpression: 'attribute_not_exists(id)'
})
)
expect(docClient.send.mock.calls[2][0].input).toEqual({
TableName: TRANSACTION_STAGING_HISTORY_TABLE.TableName,
Item: expectedRecord,
ConditionExpression: 'attribute_not_exists(id)'
})
})
})

Expand All @@ -212,7 +208,7 @@ describe('transaction service', () => {
it('includes a FulfilmentRequest when the permit and contact are for postal fulfilment', async () => {
const mockRecord = mockFinalisedTransactionRecord()
mockRecord.permissions[0].permitId = MOCK_12MONTH_SENIOR_PERMIT.id
mockDynamoDb.get.mockResolvedValueOnce({ Item: mockRecord })
docClient.send.mockResolvedValueOnce({ Item: mockRecord })
await processQueue({ id: mockRecord.id })
expect(persist).toBeCalledWith(
[
Expand All @@ -231,7 +227,7 @@ describe('transaction service', () => {
it('does not include a FulfilmentRequest when the permit and contact are not for postal fulfilment', async () => {
const mockRecord = mockFinalisedTransactionRecord()
mockRecord.permissions[0].permitId = MOCK_1DAY_SENIOR_PERMIT_ENTITY.id
mockDynamoDb.get.mockResolvedValueOnce({ Item: mockRecord })
docClient.send.mockResolvedValueOnce({ Item: mockRecord })
await processQueue({ id: mockRecord.id })
expect(persist).toBeCalledWith(
[
Expand Down Expand Up @@ -259,7 +255,7 @@ describe('transaction service', () => {
it('does not include a FulfilmentRequest when the permit and contact are for postal fulfilment', async () => {
const mockRecord = mockFinalisedTransactionRecord()
mockRecord.permissions[0].permitId = MOCK_12MONTH_SENIOR_PERMIT.id
mockDynamoDb.get.mockResolvedValueOnce({ Item: mockRecord })
docClient.send.mockResolvedValueOnce({ Item: mockRecord })
await processQueue({ id: mockRecord.id })
expect(persist).toBeCalledWith(
[
Expand All @@ -277,7 +273,7 @@ describe('transaction service', () => {
it('does not include a FulfilmentRequest when the permit and contact are not for postal fulfilment', async () => {
const mockRecord = mockFinalisedTransactionRecord()
mockRecord.permissions[0].permitId = MOCK_1DAY_SENIOR_PERMIT_ENTITY.id
mockDynamoDb.get.mockResolvedValueOnce({ Item: mockRecord })
docClient.send.mockResolvedValueOnce({ Item: mockRecord })
await processQueue({ id: mockRecord.id })
expect(persist).toBeCalledWith(
[
Expand All @@ -296,7 +292,7 @@ describe('transaction service', () => {
it('sets isLicenceForYou to Yes on the transaction, if it is true on the permission', async () => {
const mockRecord = mockFinalisedTransactionRecord()
mockRecord.permissions[0].isLicenceForYou = true
mockDynamoDb.get.mockResolvedValueOnce({ Item: mockRecord })
docClient.send.mockResolvedValueOnce({ Item: mockRecord })
await processQueue({ id: mockRecord.id })
const persistMockFirstAgument = persist.mock.calls[0]
expect(persistMockFirstAgument[0][4].isLicenceForYou).toBeDefined()
Expand All @@ -306,7 +302,7 @@ describe('transaction service', () => {
it('sets isLicenceForYou to No on the transaction, if it is false on the permission', async () => {
const mockRecord = mockFinalisedTransactionRecord()
mockRecord.permissions[0].isLicenceForYou = false
mockDynamoDb.get.mockResolvedValueOnce({ Item: mockRecord })
docClient.send.mockResolvedValueOnce({ Item: mockRecord })
await processQueue({ id: mockRecord.id })
const persistMockFirstAgument = persist.mock.calls[0]
expect(persistMockFirstAgument[0][4].isLicenceForYou).toBeDefined()
Expand All @@ -316,7 +312,7 @@ describe('transaction service', () => {
it('does not set isLicenceForYou on the transaction, if it is undefined on the permission', async () => {
const mockRecord = mockFinalisedTransactionRecord()
mockRecord.permissions[0].isLicenceForYou = undefined
mockDynamoDb.get.mockResolvedValueOnce({ Item: mockRecord })
docClient.send.mockResolvedValueOnce({ Item: mockRecord })
await processQueue({ id: mockRecord.id })
const persistMockFirstAgument = persist.mock.calls[0]
expect(persistMockFirstAgument[0][4].isLicenceForYou).toBeUndefined()
Expand All @@ -325,7 +321,7 @@ describe('transaction service', () => {
it('does not set isLicenceForYou on the transaction, if it is null on the permission', async () => {
const mockRecord = mockFinalisedTransactionRecord()
mockRecord.permissions[0].isLicenceForYou = null
mockDynamoDb.get.mockResolvedValueOnce({ Item: mockRecord })
docClient.send.mockResolvedValueOnce({ Item: mockRecord })
await processQueue({ id: mockRecord.id })
const persistMockFirstAgument = persist.mock.calls[0]
expect(persistMockFirstAgument[0][4].isLicenceForYou).toBeUndefined()
Expand All @@ -335,7 +331,7 @@ describe('transaction service', () => {
const transactionFilename = 'test-file.xml'
const mockRecord = mockFinalisedTransactionRecord()
mockRecord.transactionFile = transactionFilename
mockDynamoDb.get.mockResolvedValueOnce({ Item: mockRecord })
docClient.send.mockResolvedValueOnce({ Item: mockRecord })
const transactionToFileBindingSpy = jest.spyOn(Transaction.prototype, 'bindToAlternateKey')
const permissionToFileBindingSpy = jest.spyOn(Permission.prototype, 'bindToAlternateKey')
const testPoclFileEntity = new PoclFile()
Expand All @@ -347,7 +343,7 @@ describe('transaction service', () => {

it('throws 404 not found error if a record cannot be found for the given id', async () => {
const mockRecord = mockFinalisedTransactionRecord()
mockDynamoDb.get.mockResolvedValueOnce({ Item: undefined })
docClient.send.mockResolvedValueOnce({ Item: undefined })
try {
await processQueue({ id: mockRecord.id })
} catch (e) {
Expand All @@ -360,7 +356,7 @@ describe('transaction service', () => {
const setup = async () => {
const mockRecord = mockFinalisedTransactionRecord()
mockRecord.payment.amount = cost
mockDynamoDb.get.mockResolvedValueOnce({ Item: mockRecord })
docClient.send.mockResolvedValueOnce({ Item: mockRecord })
await processQueue({ id: mockRecord.id })
const {
mock: {
Expand Down
Loading

0 comments on commit 280f4e3

Please sign in to comment.