The Code to Refactor
src/index.test.ts
import factory from '.'import { PaymentMethod, type UserPurchaseInformation, type PromotionInformation} from './types'import { type DB } from './db/types'
// Constants for test dataconst constants = { PAYEE_ID: 'a'.repeat(32), PAYER_ID: 'b'.repeat(32), DEVELOPER_ID: 'c'.repeat(32), SQL_TRANSACTION_ID: 'd'.repeat(32), PAYER_ACCOUNT_ID: 'e'.repeat(32), PAYEE_ACCOUNT_ID: 'f'.repeat(32), PAYMENT_ID: 'g'.repeat(32), FED_NOW_PAYMENT_ID: 'h'.repeat(32), LEDGER_ENTRY_ID: 'i'.repeat(32), PROMOTION_LEDGER_ENTRY_ID: 'j'.repeat(32), TRANSACTION_RECORD_ID: 'k'.repeat(32)}
describe('executeStandardPTOperations', () => { let db: DB let executeStandardPTOperations: ReturnType<typeof factory>
beforeEach(() => { db = { insertPayment: jest.fn(async () => constants.PAYMENT_ID), insertFedNowPayment: jest.fn(async () => constants.FED_NOW_PAYMENT_ID), insertLedgerEntry: jest.fn(async () => constants.LEDGER_ENTRY_ID), insertPromotionLedgerEntry: jest.fn( async () => constants.PROMOTION_LEDGER_ENTRY_ID ), insertTransactionRecord: jest.fn( async () => constants.TRANSACTION_RECORD_ID ) } executeStandardPTOperations = factory({ db }) })
const userPurchaseInformation: UserPurchaseInformation = { payerId: constants.PAYER_ID, payeeId: constants.PAYEE_ID, developerId: constants.DEVELOPER_ID, amount: 100, interactionTypeId: 1, paymentMethod: PaymentMethod.Card }
const promotionInformation: PromotionInformation = { promoAmount: 10 }
// Testing standard payment processing describe('Standard Payment Processing', () => { let standardUserPurchaseInformation: UserPurchaseInformation
beforeEach(() => { standardUserPurchaseInformation = { ...userPurchaseInformation, paymentMethod: PaymentMethod.Card } })
it('should handle standard payment without promotion', async () => { const idObj = await executeStandardPTOperations({ userPurchaseInformation: standardUserPurchaseInformation, promotionInformation: {}, sqlTransactionId: constants.SQL_TRANSACTION_ID })
// Assertions expect(idObj).toEqual({ primaryPaymentID: constants.PAYMENT_ID, customerLedgerEntryID: constants.LEDGER_ENTRY_ID, pursTransactionID: constants.TRANSACTION_RECORD_ID }) expect(db.insertPayment).toHaveBeenCalledWith({ payerId: constants.PAYER_ID, payeeId: constants.PAYEE_ID, developerId: constants.DEVELOPER_ID, amount: 100, interactionTypeId: 1, paymentMethod: PaymentMethod.Card, sqlTransactionId: constants.SQL_TRANSACTION_ID }) expect(db.insertLedgerEntry).toHaveBeenCalledWith({ payerId: constants.PAYER_ID, payeeId: constants.PAYEE_ID, developerId: constants.DEVELOPER_ID, amount: 100, interactionTypeId: 1, sqlTransactionId: constants.SQL_TRANSACTION_ID }) expect(db.insertTransactionRecord).toHaveBeenCalledWith({ ledgerEntries: [constants.LEDGER_ENTRY_ID], sqlTransactionId: constants.SQL_TRANSACTION_ID }) expect(db.insertPromotionLedgerEntry).not.toHaveBeenCalled() })
it('should handle standard payment with promotion', async () => { const idObj = await executeStandardPTOperations({ userPurchaseInformation: standardUserPurchaseInformation, promotionInformation, sqlTransactionId: constants.SQL_TRANSACTION_ID })
// Assertions expect(idObj).toEqual({ primaryPaymentID: constants.PAYMENT_ID, customerLedgerEntryID: constants.LEDGER_ENTRY_ID, promotionLedgerEntryID: constants.PROMOTION_LEDGER_ENTRY_ID, pursTransactionID: constants.TRANSACTION_RECORD_ID }) expect(db.insertPayment).toHaveBeenCalledWith({ payerId: constants.PAYER_ID, payeeId: constants.PAYEE_ID, developerId: constants.DEVELOPER_ID, amount: 100, interactionTypeId: 1, paymentMethod: PaymentMethod.Card, sqlTransactionId: constants.SQL_TRANSACTION_ID }) expect(db.insertLedgerEntry).toHaveBeenCalledWith({ payerId: constants.PAYER_ID, payeeId: constants.PAYEE_ID, developerId: constants.DEVELOPER_ID, amount: 100, interactionTypeId: 1, sqlTransactionId: constants.SQL_TRANSACTION_ID }) expect(db.insertPromotionLedgerEntry).toHaveBeenCalledWith({ developerId: constants.DEVELOPER_ID, payeeId: constants.PAYEE_ID, payerId: constants.PAYER_ID, promoAmount: promotionInformation.promoAmount, interactionTypeId: 1, sqlTransactionId: constants.SQL_TRANSACTION_ID }) expect(db.insertTransactionRecord).toHaveBeenCalledWith({ ledgerEntries: [ constants.LEDGER_ENTRY_ID, constants.PROMOTION_LEDGER_ENTRY_ID ], sqlTransactionId: constants.SQL_TRANSACTION_ID }) }) })
// Testing invalid parameter scenarios describe('Invalid Parameter Handling', () => { it('should throw an error with invalid UserPurchaseInformation', async () => { const invalidUserPurchaseInformation = { payerId: 'invalid', payeeId: 'invalid', developerId: 'invalid', amount: -1, interactionTypeId: 1, paymentMethod: PaymentMethod.Card }
await expect( executeStandardPTOperations({ userPurchaseInformation: invalidUserPurchaseInformation, promotionInformation: {}, sqlTransactionId: constants.SQL_TRANSACTION_ID }) ).rejects.toThrow( JSON.stringify( [ { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['userPurchaseInformation', 'payeeId'] }, { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['userPurchaseInformation', 'payerId'] }, { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['userPurchaseInformation', 'developerId'] }, { code: 'too_small', minimum: 0, type: 'number', inclusive: true, exact: false, message: 'Number must be greater than or equal to 0', path: ['userPurchaseInformation', 'amount'] } ], null, 2 ) ) }) })
// Testing FedNow payment processing describe('FedNow Payment Processing', () => { let fedNowUserPurchaseInformation: UserPurchaseInformation
beforeEach(() => { fedNowUserPurchaseInformation = { ...userPurchaseInformation, paymentMethod: PaymentMethod.FedNow, payerAccountId: constants.PAYER_ACCOUNT_ID, payeeAccountId: constants.PAYEE_ACCOUNT_ID } })
it('should handle FedNow payment without promotion', async () => { const idObj = await executeStandardPTOperations({ userPurchaseInformation: fedNowUserPurchaseInformation, promotionInformation: {}, sqlTransactionId: constants.SQL_TRANSACTION_ID })
// Assertions for FedNow payment without promotion expect(idObj).toEqual({ primaryPaymentID: constants.PAYMENT_ID, primaryFedNowPaymentID: constants.FED_NOW_PAYMENT_ID, customerLedgerEntryID: constants.LEDGER_ENTRY_ID, pursTransactionID: constants.TRANSACTION_RECORD_ID }) expect(db.insertPayment).toHaveBeenCalledWith({ amount: 100, developerId: constants.DEVELOPER_ID, interactionTypeId: 1, payeeId: constants.PAYEE_ID, payerId: constants.PAYER_ID, paymentMethod: PaymentMethod.FedNow, sqlTransactionId: constants.SQL_TRANSACTION_ID }) expect(db.insertFedNowPayment).toHaveBeenCalledWith({ paymentId: constants.PAYMENT_ID, payerAccountId: constants.PAYER_ACCOUNT_ID, payeeAccountId: constants.PAYEE_ACCOUNT_ID, sqlTransactionId: constants.SQL_TRANSACTION_ID }) expect(db.insertLedgerEntry).toHaveBeenCalledWith({ amount: 100, developerId: constants.DEVELOPER_ID, interactionTypeId: 1, payeeId: constants.PAYEE_ID, payerId: constants.PAYER_ID, sqlTransactionId: constants.SQL_TRANSACTION_ID }) expect(db.insertTransactionRecord).toHaveBeenCalledWith({ ledgerEntries: [constants.LEDGER_ENTRY_ID], sqlTransactionId: constants.SQL_TRANSACTION_ID }) expect(db.insertPromotionLedgerEntry).not.toHaveBeenCalled() })
it('should handle FedNow payment with promotion', async () => { const idObj = await executeStandardPTOperations({ userPurchaseInformation: fedNowUserPurchaseInformation, promotionInformation, sqlTransactionId: constants.SQL_TRANSACTION_ID })
// Assertions for FedNow payment with promotion expect(idObj).toEqual({ primaryPaymentID: constants.PAYMENT_ID, primaryFedNowPaymentID: constants.FED_NOW_PAYMENT_ID, customerLedgerEntryID: constants.LEDGER_ENTRY_ID, promotionLedgerEntryID: constants.PROMOTION_LEDGER_ENTRY_ID, pursTransactionID: constants.TRANSACTION_RECORD_ID }) expect(db.insertPayment).toHaveBeenCalledWith({ amount: 100, developerId: constants.DEVELOPER_ID, interactionTypeId: 1, payeeId: constants.PAYEE_ID, payerId: constants.PAYER_ID, paymentMethod: PaymentMethod.FedNow, sqlTransactionId: constants.SQL_TRANSACTION_ID }) expect(db.insertFedNowPayment).toHaveBeenCalledWith({ paymentId: constants.PAYMENT_ID, payerAccountId: constants.PAYER_ACCOUNT_ID, payeeAccountId: constants.PAYEE_ACCOUNT_ID, sqlTransactionId: constants.SQL_TRANSACTION_ID }) expect(db.insertLedgerEntry).toHaveBeenCalledWith({ amount: 100, developerId: constants.DEVELOPER_ID, interactionTypeId: 1, payeeId: constants.PAYEE_ID, payerId: constants.PAYER_ID, sqlTransactionId: constants.SQL_TRANSACTION_ID }) expect(db.insertPromotionLedgerEntry).toHaveBeenCalledWith({ developerId: constants.DEVELOPER_ID, payeeId: constants.PAYEE_ID, payerId: constants.PAYER_ID, promoAmount: promotionInformation.promoAmount, interactionTypeId: 1, sqlTransactionId: constants.SQL_TRANSACTION_ID }) expect(db.insertTransactionRecord).toHaveBeenCalledWith({ ledgerEntries: [ constants.LEDGER_ENTRY_ID, constants.PROMOTION_LEDGER_ENTRY_ID ], sqlTransactionId: constants.SQL_TRANSACTION_ID }) }) })})
src/db/index.test.ts
/* eslint-disable import/first */import { type InsertFedNowPaymentParams, type InsertLedgerEntryParams, type InsertPaymentParams, type InsertPromotionLedgerEntryParams, type InsertTransactionRecordParams} from './types'import { PaymentMethod, PaymentStatus } from '../types'import { formatDate } from '../helpers'
const mockExecuteStatement = jest.fn().mockImplementation(() => ({ promise: jest.fn().mockResolvedValue({ records: [] }) // Mock resolved value}))const mockBatchExecuteStatement = jest.fn().mockImplementation(() => ({ promise: jest.fn().mockResolvedValue({ records: [] }) // Mock resolved value for batch execute}))
jest.mock('aws-sdk', () => { return { RDSDataService: jest.fn(() => ({ executeStatement: mockExecuteStatement, batchExecuteStatement: mockBatchExecuteStatement })) }})
import createDatabaseOperations from './'
describe('Database Operations', () => { let db: ReturnType<typeof createDatabaseOperations>
const mockDate = new Date('2023-12-28T20:49:19.559Z')
const PAYEE_ID = 'a'.repeat(32) const PAYER_ID = 'b'.repeat(32) const DEVELOPER_ID = 'c'.repeat(32) const SQL_TRANSACTION_ID = 'd'.repeat(32) const PAYER_ACCOUNT_ID = 'e'.repeat(32) const PAYEE_ACCOUNT_ID = 'f'.repeat(32)
const PAYMENT_ID = 'b'.repeat(32)
beforeAll(() => { process.env.DATABASE = 'mock-database' process.env.SECRET_ARN = 'mock-secret-arn' process.env.CLUSTER_ARN = 'mock-cluster-arn' const mockDependencies = { genId: () => 'a'.repeat(32) }
jest.spyOn(global, 'Date').mockImplementation(() => mockDate)
// eslint-disable-next-line @typescript-eslint/no-var-requires db = createDatabaseOperations(mockDependencies)
jest.spyOn(global, 'Date').mockImplementation(() => mockDate) })
afterEach(() => { jest.restoreAllMocks() })
describe('insertPayment', () => { describe('when the parameters are valid for card payment', () => { it('should call executeStatement with correct parameters', async () => { const params: InsertPaymentParams = { payeeId: PAYEE_ID, payerId: PAYER_ID, developerId: DEVELOPER_ID, amount: 100, interactionTypeId: 1, paymentMethod: PaymentMethod.Card, sqlTransactionId: SQL_TRANSACTION_ID }
await db.insertPayment(params)
const expected = { database: 'mock-database', secretArn: 'mock-secret-arn', resourceArn: 'mock-cluster-arn', transactionId: SQL_TRANSACTION_ID, sql: 'INSERT ...', parameters: [ { name: 'paymentId', value: { blobValue: Buffer.from([ 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170 ]) } }, { name: 'payerId', value: { blobValue: Buffer.from([ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187 ]) } }, { name: 'payeeId', value: { blobValue: Buffer.from([ 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170 ]) } }, { name: 'developerId', value: { blobValue: Buffer.from([ 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204 ]) } }, { name: 'paymentAmount', value: { doubleValue: 100 } }, { name: 'interactionTypeId', value: { doubleValue: 1 } }, { name: 'paymentMethod', value: { doubleValue: PaymentMethod.Card } }, { name: 'paymentStatus', value: { doubleValue: PaymentStatus.COMPLETE } }, { name: 'datePaid', value: { stringValue: formatDate(mockDate), isNull: false } } ] } expect(mockExecuteStatement).toHaveBeenCalledWith(expected) }) })
describe('when the parameters are valid for fednow payment', () => { it('should call executeStatement with correct parameters', async () => { const params: InsertPaymentParams = { payeeId: PAYEE_ID, payerId: PAYER_ID, developerId: DEVELOPER_ID, amount: 100, interactionTypeId: 1, paymentMethod: PaymentMethod.FedNow, sqlTransactionId: SQL_TRANSACTION_ID }
await db.insertPayment(params)
const expected = { database: 'mock-database', secretArn: 'mock-secret-arn', resourceArn: 'mock-cluster-arn', transactionId: SQL_TRANSACTION_ID, sql: 'INSERT ...', parameters: [ { name: 'paymentId', value: { blobValue: Buffer.from([ 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170 ]) } }, { name: 'payerId', value: { blobValue: Buffer.from([ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187 ]) } }, { name: 'payeeId', value: { blobValue: Buffer.from([ 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170 ]) } }, { name: 'developerId', value: { blobValue: Buffer.from([ 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204 ]) } }, { name: 'paymentAmount', value: { doubleValue: 100 } }, { name: 'interactionTypeId', value: { doubleValue: 1 } }, { name: 'paymentMethod', value: { doubleValue: PaymentMethod.FedNow } }, { name: 'paymentStatus', value: { doubleValue: PaymentStatus.PENDING } }, { name: 'datePaid', value: { stringValue: undefined, isNull: true } } ] } expect(mockExecuteStatement).toHaveBeenCalledWith(expected) }) }) describe('when parameters are invalid', () => { it('should throw an error if fields are of incorrect type', async () => { const params = { payerId: 'invalid', payeeId: 'invalid', developerId: 'invalid', amount: -1, interactionTypeId: 1, paymentMethod: 10, sqlTransactionId: 'invalid' }
await expect(db.insertPayment(params)).rejects.toThrow( JSON.stringify( [ { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['payerId'] }, { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['payeeId'] }, { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['developerId'] }, { code: 'too_small', minimum: 0, type: 'number', inclusive: true, exact: false, message: 'Number must be greater than or equal to 0', path: ['amount'] }, { received: 10, code: 'invalid_enum_value', options: [0, 1], path: ['paymentMethod'], message: "Invalid enum value. Expected 0 | 1, received '10'" }, { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['sqlTransactionId'] } ], null, 2 ) ) })
// Additional test cases for other invalid scenarios })
describe('when executeStatement throws an error', () => { it('should propagate the error', async () => { const params: InsertPaymentParams = { payeeId: PAYEE_ID, payerId: PAYER_ID, developerId: DEVELOPER_ID, amount: 100, interactionTypeId: 1, paymentMethod: PaymentMethod.Card, sqlTransactionId: SQL_TRANSACTION_ID }
const errorMessage = 'Database operation failed' mockExecuteStatement.mockImplementationOnce(() => { throw new Error(errorMessage) })
await expect(db.insertPayment(params)).rejects.toThrow(errorMessage) }) }) })
describe('insertFedNowPayment', () => { describe('when the parameters are valid', () => { it('should call executeStatement with correct parameters', async () => { const params: InsertFedNowPaymentParams = { paymentId: PAYMENT_ID, payerAccountId: PAYER_ACCOUNT_ID, payeeAccountId: PAYEE_ACCOUNT_ID, sqlTransactionId: SQL_TRANSACTION_ID }
await db.insertFedNowPayment(params)
const expected = { database: 'mock-database', secretArn: 'mock-secret-arn', resourceArn: 'mock-cluster-arn', transactionId: SQL_TRANSACTION_ID, sql: 'INSERT ...', // Replace with the actual SQL query parameters: [ { name: 'fedNowPaymentId', value: { blobValue: expect.any(Buffer) } }, { name: 'paymentId', value: { blobValue: expect.any(Buffer) } }, { name: 'payerAccountId', value: { stringValue: PAYER_ACCOUNT_ID } }, { name: 'payeeAccountId', value: { stringValue: PAYEE_ACCOUNT_ID } } ] } expect(mockExecuteStatement).toHaveBeenCalledWith(expected) }) })
describe('when parameters are invalid', () => { it('should throw an error if fields are of incorrect type', async () => { const invalidParams = { paymentId: 'invalid-length', payerAccountId: 'invalid-length', payeeAccountId: 'invalid-length', sqlTransactionId: 'invalid-length' }
await expect(db.insertFedNowPayment(invalidParams)).rejects.toThrow( JSON.stringify( [ { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['paymentId'] }, { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['payerAccountId'] }, { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['payeeAccountId'] }, { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['sqlTransactionId'] } ], null, 2 ) ) }) })
describe('when executeStatement throws an error', () => { it('should propagate the error', async () => { const params: InsertFedNowPaymentParams = { paymentId: PAYMENT_ID, payerAccountId: PAYER_ACCOUNT_ID, payeeAccountId: PAYEE_ACCOUNT_ID, sqlTransactionId: SQL_TRANSACTION_ID }
const errorMessage = 'Database operation failed' mockExecuteStatement.mockImplementationOnce(() => { throw new Error(errorMessage) })
await expect(db.insertFedNowPayment(params)).rejects.toThrow( errorMessage ) }) }) })
describe('insertLedgerEntry', () => { describe('when the parameters are valid', () => { it('should call executeStatement with correct parameters', async () => { const params: InsertLedgerEntryParams = { payerId: PAYER_ID, payeeId: PAYEE_ID, developerId: DEVELOPER_ID, amount: 100, interactionTypeId: 1, sqlTransactionId: SQL_TRANSACTION_ID }
await db.insertLedgerEntry(params)
const expected = { database: 'mock-database', secretArn: 'mock-secret-arn', resourceArn: 'mock-cluster-arn', transactionId: SQL_TRANSACTION_ID, sql: 'INSERT ...', // Replace with the actual SQL query parameters: [ { name: 'ledgerId', value: { blobValue: Buffer.from([ 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170 ]) } }, { name: 'payerId', value: { blobValue: Buffer.from([ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187 ]) } }, { name: 'payeeId', value: { blobValue: Buffer.from([ 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170 ]) } }, { name: 'amount', value: { doubleValue: 100 } }, { name: 'interactionTypeId', value: { doubleValue: 1 } }, { name: 'developerId', value: { blobValue: Buffer.from([ 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204 ]) } } ] } expect(mockExecuteStatement).toHaveBeenCalledWith(expected) }) })
describe('when parameters are invalid', () => { it('should throw an error if fields are of incorrect type', async () => { const invalidParams = { payerId: 'invalid-length', payeeId: 'invalid-length', developerId: 'invalid-length', amount: -1, interactionTypeId: 1, sqlTransactionId: 'invalid-length' }
await expect(db.insertLedgerEntry(invalidParams)).rejects.toThrow( JSON.stringify( [ { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['payerId'] }, { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['payeeId'] }, { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['developerId'] } ], null, 2 ) ) }) })
describe('when executeStatement throws an error', () => { it('should propagate the error', async () => { const params: InsertLedgerEntryParams = { payerId: PAYER_ID, payeeId: PAYEE_ID, developerId: DEVELOPER_ID, amount: 100, interactionTypeId: 1, sqlTransactionId: SQL_TRANSACTION_ID }
const errorMessage = 'Database operation failed' mockExecuteStatement.mockImplementationOnce(() => { throw new Error(errorMessage) })
await expect(db.insertLedgerEntry(params)).rejects.toThrow( errorMessage ) }) }) }) describe('insertPromotionLedgerEntry', () => { describe('when the parameters are valid', () => { it('should call executeStatement with correct parameters', async () => { const params: InsertPromotionLedgerEntryParams = { payerId: PAYER_ID, payeeId: PAYEE_ID, developerId: DEVELOPER_ID, interactionTypeId: 1, sqlTransactionId: SQL_TRANSACTION_ID, promoAmount: 10 }
await db.insertPromotionLedgerEntry(params)
const expected = { database: 'mock-database', secretArn: 'mock-secret-arn', resourceArn: 'mock-cluster-arn', transactionId: SQL_TRANSACTION_ID, sql: 'INSERT ...', // Replace with the actual SQL query parameters: [ { name: 'ledgerId', value: { blobValue: Buffer.from([ 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170 ]) } }, { name: 'payerId', value: { blobValue: Buffer.from([ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187 ]) } }, { name: 'payeeId', value: { blobValue: Buffer.from([ 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170 ]) } }, { name: 'developerId', value: { blobValue: Buffer.from([ 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204, 204 ]) } }, { name: 'amount', value: { doubleValue: 10 } }, { name: 'interactionTypeId', value: { doubleValue: 1 } } ] } expect(mockExecuteStatement).toHaveBeenCalledWith(expected) }) })
describe('when parameters are invalid', () => { it('should throw an error if fields are of incorrect type', async () => { const params: InsertPromotionLedgerEntryParams = { payerId: 'invalid-length', payeeId: 'invalid-length', developerId: 'invalid-length', interactionTypeId: 1, sqlTransactionId: 'invalid-length', promoAmount: -1 }
await expect(db.insertPromotionLedgerEntry(params)).rejects.toThrow( JSON.stringify( [ { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['developerId'] }, { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['payerId'] }, { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['payeeId'] }, { code: 'too_small', minimum: 0, type: 'number', inclusive: true, exact: false, message: 'Number must be greater than or equal to 0', path: ['promoAmount'] }, { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['sqlTransactionId'] } ], null, 2 ) ) }) })
describe('when executeStatement throws an error', () => { it('should propagate the error', async () => { const params: InsertPromotionLedgerEntryParams = { payerId: PAYER_ID, payeeId: PAYEE_ID, developerId: DEVELOPER_ID, interactionTypeId: 1, sqlTransactionId: SQL_TRANSACTION_ID, promoAmount: 10 }
const errorMessage = 'Database operation failed' mockExecuteStatement.mockImplementationOnce(() => { throw new Error(errorMessage) })
await expect(db.insertPromotionLedgerEntry(params)).rejects.toThrow( errorMessage ) }) }) })
describe('insertTransactionRecord', () => { describe('when the parameters are valid', () => { it('should call executeStatement with correct parameters', async () => { const params: InsertTransactionRecordParams = { ledgerEntries: ['a'.repeat(32), 'b'.repeat(32)], sqlTransactionId: SQL_TRANSACTION_ID }
await db.insertTransactionRecord(params)
const expected = { database: 'mock-database', secretArn: 'mock-secret-arn', resourceArn: 'mock-cluster-arn', transactionId: SQL_TRANSACTION_ID, sql: 'INSERT ...', // Replace with the actual SQL query parameterSets: [ [ { name: 'transactionId', value: { blobValue: Buffer.from([ 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170 ]) } }, { name: 'ledgerId', value: { blobValue: Buffer.from([ 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170 ]) } } ], [ { name: 'transactionId', value: { blobValue: Buffer.from([ 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170, 170 ]) } }, { name: 'ledgerId', value: { blobValue: Buffer.from([ 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187, 187 ]) } } ] ] } expect(mockBatchExecuteStatement).toHaveBeenCalledWith(expected) }) })
describe('when parameters are invalid', () => { it('should throw an error if fields are of incorrect type', async () => { const params: InsertPromotionLedgerEntryParams = { payerId: 'invalid-length', payeeId: 'invalid-length', developerId: 'invalid-length', interactionTypeId: 1, sqlTransactionId: 'invalid-length', promoAmount: -1 }
await expect(db.insertPromotionLedgerEntry(params)).rejects.toThrow( JSON.stringify( [ { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['developerId'] }, { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['payerId'] }, { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['payeeId'] }, { code: 'too_small', minimum: 0, type: 'number', inclusive: true, exact: false, message: 'Number must be greater than or equal to 0', path: ['promoAmount'] }, { code: 'too_small', minimum: 32, type: 'string', inclusive: true, exact: true, message: 'String must contain exactly 32 character(s)', path: ['sqlTransactionId'] } ], null, 2 ) ) }) })
describe('when executeStatement throws an error', () => { it('should propagate the error', async () => { const params: InsertTransactionRecordParams = { ledgerEntries: ['a'.repeat(32), 'b'.repeat(32)], sqlTransactionId: SQL_TRANSACTION_ID }
const errorMessage = 'Database operation failed' mockBatchExecuteStatement.mockImplementationOnce(() => { throw new Error(errorMessage) })
await expect(db.insertTransactionRecord(params)).rejects.toThrow( errorMessage ) }) }) })})
src/helpers.test.ts
/* eslint-disable @typescript-eslint/no-dynamic-delete */import { getEnvVariable, genId } from './helpers' // Adjust the path
describe('Helper Functions', () => { describe('getEnvVariable', () => { it('should return the value of a set environment variable', () => { const testVar = 'TEST_VARIABLE' const testValue = 'value' process.env[testVar] = testValue
expect(getEnvVariable(testVar)).toBe(testValue)
// Clean up delete process.env[testVar] })
it('should throw an error if the environment variable is not set', () => { const testVar = 'UNSET_VARIABLE' delete process.env[testVar] // Ensure it's not set
expect(() => getEnvVariable(testVar)).toThrow(`Environment variable ${testVar} not set`) }) })
describe('genId', () => { it('should generate a string of the specified length', () => { const length = 10 const id = genId(length)
expect(id).toHaveLength(length) expect(typeof id).toBe('string') })
it('should generate a string consisting only of hexadecimal characters', () => { const length = 10 const id = genId(length)
// Regex to match a hexadecimal string expect(id).toMatch(/^[0-9a-f]+$/i) }) })})