Skip to content

Commit 4d328e6

Browse files
authored
Improve error handling, fix base URL comparison bug (#28)
- Improve error handling (no more ambiguous NullPointerException when there's no matching recording in a cassette) - Fix bug with comparing base URLs
1 parent e1a5e42 commit 4d328e6

4 files changed

Lines changed: 87 additions & 2 deletions

File tree

src/main/java/com/easypost/easyvcr/MatchRules.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.easypost.easyvcr.internal.Utilities;
44
import com.easypost.easyvcr.requestelements.Request;
55

6+
import java.net.URI;
67
import java.util.ArrayList;
78
import java.util.List;
89
import java.util.Map;
@@ -50,13 +51,30 @@ private void by(BiFunction<Request, Request, Boolean> rule) {
5051
*/
5152
public MatchRules byBaseUrl() {
5253
by((received, recorded) -> {
53-
String receivedUri = received.getUri().getPath();
54-
String recordedUri = recorded.getUri().getPath();
54+
String receivedUri = getBaseUrl(received.getUri());
55+
String recordedUri = getBaseUrl(recorded.getUri());
5556
return receivedUri.equalsIgnoreCase(recordedUri);
5657
});
5758
return this;
5859
}
5960

61+
/**
62+
* Extract the base URL from a URI.
63+
*
64+
* @param url The URI to extract the base URL from.
65+
* @return The base URL.
66+
*/
67+
private static String getBaseUrl(URI url) {
68+
String baseUrl = url.getScheme() + "://" + url.getHost();
69+
if (url.getPort() != -1) {
70+
baseUrl += ":" + url.getPort();
71+
}
72+
if (url.getPath() != null) {
73+
baseUrl += url.getPath();
74+
}
75+
return baseUrl;
76+
}
77+
6078
/**
6179
* Add a rule to compare the bodies of the requests.
6280
*

src/main/java/com/easypost/easyvcr/clients/httpurlconnection/RecordableHttpURLConnection.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,17 @@ public RecordableHttpURLConnection(URL url, Cassette cassette, Mode mode)
144144
this(url, cassette, mode, new AdvancedSettings());
145145
}
146146

147+
/**
148+
* Throws an exception if a matching interaction has not been cached.
149+
*
150+
* @throws VCRException If the interaction has not been cached.
151+
*/
152+
private void cachedInteractionExistsOtherwiseError() throws VCRException {
153+
if (this.cachedInteraction == null) {
154+
throw new VCRException("No matching interaction found.");
155+
}
156+
}
157+
147158
/**
148159
* Get an object from the cache.
149160
*
@@ -725,6 +736,8 @@ Based on this Sun source code for HttpURLConnection (seen below):
725736
try {
726737
buildCache();
727738

739+
cachedInteractionExistsOtherwiseError();
740+
728741
if (this.cachedInteraction.getResponse().getStatus().getCode() >= 400) {
729742
// Client Error 4xx and Server Error 5xx
730743
return createInputStream(this.cachedInteraction.getResponse().getBody());
@@ -1297,6 +1310,7 @@ public InputStream getInputStream() throws IOException {
12971310
}
12981311
try {
12991312
buildCache();
1313+
cachedInteractionExistsOtherwiseError();
13001314
return createInputStream(this.cachedInteraction.getResponse().getBody());
13011315
} catch (VCRException | RecordingExpirationException e) {
13021316
throw new RuntimeException(e);

src/main/java/com/easypost/easyvcr/clients/httpurlconnection/RecordableHttpsURLConnection.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,17 @@ public RecordableHttpsURLConnection(URL url, Cassette cassette, Mode mode)
149149
this(url, cassette, mode, new AdvancedSettings());
150150
}
151151

152+
/**
153+
* Throws an exception if a matching interaction has not been cached.
154+
*
155+
* @throws VCRException If the interaction has not been cached.
156+
*/
157+
private void cachedInteractionExistsOtherwiseError() throws VCRException {
158+
if (this.cachedInteraction == null) {
159+
throw new VCRException("No matching interaction found.");
160+
}
161+
}
162+
152163
/**
153164
* Get an object from the cache.
154165
*
@@ -728,6 +739,8 @@ Based on this Sun source code for HttpURLConnection (seen below):
728739
try {
729740
buildCache();
730741

742+
cachedInteractionExistsOtherwiseError();
743+
731744
if (this.cachedInteraction.getResponse().getStatus().getCode() >= 400) {
732745
// Client Error 4xx and Server Error 5xx
733746
return createInputStream(this.cachedInteraction.getResponse().getBody());
@@ -1300,6 +1313,7 @@ public InputStream getInputStream() throws IOException {
13001313
}
13011314
try {
13021315
buildCache();
1316+
cachedInteractionExistsOtherwiseError();
13031317
return createInputStream(this.cachedInteraction.getResponse().getBody());
13041318
} catch (VCRException | RecordingExpirationException e) {
13051319
throw new RuntimeException(e);

src/test/java/HttpUrlConnectionTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import com.easypost.easyvcr.Mode;
1010
import com.easypost.easyvcr.RecordingExpirationException;
1111
import com.easypost.easyvcr.TimeFrame;
12+
import com.easypost.easyvcr.VCRException;
1213
import com.easypost.easyvcr.clients.httpurlconnection.RecordableHttpsURLConnection;
1314
import com.google.gson.JsonParseException;
1415
import org.junit.Assert;
@@ -534,4 +535,42 @@ public void testReplayHttpError() throws Exception {
534535
Assert.assertNotNull(clientAfterRequest);
535536
Assert.assertNotNull(clientAfterRequest.getErrorStream());
536537
}
538+
539+
@Test
540+
public void testCachedInteractionDoesNotExist() throws Exception {
541+
Cassette cassette = TestUtils.getCassette("test_cached_interaction_does_not_exist");
542+
cassette.erase(); // Erase cassette before recording
543+
544+
final String url = "https://google.com/path/to/endpoint";
545+
546+
// make connection using Mode.Record
547+
RecordableHttpsURLConnection connection =
548+
(RecordableHttpsURLConnection) HttpClients.newClient(HttpClientType.HttpsUrlConnection,
549+
url, cassette, Mode.Record);
550+
// make HTTP call (record to cassette)
551+
connection.connect();
552+
Assert.assertTrue(cassette.numInteractions() > 0); // make sure we recorded something
553+
554+
// Attempt to replay a cached interaction that does not exist
555+
// need to use strict matching to ensure we don't match a different interaction
556+
AdvancedSettings advancedSettings = new AdvancedSettings();
557+
advancedSettings.matchRules = MatchRules.strict();
558+
559+
connection = (RecordableHttpsURLConnection) HttpClients.newClient(HttpClientType.HttpsUrlConnection,
560+
url + "1", cassette, Mode.Replay, advancedSettings);
561+
// make HTTP call (attempt replay from cassette)
562+
connection.connect();
563+
564+
// Attempt to pull data (e.g. body) from the response (via the input stream)
565+
// this throws a RuntimeException because of the way the exceptions are coalesced internally
566+
try {
567+
connection.getInputStream();
568+
// if we get here, the exception was not thrown as expected
569+
Assert.fail();
570+
} catch (Exception e) {
571+
Assert.assertTrue(e instanceof RuntimeException);
572+
Assert.assertTrue(e.getCause() instanceof VCRException);
573+
Assert.assertEquals(e.getCause().getMessage(), "No matching interaction found.");
574+
}
575+
}
537576
}

0 commit comments

Comments
 (0)