@ledgerhq/coin-tezos
Version:
124 lines (105 loc) • 3.71 kB
text/typescript
// SPDX-FileCopyrightText: © 2026 LEDGER SAS
// SPDX-License-Identifier: Apache-2.0
import type { TezosCoinConfig, TezosContext } from '../config'
import { getBlockInfo } from './getBlockInfo'
const mockGetBlockByLevel = jest.fn()
jest.mock('../network', () => ({
createTzktApi: () => ({
getBlockByLevel: (...args: unknown[]) => mockGetBlockByLevel(...args),
}),
}))
const context: TezosContext = {
config: async () => ({}) as unknown as TezosCoinConfig,
logger: () => undefined,
}
describe('getBlockInfo', () => {
beforeEach(() => {
jest.clearAllMocks()
})
it('maps level, hash, timestamp, and parent from the TzKT block response', async () => {
// Given
const level = 7_000_000
mockGetBlockByLevel.mockResolvedValueOnce({
level,
hash: 'BLockHashABC123',
timestamp: '2024-06-15T10:30:00Z',
} as any)
mockGetBlockByLevel.mockResolvedValueOnce({
level: level - 1,
hash: 'BLockHashParent',
timestamp: '2024-06-15T10:29:52Z',
} as any)
// When
const result = await getBlockInfo(context, level)
// Then
expect(mockGetBlockByLevel).toHaveBeenCalledTimes(2)
expect(mockGetBlockByLevel).toHaveBeenCalledWith(level)
expect(mockGetBlockByLevel).toHaveBeenCalledWith(level - 1)
expect(result).toEqual({
height: level,
hash: 'BLockHashABC123',
time: new Date('2024-06-15T10:30:00Z'),
parent: { height: level - 1, hash: 'BLockHashParent' },
})
})
it('parses the timestamp string into a proper Date instance', async () => {
// Given
const isoDate = '2025-01-01T00:00:00.000Z'
mockGetBlockByLevel.mockResolvedValue({
level: 8_000_000,
hash: 'BLockHashXYZ',
timestamp: isoDate,
} as any)
// When
const result = await getBlockInfo(context, 8_000_000)
// Then
expect(result.time).toBeInstanceOf(Date)
expect(result.time.toISOString()).toBe(isoDate)
})
it('throws for height = 0 without calling the API', async () => {
// When / Then
await expect(getBlockInfo(context, 0)).rejects.toThrow(
'getBlockInfo: height must be a positive integer, got 0'
)
expect(mockGetBlockByLevel).not.toHaveBeenCalled()
})
it('throws for negative height without calling the API', async () => {
// When / Then
await expect(getBlockInfo(context, -5)).rejects.toThrow(
'getBlockInfo: height must be a positive integer, got -5'
)
expect(mockGetBlockByLevel).not.toHaveBeenCalled()
})
it.each([NaN, 1.5, Infinity, -Infinity])(
'throws for non-safe-integer height (%s) without calling the API',
async (height) => {
await expect(getBlockInfo(context, height)).rejects.toThrow(
'getBlockInfo: height must be a positive integer'
)
expect(mockGetBlockByLevel).not.toHaveBeenCalled()
}
)
it('includes parent from the predecessor block fetched in parallel', async () => {
// Given – genesis-adjacent block: level 1 with level 0 as parent
mockGetBlockByLevel.mockResolvedValueOnce({
level: 1,
hash: 'BLBlock1',
timestamp: '2018-06-30T16:07:40Z',
} as any)
mockGetBlockByLevel.mockResolvedValueOnce({
level: 0,
hash: 'BLGenesis',
timestamp: '2018-06-30T16:07:32Z',
} as any)
// When
const result = await getBlockInfo(context, 1)
// Then
expect(result.parent).toEqual({ height: 0, hash: 'BLGenesis' })
})
it('propagates errors thrown by the network layer', async () => {
// Given
mockGetBlockByLevel.mockRejectedValue(new Error('TzKT unreachable'))
// When / Then
await expect(getBlockInfo(context, 999_999)).rejects.toThrow('TzKT unreachable')
})
})