Files
xo-stack/packages/stack/tests/rates/rates-composite.spec.ts
T

129 lines
4.0 KiB
TypeScript

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<void> {}
public async stop(): Promise<void> {}
/** Return a fixed rate factor */
public async getRate(): Promise<number> {
return this.factor;
}
/** toBCH returns Satoshis.fromBCH(amount * factor) */
public async toBCH(amount: number): Promise<Satoshis> {
return Satoshis.fromBCH(amount / this.factor);
}
/** fromBCH returns (BCH amount) * factor */
public async fromBCH(amount: Satoshis): Promise<number> {
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);
});