From 81b25eb6c5b9dd83cde2fc7c15e211e589402177 Mon Sep 17 00:00:00 2001 From: Bruno Nascimento Date: Mon, 1 Apr 2024 12:13:57 -0300 Subject: [PATCH 1/4] test: add build transaction unit tests --- .eslintrc.json | 4 +- .../build-transaction/index.unit.test.ts | 214 ++++++++++++++++++ .../core/pipelines/build-transaction/types.ts | 2 +- src/stellar-plus/horizon/index.ts | 2 +- 4 files changed, 218 insertions(+), 4 deletions(-) create mode 100644 src/stellar-plus/core/pipelines/build-transaction/index.unit.test.ts diff --git a/.eslintrc.json b/.eslintrc.json index ed995db..ce6fdfd 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,9 +2,9 @@ "root": true, "parser": "@typescript-eslint/parser", "parserOptions": { - "project": ["tsconfig.json"] + "project": ["tsconfig.json"], + "exclude": "commitlint.config.js" }, - "exclude": "commitlint.config.js", "plugins": ["@typescript-eslint", "prettier", "unicorn", "import"], "extends": [ "eslint:recommended", diff --git a/src/stellar-plus/core/pipelines/build-transaction/index.unit.test.ts b/src/stellar-plus/core/pipelines/build-transaction/index.unit.test.ts new file mode 100644 index 0000000..5f43d92 --- /dev/null +++ b/src/stellar-plus/core/pipelines/build-transaction/index.unit.test.ts @@ -0,0 +1,214 @@ +/* eslint-disable import/order */ +import { Horizon, TransactionBuilder } from '@stellar/stellar-sdk' +import { AccountResponse } from '@stellar/stellar-sdk/lib/horizon' +import { Asset, Operation } from '@stellar/stellar-base' +import { testnet } from 'stellar-plus/constants' +import { + BuildTransactionPipelineInput as BTInput, + BuildTransactionPipelineOutput as BTOutput, +} from 'stellar-plus/core/pipelines/build-transaction/types' +import { HorizonHandler } from 'stellar-plus/horizon/types' + +import { BuildTransactionPipeline } from '.' + +jest.mock('@stellar/stellar-sdk', () => ({ + Horizon: { + Server: jest.fn(), + }, + Account: jest.fn(), + TransactionBuilder: jest.fn(() => { + return { + addOperation: jest.fn(), + setTimeout: jest.fn(), + setSorobanData: jest.fn(() => { + return {} + }), + build: jest.fn(() => { + return {} + }), + } + }), +})) + +export function createMockedHorizonHandler(): jest.Mocked { + return { + loadAccount: jest.fn().mockResolvedValue({} as AccountResponse), + server: new Horizon.Server(testnet.horizonUrl), + } +} + +const mockedHorizonHandler = createMockedHorizonHandler() + +const MOCKED_BT_INPUT: BTInput = { + header: { + source: 'source', + fee: '100', + timeout: 2, + }, + horizonHandler: mockedHorizonHandler, + operations: [], + networkPassphrase: 'networkPassphrase', +} +const MOCKED_BT_OUTPUT: BTOutput = {} as BTOutput + +describe('BuildTransactionPipeline', () => { + let buildTransactionPipeline: BuildTransactionPipeline + + beforeEach(() => { + jest.clearAllMocks() + mockedHorizonHandler.loadAccount.mockClear() + }) + + describe('Load Account', () => { + beforeEach(() => { + buildTransactionPipeline = new BuildTransactionPipeline() + jest.clearAllMocks() + }) + + it('should load account successfully', async () => { + await buildTransactionPipeline.execute(MOCKED_BT_INPUT) + + expect(mockedHorizonHandler.loadAccount).toHaveBeenCalledWith('source') + expect(mockedHorizonHandler.loadAccount).toHaveBeenCalledTimes(1) + }) + + it('should throw error', async () => { + mockedHorizonHandler.loadAccount.mockRejectedValueOnce(new Error('error')) + + await expect(buildTransactionPipeline.execute(MOCKED_BT_INPUT)).rejects.toThrow('Could not load account!') + expect(mockedHorizonHandler.loadAccount).toHaveBeenCalledWith('source') + expect(mockedHorizonHandler.loadAccount).toHaveBeenCalledTimes(1) + }) + }) + + describe('Build Envelope', () => { + const transactionBuilderOptions = { + fee: MOCKED_BT_INPUT.header.fee, + networkPassphrase: MOCKED_BT_INPUT.networkPassphrase, + } + + beforeEach(() => { + buildTransactionPipeline = new BuildTransactionPipeline() + jest.clearAllMocks() + }) + + it('should create envelope successfully', async () => { + await buildTransactionPipeline.execute(MOCKED_BT_INPUT) + + expect(TransactionBuilder).toHaveBeenCalledWith({}, transactionBuilderOptions) + }) + + it('should add sorobanData successfully', async () => { + await buildTransactionPipeline.execute({ + ...MOCKED_BT_INPUT, + sorobanData: 'sorobanData', + }) + + expect(TransactionBuilder).toHaveBeenCalledWith({}, transactionBuilderOptions) + }) + + it('should add single operation successfully', async () => { + await buildTransactionPipeline.execute({ + ...MOCKED_BT_INPUT, + operations: [ + Operation.payment({ + destination: 'GB3MXH633VRECLZRUAR3QCLQJDMXNYNHKZCO6FJEWXVWSUEIS7NU376P', + asset: Asset.native(), + amount: '100', + }), + ], + }) + + expect(TransactionBuilder).toHaveBeenCalledWith({}, transactionBuilderOptions) + }) + + it('should add multiple operation successfully', async () => { + await buildTransactionPipeline.execute({ + ...MOCKED_BT_INPUT, + operations: [ + Operation.payment({ + destination: 'GB3MXH633VRECLZRUAR3QCLQJDMXNYNHKZCO6FJEWXVWSUEIS7NU376P', + asset: Asset.native(), + amount: '100', + }), + Operation.payment({ + destination: 'GB3MXH633VRECLZRUAR3QCLQJDMXNYNHKZCO6FJEWXVWSUEIS7NU376P', + asset: Asset.native(), + amount: '100', + }), + Operation.payment({ + destination: 'GB3MXH633VRECLZRUAR3QCLQJDMXNYNHKZCO6FJEWXVWSUEIS7NU376P', + asset: Asset.native(), + amount: '100', + }), + ], + }) + + expect(TransactionBuilder).toHaveBeenCalledWith({}, transactionBuilderOptions) + }) + + it('should throw error', async () => { + ;(TransactionBuilder as unknown as jest.Mock).mockImplementationOnce(() => { + throw new Error('error') + }) + + await expect(buildTransactionPipeline.execute(MOCKED_BT_INPUT)).rejects.toThrow( + 'Could not create transaction builder!' + ) + expect(TransactionBuilder).toHaveBeenCalledWith({}, transactionBuilderOptions) + }) + + it('should throw error adding sorobanData', async () => { + ;(TransactionBuilder as unknown as jest.Mock).mockImplementationOnce(() => { + return { + setSorobanData: jest.fn(() => { + throw new Error('error') + }), + } + }) + + await expect( + buildTransactionPipeline.execute({ + ...MOCKED_BT_INPUT, + sorobanData: 'sorobanData', + }) + ).rejects.toThrow('Could not set Soroban data!') + expect(TransactionBuilder).toHaveBeenCalledWith({}, transactionBuilderOptions) + }) + + it('should throw error adding operation', async () => { + ;(TransactionBuilder as unknown as jest.Mock).mockImplementationOnce(() => { + return { + addOperation: jest.fn(() => { + throw new Error('error') + }), + } + }) + + await expect( + buildTransactionPipeline.execute({ + ...MOCKED_BT_INPUT, + operations: [ + Operation.payment({ + destination: 'GB3MXH633VRECLZRUAR3QCLQJDMXNYNHKZCO6FJEWXVWSUEIS7NU376P', + asset: Asset.native(), + amount: '100', + }), + ], + }) + ).rejects.toThrow('Could not add operations!') + expect(TransactionBuilder).toHaveBeenCalledWith({}, transactionBuilderOptions) + }) + }) + + describe('Process', () => { + beforeEach(() => { + buildTransactionPipeline = new BuildTransactionPipeline() + jest.clearAllMocks() + }) + + it('should build transaction successfully', async () => { + await expect(buildTransactionPipeline.execute(MOCKED_BT_INPUT)).resolves.toEqual(MOCKED_BT_OUTPUT) + }) + }) +}) diff --git a/src/stellar-plus/core/pipelines/build-transaction/types.ts b/src/stellar-plus/core/pipelines/build-transaction/types.ts index 4b74bee..af1b3de 100644 --- a/src/stellar-plus/core/pipelines/build-transaction/types.ts +++ b/src/stellar-plus/core/pipelines/build-transaction/types.ts @@ -1,6 +1,6 @@ import { xdr } from '@stellar/stellar-sdk' -import { HorizonHandler } from 'stellar-plus' +import { HorizonHandler } from 'stellar-plus/horizon/types' import { EnvelopeHeader, Transaction } from 'stellar-plus/types' import { BeltPluginType, GenericPlugin } from 'stellar-plus/utils/pipeline/conveyor-belts/types' diff --git a/src/stellar-plus/horizon/index.ts b/src/stellar-plus/horizon/index.ts index 28c5047..6f76954 100644 --- a/src/stellar-plus/horizon/index.ts +++ b/src/stellar-plus/horizon/index.ts @@ -5,7 +5,7 @@ import { HorizonHandler } from 'stellar-plus/horizon/types' import { NetworkConfig } from 'stellar-plus/types' export class HorizonHandlerClient implements HorizonHandler { - private networkConfig: NetworkConfig + public networkConfig: NetworkConfig public server: Horizon.Server /** From 961ad28164848d3af4c39665d64288e2c8850518 Mon Sep 17 00:00:00 2001 From: Bruno Nascimento Date: Mon, 1 Apr 2024 12:15:15 -0300 Subject: [PATCH 2/4] chore: merge with develop --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 25c8849..5fa8a3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "stellar-plus", - "version": "0.6.2", + "version": "0.7.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "stellar-plus", - "version": "0.6.2", + "version": "0.7.0", "license": "ISC", "dependencies": { "@stellar/freighter-api": "^1.7.1", From 7249e02bbb82b7a3a2065f9f41b1af688ee982b3 Mon Sep 17 00:00:00 2001 From: Bruno Nascimento Date: Mon, 1 Apr 2024 13:45:08 -0300 Subject: [PATCH 3/4] test: add build transaction failure tests --- .../pipelines/build-transaction/index.unit.test.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/stellar-plus/core/pipelines/build-transaction/index.unit.test.ts b/src/stellar-plus/core/pipelines/build-transaction/index.unit.test.ts index 5f43d92..d00dae5 100644 --- a/src/stellar-plus/core/pipelines/build-transaction/index.unit.test.ts +++ b/src/stellar-plus/core/pipelines/build-transaction/index.unit.test.ts @@ -199,6 +199,20 @@ describe('BuildTransactionPipeline', () => { ).rejects.toThrow('Could not add operations!') expect(TransactionBuilder).toHaveBeenCalledWith({}, transactionBuilderOptions) }) + + it('should throw error building operation', async () => { + ;(TransactionBuilder as unknown as jest.Mock).mockImplementationOnce(() => { + return { + setTimeout: jest.fn(), + build: jest.fn(() => { + throw new Error('error') + }), + } + }) + + await expect(buildTransactionPipeline.execute(MOCKED_BT_INPUT)).rejects.toThrow('Could not build transaction!') + expect(TransactionBuilder).toHaveBeenCalledWith({}, transactionBuilderOptions) + }) }) describe('Process', () => { From 44561cbb145670b9aca6dd823b140c6bb8b6ba63 Mon Sep 17 00:00:00 2001 From: Bruno Nascimento Date: Mon, 1 Apr 2024 13:52:52 -0300 Subject: [PATCH 4/4] refactor: make networkConfig variable private --- src/stellar-plus/horizon/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stellar-plus/horizon/index.ts b/src/stellar-plus/horizon/index.ts index 6f76954..28c5047 100644 --- a/src/stellar-plus/horizon/index.ts +++ b/src/stellar-plus/horizon/index.ts @@ -5,7 +5,7 @@ import { HorizonHandler } from 'stellar-plus/horizon/types' import { NetworkConfig } from 'stellar-plus/types' export class HorizonHandlerClient implements HorizonHandler { - public networkConfig: NetworkConfig + private networkConfig: NetworkConfig public server: Horizon.Server /**