diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e7b9ee..93b8845 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # CHANGELOG +## Next Release +- Improvements to censoring + - Ability to define censored elements individually, with per-element case sensitivity +- Improvements to matching + - Ability to ignore certain elements when matching by body + ## v0.2.0 (2022-05-31) - Enhance censoring to work on nested data diff --git a/README.md b/README.md index 3a98a1f..c731210 100644 --- a/README.md +++ b/README.md @@ -98,11 +98,14 @@ NOTE: Censors can only be applied to JSON request and response bodies. Attemptin import com.easypost.easyvcr; import com.easypost.easyvcr.AdvancedSettings; import com.easypost.easyvcr.Cassette; +import com.easypost.easyvcr.CensorElement; import com.easypost.easyvcr.Censors; import com.easypost.easyvcr.Mode; import com.easypost.easyvcr.clients.httpurlconnection.RecordableHttpsURLConnection; import com.easypost.easyvcr.clients.httpurlconnection.RecordableURL; +import java.util.ArrayList; + public class Example { public static void main(String[] args) { Cassette cassette = new Cassette("path/to/cassettes", "my_cassette"); @@ -110,9 +113,13 @@ public class Example { AdvancedSettings advancedSettings = new AdvancedSettings(); List headersToCensor = new ArrayList<>(); headersToCensor.add("Authorization"); // Hide the Authorization header - advancedSettings.censors = new Censors().hideHeader(headersToCensor); + advancedSettings.censors = new Censors().censorHeadersByKeys(headersToCensor); + advancedSettings.censors.censorBodyElements(new ArrayList<>() {{ + add(new CensorElement("table", true)); // Hide the table element (case-sensitive) in the request and response body + }}); // or - advancedSettings.censors = Censors.strict(); // use the built-in strict censoring mode (hides common sensitive data) + advancedSettings.censors = + Censors.strict(); // use the built-in strict censoring mode (hides common sensitive data) RecordableURL recordableURL = new RecordableURL("https://www.example.com", cassette, Mode.Replay, advancedSettings); diff --git a/src/main/java/com/easypost/easyvcr/CensorElement.java b/src/main/java/com/easypost/easyvcr/CensorElement.java new file mode 100644 index 0000000..75ad20c --- /dev/null +++ b/src/main/java/com/easypost/easyvcr/CensorElement.java @@ -0,0 +1,35 @@ +package com.easypost.easyvcr; + +public class CensorElement { + /** + * The name of the element to censor. + */ + private final String name; + /** + * Whether the name must match exactly to trigger a censor. + */ + private final boolean caseSensitive; + + /** + * Constructor. + * @param name The name of the element to censor. + * @param caseSensitive Whether the name must match exactly to trigger a censor. + */ + public CensorElement(String name, boolean caseSensitive) { + this.name = name; + this.caseSensitive = caseSensitive; + } + + /** + * Return whether the element matches the name, accounting for case sensitivity. + * @param key The name to check. + * @return True if the element matches the name. + */ + public boolean matches(String key) { + if (caseSensitive) { + return key.equals(name); + } else { + return key.equalsIgnoreCase(name); + } + } +} diff --git a/src/main/java/com/easypost/easyvcr/Censors.java b/src/main/java/com/easypost/easyvcr/Censors.java index a32a0b1..2540a7a 100644 --- a/src/main/java/com/easypost/easyvcr/Censors.java +++ b/src/main/java/com/easypost/easyvcr/Censors.java @@ -19,13 +19,9 @@ */ public final class Censors { /** - * The body parameters to censor. + * The body elements to censor. */ - private final List bodyParamsToCensor; - /** - * Whether censor keys are case sensitive. - */ - private final boolean caseSensitive; + private final List bodyElementsToCensor; /** * The string to replace censored data with. */ @@ -33,11 +29,11 @@ public final class Censors { /** * The headers to censor. */ - private final List headersToCensor; + private final List headersToCensor; /** * The query parameters to censor. */ - private final List queryParamsToCensor; + private final List queryParamsToCensor; /** * Initialize a new instance of the Censors factory, using default censor string. @@ -52,21 +48,10 @@ public Censors() { * @param censorString The string to use to censor sensitive information. */ public Censors(String censorString) { - this(censorString, false); - } - - /** - * Initialize a new instance of the Censors factory. - * - * @param censorString The string to use to censor sensitive information. - * @param caseSensitive Whether to use case sensitive censoring. - */ - public Censors(String censorString, boolean caseSensitive) { this.queryParamsToCensor = new ArrayList<>(); - this.bodyParamsToCensor = new ArrayList<>(); + this.bodyElementsToCensor = new ArrayList<>(); this.headersToCensor = new ArrayList<>(); this.censorText = censorString; - this.caseSensitive = caseSensitive; } /** @@ -85,75 +70,187 @@ public static Censors regular() { */ public static Censors strict() { Censors censors = new Censors(); - censors.hideHeaders(Statics.DEFAULT_CREDENTIAL_HEADERS_TO_HIDE); - censors.hideBodyParameters(Statics.DEFAULT_CREDENTIAL_PARAMETERS_TO_HIDE); - censors.hideQueryParameters(Statics.DEFAULT_CREDENTIAL_PARAMETERS_TO_HIDE); + censors.censorHeadersByKeys(Statics.DEFAULT_CREDENTIAL_HEADERS_TO_HIDE); + censors.censorBodyElementsByKeys(Statics.DEFAULT_CREDENTIAL_PARAMETERS_TO_HIDE); + censors.censorQueryParametersByKeys(Statics.DEFAULT_CREDENTIAL_PARAMETERS_TO_HIDE); return censors; } /** - * Add a rule to censor specified body parameters. - * Note: Only top-level pairs can be censored. + * Apply censors to a JSON list. * - * @param parameterKeys Keys of body parameters to censor. - * @return This Censors factory. + * @param list JSON list to process. + * @param censorText Text to use when censoring an element. + * @param elementsToCensor List of elements to find and censor. + * @return Censored JSON list. */ - public Censors hideBodyParameters(List parameterKeys) { - bodyParamsToCensor.addAll(parameterKeys); - return this; + private static List applyJsonCensors(List list, String censorText, + List elementsToCensor) { + if (list == null || list.size() == 0) { + // short circuit if list is null or empty + return list; + } + + List censoredList = new ArrayList<>(); + + for (Object object : list) { + Object value = object; + if (Utilities.isDictionary(value)) { + // recursively censor inner dictionaries + try { + // change the value if can be parsed as a dictionary + value = applyJsonCensors((Map) value, censorText, elementsToCensor); + } catch (ClassCastException e) { + // otherwise, skip censoring + } + } else if (Utilities.isList(value)) { + // recursively censor list elements + try { + // change the value if can be parsed as a list + value = applyJsonCensors((List) value, censorText, elementsToCensor); + } catch (ClassCastException e) { + // otherwise, skip censoring + } + } // either a primitive or null, no censoring needed + + censoredList.add(value); + } + + return censoredList; } /** - * Add a rule to censor specified header keys. - * Note: This will censor the header keys in both the request and response. + * Apply censors to a JSON dictionary. * - * @param headerKeys Keys of headers to censor. - * @return This Censors factory. + * @param dictionary JSON dictionary to process. + * @param censorText Text to use when censoring an element. + * @param elementsToCensor List of elements to find and censor. + * @return Censored JSON dicstionary. */ - public Censors hideHeaders(List headerKeys) { - headersToCensor.addAll(headerKeys); - return this; + private static Map applyJsonCensors(Map dictionary, String censorText, + List elementsToCensor) { + if (dictionary == null || dictionary.size() == 0) { + // short circuit if dictionary is null or empty + return dictionary; + } + + Map censoredBodyDictionary = new HashMap<>(); + + for (Map.Entry entry : dictionary.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (elementShouldBeCensored(key, elementsToCensor)) { + if (value == null) { + // don't need to worry about censoring something that's null + // (don't replace null with the censor string) + continue; + } else if (Utilities.isDictionary(value)) { + // replace with empty dictionary + censoredBodyDictionary.put(key, new HashMap<>()); + } else if (Utilities.isList(value)) { + // replace with empty array + censoredBodyDictionary.put(key, new ArrayList<>()); + } else { + // replace with censor text + censoredBodyDictionary.put(key, censorText); + } + } else { + if (Utilities.isDictionary(value)) { + // recursively censor inner dictionaries + try { + // change the value if can be parsed as a dictionary + value = applyJsonCensors((Map) value, censorText, elementsToCensor); + } catch (ClassCastException e) { + // otherwise, skip censoring + } + } else if (Utilities.isList(value)) { + // recursively censor list elements + try { + // change the value if can be parsed as a list + value = applyJsonCensors((List) value, censorText, elementsToCensor); + } catch (ClassCastException e) { + // otherwise, skip censoring + } + } + + censoredBodyDictionary.put(key, value); + } + } + + return censoredBodyDictionary; } /** - * Add a rule to censor specified query parameters. + * Apply censors to a JSON string. * - * @param parameterKeys Keys of query parameters to censor. - * @return This Censors factory. + * @param data The JSON string to censor. + * @param censorText The string to use to censor sensitive information. + * @param elementsToCensor The body elements to censor. + * @return The censored JSON string. */ - public Censors hideQueryParameters(List parameterKeys) { - queryParamsToCensor.addAll(parameterKeys); - return this; + public static String censorJsonData(String data, String censorText, List elementsToCensor) { + Map bodyDictionary; + try { + bodyDictionary = Serialization.convertJsonToObject(data, Map.class); + Map censoredBodyDictionary = applyJsonCensors(bodyDictionary, censorText, elementsToCensor); + return censoredBodyDictionary == null ? data : Serialization.convertObjectToJson(censoredBodyDictionary); + } catch (Exception ignored) { + // body is not a JSON dictionary + try { + List bodyList = Serialization.convertJsonToObject(data, List.class); + List censoredBodyList = applyJsonCensors(bodyList, censorText, elementsToCensor); + return censoredBodyList == null ? data : Serialization.convertObjectToJson(censoredBodyList); + } catch (Exception notJsonData) { + throw new JsonParseException("Body is not a JSON dictionary or list"); + } + } } /** - * Censor the appropriate body parameters. + * Check if the current JSON element should be censored. * - * @param body String representation of request body to apply censors to. + * @param foundKey Key of the JSON element to evaluate. + * @param elementsToCensor List of censors to compare against. + * @return True if the value should be censored, false otherwise. + */ + private static boolean elementShouldBeCensored(String foundKey, List elementsToCensor) { + return elementsToCensor.stream().anyMatch(queryElement -> queryElement.matches(foundKey)); + } + + /** + * Censor the appropriate body elements. + * + * @param body String representation of request body to apply censors to. + * @param censorText The string to use to censor sensitive information. + * @param bodyElementsToCensor The body elements to censor. * @return Censored string representation of request body. */ - public String censorBodyParameters(String body) { + public static String applyBodyParameterCensors(String body, String censorText, + List bodyElementsToCensor) { if (body == null || body.length() == 0) { // short circuit if body is null or empty return body; } - if (bodyParamsToCensor.size() == 0) { + if (bodyElementsToCensor.size() == 0) { // short circuit if there are no censors to apply return body; } // TODO: Future different content type support here, only JSON is supported currently - return censorJsonBodyParameters(body); + return censorJsonData(body, censorText, bodyElementsToCensor); } /** * Censor the appropriate headers. * - * @param headers Map of headers to apply censors to. + * @param headers Map of headers to apply censors to. + * @param censorText The string to use to censor sensitive information. + * @param headersToCensor The headers to censor. * @return Censored map of headers. */ - public Map> censorHeaders(Map> headers) { + public static Map> applyHeaderCensors(Map> headers, String censorText, + List headersToCensor) { if (headers == null || headers.size() == 0) { // short circuit if there are no headers to censor return headers; @@ -166,21 +263,29 @@ public Map> censorHeaders(Map> headers final Map> headersCopy = new HashMap<>(headers); - for (String headerKey : headersToCensor) { - if (headersCopy.containsKey(headerKey)) { + List headerKeys = new ArrayList<>(headersCopy.keySet()); + for (String headerKey : headerKeys) { + if (headerKey == null) { + continue; + } + if (elementShouldBeCensored(headerKey, headersToCensor)) { headersCopy.put(headerKey, Collections.singletonList(censorText)); } } + return headersCopy; } /** * Censor the appropriate query parameters. * - * @param url Full URL string to apply censors to. + * @param url Full URL string to apply censors to. + * @param censorText The string to use to censor sensitive information. + * @param queryParamsToCensor The query parameters to censor. * @return Censored URL string. */ - public String censorQueryParameters(String url) { + public static String applyQueryParameterCensors(String url, String censorText, + List queryParamsToCensor) { if (url == null || url.length() == 0) { // short circuit if url is null return url; @@ -198,9 +303,10 @@ public String censorQueryParameters(String url) { return url; } - for (String parameterKey : queryParamsToCensor) { - if (queryParameters.containsKey(parameterKey)) { - queryParameters.put(parameterKey, censorText); + List queryKeys = new ArrayList<>(queryParameters.keySet()); + for (String queryKey : queryKeys) { + if (elementShouldBeCensored(queryKey, queryParamsToCensor)) { + queryParameters.put(queryKey, censorText); } } @@ -214,117 +320,141 @@ public String censorQueryParameters(String url) { return uri.getScheme() + "://" + uri.getHost() + uri.getPath() + "?" + formattedQueryParameters; } - private List applyBodyCensors(List list) { - if (list == null || list.size() == 0) { - // short circuit if list is null or empty - return list; - } - - List censoredList = new ArrayList<>(); - - for (Object object : list) { - Object value = object; - if (Utilities.isDictionary(value)) { - // recursively censor inner dictionaries - try { - // change the value if can be parsed as a dictionary - value = applyBodyCensors((Map) value); - } catch (ClassCastException e) { - // otherwise, skip censoring - } - } else if (Utilities.isList(value)) { - // recursively censor list elements - try { - // change the value if can be parsed as a list - value = applyBodyCensors((List) value); - } catch (ClassCastException e) { - // otherwise, skip censoring - } - } // either a primitive or null, no censoring needed + /** + * Add a rule to censor specified body elements. + * + * @param elements List of body elements to censor. + * @return This Censors factory. + */ + public Censors censorBodyElements(List elements) { + bodyElementsToCensor.addAll(elements); + return this; + } - censoredList.add(value); + /** + * Add a rule to censor specified body elements by their keys. + * + * @param elementKeys Keys of body elements to censor. + * @param caseSensitive Whether to use case-sensitive censoring. + * @return This Censors factory. + */ + public Censors censorBodyElementsByKeys(List elementKeys, boolean caseSensitive) { + for (String elementKey : elementKeys) { + bodyElementsToCensor.add(new CensorElement(elementKey, caseSensitive)); } + return this; + } - return censoredList; + /** + * Add a rule to censor specified body elements by their keys. + * + * @param elementKeys Keys of body elementKeys to censor. + * @return This Censors factory. + */ + public Censors censorBodyElementsByKeys(List elementKeys) { + return censorBodyElementsByKeys(elementKeys, false); + } + /** + * Add a rule to censor specified headers. + * Note: This will censor the header keys in both the request and response. + * + * @param headers List of Headers to censor. + * @return This Censors factory. + */ + public Censors censorHeaders(List headers) { + headersToCensor.addAll(headers); + return this; } - private Map applyBodyCensors(Map dictionary) { - if (dictionary == null || dictionary.size() == 0) { - // short circuit if dictionary is null or empty - return dictionary; + /** + * Add a rule to censor specified headers by their keys. + * Note: This will censor the header keys in both the request and response. + * + * @param headerKeys Keys of headers to censor. + * @param caseSensitive Whether to use case-sensitive censoring. + * @return This Censors factory. + */ + public Censors censorHeadersByKeys(List headerKeys, boolean caseSensitive) { + for (String headerKey : headerKeys) { + headersToCensor.add(new CensorElement(headerKey, caseSensitive)); } + return this; + } - Map censoredBodyDictionary = new HashMap<>(); + /** + * Add a rule to censor specified headers by their keys. + * Note: This will censor the header keys in both the request and response. + * + * @param headerKeys Keys of headers to censor. + * @return This Censors factory. + */ + public Censors censorHeadersByKeys(List headerKeys) { + return censorHeadersByKeys(headerKeys, false); + } - for (Map.Entry entry : dictionary.entrySet()) { - String key = entry.getKey(); - Object value = entry.getValue(); - if (keyShouldBeCensored(key, this.bodyParamsToCensor)) { - if (value == null) { - // don't need to worry about censoring something that's null - // (don't replace null with the censor string) - continue; - } else if (Utilities.isDictionary(value)) { - // replace with empty dictionary - censoredBodyDictionary.put(key, new HashMap<>()); - } else if (Utilities.isList(value)) { - // replace with empty array - censoredBodyDictionary.put(key, new ArrayList<>()); - } else { - // replace with censor text - censoredBodyDictionary.put(key, this.censorText); - } - } else { - if (Utilities.isDictionary(value)) { - // recursively censor inner dictionaries - try { - // change the value if can be parsed as a dictionary - value = applyBodyCensors((Map) value); - } catch (ClassCastException e) { - // otherwise, skip censoring - } - } else if (Utilities.isList(value)) { - // recursively censor list elements - try { - // change the value if can be parsed as a list - value = applyBodyCensors((List) value); - } catch (ClassCastException e) { - // otherwise, skip censoring - } - } + /** + * Add a rule to censor specified query parameters. + * + * @param elements List of QueryElements to censor. + * @return This Censors factory. + */ + public Censors censorQueryParameters(List elements) { + queryParamsToCensor.addAll(elements); + return this; + } - censoredBodyDictionary.put(key, value); - } + /** + * Add a rule to censor specified query parameters by their keys. + * + * @param parameterKeys Keys of query parameters to censor. + * @param caseSensitive Whether to use case-sensitive censoring. + * @return This Censors factory. + */ + public Censors censorQueryParametersByKeys(List parameterKeys, boolean caseSensitive) { + for (String parameterKey : parameterKeys) { + queryParamsToCensor.add(new CensorElement(parameterKey, caseSensitive)); } + return this; + } - return censoredBodyDictionary; + /** + * Add a rule to censor specified query parameters by their keys. + * + * @param parameterKeys Keys of query parameters to censor. + * @return This Censors factory. + */ + public Censors censorQueryParametersByKeys(List parameterKeys) { + return censorQueryParametersByKeys(parameterKeys, false); } - private String censorJsonBodyParameters(String body) { - Map bodyDictionary; - try { - bodyDictionary = Serialization.convertJsonToObject(body, Map.class); - Map censoredBodyDictionary = applyBodyCensors(bodyDictionary); - return censoredBodyDictionary == null ? body : Serialization.convertObjectToJson(censoredBodyDictionary); - } catch (Exception ignored) { - // body is not a JSON dictionary - try { - List bodyList = Serialization.convertJsonToObject(body, List.class); - List censoredBodyList = applyBodyCensors(bodyList); - return censoredBodyList == null ? body : Serialization.convertObjectToJson(censoredBodyList); - } catch (Exception notJsonData) { - throw new JsonParseException("Body is not a JSON dictionary or list"); - } - } + /** + * Censor the appropriate body elements. + * + * @param body String representation of request body to apply censors to. + * @return Censored string representation of request body. + */ + public String applyBodyParameterCensors(String body) { + return applyBodyParameterCensors(body, this.censorText, this.bodyElementsToCensor); } - private boolean keyShouldBeCensored(String foundKey, List keysToCensor) { - // keysToCensor are already cased as needed - if (!this.caseSensitive) { - foundKey = foundKey.toLowerCase(); - } + /** + * Censor the appropriate headers. + * + * @param headers Map of headers to apply censors to. + * @return Censored map of headers. + */ + public Map> applyHeaderCensors(Map> headers) { + return applyHeaderCensors(headers, this.censorText, this.headersToCensor); + } - return keysToCensor.contains(foundKey); + /** + * Censor the appropriate query parameters. + * + * @param url Full URL string to apply censors to. + * @return Censored URL string. + */ + public String applyQueryParameterCensors(String url) { + return applyQueryParameterCensors(url, this.censorText, this.queryParamsToCensor); } } diff --git a/src/main/java/com/easypost/easyvcr/HttpClients.java b/src/main/java/com/easypost/easyvcr/HttpClients.java index 2a89d1b..0e9a78a 100644 --- a/src/main/java/com/easypost/easyvcr/HttpClients.java +++ b/src/main/java/com/easypost/easyvcr/HttpClients.java @@ -11,7 +11,7 @@ /** * HttpClient singleton for EasyVCR. */ -public final class HttpClients { +public abstract class HttpClients { /** * Get a new client configured to use cassettes. diff --git a/src/main/java/com/easypost/easyvcr/MatchRules.java b/src/main/java/com/easypost/easyvcr/MatchRules.java index d7e5311..1dedc0c 100644 --- a/src/main/java/com/easypost/easyvcr/MatchRules.java +++ b/src/main/java/com/easypost/easyvcr/MatchRules.java @@ -60,28 +60,45 @@ public MatchRules byBaseUrl() { /** * Add a rule to compare the bodies of the requests. * + * @param ignoredElements List of body elements to ignore when comparing the requests. * @return This MatchRules factory. */ - public MatchRules byBody() { + public MatchRules byBody(List ignoredElements) { by((received, recorded) -> { - if (received.getBody() == null && recorded.getBody() == null) { + String receivedBody = received.getBody(); + String recordedBody = recorded.getBody(); + + if (receivedBody == null && recordedBody == null) { // both have null bodies, so they match return true; } - if (received.getBody() == null || recorded.getBody() == null) { + if (receivedBody == null || recordedBody == null) { // one has a null body, so they don't match return false; } + // remove ignored elements from the body + receivedBody = Utilities.removeJsonElements(receivedBody, ignoredElements); + recordedBody = Utilities.removeJsonElements(recordedBody, ignoredElements); + // convert body to base64string to assist comparison by removing special characters - String receivedBody = Tools.toBase64String(received.getBody()); - String recordedBody = Tools.toBase64String(recorded.getBody()); + receivedBody = Tools.toBase64String(receivedBody); + recordedBody = Tools.toBase64String(recordedBody); return receivedBody.equalsIgnoreCase(recordedBody); }); return this; } + /** + * Add a rule to compare the bodies of the requests. + * + * @return This MatchRules factory. + */ + public MatchRules byBody() { + return byBody(null); + } + /** * Add a rule to compare the entire requests. * Note, this rule is very strict, and will fail if the requests are not identical (including duration). diff --git a/src/main/java/com/easypost/easyvcr/Statics.java b/src/main/java/com/easypost/easyvcr/Statics.java index dfbe511..7ad7f86 100644 --- a/src/main/java/com/easypost/easyvcr/Statics.java +++ b/src/main/java/com/easypost/easyvcr/Statics.java @@ -7,7 +7,7 @@ import java.util.List; import java.util.Map; -public final class Statics { +public abstract class Statics { public static final String VIA_RECORDING_HEADER_KEY = "X-Via-EasyVCR-Recording"; /** * Default headers to censor (credential-related headers). diff --git a/src/main/java/com/easypost/easyvcr/Utilities.java b/src/main/java/com/easypost/easyvcr/Utilities.java index e5b7b5a..55aa6b1 100644 --- a/src/main/java/com/easypost/easyvcr/Utilities.java +++ b/src/main/java/com/easypost/easyvcr/Utilities.java @@ -5,7 +5,7 @@ import java.util.List; import java.util.Map; -public class Utilities { +public abstract class Utilities { /** * Check if the connection came from an EasyVCR recording. @@ -48,4 +48,18 @@ public static boolean isDictionary(Object obj) { public static boolean isList(Object obj) { return obj instanceof List; } + + /** + * Remove elements from a JSON string. + * @param json The JSON string to remove elements from. + * @param elements The elements to remove. + * @return The JSON string without the elements. + */ + public static String removeJsonElements(String json, List elements) { + if (json == null || elements == null) { + return json; + } + + return Censors.censorJsonData(json, "FILTERED", elements); + } } diff --git a/src/main/java/com/easypost/easyvcr/interactionconverters/HttpUrlConnectionInteractionConverter.java b/src/main/java/com/easypost/easyvcr/interactionconverters/HttpUrlConnectionInteractionConverter.java index 216e62d..1bd1131 100644 --- a/src/main/java/com/easypost/easyvcr/interactionconverters/HttpUrlConnectionInteractionConverter.java +++ b/src/main/java/com/easypost/easyvcr/interactionconverters/HttpUrlConnectionInteractionConverter.java @@ -42,9 +42,9 @@ public Request createRecordedRequest(HttpURLConnection connection, RecordableReq String method = connection.getRequestMethod(); // apply censors - uriString = censors.censorQueryParameters(uriString); - headers = censors.censorHeaders(headers); - body = censors.censorBodyParameters(body); + uriString = censors.applyQueryParameterCensors(uriString); + headers = censors.applyHeaderCensors(headers); + body = censors.applyBodyParameterCensors(body); // create the request @@ -88,8 +88,8 @@ public ResponseAndTime createRecordedResponse(HttpURLConnection connection, Cens } // apply censors - uriString = censors.censorQueryParameters(uriString); - headers = censors.censorHeaders(headers); + uriString = censors.applyQueryParameterCensors(uriString); + headers = censors.applyHeaderCensors(headers); // we don't censor the response body, only the request body // create the response @@ -98,11 +98,11 @@ public ResponseAndTime createRecordedResponse(HttpURLConnection connection, Cens response.setUri(new URI(uriString)); response.setHeaders(headers); if (body != null) { - body = censors.censorBodyParameters(body); + body = censors.applyBodyParameterCensors(body); response.setBody(body); } if (errors != null) { - errors = censors.censorBodyParameters(errors); + errors = censors.applyBodyParameterCensors(errors); response.setErrors(errors); } diff --git a/src/main/java/com/easypost/easyvcr/internalutilities/Files.java b/src/main/java/com/easypost/easyvcr/internalutilities/Files.java index 5485646..a4aaf66 100644 --- a/src/main/java/com/easypost/easyvcr/internalutilities/Files.java +++ b/src/main/java/com/easypost/easyvcr/internalutilities/Files.java @@ -6,7 +6,7 @@ import java.util.ArrayList; import java.util.List; -public class Files { +public abstract class Files { /** * Creates a file if it doesn't exist. * diff --git a/src/main/java/com/easypost/easyvcr/internalutilities/Tools.java b/src/main/java/com/easypost/easyvcr/internalutilities/Tools.java index 1a02259..94d1512 100644 --- a/src/main/java/com/easypost/easyvcr/internalutilities/Tools.java +++ b/src/main/java/com/easypost/easyvcr/internalutilities/Tools.java @@ -26,7 +26,7 @@ /** * Internal tools for EasyVCR. */ -public class Tools { +public abstract class Tools { /** * Get a File object from a path. * diff --git a/src/main/java/com/easypost/easyvcr/internalutilities/Utils.java b/src/main/java/com/easypost/easyvcr/internalutilities/Utils.java index 3026567..3bcb8be 100644 --- a/src/main/java/com/easypost/easyvcr/internalutilities/Utils.java +++ b/src/main/java/com/easypost/easyvcr/internalutilities/Utils.java @@ -9,7 +9,7 @@ import static java.lang.String.format; -public final class Utils { +public abstract class Utils { private static final boolean[] T_CHAR = new boolean[256]; private static final boolean[] FIELD_V_CHAR = new boolean[256]; diff --git a/src/main/java/com/easypost/easyvcr/internalutilities/json/Serialization.java b/src/main/java/com/easypost/easyvcr/internalutilities/json/Serialization.java index 9c59786..91b085b 100644 --- a/src/main/java/com/easypost/easyvcr/internalutilities/json/Serialization.java +++ b/src/main/java/com/easypost/easyvcr/internalutilities/json/Serialization.java @@ -7,7 +7,7 @@ /** * JSON de/serialization utilities. */ -public final class Serialization { +public abstract class Serialization { /** * Convert a JSON string to an object. * @@ -41,7 +41,7 @@ public static T convertJsonToObject(JsonElement json, Class clazz) { * @return JSON string */ public static String convertObjectToJson(Object object) { - Gson gson = new GsonBuilder().setPrettyPrinting().create(); + Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().create(); return gson.toJson(object); } } diff --git a/src/test/java/FakeDataService.java b/src/test/java/FakeDataService.java index 3a9c236..5e289c9 100644 --- a/src/test/java/FakeDataService.java +++ b/src/test/java/FakeDataService.java @@ -3,24 +3,32 @@ import com.easypost.easyvcr.clients.httpurlconnection.RecordableHttpsURLConnection; import com.easypost.easyvcr.internalutilities.json.Serialization; +import java.util.List; + import static com.easypost.easyvcr.internalutilities.Tools.readFromInputStream; public class FakeDataService { - public final static String GET_POSTS_URL = "https://jsonplaceholder.typicode.com/posts"; + public class ExchangeRates { + public String code; + public String currency; + public List rates; + public String table; + } - private interface FakeDataServiceBaseInterface { + public class Rate { + public String effectiveDate; + public float mid; + public String no; + } - Post[] getPosts() throws Exception; + public static final String URL = "https://api.nbp.pl/api/exchangerates/rates/a/gbp/last/10/?format=json"; - Object getPostsRawResponse() throws Exception; - } + private interface FakeDataServiceBaseInterface { + + ExchangeRates getExchangeRates() throws Exception; - public static class Post { - public int userId; - public int id; - public String title; - public String body; + Object getExchangeRatesRawResponse() throws Exception; } public static class HttpUrlConnection extends FakeDataServiceBase implements FakeDataServiceBaseInterface { @@ -44,16 +52,16 @@ public RecordableHttpURLConnection getClient(String url) throws Exception { } @Override - public Post[] getPosts() throws Exception { - RecordableHttpURLConnection client = (RecordableHttpURLConnection) getPostsRawResponse(); + public ExchangeRates getExchangeRates() throws Exception { + RecordableHttpURLConnection client = (RecordableHttpURLConnection) getExchangeRatesRawResponse(); String json = readFromInputStream(client.getInputStream()); - return Serialization.convertJsonToObject(json, Post[].class); + return Serialization.convertJsonToObject(json, ExchangeRates.class); } @Override - public Object getPostsRawResponse() throws Exception { - RecordableHttpURLConnection client = getClient(GET_POSTS_URL); + public Object getExchangeRatesRawResponse() throws Exception { + RecordableHttpURLConnection client = getClient(URL); client.connect(); return client; } @@ -80,16 +88,16 @@ public RecordableHttpsURLConnection getClient(String url) throws Exception { } @Override - public Post[] getPosts() throws Exception { - RecordableHttpsURLConnection client = (RecordableHttpsURLConnection) getPostsRawResponse(); + public ExchangeRates getExchangeRates() throws Exception { + RecordableHttpsURLConnection client = (RecordableHttpsURLConnection) getExchangeRatesRawResponse(); String json = readFromInputStream(client.getInputStream()); - return Serialization.convertJsonToObject(json, Post[].class); + return Serialization.convertJsonToObject(json, ExchangeRates.class); } @Override - public Object getPostsRawResponse() throws Exception { - RecordableHttpsURLConnection client = getClient(GET_POSTS_URL); + public Object getExchangeRatesRawResponse() throws Exception { + RecordableHttpsURLConnection client = getClient(URL); client.connect(); return client; } diff --git a/src/test/java/HttpUrlConnectionTest.java b/src/test/java/HttpUrlConnectionTest.java index e673334..6f982e3 100644 --- a/src/test/java/HttpUrlConnectionTest.java +++ b/src/test/java/HttpUrlConnectionTest.java @@ -1,5 +1,6 @@ import com.easypost.easyvcr.AdvancedSettings; import com.easypost.easyvcr.Cassette; +import com.easypost.easyvcr.CensorElement; import com.easypost.easyvcr.Censors; import com.easypost.easyvcr.HttpClientType; import com.easypost.easyvcr.HttpClients; @@ -12,6 +13,7 @@ import java.io.IOException; import java.io.OutputStream; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.time.Duration; import java.time.Instant; @@ -22,12 +24,12 @@ public class HttpUrlConnectionTest { - private static FakeDataService.Post[] GetFakePostsRequest(Cassette cassette, Mode mode) throws Exception { + private static FakeDataService.ExchangeRates GetExchangeRatesRequest(Cassette cassette, Mode mode) throws Exception { RecordableHttpsURLConnection connection = TestUtils.getSimpleHttpsURLConnection(cassette.name, mode, null); FakeDataService.HttpsUrlConnection fakeDataService = new FakeDataService.HttpsUrlConnection(connection); - return fakeDataService.getPosts(); + return fakeDataService.getExchangeRates(); } @Test @@ -59,7 +61,7 @@ public void testNonJsonDataWithCensors() throws IOException { List bodyCensors = new ArrayList<>(); bodyCensors.add("Date"); - advancedSettings.censors = new Censors("*****").hideBodyParameters(bodyCensors); + advancedSettings.censors = new Censors("*****").censorBodyElementsByKeys(bodyCensors); advancedSettings.matchRules = new MatchRules().byMethod().byBody().byFullUrl(); RecordableHttpsURLConnection connection = @@ -102,7 +104,7 @@ public void testErase() throws Exception { Cassette cassette = TestUtils.getCassette("test_erase"); // record something to the cassette - FakeDataService.Post[] posts = GetFakePostsRequest(cassette, Mode.Record); + FakeDataService.ExchangeRates exchangeRates = GetExchangeRatesRequest(cassette, Mode.Record); Assert.assertTrue(cassette.numInteractions() > 0); // erase the cassette @@ -115,10 +117,9 @@ public void testEraseAndRecord() throws Exception { Cassette cassette = TestUtils.getCassette("test_erase_and_record"); cassette.erase(); // Erase cassette before recording - FakeDataService.Post[] posts = GetFakePostsRequest(cassette, Mode.Record); + FakeDataService.ExchangeRates exchangeRates = GetExchangeRatesRequest(cassette, Mode.Record); - Assert.assertNotNull(posts); - Assert.assertEquals(posts.length, 100); + Assert.assertNotNull(exchangeRates); Assert.assertTrue(cassette.numInteractions() > 0); // Make sure cassette is not empty } @@ -128,7 +129,7 @@ public void testEraseAndPlayback() { cassette.erase(); // Erase cassette before recording // cassette is empty, so replaying should throw an exception - Assert.assertThrows(Exception.class, () -> GetFakePostsRequest(cassette, Mode.Replay)); + Assert.assertThrows(Exception.class, () -> GetExchangeRatesRequest(cassette, Mode.Replay)); } @Test @@ -137,12 +138,12 @@ public void testAutoMode() throws Exception { cassette.erase(); // Erase cassette before recording // in replay mode, if cassette is empty, should throw an exception - Assert.assertThrows(Exception.class, () -> GetFakePostsRequest(cassette, Mode.Replay)); + Assert.assertThrows(Exception.class, () -> GetExchangeRatesRequest(cassette, Mode.Replay)); Assert.assertEquals(cassette.numInteractions(), 0); // Make sure cassette is still empty // in auto mode, if cassette is empty, should make and record a real request - FakeDataService.Post[] posts = GetFakePostsRequest(cassette, Mode.Auto); - Assert.assertNotNull(posts); + FakeDataService.ExchangeRates exchangeRates = GetExchangeRatesRequest(cassette, Mode.Auto); + Assert.assertNotNull(exchangeRates); Assert.assertTrue(cassette.numInteractions() > 0); // Make sure cassette is no longer empty } @@ -153,12 +154,12 @@ public void testInteractionElements() throws Exception { RecordableHttpsURLConnection connection = (RecordableHttpsURLConnection) HttpClients.newClient(HttpClientType.HttpsUrlConnection, - FakeDataService.GET_POSTS_URL, cassette, Mode.Record); + FakeDataService.URL, cassette, Mode.Record); FakeDataService.HttpsUrlConnection fakeDataService = new FakeDataService.HttpsUrlConnection(connection); // Most elements of a VCR request are black-boxed, so we can't test them here. // Instead, we can get the recreated HttpResponseMessage and check the details. - RecordableHttpsURLConnection response = (RecordableHttpsURLConnection) fakeDataService.getPostsRawResponse(); + RecordableHttpsURLConnection response = (RecordableHttpsURLConnection) fakeDataService.getExchangeRatesRawResponse(); Assert.assertNotNull(response); } @@ -171,7 +172,7 @@ public void testCensors() throws Exception { String censorString = "censored-by-test"; List headers = new ArrayList<>(); headers.add("Date"); - Censors censors = new Censors(censorString).hideHeaders(headers); + Censors censors = new Censors(censorString).censorHeadersByKeys(headers); AdvancedSettings advancedSettings = new AdvancedSettings(); advancedSettings.censors = censors; @@ -179,15 +180,15 @@ public void testCensors() throws Exception { // record cassette with advanced settings first RecordableHttpsURLConnection connection = (RecordableHttpsURLConnection) HttpClients.newClient(HttpClientType.HttpsUrlConnection, - FakeDataService.GET_POSTS_URL, cassette, Mode.Record, advancedSettings); + FakeDataService.URL, cassette, Mode.Record, advancedSettings); FakeDataService.HttpsUrlConnection fakeDataService = new FakeDataService.HttpsUrlConnection(connection); - Object ignore = fakeDataService.getPostsRawResponse(); + Object ignore = fakeDataService.getExchangeRatesRawResponse(); // now replay cassette connection = (RecordableHttpsURLConnection) HttpClients.newClient(HttpClientType.HttpsUrlConnection, - FakeDataService.GET_POSTS_URL, cassette, Mode.Replay, advancedSettings); + FakeDataService.URL, cassette, Mode.Replay, advancedSettings); fakeDataService = new FakeDataService.HttpsUrlConnection(connection); - RecordableHttpsURLConnection response = (RecordableHttpsURLConnection) fakeDataService.getPostsRawResponse(); + RecordableHttpsURLConnection response = (RecordableHttpsURLConnection) fakeDataService.getExchangeRatesRawResponse(); // check that the replayed response contains the censored header Assert.assertNotNull(response); @@ -205,17 +206,17 @@ public void testMatchSettings() throws Exception { // record cassette with advanced settings first RecordableHttpsURLConnection connection = (RecordableHttpsURLConnection) HttpClients.newClient(HttpClientType.HttpsUrlConnection, - FakeDataService.GET_POSTS_URL, cassette, Mode.Record); + FakeDataService.URL, cassette, Mode.Record); FakeDataService.HttpsUrlConnection fakeDataService = new FakeDataService.HttpsUrlConnection(connection); - Object ignore = fakeDataService.getPostsRawResponse(); + Object ignore = fakeDataService.getExchangeRatesRawResponse(); // replay cassette with default match rules, should find a match connection = (RecordableHttpsURLConnection) HttpClients.newClient(HttpClientType.HttpsUrlConnection, - FakeDataService.GET_POSTS_URL, cassette, Mode.Replay); + FakeDataService.URL, cassette, Mode.Replay); connection.setRequestProperty("X-Custom-Header", "custom-value"); // add custom header to request, shouldn't matter when matching by default rules fakeDataService = new FakeDataService.HttpsUrlConnection(connection); - RecordableHttpsURLConnection response = (RecordableHttpsURLConnection) fakeDataService.getPostsRawResponse(); + RecordableHttpsURLConnection response = (RecordableHttpsURLConnection) fakeDataService.getExchangeRatesRawResponse(); Assert.assertNotNull(response); // replay cassette with custom match rules, should not find a match because request is different (throw exception) @@ -224,12 +225,12 @@ public void testMatchSettings() throws Exception { advancedSettings.matchRules = matchRules; connection = (RecordableHttpsURLConnection) HttpClients.newClient(HttpClientType.HttpsUrlConnection, - FakeDataService.GET_POSTS_URL, cassette, Mode.Replay, advancedSettings); + FakeDataService.URL, cassette, Mode.Replay, advancedSettings); connection.setRequestProperty("X-Custom-Header", "custom-value"); // add custom header to request, causing a match failure when matching by everything fakeDataService = new FakeDataService.HttpsUrlConnection(connection); FakeDataService.HttpsUrlConnection finalFakeDataService = fakeDataService; - Assert.assertThrows(Exception.class, () -> finalFakeDataService.getPosts()); + Assert.assertThrows(Exception.class, () -> finalFakeDataService.getExchangeRates()); } @Test @@ -240,21 +241,21 @@ public void testDelay() throws Exception { // record cassette first RecordableHttpsURLConnection connection = (RecordableHttpsURLConnection) HttpClients.newClient(HttpClientType.HttpsUrlConnection, - FakeDataService.GET_POSTS_URL, cassette, Mode.Record); + FakeDataService.URL, cassette, Mode.Record); FakeDataService.HttpsUrlConnection fakeDataService = new FakeDataService.HttpsUrlConnection(connection); - Object ignore = fakeDataService.getPosts(); + Object ignore = fakeDataService.getExchangeRates(); // baseline - how much time does it take to replay the cassette? connection = (RecordableHttpsURLConnection) HttpClients.newClient(HttpClientType.HttpsUrlConnection, - FakeDataService.GET_POSTS_URL, cassette, Mode.Replay); + FakeDataService.URL, cassette, Mode.Replay); fakeDataService = new FakeDataService.HttpsUrlConnection(connection); Instant start = Instant.now(); - FakeDataService.Post[] posts = fakeDataService.getPosts(); + FakeDataService.ExchangeRates exchangeRates = fakeDataService.getExchangeRates(); Instant end = Instant.now(); // confirm the normal replay worked, note time - Assert.assertNotNull(posts); + Assert.assertNotNull(exchangeRates); int normalReplayTime = (int) Duration.between(start, end).toMillis(); // set up advanced settings @@ -262,16 +263,117 @@ public void testDelay() throws Exception { AdvancedSettings advancedSettings = new AdvancedSettings(); advancedSettings.manualDelay = delay; connection = (RecordableHttpsURLConnection) HttpClients.newClient(HttpClientType.HttpsUrlConnection, - FakeDataService.GET_POSTS_URL, cassette, Mode.Replay, advancedSettings); + FakeDataService.URL, cassette, Mode.Replay, advancedSettings); fakeDataService = new FakeDataService.HttpsUrlConnection(connection); // time replay request start = Instant.now(); - posts = fakeDataService.getPosts(); + exchangeRates = fakeDataService.getExchangeRates(); end = Instant.now(); // check that the delay was respected - Assert.assertNotNull(posts); + Assert.assertNotNull(exchangeRates); Assert.assertTrue((int) Duration.between(start, end).toMillis() >= delay); } + + @Test + public void testIgnoreElementsFailMatch() throws URISyntaxException, IOException { + Cassette cassette = TestUtils.getCassette("test_ignore_elements_fail_match"); + cassette.erase(); // Erase cassette before recording + + String bodyData1 = "{'name': 'Upendra', 'job': 'Programmer'}"; + String bodyData2 = "{'name': 'NewName', 'job': 'Programmer'}"; + + // record baseline request first + RecordableHttpsURLConnection connection = HttpClients.newHttpsURLConnection(FakeDataService.URL, cassette, Mode.Record); + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + // use bodyData1 to make request + OutputStream output = null; + try { + output = connection.getOutputStream(); + output.write(bodyData1.getBytes(StandardCharsets.UTF_8)); + } finally { + if (output != null) { + output.close(); + } + } + connection.connect(); + + // try to replay with slightly different data + AdvancedSettings advancedSettings = new AdvancedSettings(); + // matching strictly by body + advancedSettings.matchRules = new MatchRules().byMethod().byFullUrl().byBody(); + connection = (RecordableHttpsURLConnection) HttpClients.newClient(HttpClientType.HttpsUrlConnection, + FakeDataService.URL, cassette, Mode.Replay, advancedSettings); + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + // use bodyData2 to make request + output = null; + try { + output = connection.getOutputStream(); + output.write(bodyData2.getBytes(StandardCharsets.UTF_8)); + } finally { + if (output != null) { + output.close(); + } + } + connection.connect(); + + // should fail since we're strictly in replay mode and there's no exact match + int statusCode = connection.getResponseCode(); + Assert.assertEquals(0, statusCode); + } + + @Test + public void testIgnoreElementsPassMatch() throws URISyntaxException, IOException { + Cassette cassette = TestUtils.getCassette("test_ignore_elements_pass_match"); + cassette.erase(); // Erase cassette before recording + + String bodyData1 = "{'name': 'Upendra', 'job': 'Programmer'}"; + String bodyData2 = "{'name': 'NewName', 'job': 'Programmer'}"; + + // record baseline request first + RecordableHttpsURLConnection connection = HttpClients.newHttpsURLConnection(FakeDataService.URL, cassette, Mode.Record); + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + // use bodyData1 to make request + OutputStream output = null; + try { + output = connection.getOutputStream(); + output.write(bodyData1.getBytes(StandardCharsets.UTF_8)); + } finally { + if (output != null) { + output.close(); + } + } + connection.connect(); + + // try to replay with slightly different data, but ignoring the differences + AdvancedSettings advancedSettings = new AdvancedSettings(); + // ignore the element that is different + List ignoredElements = new ArrayList() {{ + add(new CensorElement("name", false)); + }}; + advancedSettings.matchRules = new MatchRules().byMethod().byFullUrl().byBody(ignoredElements); + connection = (RecordableHttpsURLConnection) HttpClients.newClient(HttpClientType.HttpsUrlConnection, + FakeDataService.URL, cassette, Mode.Replay, advancedSettings); + connection.setDoOutput(true); + connection.setRequestMethod("POST"); + // use bodyData2 to make request + output = null; + try { + output = connection.getOutputStream(); + output.write(bodyData2.getBytes(StandardCharsets.UTF_8)); + } finally { + if (output != null) { + output.close(); + } + } + connection.connect(); + + // should not fail since we're ignoring the body elements that differ + int statusCode = connection.getResponseCode(); + Assert.assertNotEquals(0, statusCode); + } } diff --git a/src/test/java/TestUtils.java b/src/test/java/TestUtils.java index 6c36c81..508d3c7 100644 --- a/src/test/java/TestUtils.java +++ b/src/test/java/TestUtils.java @@ -25,7 +25,7 @@ public static RecordableHttpURLConnection getSimpleHttpURLConnection(String url, public static RecordableHttpURLConnection getSimpleHttpURLConnection(String cassetteName, Mode mode, AdvancedSettings advancedSettings) throws IOException { - return getSimpleHttpURLConnection(FakeDataService.GET_POSTS_URL, cassetteName, mode, advancedSettings); + return getSimpleHttpURLConnection(FakeDataService.URL, cassetteName, mode, advancedSettings); } public static RecordableHttpsURLConnection getSimpleHttpsURLConnection(String url, String cassetteName, Mode mode, AdvancedSettings advancedSettings) @@ -36,7 +36,7 @@ public static RecordableHttpsURLConnection getSimpleHttpsURLConnection(String ur public static RecordableHttpsURLConnection getSimpleHttpsURLConnection(String cassetteName, Mode mode, AdvancedSettings advancedSettings) throws IOException { - return getSimpleHttpsURLConnection(FakeDataService.GET_POSTS_URL, cassetteName, mode, advancedSettings); + return getSimpleHttpsURLConnection(FakeDataService.URL, cassetteName, mode, advancedSettings); } public static VCR getSimpleVCR(Mode mode) { diff --git a/src/test/java/VCRTest.java b/src/test/java/VCRTest.java index 1875dc5..93e3a8d 100644 --- a/src/test/java/VCRTest.java +++ b/src/test/java/VCRTest.java @@ -77,8 +77,8 @@ public void testErase() throws Exception { // record a request to a cassette FakeDataService.HttpsUrlConnection fakeDataService = new FakeDataService.HttpsUrlConnection(vcr); - FakeDataService.Post[] posts = fakeDataService.getPosts(); - Assert.assertNotNull(posts); + FakeDataService.ExchangeRates exchangeRates = fakeDataService.getExchangeRates(); + Assert.assertNotNull(exchangeRates); Assert.assertTrue(cassette.numInteractions() > 0); // erase the cassette @@ -108,9 +108,8 @@ public void testRequest() throws Exception { vcr.insert(cassette); FakeDataService.HttpsUrlConnection fakeDataService = new FakeDataService.HttpsUrlConnection(vcr); - FakeDataService.Post[] posts = fakeDataService.getPosts(); - Assert.assertNotNull(posts); - Assert.assertEquals(100, posts.length); + FakeDataService.ExchangeRates exchangeRates = fakeDataService.getExchangeRates(); + Assert.assertNotNull(exchangeRates); } @Test @@ -120,9 +119,8 @@ public void testRecord() throws Exception { vcr.insert(cassette); FakeDataService.HttpsUrlConnection fakeDataService = new FakeDataService.HttpsUrlConnection(vcr); - FakeDataService.Post[] posts = fakeDataService.getPosts(); - Assert.assertNotNull(posts); - Assert.assertEquals(100, posts.length); + FakeDataService.ExchangeRates exchangeRates = fakeDataService.getExchangeRates(); + Assert.assertNotNull(exchangeRates); Assert.assertTrue(cassette.numInteractions() > 0); } @@ -134,14 +132,14 @@ public void testReplay() throws Exception { FakeDataService.HttpsUrlConnection fakeDataService = new FakeDataService.HttpsUrlConnection(vcr); // record first - RecordableHttpsURLConnection response = (RecordableHttpsURLConnection) fakeDataService.getPostsRawResponse(); + RecordableHttpsURLConnection response = (RecordableHttpsURLConnection) fakeDataService.getExchangeRatesRawResponse(); Assert.assertTrue(cassette.numInteractions() > 0); // make sure we recorded something // check that the response did not come from a recorded cassette Assert.assertFalse(Utilities.responseCameFromRecording(response)); // now replay vcr.replay(); - response = (RecordableHttpsURLConnection) fakeDataService.getPostsRawResponse(); + response = (RecordableHttpsURLConnection) fakeDataService.getExchangeRatesRawResponse(); Assert.assertNotNull(response); // check that the response came from a recorded cassette Assert.assertTrue(Utilities.responseCameFromRecording(response)); @@ -149,7 +147,7 @@ public void testReplay() throws Exception { // double check by erasing the cassette and trying to replay vcr.erase(); // should throw an exception because there's no matching interaction now - Assert.assertThrows(Exception.class, fakeDataService::getPosts); + Assert.assertThrows(Exception.class, fakeDataService::getExchangeRates); } @Test @@ -174,7 +172,7 @@ public void testAdvancedSettings() throws Exception { AdvancedSettings advancedSettings = new AdvancedSettings(); List censoredHeaders = new ArrayList<>(); censoredHeaders.add("Date"); - advancedSettings.censors = new Censors(censorString).hideHeaders(censoredHeaders); + advancedSettings.censors = new Censors(censorString).censorHeadersByKeys(censoredHeaders); advancedSettings.matchRules = new MatchRules().byMethod().byFullUrl().byBody(); VCR vcr = new VCR(advancedSettings); @@ -189,18 +187,18 @@ public void testAdvancedSettings() throws Exception { // record first vcr.record(); - RecordableURL client = vcr.getHttpUrlConnection(FakeDataService.GET_POSTS_URL); + RecordableURL client = vcr.getHttpUrlConnection(FakeDataService.URL); FakeDataService.HttpsUrlConnection fakeDataService = new FakeDataService.HttpsUrlConnection(client.openConnectionSecure()); - FakeDataService.Post[] posts = fakeDataService.getPosts(); + FakeDataService.ExchangeRates exchangeRates = fakeDataService.getExchangeRates(); // now replay and confirm that the censor is applied vcr.replay(); // changing the VCR settings won't affect a client after it's been grabbed from the VCR // so, we need to re-grab the VCR client and re-create the FakeDataService - client = vcr.getHttpUrlConnection(FakeDataService.GET_POSTS_URL); + client = vcr.getHttpUrlConnection(FakeDataService.URL); fakeDataService = new FakeDataService.HttpsUrlConnection(client.openConnectionSecure()); - RecordableHttpsURLConnection response = (RecordableHttpsURLConnection) fakeDataService.getPostsRawResponse(); + RecordableHttpsURLConnection response = (RecordableHttpsURLConnection) fakeDataService.getExchangeRatesRawResponse(); // check that the censor is applied Assert.assertNotNull(response); diff --git a/style_suppressions.xml b/style_suppressions.xml index dad3232..dd46618 100644 --- a/style_suppressions.xml +++ b/style_suppressions.xml @@ -5,12 +5,27 @@ "https://checkstyle.org/dtds/suppressions_1_2.dtd"> - + + + + + + + + +