import { BaseError, ContractFunctionExecutionError, ContractFunctionRevertedError, ContractFunctionZeroDataError, parseAbi, } from 'viem' import * as EarnSharePrices from './EarnSharePrices.js' const abi = parseAbi(['function previewRedeem(uint256 shares) view returns (uint256 assets)']) function executionError(cause: BaseError) { return new ContractFunctionExecutionError(cause, { abi, args: [1n], functionName: 'previewRedeem', }) } describe('timestamps', () => { test('anchors daily observations to the requested start', () => { expect( EarnSharePrices.timestamps({ from: '2026-07-01T09:30:00Z', to: '2026-07-03T12:00:00Z', }), ).toMatchInlineSnapshot(` [ "2026-07-01T09:30:00.000Z", "2026-07-02T09:30:00.000Z", "2026-07-03T09:30:00.000Z", ] `) }) }) describe('isUnavailable', () => { test('accepts only contract execution failures that prove a quote is unavailable', () => { expect([ EarnSharePrices.isUnavailable( executionError(new ContractFunctionRevertedError({ abi, functionName: 'previewRedeem' })), ), EarnSharePrices.isUnavailable( executionError(new ContractFunctionZeroDataError({ functionName: 'previewRedeem' })), ), EarnSharePrices.isUnavailable(executionError(new BaseError('RPC request timed out.'))), EarnSharePrices.isUnavailable(new Error('RPC request timed out.')), ]).toMatchInlineSnapshot(` [ true, true, false, false, ] `) }) }) describe('quoteShareAmount', () => { test('returns one whole share through the uint256 decimal limit', () => { expect([ EarnSharePrices.quoteShareAmount({ shareDecimals: 0 }).toString(), EarnSharePrices.quoteShareAmount({ shareDecimals: 18 }).toString(), EarnSharePrices.quoteShareAmount({ shareDecimals: 77 }).toString(), ]).toMatchInlineSnapshot(` [ "1", "1000000000000000000", "100000000000000000000000000000000000000000000000000000000000000000000000000000", ] `) }) test('rejects a whole share that exceeds uint256', () => { expect(() => EarnSharePrices.quoteShareAmount({ shareDecimals: 78 }), ).toThrowErrorMatchingInlineSnapshot( `[EarnSharePrices.UnsupportedShareDecimalsError: Share tokens with 78 decimals cannot represent one whole share as a uint256.]`, ) }) }) describe('parseCoverage', () => { test('accepts the latest indexed timestamp at the requested observation', () => { expect( EarnSharePrices.parseCoverage({ row: { timestamp: '2026-07-02T00:00:00.000Z' }, timestamp: '2026-07-02T00:00:00.000Z', }), ).toBeUndefined() }) test('rejects observations beyond indexed coverage', () => { expect(() => EarnSharePrices.parseCoverage({ row: { timestamp: '2026-07-01T00:00:00.000Z' }, timestamp: '2026-07-02T00:00:00.000Z', }), ).toThrowErrorMatchingInlineSnapshot( `[EarnSharePrices.IndexedCoverageError: Indexed history does not yet cover 2026-07-02T00:00:00.000Z.]`, ) }) test('rejects malformed indexed coverage', () => { expect(() => EarnSharePrices.parseCoverage({ row: { timestamp: 'not-a-timestamp' }, timestamp: '2026-07-02T00:00:00.000Z', }), ).toThrowErrorMatchingInlineSnapshot( `[EarnSharePrices.MalformedResponseError: TIDX returned no indexed coverage timestamp.]`, ) }) }) describe('parseBlocks', () => { test('returns boundaries in requested order', () => { const timestamps = ['2026-07-01T00:00:00.000Z', '2026-07-02T00:00:00.000Z'] expect( EarnSharePrices.parseBlocks({ rows: [ { kind: 'boundary', num: 20, requested_at: timestamps[1], timestamp: '2026-07-01T23:59:59.000Z', }, { kind: 'boundary', num: 10, requested_at: timestamps[0], timestamp: '2026-06-30T23:59:59.000Z', }, ], timestamps, }), ).toMatchInlineSnapshot(` [ { "number": 10, "requestedAt": "2026-07-01T00:00:00.000Z", "timestamp": "2026-06-30T23:59:59.000Z", }, { "number": 20, "requestedAt": "2026-07-02T00:00:00.000Z", "timestamp": "2026-07-01T23:59:59.000Z", }, ] `) }) test('classifies an absent boundary separately from malformed data', () => { expect(() => EarnSharePrices.parseBlocks({ rows: [], timestamps: ['2026-07-02T00:00:00.000Z'], }), ).toThrowErrorMatchingInlineSnapshot( `[EarnSharePrices.IndexedBlockNotFoundError: No indexed block exists at or before 2026-07-02T00:00:00.000Z.]`, ) }) test('rejects malformed boundary rows as upstream data failures', () => { expect(() => EarnSharePrices.parseBlocks({ rows: [ { kind: 'boundary', num: 'not-a-number', requested_at: '2026-07-02T00:00:00.000Z', timestamp: '2026-07-01T23:59:59.000Z', }, ], timestamps: ['2026-07-02T00:00:00.000Z'], }), ).toThrowErrorMatchingInlineSnapshot( `[EarnSharePrices.MalformedResponseError: TIDX returned a malformed share-price boundary.]`, ) }) })