Compare json object values instead of byte streams when matching bodies that are content-type `json` (#8860)
This commit is contained in:
Родитель
4aea26ad99
Коммит
c597728040
|
@ -8,7 +8,6 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
|
@ -689,8 +688,10 @@ namespace Azure.Sdk.Tools.TestProxy.Tests
|
|||
|
||||
Assert.Equal(0, matcher.CompareHeaderDictionaries(targetUntouchedEntry.Request.Headers, targetEntry.Request.Headers, new HashSet<string>(), new HashSet<string>()));
|
||||
Assert.Equal(0, matcher.CompareHeaderDictionaries(targetUntouchedEntry.Response.Headers, targetEntry.Response.Headers, new HashSet<string>(), new HashSet<string>()));
|
||||
Assert.Equal(0, matcher.CompareBodies(targetUntouchedEntry.Request.Body, targetEntry.Request.Body));
|
||||
Assert.Equal(0, matcher.CompareBodies(targetUntouchedEntry.Response.Body, targetEntry.Response.Body));
|
||||
|
||||
targetUntouchedEntry.Request.TryGetContentType(out var contentType);
|
||||
Assert.Equal(0, matcher.CompareBodies(targetUntouchedEntry.Request.Body, targetEntry.Request.Body, contentType));
|
||||
Assert.Equal(0, matcher.CompareBodies(targetUntouchedEntry.Response.Body, targetEntry.Response.Body, contentType));
|
||||
Assert.Equal(targetUntouchedEntry.RequestUri, targetEntry.RequestUri);
|
||||
}
|
||||
|
||||
|
@ -769,8 +770,9 @@ namespace Azure.Sdk.Tools.TestProxy.Tests
|
|||
await session.Session.Sanitize(sanitizer);
|
||||
|
||||
var resultBodyValue = Encoding.UTF8.GetString(targetEntry.Request.Body);
|
||||
Assert.Equal(0, matcher.CompareBodies(targetUntouchedEntry.Request.Body, targetEntry.Request.Body));
|
||||
Assert.Equal(0, matcher.CompareBodies(targetUntouchedEntry.Response.Body, targetEntry.Response.Body));
|
||||
targetUntouchedEntry.Request.TryGetContentType(out var contentType);
|
||||
Assert.Equal(0, matcher.CompareBodies(targetUntouchedEntry.Request.Body, targetEntry.Request.Body, contentType));
|
||||
Assert.Equal(0, matcher.CompareBodies(targetUntouchedEntry.Response.Body, targetEntry.Response.Body, contentType));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
@ -0,0 +1,157 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace Azure.Sdk.Tools.TestProxy.Common
|
||||
{
|
||||
public class JsonComparer
|
||||
{
|
||||
public static List<string> CompareJson(byte[] json1, byte[] json2)
|
||||
{
|
||||
var differences = new List<string>();
|
||||
JsonDocument doc1;
|
||||
JsonDocument doc2;
|
||||
|
||||
// Deserialize the byte arrays to JsonDocument
|
||||
try
|
||||
{
|
||||
doc1 = JsonDocument.Parse(json1);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
differences.Add($"Unable to parse the request json body. Content \"{Encoding.UTF8.GetString(json1)}.\" Exception: {ex.Message}");
|
||||
return differences;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
doc2 = JsonDocument.Parse(json2);
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
differences.Add($"Unable to parse the record json body. Content \"{Encoding.UTF8.GetString(json2)}.\" Exception: {ex.Message}");
|
||||
return differences;
|
||||
}
|
||||
|
||||
CompareElements(doc1.RootElement, doc2.RootElement, differences, "");
|
||||
|
||||
return differences;
|
||||
}
|
||||
|
||||
private static void CompareElements(JsonElement element1, JsonElement element2, List<string> 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<string, JsonElement>();
|
||||
var propDict2 = new Dictionary<string, JsonElement>();
|
||||
|
||||
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;
|
||||
}
|
||||
case JsonValueKind.String:
|
||||
{
|
||||
if (element1.GetString() != element2.GetString())
|
||||
{
|
||||
differences.Add($"{path}: \"{element1.GetString()}\" != \"{element2.GetString()}\"");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case JsonValueKind.Number:
|
||||
{
|
||||
if (element1.GetDecimal() != element2.GetDecimal())
|
||||
{
|
||||
differences.Add($"{path}: {element1.GetDecimal()} != {element2.GetDecimal()}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case JsonValueKind.True:
|
||||
case JsonValueKind.False:
|
||||
{
|
||||
if (element1.GetBoolean() != element2.GetBoolean())
|
||||
{
|
||||
differences.Add($"{path}: {element1.GetBoolean()} != {element2.GetBoolean()}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case JsonValueKind.Null:
|
||||
{
|
||||
// Both are null, nothing to compare
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
differences.Add($"{path}: Unhandled value kind {element1.ValueKind}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -112,7 +112,10 @@ namespace Azure.Sdk.Tools.TestProxy.Common
|
|||
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 +133,7 @@ namespace Azure.Sdk.Tools.TestProxy.Common
|
|||
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,27 +157,50 @@ namespace Azure.Sdk.Tools.TestProxy.Common
|
|||
return 1;
|
||||
}
|
||||
|
||||
|
||||
if (!requestBody.SequenceEqual(recordBody))
|
||||
{
|
||||
if (descriptionBuilder != null)
|
||||
// we just failed sequence equality, before erroring, lets check if we're a json body and check for property equality
|
||||
if (!string.IsNullOrWhiteSpace(contentType) && contentType.Contains("json"))
|
||||
{
|
||||
var minLength = Math.Min(requestBody.Length, recordBody.Length);
|
||||
int i;
|
||||
for (i = 0; i < minLength - 1; i++)
|
||||
var jsonDifferences = JsonComparer.CompareJson(requestBody, recordBody);
|
||||
|
||||
if (jsonDifferences.Count > 0)
|
||||
{
|
||||
if (requestBody[i] != recordBody[i])
|
||||
|
||||
if (descriptionBuilder != null)
|
||||
{
|
||||
break;
|
||||
descriptionBuilder.AppendLine($"There are differences between request and recordentry bodies:");
|
||||
foreach (var jsonDifference in jsonDifferences)
|
||||
{
|
||||
descriptionBuilder.AppendLine(jsonDifference);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
descriptionBuilder.AppendLine($"Request and record bodies do not match at index {i}:");
|
||||
var before = Math.Max(0, i - 10);
|
||||
var afterRequest = Math.Min(i + 20, requestBody.Length);
|
||||
var afterResponse = Math.Min(i + 20, recordBody.Length);
|
||||
descriptionBuilder.AppendLine($" request: \"{Encoding.UTF8.GetString(requestBody, before, afterRequest - before)}\"");
|
||||
descriptionBuilder.AppendLine($" record: \"{Encoding.UTF8.GetString(recordBody, before, afterResponse - before)}\"");
|
||||
}
|
||||
else {
|
||||
if (descriptionBuilder != null)
|
||||
{
|
||||
var minLength = Math.Min(requestBody.Length, recordBody.Length);
|
||||
int i;
|
||||
for (i = 0; i < minLength - 1; i++)
|
||||
{
|
||||
if (requestBody[i] != recordBody[i])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
descriptionBuilder.AppendLine($"Request and record bodies do not match at index {i}:");
|
||||
var before = Math.Max(0, i - 10);
|
||||
var afterRequest = Math.Min(i + 20, requestBody.Length);
|
||||
var afterResponse = Math.Min(i + 20, recordBody.Length);
|
||||
descriptionBuilder.AppendLine($" request: \"{Encoding.UTF8.GetString(requestBody, before, afterRequest - before)}\"");
|
||||
descriptionBuilder.AppendLine($" record: \"{Encoding.UTF8.GetString(recordBody, before, afterResponse - before)}\"");
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -250,7 +276,8 @@ namespace Azure.Sdk.Tools.TestProxy.Common
|
|||
|
||||
builder.AppendLine("Body differences:");
|
||||
|
||||
CompareBodies(request.Request.Body, bestScoreEntry.Request.Body, builder);
|
||||
request.Request.TryGetContentType(out var contentType);
|
||||
CompareBodies(request.Request.Body, bestScoreEntry.Request.Body, contentType, descriptionBuilder: builder);
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
|
|
@ -102,10 +102,10 @@ namespace Azure.Sdk.Tools.TestProxy.Common
|
|||
{
|
||||
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)
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче