Skip to content

Commit

Permalink
[ENG-353] Add mempool unit and e2e tests (#90)
Browse files Browse the repository at this point in the history
* Added tests for mempool client

* Covered: get_utxos_from_mempool_space, broadcast_to_mempool_space, get_address_txs

* Rename tests

* Lint

* Added real life tests

* Added real life tests for fees

* fix linter
  • Loading branch information
HashTraveler authored Apr 9, 2024
1 parent 897fc39 commit 9b4de98
Show file tree
Hide file tree
Showing 4 changed files with 563 additions and 326 deletions.
152 changes: 86 additions & 66 deletions __tests__/fees.js
Original file line number Diff line number Diff line change
@@ -1,81 +1,101 @@
describe('fees', () => {
beforeEach(() => {
jest.resetAllMocks()
jest.resetModules()
describe('real tests', () => {
const { get_fee_rate, get_min_next_block_fee_rate } = require('../fees')

it('should return positive fee', async () => {
expect.assertions(1)

const feeRate = await get_fee_rate()
expect(feeRate).toBeGreaterThan(0)
})

it('should return positive fee', async () => {
expect.assertions(1)

const feeRate = await get_min_next_block_fee_rate()
expect(feeRate).toBeGreaterThan(0)
})
})

describe('get_min_next_block_fee_rate', () => {
describe('original mempool up', () => {
beforeEach(() => {
jest.mock('axios', () => ({
...jest.requireActual('axios'),
create: () => ({
describe('mocked tests', () => {
beforeEach(() => {
jest.resetAllMocks()
jest.resetModules()
})

describe('get_min_next_block_fee_rate', () => {
describe('original mempool up', () => {
beforeEach(() => {
jest.mock('axios', () => ({
...jest.requireActual('axios'),
get: jest.fn().mockResolvedValue({ data: [{ feeRange: [8.1, 9.2, 10, 11, 17] }] }),
}),
}))
})
create: () => ({
...jest.requireActual('axios'),
get: jest.fn().mockResolvedValue({ data: [{ feeRange: [8.1, 9.2, 10, 11, 17] }] }),
}),
}))
})

test('return correct fee rate using MIN_FEE_BUFFER_PERCENT', async () => {
process.env.MIN_FEE_BUFFER_PERCENT = 1.1
delete process.env.NEXT_BLOCK_FEE_SLOT
const { get_min_next_block_fee_rate } = require('../fees')
test('return correct fee rate using MIN_FEE_BUFFER_PERCENT', async () => {
process.env.MIN_FEE_BUFFER_PERCENT = 1.1
delete process.env.NEXT_BLOCK_FEE_SLOT
const { get_min_next_block_fee_rate } = require('../fees')

expect(await get_min_next_block_fee_rate()).toBe(9.0)
})
expect(await get_min_next_block_fee_rate()).toBe(9.0)
})

test('return correct fee rate using nonzero NEXT_BLOCK_FEE_SLOT and MIN_FEE_BUFFER_PERCENT', async () => {
process.env.MIN_FEE_BUFFER_PERCENT = 1.1
process.env.NEXT_BLOCK_FEE_SLOT = 1
const { get_min_next_block_fee_rate } = require('../fees')
test('return correct fee rate using nonzero NEXT_BLOCK_FEE_SLOT and MIN_FEE_BUFFER_PERCENT', async () => {
process.env.MIN_FEE_BUFFER_PERCENT = 1.1
process.env.NEXT_BLOCK_FEE_SLOT = 1
const { get_min_next_block_fee_rate } = require('../fees')

expect(await get_min_next_block_fee_rate()).toBe(10.2)
expect(await get_min_next_block_fee_rate()).toBe(10.2)
})
})
})

describe('original mempool down', () => {
test('should print error to console, but return a valid value', async () => {
// Given
jest.mock('axios', () => jest.requireActual('axios'))
process.env.MEMPOOL_URL = 'http://mempool-not-real-to-fail.space'
process.env.MEMPOOL_RETRY_URL = 'http://mempool.space'
const consoleErrorSpy = jest.spyOn(global.console, 'error')

// When
const { get_min_next_block_fee_rate } = require('../fees')
const value = await get_min_next_block_fee_rate()

// Then
expect(value).toBeTruthy()
expect(consoleErrorSpy).toHaveBeenCalledWith(
`Attempted call with URL ${process.env.MEMPOOL_URL}/api/v1/fees/mempool-blocks failed: getaddrinfo ENOTFOUND mempool-not-real-to-fail.space`
)
})
describe('original mempool down', () => {
test('should print error to console, but return a valid value', async () => {
// Given
jest.mock('axios', () => jest.requireActual('axios'))
process.env.MEMPOOL_URL = 'http://mempool-not-real-to-fail.space'
process.env.MEMPOOL_RETRY_URL = 'http://mempool.space'
const consoleErrorSpy = jest.spyOn(global.console, 'error')

test('should print 2 errors to console and return error, when 2 mempool clients are down', async () => {
// Given
jest.mock('axios', () => jest.requireActual('axios'))
process.env.MEMPOOL_URL = 'http://mempool-not-real-to-fail.space'
process.env.MEMPOOL_RETRY_URL = 'http://mempool-not-real-to-fail2.space'
const consoleErrorSpy = jest.spyOn(global.console, 'error')

// When
const { get_min_next_block_fee_rate } = require('../fees')

// Then
try {
await get_min_next_block_fee_rate()
} catch (error) {
expect(error).toEqual(new Error('Could not get mempool blocks'))
expect(consoleErrorSpy).toHaveBeenNthCalledWith(
1,
`Attempted call with URL http://mempool-not-real-to-fail.space/api/v1/fees/mempool-blocks failed: getaddrinfo ENOTFOUND mempool-not-real-to-fail.space`
)
expect(consoleErrorSpy).toHaveBeenNthCalledWith(
2,
`Attempted 1 call(s) with URL http://mempool-not-real-to-fail2.space/api/v1/fees/mempool-blocks failed: getaddrinfo ENOTFOUND mempool-not-real-to-fail2.space`
// When
const { get_min_next_block_fee_rate } = require('../fees')
const value = await get_min_next_block_fee_rate()

// Then
expect(value).toBeTruthy()
expect(consoleErrorSpy).toHaveBeenCalledWith(
`Attempted call with URL ${process.env.MEMPOOL_URL}/api/v1/fees/mempool-blocks failed: getaddrinfo ENOTFOUND mempool-not-real-to-fail.space`
)
}
})

test('should print 2 errors to console and return error, when 2 mempool clients are down', async () => {
// Given
jest.mock('axios', () => jest.requireActual('axios'))
process.env.MEMPOOL_URL = 'http://mempool-not-real-to-fail.space'
process.env.MEMPOOL_RETRY_URL = 'http://mempool-not-real-to-fail2.space'
const consoleErrorSpy = jest.spyOn(global.console, 'error')

// When
const { get_min_next_block_fee_rate } = require('../fees')

// Then
try {
await get_min_next_block_fee_rate()
} catch (error) {
expect(error).toEqual(new Error('Could not get mempool blocks'))
expect(consoleErrorSpy).toHaveBeenNthCalledWith(
1,
`Attempted call with URL http://mempool-not-real-to-fail.space/api/v1/fees/mempool-blocks failed: getaddrinfo ENOTFOUND mempool-not-real-to-fail.space`
)
expect(consoleErrorSpy).toHaveBeenNthCalledWith(
2,
`Attempted 1 call(s) with URL http://mempool-not-real-to-fail2.space/api/v1/fees/mempool-blocks failed: getaddrinfo ENOTFOUND mempool-not-real-to-fail2.space`
)
}
})
})
})
})
Expand Down
153 changes: 153 additions & 0 deletions __tests__/mempool.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
describe('mempool', () => {
describe('real tests', () => {
const { getMempoolClient } = require('../utils/mempool')
let mempoolClient = getMempoolClient()

it('should create a mempool client', () => {
expect(mempoolClient).toBeTruthy()
})

describe('configuration', () => {
it('should have baseUrl equal to https://mempool.space', () => {
expect(mempoolClient.defaults.baseURL).toEqual('https://mempool.space')
})

it('should have 1 response interceptor', () => {
expect(mempoolClient.interceptors.response.handlers).toHaveLength(1)
})
})

describe('functionality', () => {
it('should return status 404 when unknown api is called', async () => {
try {
await mempoolClient.get('/api/v1/unknown')
} catch (error) {
console.log(error)
expect(error.response.status).toBe(404)
}
})

it('should return status 200 when /api/v1/fees/recommended is called', async () => {
expect.assertions(1)

const response = await mempoolClient.get('/api/v1/fees/recommended')
expect(response.status).toBe(200)
})

// eslint-disable-next-line max-len
it('should return status 200 and data.activity when /api/activity is called and MEMPOOL_RETRY_URL=https://www.boredapi.com (unknown endpoint for https://mempool.space, but known for https://www.boredapi.com)', async () => {
// Given
expect.assertions(2)

// When
jest.resetModules()
process.env.MEMPOOL_RETRY_URL = 'https://www.boredapi.com'
mempoolClient = require('../utils/mempool').getMempoolClient()

// Then
const response = await mempoolClient.get('/api/activity')
expect(response.data.activity).toBeTruthy()
expect(response.status).toBe(200)
})

// eslint-disable-next-line max-len
it('should return status 404 and no data.activity when /api/activity is called and MEMPOOL_RETRY_URL is default (unknown endpoint for https://mempool.space, but known for https://www.boredapi.com)', async () => {
// Given
expect.assertions(2)

// When
jest.resetModules()
process.env.MEMPOOL_RETRY_URL = undefined
mempoolClient = require('../utils/mempool').getMempoolClient()

// Then
try {
await mempoolClient.get('/api/activity')
} catch (error) {
expect(error.response.data?.activity).toBeFalsy()
expect(error.response.status).toBe(404)
}
})
})
})

describe('mocked tests', () => {
beforeEach(() => {
jest.resetAllMocks()
jest.resetModules()
})

it('should create a mempool client', () => {
const { getMempoolClient } = require('../utils/mempool')
expect(getMempoolClient()).toBeTruthy()
})

it('mempool client should have MEMPOOL_URL as baseURL', () => {
// Given
process.env.MEMPOOL_URL = 'https://mempool-url.space'

// Then
const { getMempoolClient } = require('../utils/mempool')
expect(getMempoolClient().defaults.baseURL).toBe(process.env.MEMPOOL_URL)
})

it('mempool client should use MEMPOOL_RETRY_URL and return first error when request fails', async () => {
// Given
const endpoint = '/api/not-existing-endpoint'
process.env.MEMPOOL_URL = 'https://mempool-url.space'
process.env.MEMPOOL_RETRY_URL = 'https://mempool-retry-url.space'
const { getMempoolClient } = require('../utils/mempool')
const mempoolClient = getMempoolClient()
const consoleErrorSpy = jest.spyOn(global.console, 'error')

// When
try {
await mempoolClient.get(endpoint)
} catch (error) {
// first request to MEMPOOL_URL
expect(error.message).toBe('getaddrinfo ENOTFOUND mempool-url.space')
// first request console error to MEMPOOL_URL
expect(consoleErrorSpy).toHaveBeenNthCalledWith(
1,
`Attempted call with URL ${process.env.MEMPOOL_URL}${endpoint} failed: getaddrinfo ENOTFOUND mempool-url.space`
)
// second request console error to MEMPOOL_RETRY_URL
expect(consoleErrorSpy).toHaveBeenNthCalledWith(
2,
`Attempted 1 call(s) with URL ${process.env.MEMPOOL_RETRY_URL}${endpoint} failed: getaddrinfo ENOTFOUND mempool-retry-url.space`
)
}
})

it('mempool client should use MEMPOOL_RETRY_URL as retry for 3 times when MEMPOOL_RETRY_ATTEMPTS is 3', async () => {
// Given
const endpoint = '/api/not-existing-endpoint'
process.env.MEMPOOL_URL = 'https://mempool-url.space'
process.env.MEMPOOL_RETRY_URL = 'https://mempool-retry-url.space'
process.env.MEMPOOL_RETRY_ATTEMPTS = 3
const { getMempoolClient } = require('../utils/mempool')
const mempoolClient = getMempoolClient()
const consoleErrorSpy = jest.spyOn(global.console, 'error')

// When
try {
await mempoolClient.get(endpoint)
} catch (error) {
// first request to MEMPOOL_URL
expect(consoleErrorSpy).toHaveBeenCalledTimes(4)
expect(consoleErrorSpy).toHaveBeenNthCalledWith(
2,
`Attempted 1 call(s) with URL ${process.env.MEMPOOL_RETRY_URL}${endpoint} failed: getaddrinfo ENOTFOUND mempool-retry-url.space`
)
expect(consoleErrorSpy).toHaveBeenNthCalledWith(
3,
`Attempted 2 call(s) with URL ${process.env.MEMPOOL_RETRY_URL}${endpoint} failed: getaddrinfo ENOTFOUND mempool-retry-url.space`
)
expect(consoleErrorSpy).toHaveBeenNthCalledWith(
4,
`Attempted 3 call(s) with URL ${process.env.MEMPOOL_RETRY_URL}${endpoint} failed: getaddrinfo ENOTFOUND mempool-retry-url.space`
)
}
})
})
})
Loading

0 comments on commit 9b4de98

Please sign in to comment.