diff --git a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/JsonComparer.cs b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/JsonComparer.cs new file mode 100644 index 00000000000..de636fc45da --- /dev/null +++ b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/JsonComparer.cs @@ -0,0 +1,107 @@ +using System.Collections.Generic; +using System.Text.Json; + +namespace Azure.Sdk.Tools.TestProxy.Common +{ + public class JsonComparer + { + public static List CompareJson(byte[] json1, byte[] json2) + { + // Deserialize the byte arrays to JsonDocument + JsonDocument doc1 = JsonDocument.Parse(json1); + JsonDocument doc2 = JsonDocument.Parse(json2); + + // Compare the JSON objects + var differences = new List(); + CompareElements(doc1.RootElement, doc2.RootElement, differences, ""); + + return differences; + } + + private static void CompareElements(JsonElement element1, JsonElement element2, List differences, string path) + { + if (element1.ValueKind != element2.ValueKind) + { + differences.Add($"{path}: Request and record have different types."); + return; + } + + switch (element1.ValueKind) + { + case JsonValueKind.Object: + { + var properties1 = element1.EnumerateObject(); + var properties2 = element2.EnumerateObject(); + + var propDict1 = new Dictionary(); + var propDict2 = new Dictionary(); + + foreach (var prop in properties1) + propDict1[prop.Name] = prop.Value; + + foreach (var prop in properties2) + propDict2[prop.Name] = prop.Value; + + foreach (var key in propDict1.Keys) + { + if (propDict2.ContainsKey(key)) + { + CompareElements(propDict1[key], propDict2[key], differences, $"{path}.{key}"); + } + else + { + differences.Add($"{path}.{key}: Missing in request JSON"); + } + } + + foreach (var key in propDict2.Keys) + { + if (!propDict1.ContainsKey(key)) + { + differences.Add($"{path}.{key}: Missing in record JSON"); + } + } + + break; + } + case JsonValueKind.Array: + { + var array1 = element1.EnumerateArray(); + var array2 = element2.EnumerateArray(); + + int index = 0; + var enum1 = array1.GetEnumerator(); + var enum2 = array2.GetEnumerator(); + + while (enum1.MoveNext() && enum2.MoveNext()) + { + CompareElements(enum1.Current, enum2.Current, differences, $"{path}[{index}]"); + index++; + } + + while (enum1.MoveNext()) + { + differences.Add($"{path}[{index}]: Extra element in request JSON"); + index++; + } + + while (enum2.MoveNext()) + { + differences.Add($"{path}[{index}]: Extra element in record JSON"); + index++; + } + + break; + } + default: + { + if (!element1.Equals(element2)) + { + differences.Add($"{path}: {element1} != {element2}"); + } + break; + } + } + } + } +} diff --git a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/RecordMatcher.cs b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/RecordMatcher.cs index aa8bc59dbe0..dfec1d099a1 100644 --- a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/RecordMatcher.cs +++ b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/RecordMatcher.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; +using System.Net.Mime; using System.Text; using System.Web; @@ -112,7 +113,10 @@ public virtual RecordEntry FindMatch(RecordEntry request, IList ent if (!entry.IsTrack1Recording) { score += CompareHeaderDictionaries(request.Request.Headers, entry.Request.Headers, IgnoredHeaders, ExcludeHeaders); - score += CompareBodies(request.Request.Body, entry.Request.Body); + + request.Request.TryGetContentType(out var contentType); + + score += CompareBodies(request.Request.Body, entry.Request.Body, descriptionBuilder: null, contentType: contentType); } if (score == 0) @@ -130,7 +134,7 @@ public virtual RecordEntry FindMatch(RecordEntry request, IList ent throw new TestRecordingMismatchException(GenerateException(request, bestScoreEntry, entries)); } - public virtual int CompareBodies(byte[] requestBody, byte[] recordBody, StringBuilder descriptionBuilder = null) + public virtual int CompareBodies(byte[] requestBody, byte[] recordBody, string contentType, StringBuilder descriptionBuilder = null) { if (!_compareBodies) { @@ -154,7 +158,26 @@ public virtual int CompareBodies(byte[] requestBody, byte[] recordBody, StringBu return 1; } - if (!requestBody.SequenceEqual(recordBody)) + if (!string.IsNullOrWhiteSpace(contentType) && contentType.Contains("json")) + { + var jsonDifferences = JsonComparer.CompareJson(requestBody, recordBody); + + if (jsonDifferences.Count > 0) + { + + if (descriptionBuilder != null) + { + descriptionBuilder.AppendLine($"There are differences between request and recordentry bodies:"); + foreach (var jsonDifference in jsonDifferences) + { + descriptionBuilder.AppendLine(jsonDifference); + } + } + + return 1; + } + } + else if (!requestBody.SequenceEqual(recordBody)) { if (descriptionBuilder != null) { diff --git a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/RecordSession.cs b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/RecordSession.cs index a104e4083ce..d2f19f5a6cc 100644 --- a/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/RecordSession.cs +++ b/tools/test-proxy/Azure.Sdk.Tools.TestProxy/Common/RecordSession.cs @@ -102,9 +102,6 @@ public RecordEntry Lookup(RecordEntry requestEntry, RecordMatcher matcher, IEnum { sanitizer.Sanitize(requestEntry); } - // normalize request body with STJ using relaxed escaping to match behavior when Deserializing from session files - RecordEntry.NormalizeJsonBody(requestEntry.Request); - RecordEntry entry = matcher.FindMatch(requestEntry, Entries); if (remove)