/* * Copyright (C) 2026 Fluxer Contributors * * This file is part of Fluxer. * * Fluxer is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Fluxer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Fluxer. If not, see . */ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'; import {__clearIpCache, DEFAULT_CC, FETCH_TIMEOUT_MS, formatGeoipLocation, getCountryCode} from './IpUtils'; vi.mock('~/Config', () => ({Config: {geoip: {provider: 'ipinfo', host: 'geoip:8080'}}})); describe('getCountryCode(ip)', () => { const realFetch = globalThis.fetch; beforeEach(() => { __clearIpCache(); vi.useRealTimers(); vi.restoreAllMocks(); }); afterEach(() => { globalThis.fetch = realFetch; }); it('returns US if GEOIP_HOST is not set', async () => { vi.doMock('~/Config', () => ({Config: {geoip: {provider: 'ipinfo', host: ''}}})); const {getCountryCode: modFn, __clearIpCache: reset} = await import('./IpUtils'); reset(); const cc = await modFn('8.8.8.8'); expect(cc).toBe(DEFAULT_CC); }); it('returns US for invalid IP', async () => { const cc = await getCountryCode('not_an_ip'); expect(cc).toBe(DEFAULT_CC); }); it('accepts bracketed IPv6', async () => { globalThis.fetch = vi.fn().mockResolvedValue({ok: true, text: () => Promise.resolve('se')}); const cc = await getCountryCode('[2001:db8::1]'); expect(cc).toBe('SE'); }); it('returns uppercase alpha-2 on success', async () => { globalThis.fetch = vi.fn().mockResolvedValue({ok: true, text: () => Promise.resolve('se')}); const cc = await getCountryCode('8.8.8.8'); expect(cc).toBe('SE'); expect(globalThis.fetch).toHaveBeenCalledTimes(1); }); it('falls back on non-2xx', async () => { globalThis.fetch = vi.fn().mockResolvedValue({ok: false, text: () => Promise.resolve('se')}); const cc = await getCountryCode('8.8.8.8'); expect(cc).toBe(DEFAULT_CC); }); it('falls back on invalid body', async () => { globalThis.fetch = vi.fn().mockResolvedValue({ok: true, text: () => Promise.resolve('USA')}); const cc = await getCountryCode('8.8.8.8'); expect(cc).toBe(DEFAULT_CC); }); it('falls back on network error', async () => { globalThis.fetch = vi.fn().mockRejectedValue(new Error('fail')); const cc = await getCountryCode('8.8.8.8'); expect(cc).toBe(DEFAULT_CC); }); it('uses cache for repeated lookups within TTL', async () => { const spyNow = vi.spyOn(Date, 'now'); spyNow.mockReturnValueOnce(1_000); globalThis.fetch = vi.fn().mockResolvedValue({ok: true, text: () => Promise.resolve('gb')}); const r1 = await getCountryCode('1.1.1.1'); expect(r1).toBe('GB'); spyNow.mockReturnValueOnce(2_000); const r2 = await getCountryCode('1.1.1.1'); expect(r2).toBe('GB'); expect(globalThis.fetch).toHaveBeenCalledTimes(1); }); it('aborts after timeout', async () => { vi.useFakeTimers(); const abortingFetch = vi.fn( (_url, opts) => new Promise((_res, rej) => { opts?.signal?.addEventListener('abort', () => rej(Object.assign(new Error('AbortError'), {name: 'AbortError'})), ); }), ); globalThis.fetch = abortingFetch; const p = getCountryCode('8.8.8.8'); vi.advanceTimersByTime(FETCH_TIMEOUT_MS + 5); await expect(p).resolves.toBe(DEFAULT_CC); expect(abortingFetch).toHaveBeenCalledTimes(1); }); }); describe('formatGeoipLocation(result)', () => { it('returns Unknown Location when no parts are available', () => { const label = formatGeoipLocation({ countryCode: DEFAULT_CC, normalizedIp: '1.1.1.1', reason: null, city: null, region: null, countryName: null, }); expect(label).toBe('Unknown Location'); }); it('concatenates available city, region, and country', () => { const label = formatGeoipLocation({ countryCode: 'US', normalizedIp: '1.1.1.1', reason: null, city: 'San Francisco', region: 'CA', countryName: 'United States', }); expect(label).toBe('San Francisco, CA, United States'); }); });