diff --git a/__tests__/exchangeRateService.test.ts b/__tests__/exchangeRateService.test.ts new file mode 100644 index 0000000..c9c153c --- /dev/null +++ b/__tests__/exchangeRateService.test.ts @@ -0,0 +1,94 @@ +import { describe, expect, it, jest, mock, beforeEach, beforeAll } from "bun:test"; +import { prepareExchangeRates, wipeExchangeRates } from '../src/content/exchangeRateService.ts' +import { getStorageValue, setStorageValue } from '../src/content/storage' +import { storage_exchangerates, storage_exchangerates_next, storage_exchangerates_update } from "../src/content/config"; +import { logDebug, logError } from '../src/content/utils/log'; +declare function GM_fetch(input: string | URL | globalThis.Request, init?: RequestInit): Promise + +// Mock the storage functions +mock.module('../src/content/storage', () => ({ + setStorageValue: mock(async (key: string, value: any) => {}), + getStorageValue: mock(async (key: string, defaultValue: any) => defaultValue) +})); + +// Mock the log functions +mock.module('../src/content/utils/log', () => ({ + logDebug: mock(() => {}), + log: mock(() => {}), + logError: mock(() => {}) +})); + +describe('wipeExchangeRates', () => { + it('should clear exchange rate storage and set update timestamps', async () => { + await wipeExchangeRates(); + + expect(setStorageValue).toHaveBeenCalledWith(storage_exchangerates, null); + expect(setStorageValue).toHaveBeenCalledWith(storage_exchangerates_update, expect.any(String)); + expect(setStorageValue).toHaveBeenCalledWith(storage_exchangerates_next, expect.any(String)); + expect(logDebug).toHaveBeenCalled(); + }); +}); + +describe('prepareExchangeRates', () => { + const mockRates = { USD: 1, EUR: 0.85, GBP: 0.73 }; + + beforeAll(() => { + jest + .spyOn(global, 'GM_fetch') + .mockImplementation(() => { + return Promise.resolve({ + ok: false, + status: 500 + } as Response); + }); + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return stored rates if they are up to date', async () => { + (getStorageValue as jest.Mock).mockImplementation(async (key) => { + if (key === storage_exchangerates) return mockRates; + if (key === storage_exchangerates_update) return new Date().toISOString(); + if (key === storage_exchangerates_next) return new Date(Date.now() + 100000).toISOString(); + return null; + }); + + const rates = await prepareExchangeRates(); + expect(rates).toEqual(mockRates); + expect(GM_fetch).not.toHaveBeenCalled(); + }); + + it('should fetch new rates when they are expired', async () => { + (getStorageValue as jest.Mock).mockImplementation(async (key) => { + if (key === storage_exchangerates_update) return new Date(Date.now() - 100000).toISOString(); + if (key === storage_exchangerates_next) return new Date(Date.now() - 50000).toISOString(); + return null; + }); + (GM_fetch as jest.Mock).mockResolvedValue({ + ok: true, + json: async () => ({ + rates: mockRates, + time_next_update_utc: new Date(Date.now() + 100000).toISOString() + }) + }); + + const rates = await prepareExchangeRates(); + expect(rates).toEqual(mockRates); + expect(GM_fetch).toHaveBeenCalled(); + expect(setStorageValue).toHaveBeenCalledWith(storage_exchangerates, mockRates); + }); + + it('should handle fetch errors gracefully', async () => { + (getStorageValue as jest.Mock).mockResolvedValue(null); + (GM_fetch as jest.Mock).mockResolvedValueOnce({ + ok: false, + status: 500 + } as Response); + + const rates = await prepareExchangeRates(); + expect(rates).toBeNull(); + expect(logError).toHaveBeenCalled(); + }); +});