Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 101 additions & 2 deletions EasyVCR.Tests/CensorsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using EasyVCR.InternalUtilities.JSON;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace EasyVCR.Tests
Expand Down Expand Up @@ -308,7 +309,7 @@ public async Task TestRegexCensorOnXml()
Censors = new Censors(censorString).CensorBodyElements(
new List<CensorElement>
{
// censor any value that looks like an date stamp
// censor any value that looks like a date stamp
new RegexCensorElement(@"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}", false),
}),
};
Expand Down Expand Up @@ -496,7 +497,7 @@ public async Task TestMixAndMatchCensorElements()
new TextCensorElement("r/ProgrammerHumor", false),
// censor the value of the "title" key
new KeyCensorElement("title", false),
// censor any value that looks like an date stamp
// censor any value that looks like a date stamp
new RegexCensorElement(@"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}", false),
}),
};
Expand Down Expand Up @@ -539,5 +540,103 @@ public async Task TestMixAndMatchCensorElements()
Assert.AreEqual(censorString, node.InnerText);
}
}

[TestMethod]
public async Task TestNonStringCensorKeyElements()
{
var cassette = TestUtils.GetCassette("test_non_string_censor_elements");
cassette.Erase(); // Erase cassette before recording

const string censorString = "censored-by-test";
const int intToCensor = 123456;
var dateToCensor = new DateTime(2020, 1, 1, 12, 0, 0);
var booleanToCensor = true;
var bodyObject = new
{
number = intToCensor,
date = dateToCensor,
boolean = booleanToCensor,
};
var body = Serialization.ConvertObjectToJson(bodyObject);
const InternalUtilities.ContentType contentType = InternalUtilities.ContentType.Json;

var bodyCensors = new List<KeyCensorElement>
{
new KeyCensorElement("number", false),
new KeyCensorElement("date", false),
new KeyCensorElement("boolean", false),
};
var censors = new Censors(censorString).CensorBodyElements(bodyCensors);

var result = censors.ApplyBodyParametersCensors(body, contentType);

Assert.AreEqual("{\"number\":\"censored-by-test\",\"date\":\"censored-by-test\",\"boolean\":\"censored-by-test\"}", result);
}

[TestMethod]
public async Task TestNonStringCensorTextElements()
{
var cassette = TestUtils.GetCassette("test_non_string_censor_text_elements");
cassette.Erase(); // Erase cassette before recording

const string censorString = "censored-by-test";
const int intToCensor = 123456;
var dateToCensor = new DateTime(2020, 1, 1, 12, 0, 0);
var booleanToCensor = true;
var bodyObject = new
{
number = intToCensor,
date = dateToCensor,
boolean = booleanToCensor,
};
var body = Serialization.ConvertObjectToJson(bodyObject);
const InternalUtilities.ContentType contentType = InternalUtilities.ContentType.Json;

var bodyCensors = new List<TextCensorElement>
{
new TextCensorElement(intToCensor, false),
new TextCensorElement(dateToCensor, false),
new TextCensorElement(booleanToCensor, false),
};
var censors = new Censors(censorString).CensorBodyElements(bodyCensors);

var result = censors.ApplyBodyParametersCensors(body, contentType);

Assert.AreEqual(
"{\"number\":\"censored-by-test\",\"date\":\"censored-by-test\",\"boolean\":\"censored-by-test\"}",
result);
}

[TestMethod]
public async Task TestNonStringCensorRegexElements()
{
var cassette = TestUtils.GetCassette("test_non_string_censor_regex_elements");
cassette.Erase(); // Erase cassette before recording

const string censorString = "censored-by-test";
const int intToCensor = 123456;
var booleanToCensor = true;
var bodyObject = new
{
number = intToCensor,
boolean = booleanToCensor,
};
var body = Serialization.ConvertObjectToJson(bodyObject);
const InternalUtilities.ContentType contentType = InternalUtilities.ContentType.Json;

var bodyCensors = new List<RegexCensorElement>
{
new RegexCensorElement(@"\b123456\b", false),
// Date-to-string serialization is inconsistent, so excluding from test
new RegexCensorElement(@"\btrue\b", false),
};
var censors = new Censors(censorString).CensorBodyElements(bodyCensors);

var result = censors.ApplyBodyParametersCensors(body, contentType);

Assert.AreEqual(
"{\"number\":\"censored-by-test\",\"boolean\":\"censored-by-test\"}",
result);
}
}
}
6 changes: 2 additions & 4 deletions EasyVCR.Tests/FakeDataService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ internal FakeDataService(EasyVCRHttpClient client)
_client = client;
}

