From f28833ee279705d3717a8723c7083fb42a4327c0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Feb 2026 05:39:33 +0000 Subject: [PATCH 1/6] Update ipdata client to match current API - Add Company model (name, domain, network, type) for API company object - Add regionType field to IpdataModel for API region_type - Add company field to IpdataModel for API company object - Add icloudRelay and datacenter fields to ThreatModel for API is_icloud_relay and is_datacenter threat indicators - Fix IpdataField.THREAT type from TimeZone to ThreatModel (bug) - Fix getCallingCode endpoint from /asn to /calling_code (bug) - Add missing IpdataField constants: REGION_CODE, REGION_TYPE, CONTINENT_NAME, COMPANY - Update test fixture with new API fields - Update Lombok to 1.18.34 and compiler target to Java 8 https://claude.ai/code/session_01L8wfDZ1ZCuFLzAgaecvLr1 --- pom.xml | 6 +++--- src/main/java/io/ipdata/client/model/Company.java | 13 +++++++++++++ .../java/io/ipdata/client/model/IpdataModel.java | 2 ++ .../java/io/ipdata/client/model/ThreatModel.java | 4 ++++ .../java/io/ipdata/client/service/IpdataField.java | 6 +++++- .../service/IpdataInternalSingleFieldClient.java | 2 +- src/test/resources/io/ipdata/client/fixture.json | 11 ++++++++++- 7 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 src/main/java/io/ipdata/client/model/Company.java diff --git a/pom.xml b/pom.xml index d3bde99..12174de 100644 --- a/pom.xml +++ b/pom.xml @@ -36,8 +36,8 @@ - 6 - 6 + 8 + 8 UTF-8 ${project.build.directory}/site/code-coverage/jacoco.xml https://sonarcloud.io @@ -87,7 +87,7 @@ org.projectlombok lombok - 1.18.10 + 1.18.34 org.hamcrest diff --git a/src/main/java/io/ipdata/client/model/Company.java b/src/main/java/io/ipdata/client/model/Company.java new file mode 100644 index 0000000..2c0d349 --- /dev/null +++ b/src/main/java/io/ipdata/client/model/Company.java @@ -0,0 +1,13 @@ +package io.ipdata.client.model; + +import lombok.Getter; +import lombok.ToString; +import lombok.experimental.Accessors; + +@ToString @Getter @Accessors(fluent = true) +public class Company { + private String name; + private String domain; + private String network; + private String type; +} diff --git a/src/main/java/io/ipdata/client/model/IpdataModel.java b/src/main/java/io/ipdata/client/model/IpdataModel.java index ebacab7..1bef8d9 100644 --- a/src/main/java/io/ipdata/client/model/IpdataModel.java +++ b/src/main/java/io/ipdata/client/model/IpdataModel.java @@ -22,6 +22,7 @@ public class IpdataModel { private String region; private String regionCode; + private String regionType; private String countryName; private String countryCode; private String continentName; @@ -35,6 +36,7 @@ public class IpdataModel { private String emojiUnicode; private AsnModel asn; private Carrier carrier; + private Company company; private List languages; private Currency currency; private TimeZone timeZone; diff --git a/src/main/java/io/ipdata/client/model/ThreatModel.java b/src/main/java/io/ipdata/client/model/ThreatModel.java index 949c61b..2d61f12 100644 --- a/src/main/java/io/ipdata/client/model/ThreatModel.java +++ b/src/main/java/io/ipdata/client/model/ThreatModel.java @@ -20,4 +20,8 @@ public class ThreatModel { private boolean threat; @JsonProperty("is_bogon") private boolean bogon; + @JsonProperty("is_icloud_relay") + private boolean icloudRelay; + @JsonProperty("is_datacenter") + private boolean datacenter; } diff --git a/src/main/java/io/ipdata/client/service/IpdataField.java b/src/main/java/io/ipdata/client/service/IpdataField.java index d6d0655..1b960da 100644 --- a/src/main/java/io/ipdata/client/service/IpdataField.java +++ b/src/main/java/io/ipdata/client/service/IpdataField.java @@ -16,9 +16,12 @@ public class IpdataField { public static final IpdataField IS_EU = new IpdataField("is_eu", Boolean.class); public static final IpdataField CITY = new IpdataField("city", String.class); public static final IpdataField REGION = new IpdataField("region", String.class); + public static final IpdataField REGION_CODE = new IpdataField("region_code", String.class); + public static final IpdataField REGION_TYPE = new IpdataField("region_type", String.class); public static final IpdataField COUNTRY_NAME = new IpdataField("country_name", String.class); public static final IpdataField COUNTRY_CODE = new IpdataField("country_code", String.class); public static final IpdataField CONTINENT_CODE = new IpdataField("continent_code", String.class); + public static final IpdataField CONTINENT_NAME = new IpdataField("continent_name", String.class); public static final IpdataField LATITUDE = new IpdataField("latitude", Double.class); public static final IpdataField LONGITUDE = new IpdataField("longitude", Double.class); public static final IpdataField ASN = new IpdataField("asn", AsnModel.class); @@ -29,10 +32,11 @@ public class IpdataField { public static final IpdataField EMOJI_FLAG = new IpdataField("emoji_flag", String.class); public static final IpdataField EMOJI_UNICODE = new IpdataField("emoji_unicode", String.class); public static final IpdataField CARRIER = new IpdataField("carrier", Carrier.class); + public static final IpdataField COMPANY = new IpdataField("company", Company.class); public static final IpdataField LANGUAGES = new IpdataField("languages", Language.class); public static final IpdataField CURRENCY = new IpdataField("currency", Currency.class); public static final IpdataField TIME_ZONE = new IpdataField("time_zone", TimeZone.class); - public static final IpdataField THREAT = new IpdataField("threat", TimeZone.class); + public static final IpdataField THREAT = new IpdataField("threat", ThreatModel.class); public static final IpdataField COUNT = new IpdataField("count", Integer.class); private final String name; private final Class type; diff --git a/src/main/java/io/ipdata/client/service/IpdataInternalSingleFieldClient.java b/src/main/java/io/ipdata/client/service/IpdataInternalSingleFieldClient.java index b2c7cc7..5f55f7a 100644 --- a/src/main/java/io/ipdata/client/service/IpdataInternalSingleFieldClient.java +++ b/src/main/java/io/ipdata/client/service/IpdataInternalSingleFieldClient.java @@ -40,7 +40,7 @@ interface IpdataInternalSingleFieldClient { @RequestLine("GET /{ip}/postal") String getPostal(@Param(value = "ip", encoded = true) String ip) throws IpdataException; - @RequestLine("GET /{ip}/asn") + @RequestLine("GET /{ip}/calling_code") String getCallingCode(@Param(value = "ip", encoded = true) String ip) throws IpdataException; @RequestLine("GET /{ip}/flag") diff --git a/src/test/resources/io/ipdata/client/fixture.json b/src/test/resources/io/ipdata/client/fixture.json index 9aaf20c..41488f5 100644 --- a/src/test/resources/io/ipdata/client/fixture.json +++ b/src/test/resources/io/ipdata/client/fixture.json @@ -4,6 +4,7 @@ "city": "test-city", "region": "Illinois", "region_code": "IL", + "region_type": "state", "country_name": "United States", "country_code": "US", "continent_name": "North America", @@ -27,6 +28,12 @@ "mcc": "310", "mnc": "004" }, + "company": { + "name": "Verizon Wireless", + "domain": "verizonwireless.com", + "network": "69.78.0.0/16", + "type": "business" + }, "languages": [ { "name": "English", @@ -54,7 +61,9 @@ "is_known_attacker": false, "is_known_abuser": false, "is_threat": false, - "is_bogon": false + "is_bogon": false, + "is_icloud_relay": false, + "is_datacenter": false }, "count": "236" } From 48569bdc586d39a4f8c5485a2c2c7ad7e6b8caee Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Feb 2026 06:40:28 +0000 Subject: [PATCH 2/6] Update README with new API fields and EU endpoint documentation Add region_type, company, is_icloud_relay, and is_datacenter to the example JSON output. Document how to use the EU endpoint (eu-api.ipdata.co) for GDPR compliance. https://claude.ai/code/session_01L8wfDZ1ZCuFLzAgaecvLr1 --- README.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0e6ba00..df98fbc 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,15 @@ IpdataService ipdataService = Ipdata.builder().url(url) .key("MY_KEY").get(); /.../ ``` -Optionally, you can configure a cache for faster access (less than 1ms latency on requests that hit the cache). + +To use the EU endpoint for GDPR compliance, pass the EU API URL instead: +```java +URL url = new URL("https://eu-api.ipdata.co"); +IpdataService ipdataService = Ipdata.builder().url(url) + .key("MY_KEY").get(); +``` + +Optionally, you can configure a cache for faster access (less than 1ms latency on requests that hit the cache). The cache is configurable for time and space eviction policies: @@ -79,6 +87,7 @@ Output: "city": null, "region": null, "region_code": null, + "region_type": null, "country_name": "Australia", "country_code": "AU", "continent_name": "Oceania", @@ -97,6 +106,12 @@ Output: "route": "1.1.1.0/24", "type": "hosting" }, + "company": { + "name": "Cloudflare, Inc.", + "domain": "cloudflare.com", + "network": "1.1.1.0/24", + "type": "hosting" + }, "languages": [ { "name": "English", @@ -124,7 +139,9 @@ Output: "is_known_attacker": false, "is_known_abuser": false, "is_threat": false, - "is_bogon": false + "is_bogon": false, + "is_icloud_relay": false, + "is_datacenter": false }, "count": "0" } From ee424099f14cc8ec409050a01672c3b89b0a59a3 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Feb 2026 10:45:13 +0000 Subject: [PATCH 3/6] Mock integration tests to run without external API dependency Replace live API calls with a local MockIpdataServer using JDK's HttpServer. Tests now use fixture JSON data instead of requiring an IPDATACO_KEY env var and network access to api.ipdata.co. - Add MockIpdataServer with fixture-based HTTP responses - Add JSON fixture files for all IPs used in tests - Update all 7 integration test classes to use mock server - Disable nexus-staging-maven-plugin extension (deploy-only) https://claude.ai/code/session_01KxvyXRVVZaLrgTZshvsZY6 --- pom.xml | 2 +- src/test/java/io/ipdata/client/AsnTest.java | 9 +- src/test/java/io/ipdata/client/BulkTest.java | 2 +- .../java/io/ipdata/client/CurrencyTest.java | 9 +- .../java/io/ipdata/client/FullModelTest.java | 9 +- .../io/ipdata/client/MockIpdataServer.java | 201 ++++++++++++++++++ .../client/MultipleFieldsSelectionTest.java | 2 +- .../java/io/ipdata/client/ThreatTest.java | 9 +- .../java/io/ipdata/client/TimeZoneTest.java | 9 +- .../io/ipdata/client/fixtures/1.1.1.1.json | 69 ++++++ .../client/fixtures/2001-4860-4860--8844.json | 69 ++++++ .../client/fixtures/2001-4860-4860--8888.json | 69 ++++++ .../ipdata/client/fixtures/41.128.21.123.json | 69 ++++++ .../io/ipdata/client/fixtures/8.8.8.8.json | 69 ++++++ 14 files changed, 564 insertions(+), 33 deletions(-) create mode 100644 src/test/java/io/ipdata/client/MockIpdataServer.java create mode 100644 src/test/resources/io/ipdata/client/fixtures/1.1.1.1.json create mode 100644 src/test/resources/io/ipdata/client/fixtures/2001-4860-4860--8844.json create mode 100644 src/test/resources/io/ipdata/client/fixtures/2001-4860-4860--8888.json create mode 100644 src/test/resources/io/ipdata/client/fixtures/41.128.21.123.json create mode 100644 src/test/resources/io/ipdata/client/fixtures/8.8.8.8.json diff --git a/pom.xml b/pom.xml index 12174de..e9982f2 100644 --- a/pom.xml +++ b/pom.xml @@ -199,7 +199,7 @@ org.sonatype.plugins nexus-staging-maven-plugin 1.6.7 - true + false maven-central-staging https://oss.sonatype.org diff --git a/src/test/java/io/ipdata/client/AsnTest.java b/src/test/java/io/ipdata/client/AsnTest.java index 652e461..5ae0102 100644 --- a/src/test/java/io/ipdata/client/AsnTest.java +++ b/src/test/java/io/ipdata/client/AsnTest.java @@ -5,13 +5,11 @@ import io.ipdata.client.model.AsnModel; import io.ipdata.client.service.IpdataService; import lombok.SneakyThrows; -import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.impl.client.HttpClientBuilder; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import java.net.URL; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertNotNull; @@ -19,7 +17,7 @@ @RunWith(Parameterized.class) public class AsnTest { - private static final TestContext TEST_CONTEXT = new TestContext("https://api.ipdata.co"); + private static final TestContext TEST_CONTEXT = new TestContext(MockIpdataServer.API_KEY, MockIpdataServer.getInstance().getUrl()); @Parameterized.Parameter public TestFixture fixture; @@ -45,12 +43,11 @@ public void testASN() { @SneakyThrows @Test(expected = IpdataException.class) public void testAsnError() { - URL url = new URL("https://api.ipdata.co"); - IpdataService serviceWithInvalidKey = Ipdata.builder().url(url) + IpdataService serviceWithInvalidKey = Ipdata.builder().url(TEST_CONTEXT.url()) .key("THIS_IS_AN_INVALID_KEY") .withDefaultCache() .feignClient(new ApacheHttpClient(HttpClientBuilder.create() - .setSSLHostnameVerifier(new NoopHostnameVerifier()).setConnectionTimeToLive(10, TimeUnit.SECONDS) + .setConnectionTimeToLive(10, TimeUnit.SECONDS) .build()) ).get(); serviceWithInvalidKey.asn(fixture.target()); diff --git a/src/test/java/io/ipdata/client/BulkTest.java b/src/test/java/io/ipdata/client/BulkTest.java index 2d0d7d9..916def4 100644 --- a/src/test/java/io/ipdata/client/BulkTest.java +++ b/src/test/java/io/ipdata/client/BulkTest.java @@ -15,7 +15,7 @@ @RunWith(Parameterized.class) public class BulkTest { - private static final TestContext TEST_CONTEXT = new TestContext("https://api.ipdata.co"); + private static final TestContext TEST_CONTEXT = new TestContext(MockIpdataServer.API_KEY, MockIpdataServer.getInstance().getUrl()); @Parameterized.Parameter public IpdataService ipdataService; diff --git a/src/test/java/io/ipdata/client/CurrencyTest.java b/src/test/java/io/ipdata/client/CurrencyTest.java index 9352345..78ea1c6 100644 --- a/src/test/java/io/ipdata/client/CurrencyTest.java +++ b/src/test/java/io/ipdata/client/CurrencyTest.java @@ -5,19 +5,17 @@ import io.ipdata.client.model.Currency; import io.ipdata.client.service.IpdataService; import lombok.SneakyThrows; -import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.impl.client.HttpClientBuilder; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import java.net.URL; import java.util.concurrent.TimeUnit; @RunWith(Parameterized.class) public class CurrencyTest { - private static final TestContext TEST_CONTEXT = new TestContext("https://api.ipdata.co"); + private static final TestContext TEST_CONTEXT = new TestContext(MockIpdataServer.API_KEY, MockIpdataServer.getInstance().getUrl()); @Parameterized.Parameter public TestFixture fixture; @@ -41,12 +39,11 @@ public void testCurrency() { @SneakyThrows @Test(expected = IpdataException.class) public void testCurrencyError() { - URL url = new URL("https://api.ipdata.co"); - IpdataService serviceWithInvalidKey = Ipdata.builder().url(url) + IpdataService serviceWithInvalidKey = Ipdata.builder().url(TEST_CONTEXT.url()) .key("THIS_IS_AN_INVALID_KEY") .withDefaultCache() .feignClient(new ApacheHttpClient(HttpClientBuilder.create() - .setSSLHostnameVerifier(new NoopHostnameVerifier()).setConnectionTimeToLive(10, TimeUnit.SECONDS) + .setConnectionTimeToLive(10, TimeUnit.SECONDS) .build()) ).get(); serviceWithInvalidKey.currency(fixture.target()); diff --git a/src/test/java/io/ipdata/client/FullModelTest.java b/src/test/java/io/ipdata/client/FullModelTest.java index a41b853..99bfa72 100644 --- a/src/test/java/io/ipdata/client/FullModelTest.java +++ b/src/test/java/io/ipdata/client/FullModelTest.java @@ -5,21 +5,19 @@ import io.ipdata.client.model.IpdataModel; import io.ipdata.client.service.IpdataService; import lombok.SneakyThrows; -import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.impl.client.HttpClientBuilder; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import java.net.URL; import java.util.concurrent.TimeUnit; @RunWith(Parameterized.class) public class FullModelTest { - private static final TestContext TEST_CONTEXT = new TestContext("https://api.ipdata.co"); + private static final TestContext TEST_CONTEXT = new TestContext(MockIpdataServer.API_KEY, MockIpdataServer.getInstance().getUrl()); @Parameterized.Parameter public TestFixture fixture; @@ -54,12 +52,11 @@ public void testSingleFields() { @SneakyThrows @Test(expected = IpdataException.class) public void testError() { - URL url = new URL("https://api.ipdata.co"); - IpdataService serviceWithInvalidKey = Ipdata.builder().url(url) + IpdataService serviceWithInvalidKey = Ipdata.builder().url(TEST_CONTEXT.url()) .key("THIS_IS_AN_INVALID_KEY") .withDefaultCache() .feignClient(new ApacheHttpClient(HttpClientBuilder.create() - .setSSLHostnameVerifier(new NoopHostnameVerifier()).setConnectionTimeToLive(10, TimeUnit.SECONDS) + .setConnectionTimeToLive(10, TimeUnit.SECONDS) .build())).get(); serviceWithInvalidKey.ipdata(fixture.target()); } diff --git a/src/test/java/io/ipdata/client/MockIpdataServer.java b/src/test/java/io/ipdata/client/MockIpdataServer.java new file mode 100644 index 0000000..6f44dc3 --- /dev/null +++ b/src/test/java/io/ipdata/client/MockIpdataServer.java @@ -0,0 +1,201 @@ +package io.ipdata.client; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.io.CharStreams; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpServer; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MockIpdataServer { + + public static final String API_KEY = "test-api-key"; + + private static MockIpdataServer instance; + + private final HttpServer server; + private final String url; + private final Map fixtures = new HashMap<>(); + private final ObjectMapper mapper = new ObjectMapper(); + + private MockIpdataServer() { + try { + server = HttpServer.create(new InetSocketAddress(0), 0); + int port = server.getAddress().getPort(); + url = "http://localhost:" + port; + loadFixtures(); + server.createContext("/", this::handleRequest); + server.start(); + } catch (IOException e) { + throw new RuntimeException("Failed to start mock server", e); + } + } + + public static synchronized MockIpdataServer getInstance() { + if (instance == null) { + instance = new MockIpdataServer(); + } + return instance; + } + + public String getUrl() { + return url; + } + + private void loadFixtures() { + String[] ips = {"8.8.8.8", "2001:4860:4860::8888", "1.1.1.1", "2001:4860:4860::8844", "41.128.21.123"}; + for (String ip : ips) { + String resourceName = "fixtures/" + ip.replace(":", "-") + ".json"; + try (InputStream is = getClass().getResourceAsStream(resourceName)) { + if (is != null) { + fixtures.put(ip, mapper.readTree(is)); + } + } catch (IOException e) { + throw new RuntimeException("Failed to load fixture: " + resourceName, e); + } + } + } + + private void handleRequest(HttpExchange exchange) throws IOException { + try { + String method = exchange.getRequestMethod(); + String path = exchange.getRequestURI().getPath(); + String rawQuery = exchange.getRequestURI().getRawQuery(); + Map params = parseQuery(rawQuery); + + String apiKey = params.get("api-key"); + if (!API_KEY.equals(apiKey)) { + sendError(exchange, 401, "Invalid API key"); + return; + } + + if ("POST".equals(method) && "/bulk".equals(path)) { + handleBulk(exchange); + return; + } + + if ("GET".equals(method)) { + handleGet(exchange, path, params); + return; + } + + sendError(exchange, 404, "Not found"); + } catch (Exception e) { + sendError(exchange, 500, e.getMessage()); + } + } + + private void handleGet(HttpExchange exchange, String path, Map params) throws IOException { + String trimmed = path.startsWith("/") ? path.substring(1) : path; + + String ip = null; + String field = null; + + // Match against known IPs (longest first to avoid prefix conflicts) + List sortedIps = new ArrayList<>(fixtures.keySet()); + sortedIps.sort((a, b) -> b.length() - a.length()); + + for (String knownIp : sortedIps) { + if (trimmed.equals(knownIp)) { + ip = knownIp; + break; + } + if (trimmed.startsWith(knownIp + "/")) { + ip = knownIp; + field = trimmed.substring(knownIp.length() + 1); + break; + } + } + + if (ip == null) { + sendError(exchange, 404, "IP not found"); + return; + } + + JsonNode fixture = fixtures.get(ip); + + if (field != null) { + // Sub-field request: GET /{ip}/{field} + JsonNode fieldNode = fixture.get(field); + if (fieldNode == null) { + sendError(exchange, 404, "Field not found: " + field); + return; + } + if (fieldNode.isTextual()) { + sendResponse(exchange, 200, fieldNode.asText()); + } else { + sendResponse(exchange, 200, mapper.writeValueAsString(fieldNode)); + } + } else if (params.containsKey("fields")) { + // Selected fields request: GET /{ip}?fields=a,b + String fieldsStr = params.get("fields"); + String[] fields = fieldsStr.split(","); + ObjectNode result = mapper.createObjectNode(); + for (String f : fields) { + JsonNode fieldNode = fixture.get(f.trim()); + if (fieldNode != null) { + result.set(f.trim(), fieldNode); + } + } + sendResponse(exchange, 200, mapper.writeValueAsString(result)); + } else { + // Full model request: GET /{ip} + sendResponse(exchange, 200, mapper.writeValueAsString(fixture)); + } + } + + private void handleBulk(HttpExchange exchange) throws IOException { + String body = CharStreams.toString(new InputStreamReader(exchange.getRequestBody())); + String[] ips = mapper.readValue(body, String[].class); + ArrayNode result = mapper.createArrayNode(); + for (String ip : ips) { + JsonNode fixture = fixtures.get(ip); + if (fixture != null) { + result.add(fixture); + } + } + sendResponse(exchange, 200, mapper.writeValueAsString(result)); + } + + private void sendResponse(HttpExchange exchange, int status, String body) throws IOException { + byte[] bytes = body.getBytes("UTF-8"); + exchange.getResponseHeaders().set("Content-Type", "application/json"); + exchange.sendResponseHeaders(status, bytes.length); + exchange.getResponseBody().write(bytes); + exchange.getResponseBody().close(); + } + + private void sendError(HttpExchange exchange, int status, String message) throws IOException { + String body = "{\"message\":\"" + message.replace("\"", "\\\"") + "\"}"; + sendResponse(exchange, status, body); + } + + private Map parseQuery(String rawQuery) { + Map params = new HashMap<>(); + if (rawQuery != null) { + for (String param : rawQuery.split("&")) { + String[] pair = param.split("=", 2); + if (pair.length == 2) { + try { + params.put(URLDecoder.decode(pair[0], "UTF-8"), URLDecoder.decode(pair[1], "UTF-8")); + } catch (UnsupportedEncodingException e) { + params.put(pair[0], pair[1]); + } + } + } + } + return params; + } +} diff --git a/src/test/java/io/ipdata/client/MultipleFieldsSelectionTest.java b/src/test/java/io/ipdata/client/MultipleFieldsSelectionTest.java index 4d5993e..41e1381 100644 --- a/src/test/java/io/ipdata/client/MultipleFieldsSelectionTest.java +++ b/src/test/java/io/ipdata/client/MultipleFieldsSelectionTest.java @@ -14,7 +14,7 @@ @RunWith(Parameterized.class) public class MultipleFieldsSelectionTest { - private static final TestContext TEST_CONTEXT = new TestContext("https://api.ipdata.co"); + private static final TestContext TEST_CONTEXT = new TestContext(MockIpdataServer.API_KEY, MockIpdataServer.getInstance().getUrl()); @Parameterized.Parameter public IpdataService ipdataService; diff --git a/src/test/java/io/ipdata/client/ThreatTest.java b/src/test/java/io/ipdata/client/ThreatTest.java index 01296b4..f462abd 100644 --- a/src/test/java/io/ipdata/client/ThreatTest.java +++ b/src/test/java/io/ipdata/client/ThreatTest.java @@ -5,19 +5,17 @@ import io.ipdata.client.model.ThreatModel; import io.ipdata.client.service.IpdataService; import lombok.SneakyThrows; -import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.impl.client.HttpClientBuilder; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import java.net.URL; import java.util.concurrent.TimeUnit; @RunWith(Parameterized.class) public class ThreatTest { - private static final TestContext TEST_CONTEXT = new TestContext("https://api.ipdata.co"); + private static final TestContext TEST_CONTEXT = new TestContext(MockIpdataServer.API_KEY, MockIpdataServer.getInstance().getUrl()); @Parameterized.Parameter public TestFixture fixture; @@ -41,12 +39,11 @@ public void testThreat() { @SneakyThrows @Test(expected = IpdataException.class) public void testThreatError() { - URL url = new URL("https://api.ipdata.co"); - IpdataService serviceWithInvalidKey = Ipdata.builder().url(url) + IpdataService serviceWithInvalidKey = Ipdata.builder().url(TEST_CONTEXT.url()) .key("THIS_IS_AN_INVALID_KEY") .withDefaultCache() .feignClient(new ApacheHttpClient(HttpClientBuilder.create() - .setSSLHostnameVerifier(new NoopHostnameVerifier()).setConnectionTimeToLive(10, TimeUnit.SECONDS) + .setConnectionTimeToLive(10, TimeUnit.SECONDS) .build())).get(); serviceWithInvalidKey.threat(fixture.target()); } diff --git a/src/test/java/io/ipdata/client/TimeZoneTest.java b/src/test/java/io/ipdata/client/TimeZoneTest.java index aceeea8..2ef776f 100644 --- a/src/test/java/io/ipdata/client/TimeZoneTest.java +++ b/src/test/java/io/ipdata/client/TimeZoneTest.java @@ -5,13 +5,11 @@ import io.ipdata.client.model.TimeZone; import io.ipdata.client.service.IpdataService; import lombok.SneakyThrows; -import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.impl.client.HttpClientBuilder; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; -import java.net.URL; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertNotNull; @@ -19,7 +17,7 @@ @RunWith(Parameterized.class) public class TimeZoneTest { - private static final TestContext TEST_CONTEXT = new TestContext("https://api.ipdata.co"); + private static final TestContext TEST_CONTEXT = new TestContext(MockIpdataServer.API_KEY, MockIpdataServer.getInstance().getUrl()); @Parameterized.Parameter public TestFixture fixture; @@ -45,12 +43,11 @@ public void testTimeZone() { @SneakyThrows @Test(expected = IpdataException.class) public void testTimeZoneError() { - URL url = new URL("https://api.ipdata.co"); - IpdataService serviceWithInvalidKey = Ipdata.builder().url(url) + IpdataService serviceWithInvalidKey = Ipdata.builder().url(TEST_CONTEXT.url()) .key("THIS_IS_AN_INVALID_KEY") .withDefaultCache() .feignClient(new ApacheHttpClient(HttpClientBuilder.create() - .setSSLHostnameVerifier(new NoopHostnameVerifier()).setConnectionTimeToLive(10, TimeUnit.SECONDS) + .setConnectionTimeToLive(10, TimeUnit.SECONDS) .build())).get(); serviceWithInvalidKey.timeZone(fixture.target()); } diff --git a/src/test/resources/io/ipdata/client/fixtures/1.1.1.1.json b/src/test/resources/io/ipdata/client/fixtures/1.1.1.1.json new file mode 100644 index 0000000..9e57b8d --- /dev/null +++ b/src/test/resources/io/ipdata/client/fixtures/1.1.1.1.json @@ -0,0 +1,69 @@ +{ + "ip": "1.1.1.1", + "is_eu": false, + "city": "Los Angeles", + "region": "California", + "region_code": "CA", + "region_type": "state", + "country_name": "United States", + "country_code": "US", + "continent_name": "North America", + "continent_code": "NA", + "latitude": 34.0522, + "longitude": -118.2437, + "postal": "90001", + "calling_code": "1", + "flag": "https://ipdata.co/flags/us.png", + "emoji_flag": "\ud83c\uddfa\ud83c\uddf8", + "emoji_unicode": "U+1F1FA U+1F1F8", + "asn": { + "asn": "AS13335", + "name": "Cloudflare Inc", + "domain": "cloudflare.com", + "route": "1.1.1.0/24", + "type": "hosting" + }, + "carrier": { + "name": "Cloudflare", + "mcc": "310", + "mnc": "000" + }, + "company": { + "name": "Cloudflare Inc", + "domain": "cloudflare.com", + "network": "1.1.1.0/24", + "type": "hosting" + }, + "languages": [ + { + "name": "English", + "native": "English" + } + ], + "currency": { + "name": "US Dollar", + "code": "USD", + "symbol": "$", + "native": "$", + "plural": "US dollars" + }, + "time_zone": { + "name": "America/Los_Angeles", + "abbr": "PDT", + "offset": "-0700", + "is_dst": true, + "current_time": "2020-06-12T06:37:16.595612-07:00" + }, + "threat": { + "is_tor": false, + "is_proxy": false, + "is_anonymous": false, + "is_known_attacker": false, + "is_known_abuser": false, + "is_threat": false, + "is_bogon": false, + "is_icloud_relay": false, + "is_datacenter": false + }, + "count": "1500" +} diff --git a/src/test/resources/io/ipdata/client/fixtures/2001-4860-4860--8844.json b/src/test/resources/io/ipdata/client/fixtures/2001-4860-4860--8844.json new file mode 100644 index 0000000..94b49fa --- /dev/null +++ b/src/test/resources/io/ipdata/client/fixtures/2001-4860-4860--8844.json @@ -0,0 +1,69 @@ +{ + "ip": "2001:4860:4860::8844", + "is_eu": false, + "city": "Mountain View", + "region": "California", + "region_code": "CA", + "region_type": "state", + "country_name": "United States", + "country_code": "US", + "continent_name": "North America", + "continent_code": "NA", + "latitude": 37.386, + "longitude": -122.0838, + "postal": "94035", + "calling_code": "1", + "flag": "https://ipdata.co/flags/us.png", + "emoji_flag": "\ud83c\uddfa\ud83c\uddf8", + "emoji_unicode": "U+1F1FA U+1F1F8", + "asn": { + "asn": "AS15169", + "name": "Google LLC", + "domain": "google.com", + "route": "2001:4860::/32", + "type": "hosting" + }, + "carrier": { + "name": "Google", + "mcc": "310", + "mnc": "000" + }, + "company": { + "name": "Google LLC", + "domain": "google.com", + "network": "2001:4860::/32", + "type": "hosting" + }, + "languages": [ + { + "name": "English", + "native": "English" + } + ], + "currency": { + "name": "US Dollar", + "code": "USD", + "symbol": "$", + "native": "$", + "plural": "US dollars" + }, + "time_zone": { + "name": "America/Los_Angeles", + "abbr": "PDT", + "offset": "-0700", + "is_dst": true, + "current_time": "2020-06-12T06:37:16.595612-07:00" + }, + "threat": { + "is_tor": false, + "is_proxy": false, + "is_anonymous": false, + "is_known_attacker": false, + "is_known_abuser": false, + "is_threat": false, + "is_bogon": false, + "is_icloud_relay": false, + "is_datacenter": false + }, + "count": "1500" +} diff --git a/src/test/resources/io/ipdata/client/fixtures/2001-4860-4860--8888.json b/src/test/resources/io/ipdata/client/fixtures/2001-4860-4860--8888.json new file mode 100644 index 0000000..2521f4c --- /dev/null +++ b/src/test/resources/io/ipdata/client/fixtures/2001-4860-4860--8888.json @@ -0,0 +1,69 @@ +{ + "ip": "2001:4860:4860::8888", + "is_eu": false, + "city": "Mountain View", + "region": "California", + "region_code": "CA", + "region_type": "state", + "country_name": "United States", + "country_code": "US", + "continent_name": "North America", + "continent_code": "NA", + "latitude": 37.386, + "longitude": -122.0838, + "postal": "94035", + "calling_code": "1", + "flag": "https://ipdata.co/flags/us.png", + "emoji_flag": "\ud83c\uddfa\ud83c\uddf8", + "emoji_unicode": "U+1F1FA U+1F1F8", + "asn": { + "asn": "AS15169", + "name": "Google LLC", + "domain": "google.com", + "route": "2001:4860::/32", + "type": "hosting" + }, + "carrier": { + "name": "Google", + "mcc": "310", + "mnc": "000" + }, + "company": { + "name": "Google LLC", + "domain": "google.com", + "network": "2001:4860::/32", + "type": "hosting" + }, + "languages": [ + { + "name": "English", + "native": "English" + } + ], + "currency": { + "name": "US Dollar", + "code": "USD", + "symbol": "$", + "native": "$", + "plural": "US dollars" + }, + "time_zone": { + "name": "America/Los_Angeles", + "abbr": "PDT", + "offset": "-0700", + "is_dst": true, + "current_time": "2020-06-12T06:37:16.595612-07:00" + }, + "threat": { + "is_tor": false, + "is_proxy": false, + "is_anonymous": false, + "is_known_attacker": false, + "is_known_abuser": false, + "is_threat": false, + "is_bogon": false, + "is_icloud_relay": false, + "is_datacenter": false + }, + "count": "1500" +} diff --git a/src/test/resources/io/ipdata/client/fixtures/41.128.21.123.json b/src/test/resources/io/ipdata/client/fixtures/41.128.21.123.json new file mode 100644 index 0000000..593d42b --- /dev/null +++ b/src/test/resources/io/ipdata/client/fixtures/41.128.21.123.json @@ -0,0 +1,69 @@ +{ + "ip": "41.128.21.123", + "is_eu": false, + "city": "Cairo", + "region": "Cairo Governorate", + "region_code": "C", + "region_type": "governorate", + "country_name": "Egypt", + "country_code": "EG", + "continent_name": "Africa", + "continent_code": "AF", + "latitude": 30.0444, + "longitude": 31.2357, + "postal": "11511", + "calling_code": "20", + "flag": "https://ipdata.co/flags/eg.png", + "emoji_flag": "\ud83c\uddea\ud83c\uddec", + "emoji_unicode": "U+1F1EA U+1F1EC", + "asn": { + "asn": "AS8452", + "name": "TE Data", + "domain": "tedata.net", + "route": "41.128.0.0/16", + "type": "isp" + }, + "carrier": { + "name": "TE Data", + "mcc": "602", + "mnc": "001" + }, + "company": { + "name": "TE Data", + "domain": "tedata.net", + "network": "41.128.0.0/16", + "type": "isp" + }, + "languages": [ + { + "name": "Arabic", + "native": "\u0627\u0644\u0639\u0631\u0628\u064a\u0629" + } + ], + "currency": { + "name": "Egyptian Pound", + "code": "EGP", + "symbol": "E\u00a3", + "native": "\u062c.\u0645.\u200f", + "plural": "Egyptian pounds" + }, + "time_zone": { + "name": "Africa/Cairo", + "abbr": "EET", + "offset": "+0200", + "is_dst": false, + "current_time": "2020-06-12T15:37:16.595612+02:00" + }, + "threat": { + "is_tor": false, + "is_proxy": false, + "is_anonymous": false, + "is_known_attacker": false, + "is_known_abuser": false, + "is_threat": false, + "is_bogon": false, + "is_icloud_relay": false, + "is_datacenter": false + }, + "count": "1500" +} diff --git a/src/test/resources/io/ipdata/client/fixtures/8.8.8.8.json b/src/test/resources/io/ipdata/client/fixtures/8.8.8.8.json new file mode 100644 index 0000000..73ecd30 --- /dev/null +++ b/src/test/resources/io/ipdata/client/fixtures/8.8.8.8.json @@ -0,0 +1,69 @@ +{ + "ip": "8.8.8.8", + "is_eu": false, + "city": "Mountain View", + "region": "California", + "region_code": "CA", + "region_type": "state", + "country_name": "United States", + "country_code": "US", + "continent_name": "North America", + "continent_code": "NA", + "latitude": 37.386, + "longitude": -122.0838, + "postal": "94035", + "calling_code": "1", + "flag": "https://ipdata.co/flags/us.png", + "emoji_flag": "\ud83c\uddfa\ud83c\uddf8", + "emoji_unicode": "U+1F1FA U+1F1F8", + "asn": { + "asn": "AS15169", + "name": "Google LLC", + "domain": "google.com", + "route": "8.8.8.0/24", + "type": "hosting" + }, + "carrier": { + "name": "Google", + "mcc": "310", + "mnc": "000" + }, + "company": { + "name": "Google LLC", + "domain": "google.com", + "network": "8.8.8.0/24", + "type": "hosting" + }, + "languages": [ + { + "name": "English", + "native": "English" + } + ], + "currency": { + "name": "US Dollar", + "code": "USD", + "symbol": "$", + "native": "$", + "plural": "US dollars" + }, + "time_zone": { + "name": "America/Los_Angeles", + "abbr": "PDT", + "offset": "-0700", + "is_dst": true, + "current_time": "2020-06-12T06:37:16.595612-07:00" + }, + "threat": { + "is_tor": false, + "is_proxy": false, + "is_anonymous": false, + "is_known_attacker": false, + "is_known_abuser": false, + "is_threat": false, + "is_bogon": false, + "is_icloud_relay": false, + "is_datacenter": false + }, + "count": "1500" +} From 3683a85bf698c26158659618e9b6fd232fbf0801 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Feb 2026 13:32:43 +0000 Subject: [PATCH 4/6] Update dependency versions to latest Java 8-compatible releases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dependencies: - OpenFeign: 9.7.0 → 11.10 (last Java 8-compatible line) - Guava: 20.0 → 33.4.8-jre - SLF4J: 1.7.30 → 1.7.36 - Lombok: 1.18.34 → 1.18.38 - JUnit: 4.13 → 4.13.2 - json-unit: 2.17.0 → 2.40.1 Build plugins: - JaCoCo: 0.8.4 → 0.8.13 - Sonar: 3.7.0.1746 → 5.1.0.4751 - Surefire: 3.0.0-M3 → 3.5.4 - Resources: 2.6 → 3.4.0 - Source: 3.2.1 → 3.4.0 - Javadoc: 3.1.1 → 3.12.0 - Nexus Staging: 1.6.7 → 1.7.0 - GPG: 1.6 → 3.2.8 https://claude.ai/code/session_01KxvyXRVVZaLrgTZshvsZY6 --- pom.xml | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/pom.xml b/pom.xml index e9982f2..346a662 100644 --- a/pom.xml +++ b/pom.xml @@ -48,10 +48,10 @@ yassine_ipdata-java-client ipdata-java-client ${project.version} - 0.8.4 - 3.7.0.1746 - 3.0.0-M3 - 9.7.0 + 0.8.13 + 5.1.0.4751 + 3.5.4 + 11.10 @@ -72,22 +72,22 @@ com.google.guava guava - 20.0 + 33.4.8-jre org.slf4j slf4j-api - 1.7.30 + 1.7.36 org.slf4j slf4j-log4j12 - 1.7.30 + 1.7.36 org.projectlombok lombok - 1.18.34 + 1.18.38 org.hamcrest @@ -98,13 +98,13 @@ junit junit - 4.13 + 4.13.2 test net.javacrumbs.json-unit json-unit - 2.17.0 + 2.40.1 test @@ -121,7 +121,7 @@ maven-resources-plugin - 2.6 + 3.4.0 ${project.build.sourceEncoding} @@ -175,7 +175,7 @@ maven-source-plugin - 3.2.1 + 3.4.0 @@ -186,7 +186,7 @@ maven-javadoc-plugin - 3.1.1 + 3.12.0 @@ -198,7 +198,7 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.7 + 1.7.0 false maven-central-staging @@ -208,7 +208,7 @@ maven-gpg-plugin - 1.6 + 3.2.8 A143FB3D9A4D90CC76DE768495FD65EAEFC32F7E From 7c3d26660943c8ed1f5d3250b66477435da9c4fe Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Feb 2026 13:34:02 +0000 Subject: [PATCH 5/6] Fix plugin versions for Java 8 and Maven 3.x compatibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - maven-resources-plugin: 3.4.0 → 3.3.1 (3.4.0 requires Maven 4 + Java 17) - maven-source-plugin: 3.4.0 → 3.3.1 (3.4.0 requires Maven 4) - sonar-maven-plugin: 5.1.0.4751 → 3.11.0.3922 (5.x requires Java 11+) - jacoco-maven-plugin: 0.8.13 → 0.8.14 (latest patch) - maven-surefire-plugin: 3.5.4 → 3.5.5 (latest patch) https://claude.ai/code/session_01KxvyXRVVZaLrgTZshvsZY6 --- pom.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 346a662..474a38f 100644 --- a/pom.xml +++ b/pom.xml @@ -48,9 +48,9 @@ yassine_ipdata-java-client ipdata-java-client ${project.version} - 0.8.13 - 5.1.0.4751 - 3.5.4 + 0.8.14 + 3.11.0.3922 + 3.5.5 11.10 @@ -121,7 +121,7 @@ maven-resources-plugin - 3.4.0 + 3.3.1 ${project.build.sourceEncoding} @@ -175,7 +175,7 @@ maven-source-plugin - 3.4.0 + 3.3.1 From b5bd483b522f4219c92da9f7338052923753f69f Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 23 Feb 2026 13:45:27 +0000 Subject: [PATCH 6/6] Bump minimum Java version from 8 to 11 Java 8 reached end of public updates in 2019. Java 11 is the lowest currently relevant LTS and unlocks newer dependency versions while maintaining broad compatibility for library consumers. https://claude.ai/code/session_01KxvyXRVVZaLrgTZshvsZY6 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 474a38f..05a571d 100644 --- a/pom.xml +++ b/pom.xml @@ -36,8 +36,8 @@ - 8 - 8 + 11 + 11 UTF-8 ${project.build.directory}/site/code-coverage/jacoco.xml https://sonarcloud.io