import { describe, it, expect, vi, beforeEach } from "vitest"; import { toCzk, getRate, __resetRateCacheForTest, } from "../services/exchange-rates"; // Mock global fetch const mockFetch = vi.fn(); global.fetch = mockFetch; describe("exchange-rates", () => { beforeEach(() => { mockFetch.mockReset(); // The rate cache is module-level and persists across tests. Without this // reset, the first test to populate "today" would short-circuit every // later mockFetch resolution (cache hit), making assertions order-dependent // and asserting stale rates. Reset it so each test gets a clean fetch. __resetRateCacheForTest(); }); describe("toCzk", () => { it("returns amount unchanged for CZK", async () => { const result = await toCzk(123.45, "CZK"); expect(result).toBe(123.45); }); it("throws for unknown currency when API fails and no cache", async () => { mockFetch.mockRejectedValue(new Error("Network error")); await expect(toCzk(100, "XYZ")).rejects.toThrow( /Nepodařilo se získat aktuální kurzy/, ); }); it("throws for unknown currency even when API succeeds", async () => { mockFetch.mockResolvedValue({ ok: true, json: async () => ({ rates: [{ currencyCode: "EUR", rate: 25, amount: 1 }], }), }); await expect(toCzk(100, "XYZ")).rejects.toThrow(/Neznámá měna: XYZ/); }); it("converts EUR using fetched rate", async () => { mockFetch.mockResolvedValue({ ok: true, json: async () => ({ rates: [{ currencyCode: "EUR", rate: 25, amount: 1 }], }), }); const result = await toCzk(100, "EUR"); expect(result).toBe(2500); }); it("honours amount != 1 (currency quoted per 100 units)", async () => { // CNB quotes some currencies (e.g. HUF, JPY) per 100 units: the `rate` // is for `amount` units, so the per-unit rate is rate/amount. Here the // per-unit rate is 250/100 = 2.5 CZK; converting 100 units → 250 CZK. // If the service ignored `amount` it would compute 100*250 = 25000. mockFetch.mockResolvedValue({ ok: true, json: async () => ({ rates: [{ currencyCode: "HUF", rate: 250, amount: 100 }], }), }); const result = await toCzk(100, "HUF"); expect(result).toBe(250); }); }); describe("getRate", () => { it("returns 1 for CZK", async () => { const result = await getRate("CZK"); expect(result).toBe(1); }); it("throws for unknown currency", async () => { mockFetch.mockResolvedValue({ ok: true, json: async () => ({ rates: [{ currencyCode: "EUR", rate: 25, amount: 1 }], }), }); await expect(getRate("XYZ")).rejects.toThrow(/Neznámá měna: XYZ/); }); it("returns the per-unit rate for amount != 1", async () => { // rate 250 for amount 100 → per-unit rate 2.5 (CZK per 1 unit). mockFetch.mockResolvedValue({ ok: true, json: async () => ({ rates: [{ currencyCode: "HUF", rate: 250, amount: 100 }], }), }); const result = await getRate("HUF"); expect(result).toBe(2.5); }); }); });