public static string JsonDataUrl => "https://www.reddit.com/r/ProgrammerHumor.json";
public static string JsonDataUrl => "https://raw.githubusercontent.com/EasyPost/easyvcr-csharp/refs/heads/master/sample_data/sample_json.json";

public static string XmlDataUrl => "https://www.reddit.com/r/ProgrammerHumor.rss";
public static string XmlDataUrl => "https://raw.githubusercontent.com/EasyPost/easyvcr-csharp/refs/heads/master/sample_data/sample_xml.rss";

public static string HtmlDataUrl => "https://www.reddit.com/r/ProgrammerHumor";

Expand All @@ -54,7 +54,6 @@ internal FakeDataService(EasyVCRHttpClient client)

public async Task<HttpResponseMessage> GetJsonDataRawResponse()
{
Client.DefaultRequestHeaders.Add("User-Agent", "EasyVCR"); // reddit requires a user agent
return await Client.GetAsync(JsonDataUrl);
}

Expand All @@ -66,7 +65,6 @@ public async Task<HttpResponseMessage> GetJsonDataRawResponse()

public async Task<HttpResponseMessage> GetXmlDataRawResponse()
{
Client.DefaultRequestHeaders.Add("User-Agent", "EasyVCR"); // reddit requires a user agent
return await Client.GetAsync(XmlDataUrl);
}

Expand Down
41 changes: 25 additions & 16 deletions EasyVCR/CensorElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ public abstract class CensorElement
/// <summary>
/// Value to look for.
/// </summary>
protected string Value { get; }
protected object Value { get; }

/// <summary>
/// Constructor for a new censor element.
/// </summary>
/// <param name="value">Value to censor.</param>
/// <param name="caseSensitive">Whether the value is case-sensitive.</param>
protected CensorElement(string value, bool caseSensitive)
protected CensorElement(object value, bool caseSensitive)
{
Value = value;
CaseSensitive = caseSensitive;
Expand All @@ -35,7 +35,7 @@ protected CensorElement(string value, bool caseSensitive)
/// <param name="value">The value to check.</param>
/// <param name="key">The key to check.</param>
/// <returns>True if the element matches, false otherwise.</returns>
internal abstract bool Matches(string value, string? key = null);
internal abstract bool Matches(object value, string? key = null);
}

/// <summary>
Expand All @@ -48,7 +48,7 @@ public class TextCensorElement : CensorElement
/// </summary>
/// <param name="value">The raw text value of the element to censor.</param>
/// <param name="caseSensitive">Whether the value is case-sensitive.</param>
public TextCensorElement(string value, bool caseSensitive) : base(value, caseSensitive)
public TextCensorElement(object value, bool caseSensitive) : base(value, caseSensitive)
{
}

Expand All @@ -58,10 +58,12 @@ public TextCensorElement(string value, bool caseSensitive) : base(value, caseSen
/// <param name="value">The value to check.</param>
/// <param name="key">The key to check.</param>
/// <returns>True if the element matches, false otherwise.</returns>
internal override bool Matches(string value, string? key = null)
internal override bool Matches(object value, string? key = null)
{
// we only care about the value here
return CaseSensitive ? value.Contains(Value) : value.ToLower().Contains(Value.ToLower());
var inputValueString = value?.ToString() ?? string.Empty;
var compareValueString = Value?.ToString() ?? string.Empty;
return CaseSensitive ? inputValueString.Contains(compareValueString) : inputValueString.ToLower().Contains(compareValueString.ToLower());
}

/// <summary>
Expand All @@ -71,11 +73,13 @@ internal override bool Matches(string value, string? key = null)
/// <param name="value">Value to replace.</param>
/// <param name="replacement">Replacement for the value.</param>
/// <returns>The value with the replacement inserted if it matches this censor element, otherwise the value as-is.</returns>
internal string MatchAndReplaceAsNeeded(string value, string replacement)
internal string MatchAndReplaceAsNeeded(object value, string replacement)
{
// if the passed-in value contains the Value to censor, replace the Value substring with the replacement
// otherwise, return the original value
return !Matches(value) ? value : value.Replace(Value, replacement);
var inputValueString = value?.ToString() ?? string.Empty;
var compareValueString = Value?.ToString() ?? string.Empty;
return !Matches(value!) ? inputValueString : inputValueString.Replace(compareValueString, replacement);
}
}

