Added Rates Service: Base, Coinbase, Composite
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
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);
|
||||
});
|
||||
Reference in New Issue
Block a user