Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENG-353] Add mempool unit and e2e tests #90

Merged
merged 7 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
})
})
habibitcoin marked this conversation as resolved.
Show resolved Hide resolved

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)
}
})
})
})
habibitcoin marked this conversation as resolved.
Show resolved Hide resolved

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
Loading