Expand All @@ -99,10 +103,11 @@ public KeyCensorElement(string key, bool caseSensitive) : base(key, caseSensitiv
/// <param name="value">The value to check.</param>
/// <param name="key">The key to check.</param>
/// <returns>True if the element matches, false otherwise.</returns>
internal override bool Matches(string value, string? key = null)
internal override bool Matches(object value, string? key = null)
{
// we only care about the key here
return CaseSensitive ? Value.Equals(key) : Value.Equals(key, StringComparison.OrdinalIgnoreCase);
var compareKeyString = (string)Value;
return CaseSensitive ? compareKeyString.Equals(key) : compareKeyString.Equals(key, StringComparison.OrdinalIgnoreCase);
}
}

Expand All @@ -127,17 +132,19 @@ public RegexCensorElement(string pattern, bool caseSensitive) : base(pattern, ca
/// <param name="value">Value to apply the replacement to.</param>
/// <param name="replacement">Replacement for a detected matching section.</param>
/// <returns>The value with the replacement inserted, or the original value if no match was found.</returns>
internal string MatchAndReplaceAsNeeded(string value, string replacement)
internal string MatchAndReplaceAsNeeded(object value, string replacement)
{
var options = RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.Singleline;
if (!CaseSensitive)
{
options |= RegexOptions.IgnoreCase;
}

var inputValueString = value?.ToString() ?? string.Empty;
var compareValueString = Value?.ToString() ?? string.Empty;
return Regex.Replace(
value,
Value,
inputValueString,
compareValueString,
replacement,
options,
TimeSpan.FromMilliseconds(250)
Expand All @@ -150,7 +157,7 @@ internal string MatchAndReplaceAsNeeded(string value, string replacement)
/// <param name="value">The value to check.</param>
/// <param name="key">The key to check.</param>
/// <returns>True if the element matches, false otherwise.</returns>
internal override bool Matches(string value, string? key = null)
internal override bool Matches(object value, string? key = null)
{
// we only care about the value here
var options = RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture | RegexOptions.Singleline;
Expand All @@ -161,9 +168,11 @@ internal override bool Matches(string value, string? key = null)

try
{
var inputValueString = value?.ToString() ?? string.Empty;
var compareValueString = Value?.ToString() ?? string.Empty;
return Regex.IsMatch(
value,
Value,
inputValueString,
compareValueString,
options,
TimeSpan.FromMilliseconds(250)
);
Expand Down
6 changes: 3 additions & 3 deletions EasyVCR/Censors.cs
Original file line number Diff line number Diff line change
Expand Up @@ -541,13 +541,13 @@ private static Dictionary<string, object> ApplyJsonXmlDataCensors(Dictionary<str
/// <returns>True if the JSON value should be censored, false otherwise.</returns>
private static bool ElementShouldBeCensored(object? foundValue, string foundKey, IReadOnlyCollection<CensorElement> elementsToCensor)
{
if (!(foundValue is string))
if (foundValue == null)
{
// short circuit if the value is not a string
// short circuit if the value is null
return false;
}

return elementsToCensor.Count != 0 && elementsToCensor.Any(element => element.Matches(value: (string)foundValue, key: foundKey));
return elementsToCensor.Count != 0 && elementsToCensor.Any(element => element.Matches(value: foundValue, key: foundKey));
}

/// <summary>
Expand Down
1 change: 1 addition & 0 deletions sample_data/sample_json.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions sample_data/sample_xml.rss

Large diffs are not rendered by default.