import { beforeEach, describe, expect, test } from "bun:test"; import { _resetForTesting, _setReaderForTesting, extractClientIP, getGeoInfo, getUserAgent, lookupGeoFromIP, } from "./geo.js"; const createHeaders = (entries: Record): Headers => { return new Headers(entries); }; describe("extractClientIP", () => { test("extracts from CF-Connecting-IP", () => { const headers = createHeaders({ "CF-Connecting-IP": "1.2.3.4" }); expect(extractClientIP(headers)).toBe("1.2.3.4"); }); test("extracts from True-Client-IP", () => { const headers = createHeaders({ "True-Client-IP": "5.6.7.8" }); expect(extractClientIP(headers)).toBe("5.6.7.8"); }); test("extracts from X-Real-IP", () => { const headers = createHeaders({ "X-Real-IP": "10.20.30.40" }); expect(extractClientIP(headers)).toBe("10.20.30.40"); }); test("extracts first IP from X-Forwarded-For", () => { const headers = createHeaders({ "X-Forwarded-For": "100.1.2.3, 200.4.5.6, 10.0.0.1", }); expect(extractClientIP(headers)).toBe("100.1.2.3"); }); test("extracts from X-Client-IP", () => { const headers = createHeaders({ "X-Client-IP": "192.168.1.1" }); expect(extractClientIP(headers)).toBe("192.168.1.1"); }); test("prioritizes CF-Connecting-IP over others", () => { const headers = createHeaders({ "CF-Connecting-IP": "1.1.1.1", "True-Client-IP": "2.2.2.2", "X-Real-IP": "3.3.3.3", "X-Forwarded-For": "4.4.4.4", "X-Client-IP": "5.5.5.5", }); expect(extractClientIP(headers)).toBe("1.1.1.1"); }); test("returns null when no IP headers present", () => { expect(extractClientIP(createHeaders({}))).toBeNull(); }); test("trims whitespace", () => { const headers = createHeaders({ "CF-Connecting-IP": " 1.2.3.4 " }); expect(extractClientIP(headers)).toBe("1.2.3.4"); }); test("handles IPv6", () => { const headers = createHeaders({ "CF-Connecting-IP": "2001:0db8:85a3::8a2e:0370:7334", }); expect(extractClientIP(headers)).toBe("2001:0db8:85a3::8a2e:0370:7334"); }); }); describe("lookupGeoFromIP", () => { beforeEach(() => { _resetForTesting(); }); test("returns nulls when reader not initialized", () => { expect(lookupGeoFromIP("8.8.8.8")).toEqual({ city: null, region: null, country: null, }); }); test("returns geo data from reader", () => { _setReaderForTesting({ get: (ip: string) => ip === "8.8.8.8" ? { city: { names: { en: "Mountain View" } }, subdivisions: [{ names: { en: "California" } }], country: { iso_code: "US" }, } : null, } as never); expect(lookupGeoFromIP("8.8.8.8")).toEqual({ city: "Mountain View", region: "California", country: "US", }); }); test("returns nulls for unknown IP", () => { _setReaderForTesting({ get: () => null } as never); expect(lookupGeoFromIP("0.0.0.0")).toEqual({ city: null, region: null, country: null, }); }); test("handles partial geo data", () => { _setReaderForTesting({ get: () => ({ country: { iso_code: "DE" } }), } as never); expect(lookupGeoFromIP("1.2.3.4")).toEqual({ city: null, region: null, country: "DE", }); }); test("handles reader errors gracefully", () => { _setReaderForTesting({ get: () => { throw new Error("Lookup failed"); }, } as never); expect(lookupGeoFromIP("invalid")).toEqual({ city: null, region: null, country: null, }); }); }); describe("getGeoInfo", () => { beforeEach(() => { _resetForTesting(); }); test("uses Cloudflare geo headers when present", () => { const headers = createHeaders({ "CF-Connecting-IP": "1.2.3.4", "CF-IPCountry": "US", "CF-IPCity": "San Francisco", "CF-Region": "California", }); expect(getGeoInfo(headers)).toEqual({ ip: "1.2.3.4", city: "San Francisco", region: "California", country: "US", }); }); test("prefers CF headers over GeoIP lookup", () => { _setReaderForTesting({ get: () => ({ city: { names: { en: "Wrong" } }, country: { iso_code: "XX" }, }), } as never); const headers = createHeaders({ "CF-Connecting-IP": "1.2.3.4", "CF-IPCountry": "US", }); expect(getGeoInfo(headers).country).toBe("US"); }); test("falls back to GeoIP lookup when no CF geo headers", () => { _setReaderForTesting({ get: () => ({ city: { names: { en: "Berlin" } }, subdivisions: [{ names: { en: "Berlin" } }], country: { iso_code: "DE" }, }), } as never); const headers = createHeaders({ "X-Real-IP": "1.2.3.4" }); expect(getGeoInfo(headers)).toEqual({ ip: "1.2.3.4", city: "Berlin", region: "Berlin", country: "DE", }); }); test("returns IP even without geo data", () => { const headers = createHeaders({ "X-Forwarded-For": "203.0.113.50" }); const result = getGeoInfo(headers); expect(result.ip).toBe("203.0.113.50"); expect(result.city).toBeNull(); }); test("returns all nulls when no headers", () => { expect(getGeoInfo(createHeaders({}))).toEqual({ ip: null, city: null, region: null, country: null, }); }); }); describe("getUserAgent", () => { test("returns User-Agent header", () => { const headers = createHeaders({ "User-Agent": "Mozilla/5.0 Chrome/120" }); expect(getUserAgent(headers)).toBe("Mozilla/5.0 Chrome/120"); }); test('returns "Unknown" when missing', () => { expect(getUserAgent(createHeaders({}))).toBe("Unknown"); }); });