import { describe, expect, test } from 'vitest' import * as Rates from './Rates.js' describe('parseMorpho', () => { test('accepts additive Morpho GraphQL metadata', () => { expect( Rates.parseMorpho({ maxBps: 2_000, minBps: 0, response: { data: { vaultByAddress: { state: { dailyNetApy: 0.0342 } } }, extensions: { tracing: { duration: 1 } }, }, }), ).toMatchInlineSnapshot('342') }) test.each([ { apy: 0, bps: 0 }, { apy: 0.0341999, bps: 341 }, { apy: 0.0342, bps: 342 }, { apy: 0.0342999, bps: 342 }, { apy: 0.2, bps: 2_000 }, { apy: 1, bps: 10_000 }, ])('floors daily APY $apy to $bps basis points', ({ apy, bps }) => { expect( Rates.parseMorpho({ maxBps: 10_000, minBps: 0, response: { data: { vaultByAddress: { state: { dailyNetApy: apy } } } }, }), ).toBe(bps) }) test.each([ { apy: 0.0342, bps: 342, label: 'minimum' }, { apy: 0.2, bps: 2_000, label: 'maximum' }, ])('accepts the configured $label', ({ apy, bps }) => { expect( Rates.parseMorpho({ maxBps: 2_000, minBps: 342, response: { data: { vaultByAddress: { state: { dailyNetApy: apy } } } }, }), ).toBe(bps) }) test.each([0.0341, 0.2001])('rejects daily APY %s outside configured bounds', (apy) => { expect(() => Rates.parseMorpho({ maxBps: 2_000, minBps: 342, response: { data: { vaultByAddress: { state: { dailyNetApy: apy } } } }, }), ).toThrowErrorMatchingInlineSnapshot( '[Error: Morpho API rate is outside the configured bounds.]', ) }) test.each([-0.01, 1.0001, null])('rejects invalid daily APY %s', (apy) => { expect(() => Rates.parseMorpho({ maxBps: 10_000, minBps: 0, response: { data: { vaultByAddress: { state: { dailyNetApy: apy } } } }, }), ).toThrowErrorMatchingInlineSnapshot('[Error: Morpho API returned an invalid daily net APY.]') }) test.each([NaN, Infinity, -Infinity, '0.0342', undefined])( 'rejects malformed daily APY %s', (apy) => { expect(() => Rates.parseMorpho({ maxBps: 10_000, minBps: 0, response: { data: { vaultByAddress: { state: { dailyNetApy: apy } } } }, }), ).toThrow() }, ) test.each([ { data: { vaultByAddress: null } }, { data: { vaultByAddress: { state: null } } }, { data: { vaultByAddress: { state: {} } } }, { data: { vaultByAddress: { state: { netApy: 0.1999 } } } }, { data: null, errors: [{ message: 'Vault unavailable.' }] }, {}, ])('rejects missing daily observations without using spot APY: %j', (response) => { expect(() => Rates.parseMorpho({ maxBps: 2_000, minBps: 0, response })).toThrow() }) }) describe('target', () => { test('returns a fixed target without consulting an external dependency', async () => { await expect( Rates.target({ boundary: '2026-08-29T12:00:00.000Z', fetch: globalThis.fetch, now: () => new Date('2026-08-29T12:17:00.000Z'), source: { bps: 425 }, }), ).resolves.toMatchInlineSnapshot(` { "annualRateBps": 425, "observedAt": "2026-08-29T12:00:00.000Z", } `) }) test('requests the explicit one-day average from Morpho', async () => { const result = await Rates.target({ boundary: '2026-08-29T12:00:00.000Z', fetch: (input, init) => { if (typeof init?.body !== 'string') throw new Error('Expected a JSON request body.') expect(JSON.parse(init.body)).toMatchInlineSnapshot(` { "query": "query EarnRewardRate($address: String!) { vaultByAddress(address: $address, chainId: 8453) { state { dailyNetApy: avgNetApy(lookback: ONE_DAY) } } }", "variables": { "address": "0xeE8F4eC5672F09119b96Ab6fB59C27E1b7e44b61", }, } `) return globalThis.fetch(input, init) }, now: () => new Date('2026-08-29T12:17:00.000Z'), source: { maxBps: 10_000, minBps: 0, morphoVault: '0xeE8F4eC5672F09119b96Ab6fB59C27E1b7e44b61', staleAfterSeconds: 300, }, }) expect(result).toMatchObject({ annualRateBps: expect.any(Number), observedAt: '2026-08-29T12:17:00.000Z', }) expect(Number.isInteger(result.annualRateBps)).toBe(true) expect(result.stale).toBeUndefined() }) test.each([ { age: 299, reusable: true }, { age: 300, reusable: true }, { age: 301, reusable: false }, ])('applies the fallback freshness limit at $age seconds', async ({ age, reusable }) => { const result = Rates.target({ boundary: '2026-08-29T12:00:00.000Z', fallback: { annualRateBps: 342, observedAt: '2026-08-29T12:00:00.000Z' }, fetch: globalThis.fetch, now: () => new Date(Date.parse('2026-08-29T12:00:00.000Z') + age * 1_000), source: { maxBps: 2_000, minBps: 0, morphoVault: '0x0000000000000000000000000000000000000000', staleAfterSeconds: 300, }, }) if (reusable) await expect(result).resolves.toMatchInlineSnapshot(` { "annualRateBps": 342, "observedAt": "2026-08-29T12:00:00.000Z", "stale": true, } `) else await expect(result).rejects.toThrow() }) })