UNPKG

@bsv/sdk

Version:

BSV Blockchain Software Development Kit

324 lines (274 loc) 9.91 kB
import ARC from '../../../transaction/broadcasters/ARC' import Transaction from '../../../transaction/Transaction' import { NodejsHttpClient } from '../../../transaction/http/NodejsHttpClient' import { FetchHttpClient } from '../../../transaction/http/FetchHttpClient' import { HttpClientRequestOptions } from '../../http' import { RequestOptions } from 'https' // Mock Transaction jest.mock('../../../transaction/Transaction', () => { class MockTransaction { toHex (): string { return 'mocked_transaction_hex' } toHexEF (): string { return 'mocked_transaction_hexEF' } } return { __esModule: true, default: MockTransaction } }) describe('ARC Broadcaster', () => { const URL = 'https://example.com' const successResponse = { status: 200, data: { txid: 'mocked_txid', txStatus: 'success', extraInfo: 'received' } } let transaction: Transaction beforeEach(() => { transaction = new Transaction() }) it('should broadcast successfully using window.fetch', async () => { // Mocking window.fetch const mockFetch = mockedFetch(successResponse) global.window = { fetch: mockFetch } as unknown as Window & typeof globalThis const broadcaster = new ARC(URL) const response = await broadcaster.broadcast(transaction) expect(mockFetch).toHaveBeenCalled() expect(response).toEqual({ status: 'success', txid: 'mocked_txid', message: 'success received' }) }) it('should broadcast successfully using Node.js https', async () => { // Mocking Node.js https module mockedHttps(successResponse) if ('window' in globalThis) { delete (globalThis as { window?: unknown }).window // ✅ Explicit property check } const broadcaster = new ARC(URL) const response = await broadcaster.broadcast(transaction) expect(response).toEqual({ status: 'success', txid: 'mocked_txid', message: 'success received' }) }) it('should broadcast successfully using provided fetch', async () => { const mockFetch = mockedFetch(successResponse) const broadcaster = new ARC(URL, { httpClient: new FetchHttpClient(mockFetch) }) const response = await broadcaster.broadcast(transaction) expect(mockFetch).toHaveBeenCalled() expect(response).toEqual({ status: 'success', txid: 'mocked_txid', message: 'success received' }) }) it('should broadcast successfully using provided https', async () => { const mockHttps = mockedHttps(successResponse) const broadcaster = new ARC(URL, { httpClient: new NodejsHttpClient(mockHttps) }) const response = await broadcaster.broadcast(transaction) expect(response).toEqual({ status: 'success', txid: 'mocked_txid', message: 'success received' }) }) it('should send default request headers when broadcasting', async () => { const mockFetch = mockedFetch(successResponse) const broadcaster = new ARC(URL, { httpClient: new FetchHttpClient(mockFetch) }) await broadcaster.broadcast(transaction) // Ensure headers exist and cast to the correct type const requestOptions = mockFetch.mock.calls[0][1] as HttpClientRequestOptions const headers = (requestOptions?.headers ?? {}) // ✅ Proper typing expect(headers['Content-Type']).toEqual('application/json') expect(headers['XDeployment-ID']).toBeDefined() expect(headers['XDeployment-ID']).toMatch(/ts-sdk-.*/) expect(headers.Authorization).toBeUndefined() }) it('should send authorization header when api key is provided', async () => { const mockFetch = mockedFetch(successResponse) const apiKey = 'mainnet_1234567890' const broadcaster = new ARC(URL, { apiKey, httpClient: new FetchHttpClient(mockFetch) }) await broadcaster.broadcast(transaction) // Extract and properly type headers const requestOptions = mockFetch.mock.calls[0][1] as HttpClientRequestOptions const headers = (requestOptions?.headers ?? {}) // ✅ Correct typing expect(headers['XDeployment-ID']).toBeDefined() expect(headers['XDeployment-ID']).toMatch(/ts-sdk-.*/) expect(headers.Authorization).toEqual(`Bearer ${apiKey}`) // ✅ Now properly typed }) it('should handle api key as second argument', async () => { const mockFetch = mockedFetch(successResponse) global.window = { fetch: mockFetch } as unknown as Window & typeof globalThis const apiKey = 'mainnet_1234567890' const broadcaster = new ARC(URL, apiKey) await broadcaster.broadcast(transaction) // Ensure headers is always defined const headers = (mockFetch.mock.calls[0][1] as HttpClientRequestOptions)?.headers ?? {} expect(headers.Authorization).toEqual(`Bearer ${apiKey}`) }) it('should send provided deployment id', async () => { const mockFetch = mockedFetch(successResponse) const deploymentId = 'custom_deployment_id' const broadcaster = new ARC(URL, { deploymentId, httpClient: new FetchHttpClient(mockFetch) }) await broadcaster.broadcast(transaction) // Ensure headers is always defined const headers = (mockFetch.mock.calls[0]?.[1]?.headers) ?? {} expect(headers['XDeployment-ID']).toEqual(deploymentId) }) it('should handle network errors', async () => { const mockFetch = jest.fn().mockRejectedValue(new Error('Network error')) global.window = { fetch: mockFetch } as unknown as Window & typeof globalThis const broadcaster = new ARC(URL, { httpClient: new FetchHttpClient(mockFetch) }) const response = await broadcaster.broadcast(transaction) expect(mockFetch).toHaveBeenCalled() expect(response).toEqual({ status: 'error', code: '500', description: 'Network error' }) }) it('should handle non-200 responses', async () => { const mockFetch = mockedFetch({ status: 400, data: JSON.stringify({ detail: 'Bad request' }) }) const broadcaster = new ARC(URL, { httpClient: new FetchHttpClient(mockFetch) }) const response = await broadcaster.broadcast(transaction) expect(mockFetch).toHaveBeenCalled() expect(response).toEqual({ status: 'error', code: '400', description: 'Bad request', more: { detail: 'Bad request' } }) }) it('handles error 460', async () => { // Mock the fetch response to simulate the error 460 response const mockFetch = mockedFetch({ status: 460, data: { status: 460, detail: 'Transaction is not in extended format, missing input scripts', txid: 'd21633ba23f70118185227be58a63527675641ad37967e2aa461559f577aec43' } }) // Initialize the ARC broadcaster with the mocked fetch client const URL = 'https://arc.gorillapool.io' const apiKey = 'mock_api_key' // Example API key const broadcaster = new ARC(URL, { apiKey, httpClient: new FetchHttpClient(mockFetch) }) // Simulate broadcasting the transaction const response = await broadcaster.broadcast(transaction) // Check if the response is an error as expected if (response.status === 'error') { // Validate the BroadcastFailure response properties expect(response.status).toBe('error') expect(response.code).toBe('460') expect(response.description).toBe( 'Transaction is not in extended format, missing input scripts' ) expect(response.txid).toBe( 'd21633ba23f70118185227be58a63527675641ad37967e2aa461559f577aec43' ) expect(response.more).toBeTruthy() // Validate the presence of additional error details } else { // Fail the test if the response is not an error fail('Expected a BroadcastFailure but got a BroadcastResponse') } }) function mockedFetch (response: { status: number, data: any }): jest.Mock { return jest.fn().mockResolvedValue({ ok: response.status === 200, status: response.status, statusText: response.status === 200 ? 'OK' : 'Bad request', headers: { get: (key: string): string | undefined => { if (key === 'Content-Type') { return 'application/json; charset=UTF-8' } return undefined } }, json: async () => response.data }) } function mockedHttps (response: { status: number, data: any }): { request: ( url: string, options: RequestOptions, callback: (res: { statusCode: number statusMessage: string headers: { 'content-type': string } on: (event: string, handler: (chunk?: any) => void) => void }) => void ) => { on: jest.Mock write: jest.Mock end: jest.Mock } } { const https = { request: ( url: string, options: RequestOptions, callback: (res: { statusCode: number statusMessage: string headers: { 'content-type': string } on: (event: string, handler: (chunk?: any) => void) => void }) => void ) => { const mockResponse = { statusCode: response.status, statusMessage: response.status === 200 ? 'OK' : 'Bad request', headers: { 'content-type': 'application/json; charset=UTF-8' }, on (event: string, handler: (chunk?: any) => void) { if (event === 'data') handler(JSON.stringify(response.data)) if (event === 'end') handler() } } // ✅ Call the callback asynchronously to match Node.js behavior process.nextTick(() => callback(mockResponse)) return { on: jest.fn(), write: jest.fn(), end: jest.fn() } } } jest.mock('https', () => https) return https } })