/** * Tests for proxy API client */ import { beforeEach, describe, expect, test, vi, } from 'vitest'; import type { UnifiedFont } from '../../model/types'; import type { ProxyFontsResponse } from './proxyFonts'; vi.mock('$shared/api/api', () => ({ api: { get: vi.fn(), }, })); import { api } from '$shared/api/api'; import { fetchFontsByIds, fetchProxyFontById, fetchProxyFonts, } from './proxyFonts'; const PROXY_API_URL = 'https://api.glyphdiff.com/api/v1/fonts'; function createMockFont(overrides: Partial = {}): UnifiedFont { return { id: 'roboto', family: 'Roboto', provider: 'google', category: 'sans-serif', variants: [], subsets: [], ...overrides, } as UnifiedFont; } function mockApiGet(data: T) { vi.mocked(api.get).mockResolvedValueOnce({ data, status: 200 }); } describe('proxyFonts', () => { beforeEach(() => { vi.mocked(api.get).mockReset(); }); describe('fetchProxyFonts', () => { test('should fetch fonts with no params', async () => { const mockResponse: ProxyFontsResponse = { fonts: [createMockFont()], total: 1, limit: 50, offset: 0, }; mockApiGet(mockResponse); const result = await fetchProxyFonts(); expect(api.get).toHaveBeenCalledWith(PROXY_API_URL); expect(result).toEqual(mockResponse); }); test('should build URL with query params', async () => { const mockResponse: ProxyFontsResponse = { fonts: [createMockFont()], total: 1, limit: 20, offset: 0, }; mockApiGet(mockResponse); await fetchProxyFonts({ provider: 'google', category: 'sans-serif', limit: 20, offset: 0 }); const calledUrl = vi.mocked(api.get).mock.calls[0][0]; expect(calledUrl).toContain('provider=google'); expect(calledUrl).toContain('category=sans-serif'); expect(calledUrl).toContain('limit=20'); expect(calledUrl).toContain('offset=0'); }); test('should throw on invalid response (missing fonts array)', async () => { mockApiGet({ total: 0 }); await expect(fetchProxyFonts()).rejects.toThrow('Proxy API returned invalid response'); }); test('should throw on null response data', async () => { vi.mocked(api.get).mockResolvedValueOnce({ data: null, status: 200 }); await expect(fetchProxyFonts()).rejects.toThrow('Proxy API returned invalid response'); }); }); describe('fetchProxyFontById', () => { test('should return font matching the ID', async () => { const targetFont = createMockFont({ id: 'satoshi', name: 'Satoshi' }); const mockResponse: ProxyFontsResponse = { fonts: [createMockFont(), targetFont], total: 2, limit: 1000, offset: 0, }; mockApiGet(mockResponse); const result = await fetchProxyFontById('satoshi'); expect(result).toEqual(targetFont); }); test('should return undefined when font not found', async () => { const mockResponse: ProxyFontsResponse = { fonts: [createMockFont()], total: 1, limit: 1000, offset: 0, }; mockApiGet(mockResponse); const result = await fetchProxyFontById('nonexistent'); expect(result).toBeUndefined(); }); test('should search with the ID as query param', async () => { const mockResponse: ProxyFontsResponse = { fonts: [], total: 0, limit: 1000, offset: 0, }; mockApiGet(mockResponse); await fetchProxyFontById('Roboto'); const calledUrl = vi.mocked(api.get).mock.calls[0][0]; expect(calledUrl).toContain('limit=1000'); expect(calledUrl).toContain('q=Roboto'); }); }); describe('fetchFontsByIds', () => { test('should return empty array for empty input', async () => { const result = await fetchFontsByIds([]); expect(result).toEqual([]); expect(api.get).not.toHaveBeenCalled(); }); test('should call batch endpoint with comma-separated IDs', async () => { const fonts = [createMockFont({ id: 'roboto' }), createMockFont({ id: 'satoshi' })]; mockApiGet(fonts); const result = await fetchFontsByIds(['roboto', 'satoshi']); expect(api.get).toHaveBeenCalledWith(`${PROXY_API_URL}/batch?ids=roboto,satoshi`); expect(result).toEqual(fonts); }); test('should return empty array when response data is nullish', async () => { vi.mocked(api.get).mockResolvedValueOnce({ data: null, status: 200 }); const result = await fetchFontsByIds(['roboto']); expect(result).toEqual([]); }); }); });