import { describe, it, expect } from 'vitest'; import { Satoshis } from '@xocash/primitives'; import { RatesComposite, BaseRates, RatesCoinbase, } from '~/src/index.js'; /** * A simple stub that implements BaseRates and returns fixed values. */ class StubRate extends BaseRates { constructor(private factor: number) { super(); } public async start(): Promise {} public async stop(): Promise {} /** Return a fixed rate factor */ public async getRate(): Promise { return this.factor; } /** toBCH returns Satoshis.fromBCH(amount * factor) */ public async toBCH(amount: number): Promise { return Satoshis.fromBCH(amount / this.factor); } /** fromBCH returns (BCH amount) * factor */ public async fromBCH(amount: Satoshis): Promise { return amount.toBCH() * this.factor; } } // --- Unit tests with in-memory stub rates --- describe('RatesComposite (unit) with two stub rates', () => { // Use 99 and 101 so average will be exactly 100 const stub1 = new StubRate(99); const stub2 = new StubRate(101); const composite = new RatesComposite([stub1, stub2]); it('getRate should average the two rates to 100', async () => { // Act: composite over stub rates const avg = await composite.getRate('ANY'); // Assert: (99 + 101) / 2 = 100 expect(avg).toBeCloseTo(100, 3); }); it('toBCH should average returned BCH to 100 BCH', async () => { // amount=1, stub1 gives 99 BCH, stub2 gives 101 BCH -> avg = 100 BCH const sats = await composite.toBCH(1, 'ANY'); // Expect 1 USD = 1/100 BCH expect(sats.toBCH()).toBeCloseTo(0.01, 3); }); it('fromBCH should average returned foreign value to 100', async () => { // 1 BCH => stub1 returns 99, stub2 returns 101 -> avg = 100 const value = await composite.fromBCH(Satoshis.fromBCH(1), 'ANY'); expect(value).toBeCloseTo(100, 3); }); }); describe('RatesComposite (unit) with two very different stub rates', () => { const stub1 = new StubRate(100); const stub2 = new StubRate(600); const composite = new RatesComposite([stub1, stub2]); it('toBCH should average returned BCH to 100 BCH', async () => { // amount=1, stub1 gives 100 per BCH, stub2 gives 600 per BCH -> avg = 350 per BCH const sats = await composite.toBCH(1, 'ANY'); // Expect 1 USD = 1/350 BCH expect(sats.toBCH()).toBeCloseTo(0.00285, 4); }); it('fromBCH should average returned foreign value to 100', async () => { // 1 BCH => stub1 returns 100, stub2 returns 600 -> avg = 350 const value = await composite.fromBCH(Satoshis.fromBCH(1), 'ANY'); expect(value).toBeCloseTo(350, 3); }); it('composite toBCH/fromBCH roundtrip USD', async () => { const sats = await composite.toBCH(1, 'USD'); expect(sats.toBigInt()).toBeGreaterThan(BigInt(0)); const back = await composite.fromBCH(sats, 'USD'); expect(back).toBeGreaterThan(0); expect(back).toBeCloseTo(1.00, 3); }); }); // --- Integration tests combining CoinbaseRates and GpOraclesRates --- describe('AvgCompositeRates (integration) combining Coinbase & OracleRates', () => { it('integration: composite BCH->USD from Coinbase+Oracle should yield > 0', async () => { const cb = RatesCoinbase.from(); const mockRate = new StubRate(99); const composite = new RatesComposite([cb, mockRate]); await composite.start(); const rate = await composite.getRate('USD'); await composite.stop(); expect(rate).toBeGreaterThan(0); }, 20000); it('integration: composite toBCH/fromBCH roundtrip USD', async () => { const cb = RatesCoinbase.from(); const mockRate = new StubRate(100); const composite = new RatesComposite([cb, mockRate]); await composite.start(); const sats = await composite.toBCH(1, 'USD'); expect(sats.toBigInt()).toBeGreaterThan(BigInt(0)); const back = await composite.fromBCH(sats, 'USD'); expect(back).toBeGreaterThan(0); expect(back).toBeCloseTo(1.00, 3); await composite.stop(); }, 20000); });