- Create currency.service.ts with frankfurter.app ECB rate fetching - 24h in-memory cache with stale-serve fallback on fetch failure - convertPrice() handles EUR-base cross-currency conversion - CURRENCY_MARKET_MAP maps currencies to market regions - 12 unit tests covering conversion, rounding, unknowns, and mapping Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
88 lines
2.5 KiB
TypeScript
88 lines
2.5 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import {
|
|
CURRENCY_MARKET_MAP,
|
|
type ExchangeRates,
|
|
convertPrice,
|
|
getMarketForCurrency,
|
|
} from "../../src/server/services/currency.service";
|
|
|
|
const mockRates: ExchangeRates = {
|
|
base: "EUR",
|
|
date: "2026-04-13",
|
|
rates: {
|
|
EUR: 1,
|
|
USD: 1.08,
|
|
GBP: 0.86,
|
|
JPY: 163.5,
|
|
CAD: 1.47,
|
|
AUD: 1.65,
|
|
},
|
|
};
|
|
|
|
describe("convertPrice", () => {
|
|
it("returns same amount when from === to", () => {
|
|
expect(convertPrice(1000, "EUR", "EUR", mockRates)).toBe(1000);
|
|
expect(convertPrice(2500, "USD", "USD", mockRates)).toBe(2500);
|
|
});
|
|
|
|
it("converts EUR to USD correctly", () => {
|
|
// 1000 EUR cents * 1.08 = 1080 USD cents
|
|
expect(convertPrice(1000, "EUR", "USD", mockRates)).toBe(1080);
|
|
});
|
|
|
|
it("converts USD to EUR correctly", () => {
|
|
// 1080 USD cents / 1.08 * 1 = 1000 EUR cents
|
|
expect(convertPrice(1080, "USD", "EUR", mockRates)).toBe(1000);
|
|
});
|
|
|
|
it("converts between non-EUR currencies via EUR base", () => {
|
|
// 1000 USD cents → EUR: 1000 / 1.08 = 925.926 → GBP: 925.926 * 0.86 = 796.296 → rounded 796
|
|
expect(convertPrice(1000, "USD", "GBP", mockRates)).toBe(796);
|
|
});
|
|
|
|
it("rounds to nearest integer", () => {
|
|
// 100 EUR cents * 1.08 = 108 USD cents (exact)
|
|
expect(convertPrice(100, "EUR", "USD", mockRates)).toBe(108);
|
|
// 100 EUR cents * 0.86 = 86 GBP cents (exact)
|
|
expect(convertPrice(100, "EUR", "GBP", mockRates)).toBe(86);
|
|
});
|
|
|
|
it("returns original for unknown currency", () => {
|
|
expect(convertPrice(1000, "EUR", "XYZ", mockRates)).toBe(1000);
|
|
expect(convertPrice(1000, "XYZ", "EUR", mockRates)).toBe(1000);
|
|
});
|
|
});
|
|
|
|
describe("CURRENCY_MARKET_MAP", () => {
|
|
it("maps EUR to EU", () => {
|
|
expect(CURRENCY_MARKET_MAP.EUR).toBe("EU");
|
|
});
|
|
|
|
it("maps USD to US", () => {
|
|
expect(CURRENCY_MARKET_MAP.USD).toBe("US");
|
|
});
|
|
|
|
it("maps GBP to UK", () => {
|
|
expect(CURRENCY_MARKET_MAP.GBP).toBe("UK");
|
|
});
|
|
|
|
it("maps all 6 supported currencies", () => {
|
|
expect(Object.keys(CURRENCY_MARKET_MAP)).toHaveLength(6);
|
|
expect(CURRENCY_MARKET_MAP.JPY).toBe("JP");
|
|
expect(CURRENCY_MARKET_MAP.CAD).toBe("CA");
|
|
expect(CURRENCY_MARKET_MAP.AUD).toBe("AU");
|
|
});
|
|
});
|
|
|
|
describe("getMarketForCurrency", () => {
|
|
it("returns mapped market for known currency", () => {
|
|
expect(getMarketForCurrency("EUR")).toBe("EU");
|
|
expect(getMarketForCurrency("USD")).toBe("US");
|
|
expect(getMarketForCurrency("GBP")).toBe("UK");
|
|
});
|
|
|
|
it("returns currency itself for unknown currency", () => {
|
|
expect(getMarketForCurrency("XYZ")).toBe("XYZ");
|
|
});
|
|
});
|