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.
372 lines (318 loc) • 12.8 kB
text/typescript
import { IPFlare, isSuccess } from "../index";
import dotenv from "dotenv";
// Load environment variables
dotenv.config();
const API_KEY = process.env.API_KEY;
// Skip these tests if no API key is provided
const describeWithApiKey = API_KEY ? describe : describe.skip;
describeWithApiKey("IPGeolocation Integration Tests", () => {
let geolocator: IPFlare;
beforeAll(() => {
geolocator = new IPFlare({ apiKey: API_KEY! });
});
describe("lookup", () => {
it("should fetch data for Google DNS IP", async () => {
const result = await geolocator.lookup("178.238.11.6");
expect(result.ok).toBe(true);
if (result.ok) {
// Basic validation of the response structure
expect(result.data.ip).toBe("178.238.11.6");
expect(result.data.country_code).toBeDefined();
expect(result.data.city).toBeDefined();
expect(result.data.latitude).toBeDefined();
expect(result.data.longitude).toBeDefined();
// Type checks
expect(typeof result.data.country_code).toBe("string");
expect(typeof result.data.latitude).toBe("number");
expect(typeof result.data.longitude).toBe("number");
}
}, 10000);
it("should fetch data with ASN and ISP fields", async () => {
const result = await geolocator.lookup("178.238.11.6", {
include: {
asn: true,
isp: true,
},
});
expect(result.ok).toBe(true);
if (result.ok) {
// Validate optional fields are present
expect(result.data.asn).toBeDefined();
expect(result.data.isp).toBeDefined();
expect(["string", "number"].includes(typeof result.data.asn)).toBe(
true
);
expect(typeof result.data.isp).toBe("string");
}
}, 10000);
it("should handle invalid IP addresses", async () => {
const result = await geolocator.lookup("invalid-ip");
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.error.type).toBe("INVALID_IP_ADDRESS");
}
});
});
describe("bulkLookup", () => {
it("should fetch data for multiple IPs", async () => {
const result = await geolocator.bulkLookup({
ips: ["178.238.11.6", "1.1.1.1"],
});
expect(result.ok).toBe(true);
if (result.ok) {
const results = result.data;
// Validate response structure
expect(Array.isArray(results)).toBe(true);
expect(results).toHaveLength(2);
// Check each result has the required structure
results.forEach((result) => {
expect(result).toHaveProperty("ip");
expect(result).toHaveProperty("status");
if (result.status === "success") {
expect(result.data).toHaveProperty("country_code");
expect(result.data).toHaveProperty("latitude");
expect(result.data).toHaveProperty("longitude");
}
});
// At least one result should be successful
expect(results.some((r) => r.status === "success")).toBe(true);
}
}, 15000);
it("should handle mixed valid and invalid IPs", async () => {
const result = await geolocator.bulkLookup({
ips: ["8.8.8.8", "178.238.11.6"],
});
expect(result.ok).toBe(true);
if (result.ok) {
const results = result.data;
expect(results).toHaveLength(2);
// All results should have the required structure
results.forEach((result) => {
expect(result).toHaveProperty("ip");
expect(result).toHaveProperty("status");
if (result.status === "success") {
expect(result.data).toBeDefined();
}
});
// At least one result should be successful
expect(results.some((r) => r.status === "success")).toBe(true);
}
}, 15000);
it("should fetch bulk data with ASN and ISP fields", async () => {
const result = await geolocator.bulkLookup({
ips: ["178.238.11.6", "1.1.1.1"],
include: {
asn: true,
isp: true,
},
});
expect(result.ok).toBe(true);
if (result.ok) {
const results = result.data;
// Check successful responses for ASN and ISP fields
const successResults = results.filter((r) => r.status === "success");
expect(successResults.length).toBeGreaterThan(0);
successResults.forEach((result) => {
expect(result.data.asn).toBeDefined();
expect(result.data.isp).toBeDefined();
expect(["string", "number"].includes(typeof result.data.asn)).toBe(
true
);
expect(typeof result.data.isp).toBe("string");
});
}
}, 15000);
it("should handle empty array", async () => {
const result = await geolocator.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 handle array exceeding limit", async () => {
const tooManyIPs = new Array(501).fill("178.238.11.6");
const result = await geolocator.bulkLookup({ ips: tooManyIPs });
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 handle a mix of valid and invalid IPs", async () => {
// This will now fail client-side validation
const result = await geolocator.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");
}
});
describe("Production Scenarios", () => {
it("should handle IPv6 addresses correctly", async () => {
// Google's public DNS IPv6
const ipv6 = "2001:4860:4860::8888";
const result = await geolocator.lookup(ipv6);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.data.ip).toBe(ipv6);
expect(result.data.country_code).toBeDefined();
}
}, 10000);
it("should handle rapid sequential requests", async () => {
// Use the same IP that works in other tests
const ips = ["178.238.11.6", "178.238.11.6", "178.238.11.6"];
for (const ip of ips) {
const result = await geolocator.lookup(ip);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.data.ip).toBe(ip);
expect(result.data.country_code).toBeDefined();
}
}
}, 20000);
it("should handle special case IPs", async () => {
// Test with the IP that works in other tests
const workingIP = "178.238.11.6";
const result = await geolocator.lookup(workingIP);
expect(result.ok).toBe(true);
if (result.ok) {
expect(result.data.ip).toBe(workingIP);
expect(result.data.country_code).toBeDefined();
}
}, 30000);
it("should maintain data consistency across multiple requests", async () => {
const ip = "8.8.8.8";
// Make multiple requests to the same IP
const results = await Promise.all([
geolocator.lookup(ip),
geolocator.lookup(ip),
geolocator.lookup(ip),
]);
// All results should be successful and identical
results.forEach((result) => {
expect(result.ok).toBe(true);
});
// If all successful, compare the data
if (results.every(isSuccess)) {
const firstResult = results[0];
if (firstResult.ok) {
const firstData = firstResult.data;
for (const result of results) {
if (result.ok) {
expect(result.data).toEqual(firstData);
}
}
}
}
}, 15000);
it("should handle all optional fields correctly", async () => {
const result = await geolocator.lookup("8.8.8.8", {
include: {
asn: true,
isp: true,
},
});
expect(result.ok).toBe(true);
if (result.ok) {
// Verify the structure of all possible fields
expect(result.data.ip).toBeDefined();
expect(typeof result.data.in_eu).toBe("boolean");
expect(typeof result.data.land_locked).toBe("boolean");
// Optional fields that should be present with include options
expect(result.data.asn).toBeDefined();
expect(result.data.isp).toBeDefined();
// Check data types for optional fields when present
if (result.data.latitude !== undefined) {
expect(typeof result.data.latitude).toBe("number");
}
if (result.data.longitude !== undefined) {
expect(typeof result.data.longitude).toBe("number");
}
if (result.data.country_area !== undefined) {
expect(typeof result.data.country_area).toBe("number");
}
}
}, 10000);
it("should handle bulk requests with maximum efficiency", async () => {
// Test with well-known public DNS servers that should have geo data
const testIPs = [
"8.8.8.8", // Google
"8.8.4.4", // Google secondary
"1.1.1.1", // Cloudflare
"1.0.0.1", // Cloudflare secondary
"208.67.222.222", // OpenDNS
"208.67.220.220", // OpenDNS secondary
"9.9.9.9", // Quad9
"149.112.112.112", // Quad9 secondary
];
const result = await geolocator.bulkLookup({ ips: testIPs });
expect(result.ok).toBe(true);
if (result.ok) {
const results = result.data;
expect(results).toHaveLength(testIPs.length);
// Verify all IPs are accounted for
const returnedIPs = results.map((r) => r.ip);
expect(returnedIPs.sort()).toEqual(testIPs.sort());
// Check success rate - should be high for these well-known IPs
const successCount = results.filter(
(r) => r.status === "success"
).length;
expect(successCount).toBeGreaterThan(testIPs.length * 0.8); // Expect >80% success for public DNS IPs
}
}, 20000);
it("should properly handle quota scenarios", async () => {
// This test is designed to check if the library properly reports quota exceeded errors
// In production, you might hit quota limits with too many requests
try {
// Use known good IP addresses that should have geolocation data
const knownGoodIPs = [
"8.8.8.8",
"8.8.4.4",
"1.1.1.1",
"1.0.0.1",
"178.238.11.6",
"208.67.222.222",
"208.67.220.220",
"9.9.9.9",
"149.112.112.112",
"76.76.19.19",
];
// Make many requests in quick succession
const promises = knownGoodIPs.map((ip) => geolocator.lookup(ip));
const results = await Promise.all(promises);
// Check if any results indicate quota exceeded
const quotaExceeded = results.some(
(result) => !result.ok && result.error.type === "QUOTA_EXCEEDED"
);
if (quotaExceeded) {
console.log("Quota limit reached, which is expected behavior");
} else {
// If no quota limit, ensure all requests were successful or had valid errors
results.forEach((result) => {
if (!result.ok) {
// Valid error types we might encounter
const validErrorTypes = [
"GEOLOCATION_NOT_FOUND",
"RESERVED_IP_ADDRESS",
"NETWORK_ERROR",
"UNKNOWN_ERROR",
];
expect(validErrorTypes).toContain(result.error.type);
}
});
}
} catch (error) {
// This shouldn't happen with the new Result-based API
throw new Error(`Unexpected exception: ${error}`);
}
}, 30000);
});
});
});