Skip to content

Commit ee42409

Browse files
committed
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
1 parent 48569bd commit ee42409

14 files changed

Lines changed: 564 additions & 33 deletions

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@
199199
<groupId>org.sonatype.plugins</groupId>
200200
<artifactId>nexus-staging-maven-plugin</artifactId>
201201
<version>1.6.7</version>
202-
<extensions>true</extensions>
202+
<extensions>false</extensions>
203203
<configuration>
204204
<serverId>maven-central-staging</serverId>
205205
<nexusUrl>https://oss.sonatype.org</nexusUrl>

src/test/java/io/ipdata/client/AsnTest.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,19 @@
55
import io.ipdata.client.model.AsnModel;
66
import io.ipdata.client.service.IpdataService;
77
import lombok.SneakyThrows;
8-
import org.apache.http.conn.ssl.NoopHostnameVerifier;
98
import org.apache.http.impl.client.HttpClientBuilder;
109
import org.junit.Test;
1110
import org.junit.runner.RunWith;
1211
import org.junit.runners.Parameterized;
1312

14-
import java.net.URL;
1513
import java.util.concurrent.TimeUnit;
1614

1715
import static org.junit.Assert.assertNotNull;
1816

1917
@RunWith(Parameterized.class)
2018
public class AsnTest {
2119

22-
private static final TestContext TEST_CONTEXT = new TestContext("https://api.ipdata.co");
20+
private static final TestContext TEST_CONTEXT = new TestContext(MockIpdataServer.API_KEY, MockIpdataServer.getInstance().getUrl());
2321

2422
@Parameterized.Parameter
2523
public TestFixture fixture;
@@ -45,12 +43,11 @@ public void testASN() {
4543
@SneakyThrows
4644
@Test(expected = IpdataException.class)
4745
public void testAsnError() {
48-
URL url = new URL("https://api.ipdata.co");
49-
IpdataService serviceWithInvalidKey = Ipdata.builder().url(url)
46+
IpdataService serviceWithInvalidKey = Ipdata.builder().url(TEST_CONTEXT.url())
5047
.key("THIS_IS_AN_INVALID_KEY")
5148
.withDefaultCache()
5249
.feignClient(new ApacheHttpClient(HttpClientBuilder.create()
53-
.setSSLHostnameVerifier(new NoopHostnameVerifier()).setConnectionTimeToLive(10, TimeUnit.SECONDS)
50+
.setConnectionTimeToLive(10, TimeUnit.SECONDS)
5451
.build())
5552
).get();
5653
serviceWithInvalidKey.asn(fixture.target());

src/test/java/io/ipdata/client/BulkTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
@RunWith(Parameterized.class)
1616
public class BulkTest {
1717

18-
private static final TestContext TEST_CONTEXT = new TestContext("https://api.ipdata.co");
18+
private static final TestContext TEST_CONTEXT = new TestContext(MockIpdataServer.API_KEY, MockIpdataServer.getInstance().getUrl());
1919

2020
@Parameterized.Parameter
2121
public IpdataService ipdataService;

src/test/java/io/ipdata/client/CurrencyTest.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,17 @@
55
import io.ipdata.client.model.Currency;
66
import io.ipdata.client.service.IpdataService;
77
import lombok.SneakyThrows;
8-
import org.apache.http.conn.ssl.NoopHostnameVerifier;
98
import org.apache.http.impl.client.HttpClientBuilder;
109
import org.junit.Test;
1110
import org.junit.runner.RunWith;
1211
import org.junit.runners.Parameterized;
1312

14-
import java.net.URL;
1513
import java.util.concurrent.TimeUnit;
1614

1715
@RunWith(Parameterized.class)
1816
public class CurrencyTest {
1917

20-
private static final TestContext TEST_CONTEXT = new TestContext("https://api.ipdata.co");
18+
private static final TestContext TEST_CONTEXT = new TestContext(MockIpdataServer.API_KEY, MockIpdataServer.getInstance().getUrl());
2119

2220
@Parameterized.Parameter
2321
public TestFixture fixture;
@@ -41,12 +39,11 @@ public void testCurrency() {
4139
@SneakyThrows
4240
@Test(expected = IpdataException.class)
4341
public void testCurrencyError() {
44-
URL url = new URL("https://api.ipdata.co");
45-
IpdataService serviceWithInvalidKey = Ipdata.builder().url(url)
42+
IpdataService serviceWithInvalidKey = Ipdata.builder().url(TEST_CONTEXT.url())
4643
.key("THIS_IS_AN_INVALID_KEY")
4744
.withDefaultCache()
4845
.feignClient(new ApacheHttpClient(HttpClientBuilder.create()
49-
.setSSLHostnameVerifier(new NoopHostnameVerifier()).setConnectionTimeToLive(10, TimeUnit.SECONDS)
46+
.setConnectionTimeToLive(10, TimeUnit.SECONDS)
5047
.build())
5148
).get();
5249
serviceWithInvalidKey.currency(fixture.target());

src/test/java/io/ipdata/client/FullModelTest.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,19 @@
55
import io.ipdata.client.model.IpdataModel;
66
import io.ipdata.client.service.IpdataService;
77
import lombok.SneakyThrows;
8-
import org.apache.http.conn.ssl.NoopHostnameVerifier;
98
import org.apache.http.impl.client.HttpClientBuilder;
109
import org.junit.Assert;
1110
import org.junit.Test;
1211
import org.junit.runner.RunWith;
1312
import org.junit.runners.Parameterized;
1413

15-
import java.net.URL;
1614
import java.util.concurrent.TimeUnit;
1715

1816

1917
@RunWith(Parameterized.class)
2018
public class FullModelTest {
2119

22-
private static final TestContext TEST_CONTEXT = new TestContext("https://api.ipdata.co");
20+
private static final TestContext TEST_CONTEXT = new TestContext(MockIpdataServer.API_KEY, MockIpdataServer.getInstance().getUrl());
2321

2422
@Parameterized.Parameter
2523
public TestFixture fixture;
@@ -54,12 +52,11 @@ public void testSingleFields() {
5452
@SneakyThrows
5553
@Test(expected = IpdataException.class)
5654
public void testError() {
57-
URL url = new URL("https://api.ipdata.co");
58-
IpdataService serviceWithInvalidKey = Ipdata.builder().url(url)
55+
IpdataService serviceWithInvalidKey = Ipdata.builder().url(TEST_CONTEXT.url())
5956
.key("THIS_IS_AN_INVALID_KEY")
6057
.withDefaultCache()
6158
.feignClient(new ApacheHttpClient(HttpClientBuilder.create()
62-
.setSSLHostnameVerifier(new NoopHostnameVerifier()).setConnectionTimeToLive(10, TimeUnit.SECONDS)
59+
.setConnectionTimeToLive(10, TimeUnit.SECONDS)
6360
.build())).get();
6461
serviceWithInvalidKey.ipdata(fixture.target());
6562
}
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package io.ipdata.client;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import com.fasterxml.jackson.databind.node.ArrayNode;
6+
import com.fasterxml.jackson.databind.node.ObjectNode;
7+
import com.google.common.io.CharStreams;
8+
import com.sun.net.httpserver.HttpExchange;
9+
import com.sun.net.httpserver.HttpServer;
10+
11+
import java.io.IOException;
12+
import java.io.InputStream;
13+
import java.io.InputStreamReader;
14+
import java.io.UnsupportedEncodingException;
15+
import java.net.InetSocketAddress;
16+
import java.net.URLDecoder;
17+
import java.util.ArrayList;
18+
import java.util.HashMap;
19+
import java.util.List;
20+
import java.util.Map;
21+
22+
public class MockIpdataServer {
23+
24+
public static final String API_KEY = "test-api-key";
25+
26+
private static MockIpdataServer instance;
27+
28+
private final HttpServer server;
29+
private final String url;
30+
private final Map<String, JsonNode> fixtures = new HashMap<>();
31+
private final ObjectMapper mapper = new ObjectMapper();
32+
33+
private MockIpdataServer() {
34+
try {
35+
server = HttpServer.create(new InetSocketAddress(0), 0);
36+
int port = server.getAddress().getPort();
37+
url = "http://localhost:" + port;
38+
loadFixtures();
39+
server.createContext("/", this::handleRequest);
40+
server.start();
41+
} catch (IOException e) {
42+
throw new RuntimeException("Failed to start mock server", e);
43+
}
44+
}
45+
46+
public static synchronized MockIpdataServer getInstance() {
47+
if (instance == null) {
48+
instance = new MockIpdataServer();
49+
}
50+
return instance;
51+
}
52+
53+
public String getUrl() {
54+
return url;
55+
}
56+
57+
private void loadFixtures() {
58+
String[] ips = {"8.8.8.8", "2001:4860:4860::8888", "1.1.1.1", "2001:4860:4860::8844", "41.128.21.123"};
59+
for (String ip : ips) {
60+
String resourceName = "fixtures/" + ip.replace(":", "-") + ".json";
61+
try (InputStream is = getClass().getResourceAsStream(resourceName)) {
62+
if (is != null) {
63+
fixtures.put(ip, mapper.readTree(is));
64+
}
65+
} catch (IOException e) {
66+
throw new RuntimeException("Failed to load fixture: " + resourceName, e);
67+
}
68+
}
69+
}
70+
71+
private void handleRequest(HttpExchange exchange) throws IOException {
72+
try {
73+
String method = exchange.getRequestMethod();
74+
String path = exchange.getRequestURI().getPath();
75+
String rawQuery = exchange.getRequestURI().getRawQuery();
76+
Map<String, String> params = parseQuery(rawQuery);
77+
78+
String apiKey = params.get("api-key");
79+
if (!API_KEY.equals(apiKey)) {
80+
sendError(exchange, 401, "Invalid API key");
81+
return;
82+
}
83+
84+
if ("POST".equals(method) && "/bulk".equals(path)) {
85+
handleBulk(exchange);
86+
return;
87+
}
88+
89+
if ("GET".equals(method)) {
90+
handleGet(exchange, path, params);
91+
return;
92+
}
93+
94+
sendError(exchange, 404, "Not found");
95+
} catch (Exception e) {
96+
sendError(exchange, 500, e.getMessage());
97+
}
98+
}
99+
100+
private void handleGet(HttpExchange exchange, String path, Map<String, String> params) throws IOException {
101+
String trimmed = path.startsWith("/") ? path.substring(1) : path;
102+
103+
String ip = null;
104+
String field = null;
105+
106+
// Match against known IPs (longest first to avoid prefix conflicts)
107+
List<String> sortedIps = new ArrayList<>(fixtures.keySet());
108+
sortedIps.sort((a, b) -> b.length() - a.length());
109+
110+
for (String knownIp : sortedIps) {
111+
if (trimmed.equals(knownIp)) {
112+
ip = knownIp;
113+
break;
114+
}
115+
if (trimmed.startsWith(knownIp + "/")) {
116+
ip = knownIp;
117+
field = trimmed.substring(knownIp.length() + 1);
118+
break;
119+
}
120+
}
121+
122+
if (ip == null) {
123+
sendError(exchange, 404, "IP not found");
124+
return;
125+
}
126+
127+
JsonNode fixture = fixtures.get(ip);
128+
129+
if (field != null) {
130+
// Sub-field request: GET /{ip}/{field}
131+
JsonNode fieldNode = fixture.get(field);
132+
if (fieldNode == null) {
133+
sendError(exchange, 404, "Field not found: " + field);
134+
return;
135+
}
136+
if (fieldNode.isTextual()) {
137+
sendResponse(exchange, 200, fieldNode.asText());
138+
} else {
139+
sendResponse(exchange, 200, mapper.writeValueAsString(fieldNode));
140+
}
141+
} else if (params.containsKey("fields")) {
142+
// Selected fields request: GET /{ip}?fields=a,b
143+
String fieldsStr = params.get("fields");
144+
String[] fields = fieldsStr.split(",");
145+
ObjectNode result = mapper.createObjectNode();
146+
for (String f : fields) {
147+
JsonNode fieldNode = fixture.get(f.trim());
148+
if (fieldNode != null) {
149+
result.set(f.trim(), fieldNode);
150+
}
151+
}
152+
sendResponse(exchange, 200, mapper.writeValueAsString(result));
153+
} else {
154+
// Full model request: GET /{ip}
155+
sendResponse(exchange, 200, mapper.writeValueAsString(fixture));
156+
}
157+
}
158+
159+
private void handleBulk(HttpExchange exchange) throws IOException {
160+
String body = CharStreams.toString(new InputStreamReader(exchange.getRequestBody()));
161+
String[] ips = mapper.readValue(body, String[].class);
162+
ArrayNode result = mapper.createArrayNode();
163+
for (String ip : ips) {
164+
JsonNode fixture = fixtures.get(ip);
165+
if (fixture != null) {
166+
result.add(fixture);
167+
}
168+
}
169+
sendResponse(exchange, 200, mapper.writeValueAsString(result));
170+
}
171+
172+
private void sendResponse(HttpExchange exchange, int status, String body) throws IOException {
173+
byte[] bytes = body.getBytes("UTF-8");
174+
exchange.getResponseHeaders().set("Content-Type", "application/json");
175+
exchange.sendResponseHeaders(status, bytes.length);
176+
exchange.getResponseBody().write(bytes);
177+
exchange.getResponseBody().close();
178+
}
179+
180+
private void sendError(HttpExchange exchange, int status, String message) throws IOException {
181+
String body = "{\"message\":\"" + message.replace("\"", "\\\"") + "\"}";
182+
sendResponse(exchange, status, body);
183+
}
184+
185+
private Map<String, String> parseQuery(String rawQuery) {
186+
Map<String, String> params = new HashMap<>();
187+
if (rawQuery != null) {
188+
for (String param : rawQuery.split("&")) {
189+
String[] pair = param.split("=", 2);
190+
if (pair.length == 2) {
191+
try {
192+
params.put(URLDecoder.decode(pair[0], "UTF-8"), URLDecoder.decode(pair[1], "UTF-8"));
193+
} catch (UnsupportedEncodingException e) {
194+
params.put(pair[0], pair[1]);
195+
}
196+
}
197+
}
198+
}
199+
return params;
200+
}
201+
}

src/test/java/io/ipdata/client/MultipleFieldsSelectionTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
@RunWith(Parameterized.class)
1515
public class MultipleFieldsSelectionTest {
1616

17-
private static final TestContext TEST_CONTEXT = new TestContext("https://api.ipdata.co");
17+
private static final TestContext TEST_CONTEXT = new TestContext(MockIpdataServer.API_KEY, MockIpdataServer.getInstance().getUrl());
1818

1919
@Parameterized.Parameter
2020
public IpdataService ipdataService;

src/test/java/io/ipdata/client/ThreatTest.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,17 @@
55
import io.ipdata.client.model.ThreatModel;
66
import io.ipdata.client.service.IpdataService;
77
import lombok.SneakyThrows;
8-
import org.apache.http.conn.ssl.NoopHostnameVerifier;
98
import org.apache.http.impl.client.HttpClientBuilder;
109
import org.junit.Test;
1110
import org.junit.runner.RunWith;
1211
import org.junit.runners.Parameterized;
1312

14-
import java.net.URL;
1513
import java.util.concurrent.TimeUnit;
1614

1715
@RunWith(Parameterized.class)
1816
public class ThreatTest {
1917

20-
private static final TestContext TEST_CONTEXT = new TestContext("https://api.ipdata.co");
18+
private static final TestContext TEST_CONTEXT = new TestContext(MockIpdataServer.API_KEY, MockIpdataServer.getInstance().getUrl());
2119

2220
@Parameterized.Parameter
2321
public TestFixture fixture;
@@ -41,12 +39,11 @@ public void testThreat() {
4139
@SneakyThrows
4240
@Test(expected = IpdataException.class)
4341
public void testThreatError() {
44-
URL url = new URL("https://api.ipdata.co");
45-
IpdataService serviceWithInvalidKey = Ipdata.builder().url(url)
42+
IpdataService serviceWithInvalidKey = Ipdata.builder().url(TEST_CONTEXT.url())
4643
.key("THIS_IS_AN_INVALID_KEY")
4744
.withDefaultCache()
4845
.feignClient(new ApacheHttpClient(HttpClientBuilder.create()
49-
.setSSLHostnameVerifier(new NoopHostnameVerifier()).setConnectionTimeToLive(10, TimeUnit.SECONDS)
46+
.setConnectionTimeToLive(10, TimeUnit.SECONDS)
5047
.build())).get();
5148
serviceWithInvalidKey.threat(fixture.target());
5249
}

0 commit comments

Comments
 (0)