Skip to content

Commit b809c0d

Browse files
authored
Add unit tests for NightfallClient (#23)
1 parent 18a01a3 commit b809c0d

File tree

7 files changed

+490
-18
lines changed

7 files changed

+490
-18
lines changed

pom.xml

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,35 @@
187187
</checkModificationExcludes>
188188
</configuration>
189189
</plugin>
190+
191+
<!-- these plugins are for junit tests -->
192+
<plugin>
193+
<artifactId>maven-surefire-plugin</artifactId>
194+
<version>2.22.2</version>
195+
</plugin>
196+
<plugin>
197+
<artifactId>maven-failsafe-plugin</artifactId>
198+
<version>2.22.2</version>
199+
</plugin>
200+
<plugin>
201+
<groupId>org.jacoco</groupId>
202+
<artifactId>jacoco-maven-plugin</artifactId>
203+
<version>0.8.7</version>
204+
<executions>
205+
<execution>
206+
<goals>
207+
<goal>prepare-agent</goal>
208+
</goals>
209+
</execution>
210+
<execution>
211+
<id>report</id>
212+
<phase>verify</phase>
213+
<goals>
214+
<goal>report</goal>
215+
</goals>
216+
</execution>
217+
</executions>
218+
</plugin>
190219
</plugins>
191220
</build>
192221

@@ -211,9 +240,21 @@
211240

212241
<!-- test dependencies -->
213242
<dependency>
214-
<groupId>junit</groupId>
215-
<artifactId>junit</artifactId>
216-
<version>4.13.2</version>
243+
<groupId>org.junit.jupiter</groupId>
244+
<artifactId>junit-jupiter</artifactId>
245+
<version>5.8.1</version>
246+
<scope>test</scope>
247+
</dependency>
248+
<dependency>
249+
<groupId>org.junit.jupiter</groupId>
250+
<artifactId>junit-jupiter-migrationsupport</artifactId>
251+
<version>5.8.1</version>
252+
<scope>test</scope>
253+
</dependency>
254+
<dependency>
255+
<groupId>com.squareup.okhttp3</groupId>
256+
<artifactId>mockwebserver</artifactId>
257+
<version>4.9.2</version>
217258
<scope>test</scope>
218259
</dependency>
219260
</dependencies>

src/main/java/ai/nightfall/scan/NightfallClient.java

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,20 @@
4343
public class NightfallClient implements Closeable {
4444

4545
private static final ObjectMapper objectMapper = new ObjectMapper();
46-
4746
private static final long wakeupDurationMillis = Duration.ofSeconds(15).toMillis();
48-
4947
private static final int DEFAULT_RETRY_COUNT = 5;
50-
5148
private static final String API_HOST = "https://api.nightfall.ai";
5249

50+
private final String apiHost;
5351
private final String apiKey;
5452
private final int fileUploadConcurrency;
5553
private final int retryCount;
5654
private final ExecutorService executor;
5755
private final OkHttpClient httpClient;
5856

59-
private NightfallClient(String apiKey, int fileUploadConcurrency, OkHttpClient httpClient) {
57+
// package-visible for testing
58+
NightfallClient(String apiHost, String apiKey, int fileUploadConcurrency, OkHttpClient httpClient) {
59+
this.apiHost = apiHost;
6060
this.apiKey = apiKey;
6161
this.fileUploadConcurrency = fileUploadConcurrency;
6262
this.retryCount = DEFAULT_RETRY_COUNT;
@@ -351,7 +351,7 @@ private ScanFileResponse scanUploadedFile(ScanFileRequest request, UUID fileID)
351351
*/
352352
private <E> E issueRequest(
353353
String path, String method, MediaType mediaType, byte[] body, Headers headers, Class<E> responseClass) {
354-
String url = API_HOST + path;
354+
String url = this.apiHost + path;
355355
Request.Builder builder = new Request.Builder().url(url);
356356

357357
if (headers != null) {
@@ -399,7 +399,7 @@ private <E> E issueRequest(
399399
return objectMapper.readValue(response.body().bytes(), responseClass);
400400
} catch (IOException e) {
401401
// If OkHTTP times out, allow retries
402-
if (e.getMessage().equals("timeout")) {
402+
if (e.getMessage().equalsIgnoreCase("timeout") || e.getMessage().equalsIgnoreCase("read timed out")) {
403403
if (attempt >= this.retryCount - 1) {
404404
throw new NightfallRequestTimeoutException("request timed out");
405405
}
@@ -430,7 +430,6 @@ private <E> E issueRequest(
430430
* A builder class that configures, validates, then creates instances of a Nightfall Client.
431431
*/
432432
public static class Builder {
433-
434433
private String apiKey;
435434
private int fileUploadConcurrency = 1;
436435

@@ -456,7 +455,7 @@ public static NightfallClient defaultClient() {
456455
.writeTimeout(Duration.ofSeconds(60))
457456
.connectionPool(cxnPool)
458457
.build();
459-
return new NightfallClient(readAPIKeyFromEnvironment(), 1, httpClient);
458+
return new NightfallClient(API_HOST, readAPIKeyFromEnvironment(), 1, httpClient);
460459
}
461460

462461
/**
@@ -589,7 +588,7 @@ public NightfallClient build() {
589588
.writeTimeout(this.writeTimeout)
590589
.connectionPool(cxnPool)
591590
.build();
592-
return new NightfallClient(this.apiKey, this.fileUploadConcurrency, httpClient);
591+
return new NightfallClient(API_HOST, this.apiKey, this.fileUploadConcurrency, httpClient);
593592
}
594593

595594
private static String readAPIKeyFromEnvironment() {

src/main/java/ai/nightfall/scan/model/NightfallErrorResponse.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package ai.nightfall.scan.model;
22

3+
import com.fasterxml.jackson.annotation.JsonCreator;
34
import com.fasterxml.jackson.annotation.JsonProperty;
45

56
import java.util.Map;
7+
import java.util.Objects;
68

79
/**
810
* The error model returned by Nightfall API requests that are unsuccessful. This object is generally returned
@@ -22,6 +24,24 @@ public class NightfallErrorResponse {
2224
@JsonProperty("additionalData")
2325
private Map<String, String> additionalData;
2426

27+
// appease jackson serialization
28+
public NightfallErrorResponse() {}
29+
30+
/**
31+
* Builds a new NightfallErrorResponse object.
32+
*
33+
* @param code the error code
34+
* @param message the error message
35+
* @param description further description
36+
* @param additionalData a map of key-value pairs that contain even further debugging information
37+
*/
38+
public NightfallErrorResponse(int code, String message, String description, Map<String, String> additionalData) {
39+
this.code = code;
40+
this.message = message;
41+
this.description = description;
42+
this.additionalData = additionalData;
43+
}
44+
2545
/**
2646
* Get the error code.
2747
*
@@ -67,4 +87,23 @@ public String toString() {
6787
+ ", additionalData=" + additionalData
6888
+ '}';
6989
}
90+
91+
@Override
92+
public boolean equals(Object o) {
93+
if (this == o) {
94+
return true;
95+
}
96+
if (o == null || getClass() != o.getClass()) {
97+
return false;
98+
}
99+
NightfallErrorResponse that = (NightfallErrorResponse) o;
100+
return code == that.code && Objects.equals(message, that.message)
101+
&& Objects.equals(description, that.description)
102+
&& Objects.equals(additionalData, that.additionalData);
103+
}
104+
105+
@Override
106+
public int hashCode() {
107+
return Objects.hash(code, message, description, additionalData);
108+
}
70109
}

src/main/java/ai/nightfall/scan/model/ScanFileResponse.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,20 @@ public class ScanFileResponse {
1515
@JsonProperty("message")
1616
private String message;
1717

18+
// appease jackson serialization
19+
public ScanFileResponse() {}
20+
21+
/**
22+
* Construct a new ScanFileResponse object.
23+
*
24+
* @param id the ID of the file to-be-scanned
25+
* @param message the status message
26+
*/
27+
public ScanFileResponse(UUID id, String message) {
28+
this.id = id;
29+
this.message = message;
30+
}
31+
1832
/**
1933
* Get the file ID.
2034
*
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package ai.nightfall.scan;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.junit.jupiter.params.ParameterizedTest;
5+
import org.junit.jupiter.params.provider.CsvSource;
6+
7+
import java.time.Duration;
8+
9+
import static org.junit.jupiter.api.Assertions.assertNotNull;
10+
import static org.junit.jupiter.api.Assertions.assertThrows;
11+
import static org.junit.jupiter.api.Assertions.fail;
12+
13+
/**
14+
* Unit tests for the NightfallClient.Builder.
15+
*/
16+
public class NightfallClientBuilderTest {
17+
18+
@Test
19+
public void testHappyPath() {
20+
NightfallClient client = new NightfallClient.Builder()
21+
.withAPIKey("foo") // only argument for builder with no default value
22+
.build();
23+
assertNotNull(client);
24+
}
25+
26+
@Test
27+
public void testDefault_missingAPIKey() {
28+
assertThrows(IllegalArgumentException.class, NightfallClient.Builder::defaultClient);
29+
}
30+
31+
@ParameterizedTest
32+
@CsvSource(value = {
33+
// invalid upload concurrency
34+
"-1, 60, 50, 40, 30, 20",
35+
"0, 60, 50, 40, 30, 20",
36+
"101, 60, 50, 40, 30, 20",
37+
// invalid connection timeout
38+
"100, -1, 50, 40, 30, 20",
39+
"100, 61, 50, 40, 30, 20",
40+
// invalid read timeout
41+
"100, 60, -1, 40, 30, 20",
42+
"100, 60, 121, 40, 30, 20",
43+
// invalid write timeout
44+
"100, 60, 50, -1, 30, 20",
45+
"100, 60, 50, 121, 30, 20",
46+
// invalid max idle connections
47+
"100, 60, 50, 40, 0, 20",
48+
"100, 60, 50, 40, 501, 20",
49+
// invalid keep alive
50+
"100, 60, 50, 40, 30, -1",
51+
"100, 60, 50, 40, 30, 121",
52+
})
53+
public void testInvalid(int uploadConcurrency, int connTimeout, int readTimeout,
54+
int writeTimeout, int maxIdleConns, int keepAliveSec) {
55+
assertThrows(IllegalArgumentException.class, () -> {
56+
new NightfallClient.Builder()
57+
.withAPIKey("foo")
58+
.withFileUploadConcurrency(uploadConcurrency)
59+
.withConnectionTimeout(Duration.ofSeconds(connTimeout))
60+
.withReadTimeout(Duration.ofSeconds(readTimeout))
61+
.withWriteTimeout(Duration.ofSeconds(writeTimeout))
62+
.withMaxIdleConnections(maxIdleConns)
63+
.withKeepAliveDuration(Duration.ofSeconds(keepAliveSec))
64+
.build();
65+
fail("build should not have succeeded");
66+
});
67+
}
68+
}

0 commit comments

Comments
 (0)