UNPKG

ipflare

Version:

IP Geolocation API, our API enables you to effortlessly obtain precise geolocation data for any IP address through a single endpoint. Benefit from ultra-fast responses—typically between 50-100ms—and enjoy reliable performance with 99.9% uptime.

606 lines (506 loc) 17.9 kB
import { IPFlare, isSuccess, isError } from "../index"; // Mock axios to avoid real API calls in tests jest.mock("axios", () => ({ create: jest.fn(() => ({ get: jest.fn(), post: jest.fn(), })), isAxiosError: jest.fn(), })); describe("IPFlare Safe API", () => { let ipflare: IPFlare; let mockClient: { get: jest.Mock; post: jest.Mock; }; beforeEach(() => { const axios = require("axios"); mockClient = { get: jest.fn(), post: jest.fn(), }; axios.create.mockReturnValue(mockClient); ipflare = new IPFlare({ apiKey: "test-api-key" }); }); describe("Type Guards", () => { it("should correctly identify success results", () => { const successResult = { ok: true as const, data: { ip: "8.8.8.8", in_eu: false, land_locked: false }, }; expect(isSuccess(successResult)).toBe(true); expect(isError(successResult)).toBe(false); }); it("should correctly identify error results", () => { const errorResult = { ok: false as const, error: { type: "INVALID_IP_ADDRESS" as const, message: "Invalid IP", }, }; expect(isSuccess(errorResult)).toBe(false); expect(isError(errorResult)).toBe(true); }); }); describe("lookup", () => { it("should return error for missing IP", async () => { const result = await ipflare.lookup(""); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("INVALID_INPUT"); expect(result.error.message).toBe("IP address is required"); } }); it("should return error for invalid IP", async () => { const result = await ipflare.lookup("invalid-ip"); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("INVALID_IP_ADDRESS"); expect(result.error.message).toContain("Invalid IP address format"); } }); it("should return error for non-string IP", async () => { const result = await ipflare.lookup(123 as unknown as string); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("INVALID_INPUT"); expect(result.error.message).toBe("IP address must be a string"); } }); it("should return success for valid IPv4", async () => { const mockResponse = { data: { ip: "8.8.8.8", city: "Mountain View", country_name: "United States", latitude: 37.4056, longitude: -122.0775, in_eu: false, land_locked: false, }, }; mockClient.get.mockResolvedValue(mockResponse); const result = await ipflare.lookup("8.8.8.8"); expect(result.ok).toBe(true); if (result.ok) { expect(result.data.ip).toBe("8.8.8.8"); expect(result.data.city).toBe("Mountain View"); expect(result.data.country_name).toBe("United States"); } }); it("should return success for valid IPv6", async () => { const mockResponse = { data: { ip: "2001:4860:4860::8888", city: "Mountain View", country_name: "United States", in_eu: false, land_locked: false, }, }; mockClient.get.mockResolvedValue(mockResponse); const result = await ipflare.lookup("2001:4860:4860::8888"); expect(result.ok).toBe(true); if (result.ok) { expect(result.data.ip).toBe("2001:4860:4860::8888"); } }); it("should include ASN and ISP fields when requested", async () => { const mockResponse = { data: { ip: "8.8.8.8", city: "Mountain View", country_name: "United States", in_eu: false, land_locked: false, asn: "AS15169", isp: "Google LLC", }, }; mockClient.get.mockResolvedValue(mockResponse); const result = await ipflare.lookup("8.8.8.8", { include: { asn: true, isp: true }, }); expect(result.ok).toBe(true); if (result.ok) { expect(result.data.asn).toBe("AS15169"); expect(result.data.isp).toBe("Google LLC"); } expect(mockClient.get).toHaveBeenCalledWith("/8.8.8.8", { params: { fields: "asn,isp" }, }); }); it("should handle 401 unauthorized error", async () => { const axios = require("axios"); const mockError = { response: { status: 401, data: { error: "Invalid API key" }, }, }; axios.isAxiosError.mockReturnValue(true); mockClient.get.mockRejectedValue(mockError); const result = await ipflare.lookup("8.8.8.8"); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("UNAUTHORIZED"); expect(result.error.message).toBe("Invalid API key"); expect(result.error.details).toEqual({ error: "Invalid API key" }); } }); it("should handle 429 quota exceeded error", async () => { const axios = require("axios"); const mockError = { response: { status: 429, data: { error: "Quota exceeded" }, }, }; axios.isAxiosError.mockReturnValue(true); mockClient.get.mockRejectedValue(mockError); const result = await ipflare.lookup("8.8.8.8"); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("QUOTA_EXCEEDED"); expect(result.error.message).toBe("Quota exceeded"); } }); it("should handle 500 internal server error", async () => { const axios = require("axios"); const mockError = { response: { status: 500, data: { error: "Internal server error" }, }, }; axios.isAxiosError.mockReturnValue(true); mockClient.get.mockRejectedValue(mockError); const result = await ipflare.lookup("8.8.8.8"); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("INTERNAL_SERVER_ERROR"); expect(result.error.message).toBe("Internal server error"); } }); it("should map API error for invalid IP", async () => { const axios = require("axios"); const mockError = { response: { status: 400, data: { error: "invalid ip address provided" }, }, }; axios.isAxiosError.mockReturnValue(true); mockClient.get.mockRejectedValue(mockError); const result = await ipflare.lookup("8.8.8.8"); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("INVALID_IP_ADDRESS"); expect(result.error.message).toBe("invalid ip address provided"); } }); it("should map API error for reserved IP", async () => { const axios = require("axios"); const mockError = { response: { status: 400, data: { error: "reserved ip address" }, }, }; axios.isAxiosError.mockReturnValue(true); mockClient.get.mockRejectedValue(mockError); const result = await ipflare.lookup("192.168.1.1"); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("RESERVED_IP_ADDRESS"); expect(result.error.message).toBe("reserved ip address"); } }); it("should map API error for geolocation not found", async () => { const axios = require("axios"); const mockError = { response: { status: 404, data: { error: "geolocation not found for this ip" }, }, }; axios.isAxiosError.mockReturnValue(true); mockClient.get.mockRejectedValue(mockError); const result = await ipflare.lookup("8.8.8.8"); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("GEOLOCATION_NOT_FOUND"); expect(result.error.message).toBe("geolocation not found for this ip"); } }); it("should map API error for no API key", async () => { const axios = require("axios"); const mockError = { response: { status: 400, data: { error: "no api key provided" }, }, }; axios.isAxiosError.mockReturnValue(true); mockClient.get.mockRejectedValue(mockError); const result = await ipflare.lookup("8.8.8.8"); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("NO_API_KEY_PROVIDED"); expect(result.error.message).toBe("no api key provided"); } }); it("should map API error for invalid input", async () => { const axios = require("axios"); const mockError = { response: { status: 400, data: { error: "invalid input parameters" }, }, }; axios.isAxiosError.mockReturnValue(true); mockClient.get.mockRejectedValue(mockError); const result = await ipflare.lookup("8.8.8.8"); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("INVALID_INPUT"); expect(result.error.message).toBe("invalid input parameters"); } }); it("should handle unknown errors", async () => { const axios = require("axios"); const unknownError = new Error("Something unexpected happened"); axios.isAxiosError.mockReturnValue(false); mockClient.get.mockRejectedValue(unknownError); const result = await ipflare.lookup("8.8.8.8"); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("UNKNOWN_ERROR"); expect(result.error.message).toBe("An unexpected error occurred"); expect(result.error.details).toBe(unknownError); } }); }); describe("bulkLookup", () => { it("should return error for non-array IPs", async () => { const result = await ipflare.bulkLookup({ ips: "not-an-array" as unknown as string[], }); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("INVALID_INPUT"); expect(result.error.message).toBe("IPs must be an array"); } }); it("should return error for empty IPs array", async () => { const result = await ipflare.bulkLookup({ ips: [] }); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("INVALID_INPUT"); expect(result.error.message).toBe( "At least one IP address is required" ); } }); it("should return error for too many IPs", async () => { const ips = Array(501).fill("8.8.8.8"); const result = await ipflare.bulkLookup({ ips }); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("INVALID_INPUT"); expect(result.error.message).toBe( "Maximum of 500 IPs per request allowed" ); } }); it("should return error for invalid IPs in array", async () => { const result = await ipflare.bulkLookup({ ips: ["8.8.8.8", "invalid-ip", "1.1.1.1"], }); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("INVALID_IP_ADDRESS"); expect(result.error.message).toContain("Invalid IP addresses found"); expect(result.error.details).toEqual({ invalidIPs: ["invalid-ip"] }); } }); it("should return error for non-string IPs in array", async () => { const result = await ipflare.bulkLookup({ ips: ["8.8.8.8", 123 as unknown as string, "1.1.1.1"], }); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("INVALID_IP_ADDRESS"); expect(result.error.message).toContain("Invalid IP addresses found"); } }); it("should return success for valid bulk lookup", async () => { const mockResponse = { data: { results: [ { ip: "8.8.8.8", status: "success", data: { ip: "8.8.8.8", city: "Mountain View", country_name: "United States", in_eu: false, land_locked: false, }, }, { ip: "1.1.1.1", status: "success", data: { ip: "1.1.1.1", city: "San Francisco", country_name: "United States", in_eu: false, land_locked: false, }, }, ], }, }; mockClient.post.mockResolvedValue(mockResponse); const result = await ipflare.bulkLookup({ ips: ["8.8.8.8", "1.1.1.1"], }); expect(result.ok).toBe(true); if (result.ok) { expect(result.data).toHaveLength(2); expect(result.data[0].status).toBe("success"); expect(result.data[1].status).toBe("success"); } }); it("should include fields in bulk lookup", async () => { const mockResponse = { data: { results: [ { ip: "8.8.8.8", status: "success", data: { ip: "8.8.8.8", city: "Mountain View", country_name: "United States", in_eu: false, land_locked: false, asn: "AS15169", isp: "Google LLC", }, }, ], }, }; mockClient.post.mockResolvedValue(mockResponse); const result = await ipflare.bulkLookup({ ips: ["8.8.8.8"], include: { asn: true, isp: true }, }); expect(result.ok).toBe(true); expect(mockClient.post).toHaveBeenCalledWith( "/bulk-lookup", { ips: ["8.8.8.8"] }, { params: { fields: "asn,isp" } } ); }); it("should handle invalid response format", async () => { const mockResponse = { data: { // Missing results or invalid format invalid: "response", }, }; mockClient.post.mockResolvedValue(mockResponse); const result = await ipflare.bulkLookup({ ips: ["8.8.8.8"], }); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("INTERNAL_SERVER_ERROR"); expect(result.error.message).toBe("Invalid response format from API"); } }); it("should handle all error scenarios in bulk lookup", async () => { const axios = require("axios"); // Test 401 error let mockError = { response: { status: 401, data: { error: "Invalid API key" }, }, }; axios.isAxiosError.mockReturnValue(true); mockClient.post.mockRejectedValue(mockError); let result = await ipflare.bulkLookup({ ips: ["8.8.8.8"] }); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("UNAUTHORIZED"); } // Test 429 error mockError = { response: { status: 429, data: { error: "Quota exceeded" }, }, }; mockClient.post.mockRejectedValue(mockError); result = await ipflare.bulkLookup({ ips: ["8.8.8.8"] }); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("QUOTA_EXCEEDED"); } // Test 500 error mockError = { response: { status: 500, data: { error: "Internal server error" }, }, }; mockClient.post.mockRejectedValue(mockError); result = await ipflare.bulkLookup({ ips: ["8.8.8.8"] }); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("INTERNAL_SERVER_ERROR"); } // Test API error mapping mockError = { response: { status: 400, data: { error: "reserved ip address detected" }, }, }; mockClient.post.mockRejectedValue(mockError); result = await ipflare.bulkLookup({ ips: ["192.168.1.1"] }); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("RESERVED_IP_ADDRESS"); } // Test API error with unknown message (falls back to UNKNOWN_ERROR) mockError = { response: { status: 400, data: { error: "some unknown error message" }, }, }; mockClient.post.mockRejectedValue(mockError); result = await ipflare.bulkLookup({ ips: ["8.8.8.8"] }); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("UNKNOWN_ERROR"); } // Test unknown error const unknownError = new Error("Something unexpected"); axios.isAxiosError.mockReturnValue(false); mockClient.post.mockRejectedValue(unknownError); result = await ipflare.bulkLookup({ ips: ["8.8.8.8"] }); expect(result.ok).toBe(false); if (!result.ok) { expect(result.error.type).toBe("UNKNOWN_ERROR"); } }); }); });