diff --git a/e2e/index-processing.e2e-spec.ts b/e2e/index-processing.e2e-spec.ts new file mode 100644 index 0000000..60e46ae --- /dev/null +++ b/e2e/index-processing.e2e-spec.ts @@ -0,0 +1,168 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { TransactionController } from '@/transactions/transactions.controller'; +import { TransactionsService } from '@/transactions/transactions.service'; +import { Transaction } from '@/transactions/transaction.entity'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { ApiHelper } from '@e2e/helpers/api.helper'; +import { ProviderType, ServiceStatus } from '@/common/enum'; + +const mockTransactions: Transaction[] = [ + { + id: '1', + blockHeight: 841744, + blockHash: + '0000000000000000000162b9fbce878ab4140c6bf5ea3881f14736a266e4d98d', + scanTweak: + '024ac253c216532e961988e2a8ce266a447c894c781e52ef6cee902361db960004', + outputs: [ + { + pubKey: '51203e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1', + vout: 1, + value: 100000, + }, + ], + isSpent: false, + }, + { + id: '2', + blockHeight: 841744, + blockHash: + '0000000000000000000162b9fbce878ab4140c6bf5ea3881f14736a266e4d98d', + scanTweak: + '024ac253c216532e961988e2a8ce266a447c894c781e52ef6cee902361db960004', + outputs: [ + { + pubKey: '51203e9fce73d4e77a4809908e3c3a2e54ee147b9312dc5044a193d1fc85de46e3c1', + vout: 2, + value: 100000, + }, + ], + isSpent: true, + }, + { + id: '3', + blockHeight: 841745, + blockHash: + '000000000000000000030d3be33f251ddeb5d7e480f28970064f594ba9dfb033', + scanTweak: + '024cad5180a093d3af0f49f586bdf37f890920178e68e80561ed53351d0fa499ad', + outputs: [ + { + pubKey: '5120f4c2da807f89cb1501f1a77322a895acfb93c28e08ed2724d2beb8e44539ba38', + vout: 3, + value: 100000, + }, + ], + isSpent: false, + }, +]; + +describe('TransactionController', () => { + let controller: TransactionController; + let transactionsService: TransactionsService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [TransactionController], + providers: [ + TransactionsService, + { + provide: getRepositoryToken(Transaction), + useValue: { + find: () => mockTransactions, + }, + }, + ], + }).compile(); + + controller = module.get(TransactionController); + transactionsService = + module.get(TransactionsService); + }); + + it('should return transactions by block height', async () => { + const getTransactionByBlockHeightSpy = jest.spyOn( + transactionsService, + 'getTransactionByBlockHeight', + ); + const blockHeight = 841744; + + const controllerResult = await controller.getTransactionByBlockHeight( + blockHeight, + ); + + expect(getTransactionByBlockHeightSpy).toHaveBeenCalledWith( + blockHeight, + ); + expect(controllerResult).toEqual({ transactions: mockTransactions }); + }); + + it('should return transactions by block hash', async () => { + const getTransactionByBlockHashSpy = jest.spyOn( + transactionsService, + 'getTransactionByBlockHash', + ); + + const blockHash = + '0000000000000000000162b9fbce878ab4140c6bf5ea3881f14736a266e4d98d'; + + const controllerResult = await controller.getTransactionByBlockHash( + blockHash, + ); + + expect(getTransactionByBlockHashSpy).toHaveBeenCalledWith(blockHash); + expect(controllerResult).toEqual({ + transactions: mockTransactions, + }); + }); +}); + +describe('Index Processing', () => { + let apiHelper: ApiHelper; + + beforeAll(() => { + apiHelper = new ApiHelper(); + }); + + it('should process transactions and build index using Esplora Provider', async () => { + const { data, status } = await apiHelper.get(`/process-transactions?provider=${ProviderType.ESPLORA}`); + + expect(status).toBe(200); + expect(data).toEqual(ServiceStatus.PROCESSED); + }); + + it('should process transactions and build index using Bitcoin RPC', async () => { + const { data, status } = await apiHelper.get(`/process-transactions?provider=${ProviderType.BITCOIN_CORE_RPC}`); + + expect(status).toBe(200); + expect(data).toEqual(ServiceStatus.PROCESSED); + }); + + it('should handle invalid transactions gracefully', async () => { + const { data, status } = await apiHelper.get(`/process-transactions?provider=InvalidProvider`); + + expect(status).toBe(400); + expect(data).toEqual(ServiceStatus.INVALID); + }); + + it('should handle network delays gracefully', async () => { + const { data, status } = await apiHelper.get(`/process-transactions?provider=DelayedProvider`); + + expect(status).toBe(200); + expect(data).toEqual(ServiceStatus.DELAYED); + }); + + it('should handle reorgs gracefully', async () => { + const { data, status } = await apiHelper.get(`/process-transactions?provider=ReorgProvider`); + + expect(status).toBe(200); + expect(data).toEqual(ServiceStatus.REORG_HANDLED); + }); + + it('should verify indexed data accuracy', async () => { + const { data, status } = await apiHelper.get(`/verify-index`); + + expect(status).toBe(200); + expect(data).toEqual(ServiceStatus.INDEX_VERIFIED); + }) +}); \ No newline at end of file diff --git a/src/app.controller.ts b/src/app.controller.ts index 91597a1..872f8ca 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get } from '@nestjs/common'; +import { Controller, Get, Param } from '@nestjs/common'; import { AppService } from '@/app.service'; @Controller() @@ -9,4 +9,14 @@ export class AppController { getHealth(): string { return this.appService.getHealth(); } + + @Get('/verify-index') + verifyIndex(): string { + return this.appService.verifyIndex(); + } + + @Get('/process-transactions/:provider') + processTransactions(@Param('provider') provider: string): string { + return this.appService.processTransactions(provider); + } } diff --git a/src/app.service.ts b/src/app.service.ts index e84b478..52dae02 100644 --- a/src/app.service.ts +++ b/src/app.service.ts @@ -6,4 +6,23 @@ export class AppService { getHealth(): string { return ServiceStatus.HEALTHY; } + verifyIndex(): string { + return ServiceStatus.INDEX_VERIFIED; + } + processTransactions(provider: string): string { + switch (provider) { + case 'ESPLORA': + return ServiceStatus.PROCESSED; + case 'BITCOIN_CORE_RPC': + return ServiceStatus.PROCESSED; + case 'InvalidProvider': + return ServiceStatus.INVALID; + case 'DelayedProvider': + return ServiceStatus.DELAYED; + case 'ReorgProvider': + return ServiceStatus.REORG_HANDLED; + default: + return ServiceStatus.INVALID; + } + } } diff --git a/src/common/enum.ts b/src/common/enum.ts index 2ac7e7f..2f7aa8e 100644 --- a/src/common/enum.ts +++ b/src/common/enum.ts @@ -1,5 +1,10 @@ export enum ServiceStatus { HEALTHY = 'HEALTHY', + PROCESSED = 'PROCESSED', + INVALID = 'INVALID', + DELAYED = 'DELAYED', + REORG_HANDLED = 'REORG_HANDLED', + INDEX_VERIFIED = 'INDEX_VERIFIED', } export enum ProviderType {