mirror of
https://github.com/wiremock/WireMock.Net.git
synced 2026-07-01 18:41:48 +02:00
...
This commit is contained in:
@@ -0,0 +1,5 @@
|
|||||||
|
# Copilot Instructions
|
||||||
|
|
||||||
|
## Project Guidelines
|
||||||
|
- When running tests in this workspace, do not run tests for the net48 target framework.
|
||||||
|
- When changing System.Text.Json code in this workspace, verify API availability for netstandard2.0 and netstandard2.1 instead of assuming newer APIs exist.
|
||||||
@@ -10,6 +10,7 @@ using WireMock.Constants;
|
|||||||
using WireMock.Logging;
|
using WireMock.Logging;
|
||||||
using WireMock.Models;
|
using WireMock.Models;
|
||||||
using WireMock.Types;
|
using WireMock.Types;
|
||||||
|
using WireMock.Transformers;
|
||||||
using WireMock.Util;
|
using WireMock.Util;
|
||||||
|
|
||||||
namespace WireMock.Settings;
|
namespace WireMock.Settings;
|
||||||
@@ -74,6 +75,8 @@ public static class WireMockServerSettingsParser
|
|||||||
WatchStaticMappingsInSubdirectories = parser.GetBoolValue(nameof(WireMockServerSettings.WatchStaticMappingsInSubdirectories)),
|
WatchStaticMappingsInSubdirectories = parser.GetBoolValue(nameof(WireMockServerSettings.WatchStaticMappingsInSubdirectories)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
settings.DefaultJsonBodyTransformer = new NewtonsoftJsonBodyTransformer(settings);
|
||||||
|
|
||||||
#if USE_ASPNETCORE
|
#if USE_ASPNETCORE
|
||||||
settings.CorsPolicyOptions = parser.GetEnumValue(nameof(WireMockServerSettings.CorsPolicyOptions), CorsPolicyOptions.None);
|
settings.CorsPolicyOptions = parser.GetEnumValue(nameof(WireMockServerSettings.CorsPolicyOptions), CorsPolicyOptions.None);
|
||||||
settings.ClientCertificateMode = parser.GetEnumValue(nameof(WireMockServerSettings.ClientCertificateMode), ClientCertificateMode.NoCertificate);
|
settings.ClientCertificateMode = parser.GetEnumValue(nameof(WireMockServerSettings.ClientCertificateMode), ClientCertificateMode.NoCertificate);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using HandlebarsDotNet;
|
using HandlebarsDotNet;
|
||||||
|
using WireMock.Transformers;
|
||||||
|
|
||||||
namespace WireMock.Transformers.Handlebars;
|
namespace WireMock.Transformers.Handlebars;
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
// Copyright © WireMock.Net
|
|
||||||
|
|
||||||
using WireMock.Handlers;
|
|
||||||
|
|
||||||
namespace WireMock.Transformers;
|
|
||||||
|
|
||||||
internal interface ITransformerContext
|
|
||||||
{
|
|
||||||
IFileSystemHandler FileSystemHandler { get; }
|
|
||||||
|
|
||||||
string ParseAndRender(string text, object model);
|
|
||||||
|
|
||||||
object? ParseAndEvaluate(string text, object model);
|
|
||||||
}
|
|
||||||
@@ -1,10 +1,5 @@
|
|||||||
// Copyright © WireMock.Net
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
using System.Collections;
|
|
||||||
using System.Linq;
|
|
||||||
using HandlebarsDotNet.Helpers.Models;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Stef.Validation;
|
using Stef.Validation;
|
||||||
using WireMock.Settings;
|
using WireMock.Settings;
|
||||||
using WireMock.Types;
|
using WireMock.Types;
|
||||||
@@ -14,17 +9,13 @@ namespace WireMock.Transformers;
|
|||||||
|
|
||||||
internal class Transformer : ITransformer
|
internal class Transformer : ITransformer
|
||||||
{
|
{
|
||||||
private readonly JsonSerializer _jsonSerializer;
|
private readonly IJsonBodyTransformer _jsonBodyTransformer;
|
||||||
private readonly ITransformerContextFactory _factory;
|
private readonly ITransformerContextFactory _factory;
|
||||||
|
|
||||||
public Transformer(WireMockServerSettings settings, ITransformerContextFactory factory)
|
public Transformer(WireMockServerSettings settings, ITransformerContextFactory factory)
|
||||||
{
|
{
|
||||||
_factory = Guard.NotNull(factory);
|
_factory = Guard.NotNull(factory);
|
||||||
|
_jsonBodyTransformer = Guard.NotNull(settings).DefaultJsonBodyTransformer;
|
||||||
_jsonSerializer = new JsonSerializer
|
|
||||||
{
|
|
||||||
Culture = Guard.NotNull(settings).Culture
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IBodyData? TransformBody(
|
public IBodyData? TransformBody(
|
||||||
@@ -121,13 +112,17 @@ internal class Transformer : ITransformer
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private IBodyData? TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, TransformModel model, IBodyData original, bool useTransformerForBodyAsFile)
|
private BodyData? TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, TransformModel model, IBodyData original, bool useTransformerForBodyAsFile)
|
||||||
{
|
{
|
||||||
switch (original.DetectedBodyType)
|
switch (original.DetectedBodyType)
|
||||||
{
|
{
|
||||||
case BodyType.Json:
|
case BodyType.Json:
|
||||||
case BodyType.ProtoBuf:
|
case BodyType.ProtoBuf:
|
||||||
return TransformBodyAsJson(transformerContext, options, model, original);
|
return _jsonBodyTransformer.TransformBodyAsJson(
|
||||||
|
transformerContext,
|
||||||
|
options,
|
||||||
|
model,
|
||||||
|
original);
|
||||||
|
|
||||||
case BodyType.File:
|
case BodyType.File:
|
||||||
return TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile);
|
return TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile);
|
||||||
@@ -159,185 +154,7 @@ internal class Transformer : ITransformer
|
|||||||
return newHeaders;
|
return newHeaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IBodyData TransformBodyAsJson(ITransformerContext transformerContext, ReplaceNodeOptions options, object model, IBodyData original)
|
private static BodyData TransformBodyAsString(ITransformerContext transformerContext, object model, IBodyData original)
|
||||||
{
|
|
||||||
JToken? jToken = null;
|
|
||||||
switch (original.BodyAsJson)
|
|
||||||
{
|
|
||||||
case JObject bodyAsJObject:
|
|
||||||
jToken = bodyAsJObject.DeepClone();
|
|
||||||
WalkNode(transformerContext, options, jToken, model);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case JArray bodyAsJArray:
|
|
||||||
jToken = bodyAsJArray.DeepClone();
|
|
||||||
WalkNode(transformerContext, options, jToken, model);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case var bodyAsEnumerable when bodyAsEnumerable is IEnumerable and not string:
|
|
||||||
jToken = JArray.FromObject(bodyAsEnumerable, _jsonSerializer);
|
|
||||||
WalkNode(transformerContext, options, jToken, model);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case string bodyAsString:
|
|
||||||
jToken = ReplaceSingleNode(transformerContext, options, bodyAsString, model);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case not null:
|
|
||||||
jToken = JObject.FromObject(original.BodyAsJson, _jsonSerializer);
|
|
||||||
WalkNode(transformerContext, options, jToken, model);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new BodyData
|
|
||||||
{
|
|
||||||
Encoding = original.Encoding,
|
|
||||||
DetectedBodyType = original.DetectedBodyType,
|
|
||||||
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
|
|
||||||
ProtoDefinition = original.ProtoDefinition,
|
|
||||||
ProtoBufMessageType = original.ProtoBufMessageType,
|
|
||||||
BodyAsJson = jToken
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private JToken ReplaceSingleNode(ITransformerContext transformerContext, ReplaceNodeOptions options, string stringValue, object model)
|
|
||||||
{
|
|
||||||
var transformedString = transformerContext.ParseAndRender(stringValue, model);
|
|
||||||
|
|
||||||
if (!string.Equals(stringValue, transformedString))
|
|
||||||
{
|
|
||||||
const string property = "_";
|
|
||||||
JObject dummy = JObject.Parse($"{{ \"{property}\": null }}");
|
|
||||||
if (dummy[property] == null)
|
|
||||||
{
|
|
||||||
// TODO: check if just returning null is fine
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
JToken node = dummy[property]!;
|
|
||||||
|
|
||||||
ReplaceNodeValue(options, node, transformedString);
|
|
||||||
|
|
||||||
return dummy[property]!;
|
|
||||||
}
|
|
||||||
|
|
||||||
return stringValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void WalkNode(ITransformerContext transformerContext, ReplaceNodeOptions options, JToken node, object model)
|
|
||||||
{
|
|
||||||
switch (node.Type)
|
|
||||||
{
|
|
||||||
case JTokenType.Object:
|
|
||||||
// In case of Object, loop all children. Do a ToArray() to avoid `Collection was modified` exceptions.
|
|
||||||
foreach (var child in node.Children<JProperty>().ToArray())
|
|
||||||
{
|
|
||||||
WalkNode(transformerContext, options, child.Value, model);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case JTokenType.Array:
|
|
||||||
// In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions.
|
|
||||||
foreach (var child in node.Children().ToArray())
|
|
||||||
{
|
|
||||||
WalkNode(transformerContext, options, child, model);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case JTokenType.String:
|
|
||||||
// In case of string, try to transform the value.
|
|
||||||
var stringValue = node.Value<string>();
|
|
||||||
if (string.IsNullOrEmpty(stringValue))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var transformed = transformerContext.ParseAndEvaluate(stringValue!, model);
|
|
||||||
if (!Equals(stringValue, transformed))
|
|
||||||
{
|
|
||||||
ReplaceNodeValue(options, node, transformed);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReSharper disable once UnusedParameter.Local
|
|
||||||
private void ReplaceNodeValue(ReplaceNodeOptions options, JToken node, object? transformedValue)
|
|
||||||
{
|
|
||||||
switch (transformedValue)
|
|
||||||
{
|
|
||||||
case JValue jValue:
|
|
||||||
node.Replace(jValue);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case string transformedString:
|
|
||||||
var (isConvertedFromString, convertedValueFromString) = TryConvert(options, transformedString);
|
|
||||||
if (isConvertedFromString)
|
|
||||||
{
|
|
||||||
node.Replace(JToken.FromObject(convertedValueFromString, _jsonSerializer));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
node.Replace(ParseAsJObject(transformedString));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WireMockList<string> strings:
|
|
||||||
switch (strings.Count)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
node.Replace(ParseAsJObject(strings[0]));
|
|
||||||
return;
|
|
||||||
|
|
||||||
case > 1:
|
|
||||||
node.Replace(JToken.FromObject(strings.ToArray(), _jsonSerializer));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case { }:
|
|
||||||
var (isConverted, convertedValue) = TryConvert(options, transformedValue);
|
|
||||||
if (isConverted)
|
|
||||||
{
|
|
||||||
node.Replace(JToken.FromObject(convertedValue, _jsonSerializer));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
|
|
||||||
default: // It's null, skip it. Maybe remove it ?
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static JToken ParseAsJObject(string stringValue)
|
|
||||||
{
|
|
||||||
return JsonUtils.TryParseAsJObject(stringValue, out var parsedAsjObject) ? parsedAsjObject : stringValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (bool IsConverted, object ConvertedValue) TryConvert(ReplaceNodeOptions options, object value)
|
|
||||||
{
|
|
||||||
var valueAsString = value as string;
|
|
||||||
|
|
||||||
if (options == ReplaceNodeOptions.Evaluate)
|
|
||||||
{
|
|
||||||
if (valueAsString != null && WrappedString.TryDecode(valueAsString, out var decoded))
|
|
||||||
{
|
|
||||||
return (true, decoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (false, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valueAsString != null)
|
|
||||||
{
|
|
||||||
return WrappedString.TryDecode(valueAsString, out var decoded) ?
|
|
||||||
(true, decoded) :
|
|
||||||
StringUtils.TryConvertToKnownType(valueAsString);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (false, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IBodyData TransformBodyAsString(ITransformerContext transformerContext, object model, IBodyData original)
|
|
||||||
{
|
{
|
||||||
return new BodyData
|
return new BodyData
|
||||||
{
|
{
|
||||||
@@ -348,7 +165,7 @@ internal class Transformer : ITransformer
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IBodyData TransformBodyAsFile(ITransformerContext transformerContext, object model, IBodyData original, bool useTransformerForBodyAsFile)
|
private static BodyData TransformBodyAsFile(ITransformerContext transformerContext, object model, IBodyData original, bool useTransformerForBodyAsFile)
|
||||||
{
|
{
|
||||||
var transformedBodyAsFilename = transformerContext.ParseAndRender(original.BodyAsFile!, model);
|
var transformedBodyAsFilename = transformerContext.ParseAndRender(original.BodyAsFile!, model);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ using WireMock.Logging;
|
|||||||
using WireMock.Matchers;
|
using WireMock.Matchers;
|
||||||
using WireMock.Models;
|
using WireMock.Models;
|
||||||
using WireMock.RegularExpressions;
|
using WireMock.RegularExpressions;
|
||||||
|
using WireMock.Transformers;
|
||||||
using WireMock.Types;
|
using WireMock.Types;
|
||||||
|
|
||||||
namespace WireMock.Settings;
|
namespace WireMock.Settings;
|
||||||
@@ -362,11 +363,31 @@ public class WireMockServerSettings
|
|||||||
/// Default is <see cref="NewtonsoftJsonConverter"/>.
|
/// Default is <see cref="NewtonsoftJsonConverter"/>.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[PublicAPI]
|
[PublicAPI]
|
||||||
public IJsonConverter DefaultJsonSerializer { get; set; } = new NewtonsoftJsonConverter();
|
public IJsonConverter DefaultJsonSerializer { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the default JSON body transformer used for template-based JSON body transformations.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Set this property to provide a custom implementation for transforming JSON and ProtoBuf body content.
|
||||||
|
/// Default is <see cref="NewtonsoftJsonBodyTransformer"/>.
|
||||||
|
/// </remarks>
|
||||||
|
[PublicAPI]
|
||||||
|
[JsonIgnore]
|
||||||
|
public IJsonBodyTransformer DefaultJsonBodyTransformer { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// WebSocket settings.
|
/// WebSocket settings.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[PublicAPI]
|
[PublicAPI]
|
||||||
public WebSocketSettings? WebSocketSettings { get; set; }
|
public WebSocketSettings? WebSocketSettings { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="WireMockServerSettings"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public WireMockServerSettings()
|
||||||
|
{
|
||||||
|
DefaultJsonSerializer = new NewtonsoftJsonConverter();
|
||||||
|
DefaultJsonBodyTransformer = new NewtonsoftJsonBodyTransformer(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using WireMock.Types;
|
||||||
|
using WireMock.Util;
|
||||||
|
|
||||||
|
namespace WireMock.Transformers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the contract for transforming JSON-like body data using a transformer context.
|
||||||
|
/// </summary>
|
||||||
|
[PublicAPI]
|
||||||
|
public interface IJsonBodyTransformer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Transforms the JSON body using the provided transformer context and model.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="transformerContext">The transformer context used to render and evaluate template values.</param>
|
||||||
|
/// <param name="options">The JSON node replacement behavior to apply during transformation.</param>
|
||||||
|
/// <param name="model">The model used when rendering or evaluating template values.</param>
|
||||||
|
/// <param name="original">The original body data to transform.</param>
|
||||||
|
/// <returns>The transformed JSON body data.</returns>
|
||||||
|
BodyData TransformBodyAsJson(
|
||||||
|
ITransformerContext transformerContext,
|
||||||
|
ReplaceNodeOptions options,
|
||||||
|
object model,
|
||||||
|
IBodyData original);
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using WireMock.Handlers;
|
||||||
|
|
||||||
|
namespace WireMock.Transformers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the transformer context used to render and evaluate templates during response transformation.
|
||||||
|
/// </summary>
|
||||||
|
[PublicAPI]
|
||||||
|
public interface ITransformerContext
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the file system handler used by the current transformer context.
|
||||||
|
/// </summary>
|
||||||
|
IFileSystemHandler FileSystemHandler { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Renders the specified template text using the supplied model.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">The template text to render.</param>
|
||||||
|
/// <param name="model">The model used during rendering.</param>
|
||||||
|
/// <returns>The rendered text.</returns>
|
||||||
|
string ParseAndRender(string text, object model);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates the specified template text using the supplied model.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="text">The template text to evaluate.</param>
|
||||||
|
/// <param name="model">The model used during evaluation.</param>
|
||||||
|
/// <returns>The evaluated value.</returns>
|
||||||
|
object? ParseAndEvaluate(string text, object model);
|
||||||
|
}
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using System.Collections;
|
||||||
|
using HandlebarsDotNet.Helpers.Models;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using JsonConverter.Abstractions;
|
||||||
|
using JsonConverter.Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using WireMock.Settings;
|
||||||
|
using WireMock.Types;
|
||||||
|
using WireMock.Util;
|
||||||
|
|
||||||
|
namespace WireMock.Transformers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default JSON body transformer implementation based on Newtonsoft.Json.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Initializes a new instance of the <see cref="NewtonsoftJsonBodyTransformer"/> class.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="settings">The server settings used to configure JSON transformation behavior.</param>
|
||||||
|
[PublicAPI]
|
||||||
|
public class NewtonsoftJsonBodyTransformer(WireMockServerSettings settings) : IJsonBodyTransformer
|
||||||
|
{
|
||||||
|
private readonly IJsonConverter _jsonConverter = new NewtonsoftJsonConverter();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public BodyData TransformBodyAsJson(
|
||||||
|
ITransformerContext transformerContext,
|
||||||
|
ReplaceNodeOptions options,
|
||||||
|
object model,
|
||||||
|
IBodyData original)
|
||||||
|
{
|
||||||
|
var jsonSerializer = new JsonSerializer
|
||||||
|
{
|
||||||
|
Culture = settings.Culture
|
||||||
|
};
|
||||||
|
|
||||||
|
JToken? jToken = null;
|
||||||
|
switch (original.BodyAsJson)
|
||||||
|
{
|
||||||
|
case JObject bodyAsJObject:
|
||||||
|
jToken = bodyAsJObject.DeepClone();
|
||||||
|
WalkNode(transformerContext, jsonSerializer, options, jToken, model);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JArray bodyAsJArray:
|
||||||
|
jToken = bodyAsJArray.DeepClone();
|
||||||
|
WalkNode(transformerContext, jsonSerializer, options, jToken, model);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case var bodyAsEnumerable when bodyAsEnumerable is IEnumerable and not string:
|
||||||
|
jToken = JArray.FromObject(bodyAsEnumerable, jsonSerializer);
|
||||||
|
WalkNode(transformerContext, jsonSerializer, options, jToken, model);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case string bodyAsString:
|
||||||
|
jToken = ReplaceSingleNode(transformerContext, jsonSerializer, options, bodyAsString, model);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case not null:
|
||||||
|
jToken = JObject.FromObject(original.BodyAsJson, jsonSerializer);
|
||||||
|
WalkNode(transformerContext, jsonSerializer, options, jToken, model);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BodyData
|
||||||
|
{
|
||||||
|
Encoding = original.Encoding,
|
||||||
|
DetectedBodyType = original.DetectedBodyType,
|
||||||
|
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
|
||||||
|
ProtoDefinition = original.ProtoDefinition,
|
||||||
|
ProtoBufMessageType = original.ProtoBufMessageType,
|
||||||
|
BodyAsJson = jToken
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private JToken ParseAsJObject(string stringValue)
|
||||||
|
{
|
||||||
|
if (_jsonConverter.IsValidJson(stringValue))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Try to convert this string into a JObject
|
||||||
|
return JObject.Parse(stringValue!);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JToken ReplaceSingleNode(ITransformerContext transformerContext, JsonSerializer jsonSerializer, ReplaceNodeOptions options, string stringValue, object model)
|
||||||
|
{
|
||||||
|
var transformedString = transformerContext.ParseAndRender(stringValue, model);
|
||||||
|
|
||||||
|
if (!string.Equals(stringValue, transformedString))
|
||||||
|
{
|
||||||
|
const string property = "_";
|
||||||
|
JObject dummy = JObject.Parse($"{{ \"{property}\": null }}");
|
||||||
|
if (dummy[property] == null)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
JToken node = dummy[property]!;
|
||||||
|
|
||||||
|
ReplaceNodeValue(jsonSerializer, options, node, transformedString);
|
||||||
|
|
||||||
|
return dummy[property]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WalkNode(ITransformerContext transformerContext, JsonSerializer jsonSerializer, ReplaceNodeOptions options, JToken node, object model)
|
||||||
|
{
|
||||||
|
switch (node.Type)
|
||||||
|
{
|
||||||
|
case JTokenType.Object:
|
||||||
|
foreach (var child in node.Children<JProperty>().ToArray())
|
||||||
|
{
|
||||||
|
WalkNode(transformerContext, jsonSerializer, options, child.Value, model);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JTokenType.Array:
|
||||||
|
foreach (var child in node.Children().ToArray())
|
||||||
|
{
|
||||||
|
WalkNode(transformerContext, jsonSerializer, options, child, model);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JTokenType.String:
|
||||||
|
var stringValue = node.Value<string>();
|
||||||
|
if (string.IsNullOrEmpty(stringValue))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var transformed = transformerContext.ParseAndEvaluate(stringValue!, model);
|
||||||
|
if (!Equals(stringValue, transformed))
|
||||||
|
{
|
||||||
|
ReplaceNodeValue(jsonSerializer, options, node, transformed);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReplaceNodeValue(JsonSerializer jsonSerializer, ReplaceNodeOptions options, JToken node, object? transformedValue)
|
||||||
|
{
|
||||||
|
switch (transformedValue)
|
||||||
|
{
|
||||||
|
case JValue jValue:
|
||||||
|
node.Replace(jValue);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case string transformedString:
|
||||||
|
var (isConvertedFromString, convertedValueFromString) = TryConvert(options, transformedString);
|
||||||
|
if (isConvertedFromString)
|
||||||
|
{
|
||||||
|
node.Replace(JToken.FromObject(convertedValueFromString, jsonSerializer));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
node.Replace(ParseAsJObject(transformedString));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case WireMockList<string> strings:
|
||||||
|
switch (strings.Count)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
node.Replace(ParseAsJObject(strings[0]));
|
||||||
|
return;
|
||||||
|
|
||||||
|
case > 1:
|
||||||
|
node.Replace(JToken.FromObject(strings.ToArray(), jsonSerializer));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case { }:
|
||||||
|
var (isConverted, convertedValue) = TryConvert(options, transformedValue);
|
||||||
|
if (isConverted)
|
||||||
|
{
|
||||||
|
node.Replace(JToken.FromObject(convertedValue, jsonSerializer));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (bool IsConverted, object ConvertedValue) TryConvert(ReplaceNodeOptions options, object value)
|
||||||
|
{
|
||||||
|
var valueAsString = value as string;
|
||||||
|
|
||||||
|
if (options == ReplaceNodeOptions.Evaluate)
|
||||||
|
{
|
||||||
|
if (valueAsString != null && WrappedString.TryDecode(valueAsString, out var decoded))
|
||||||
|
{
|
||||||
|
return (true, decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (false, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valueAsString != null)
|
||||||
|
{
|
||||||
|
return WrappedString.TryDecode(valueAsString, out var decoded)
|
||||||
|
? (true, decoded)
|
||||||
|
: TryConvertToKnownType(valueAsString);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (false, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static (bool IsConverted, object ConvertedValue) TryConvertToKnownType(string value)
|
||||||
|
{
|
||||||
|
if (bool.TryParse(value, out var boolResult))
|
||||||
|
{
|
||||||
|
return (true, boolResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (int.TryParse(value, out var intResult))
|
||||||
|
{
|
||||||
|
return (true, intResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (long.TryParse(value, out var longResult))
|
||||||
|
{
|
||||||
|
return (true, longResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (double.TryParse(value, out var doubleResult))
|
||||||
|
{
|
||||||
|
return (true, doubleResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Guid.TryParseExact(value, "D", out var guidResult))
|
||||||
|
{
|
||||||
|
return (true, guidResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TimeSpan.TryParse(value, out var timeSpanResult))
|
||||||
|
{
|
||||||
|
return (true, timeSpanResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DateTime.TryParse(value, out var dateTimeResult))
|
||||||
|
{
|
||||||
|
return (true, dateTimeResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((value.StartsWith("ftp://", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
value.StartsWith("ftps://", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
value.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
value.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) &&
|
||||||
|
Uri.TryCreate(value, UriKind.RelativeOrAbsolute, out var uriResult))
|
||||||
|
{
|
||||||
|
return (true, uriResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (false, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using System.Collections;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using HandlebarsDotNet.Helpers.Models;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using JsonConverter.Abstractions;
|
||||||
|
using JsonConverter.System.Text.Json;
|
||||||
|
using WireMock.Settings;
|
||||||
|
using WireMock.Types;
|
||||||
|
using WireMock.Util;
|
||||||
|
|
||||||
|
namespace WireMock.Transformers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// JSON body transformer implementation based on System.Text.Json.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="settings">The server settings used to configure JSON transformation behavior.</param>
|
||||||
|
[PublicAPI]
|
||||||
|
public class SystemTextJsonBodyTransformer(WireMockServerSettings settings) : IJsonBodyTransformer
|
||||||
|
{
|
||||||
|
private readonly IJsonConverter _jsonConverter = new SystemTextJsonConverter();
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
public BodyData TransformBodyAsJson(
|
||||||
|
ITransformerContext transformerContext,
|
||||||
|
ReplaceNodeOptions options,
|
||||||
|
object model,
|
||||||
|
IBodyData original)
|
||||||
|
{
|
||||||
|
JsonNode? jsonNode = null;
|
||||||
|
switch (original.BodyAsJson)
|
||||||
|
{
|
||||||
|
case JsonObject bodyAsJsonObject:
|
||||||
|
jsonNode = CloneNode(bodyAsJsonObject);
|
||||||
|
jsonNode = WalkNode(transformerContext, options, jsonNode, model);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case JsonArray bodyAsJsonArray:
|
||||||
|
jsonNode = CloneNode(bodyAsJsonArray);
|
||||||
|
jsonNode = WalkNode(transformerContext, options, jsonNode, model);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case var bodyAsEnumerable when bodyAsEnumerable is IEnumerable and not string:
|
||||||
|
jsonNode = JsonSerializer.SerializeToNode(bodyAsEnumerable);
|
||||||
|
if (jsonNode != null)
|
||||||
|
{
|
||||||
|
jsonNode = WalkNode(transformerContext, options, jsonNode, model);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case string bodyAsString:
|
||||||
|
jsonNode = ReplaceSingleNode(transformerContext, options, bodyAsString, model);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case not null:
|
||||||
|
jsonNode = JsonSerializer.SerializeToNode(original.BodyAsJson);
|
||||||
|
if (jsonNode != null)
|
||||||
|
{
|
||||||
|
jsonNode = WalkNode(transformerContext, options, jsonNode, model);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BodyData
|
||||||
|
{
|
||||||
|
Encoding = original.Encoding,
|
||||||
|
DetectedBodyType = original.DetectedBodyType,
|
||||||
|
DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
|
||||||
|
ProtoDefinition = original.ProtoDefinition,
|
||||||
|
ProtoBufMessageType = original.ProtoBufMessageType,
|
||||||
|
BodyAsJson = jsonNode
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode ParseAsJsonObject(string stringValue)
|
||||||
|
{
|
||||||
|
if (_jsonConverter.IsValidJson(stringValue))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var parsed = JsonNode.Parse(stringValue);
|
||||||
|
if (parsed is JsonObject)
|
||||||
|
{
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Ignore and return as string.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonValue.Create(stringValue)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode? ReplaceSingleNode(ITransformerContext transformerContext, ReplaceNodeOptions options, string stringValue, object model)
|
||||||
|
{
|
||||||
|
var transformedString = transformerContext.ParseAndRender(stringValue, model);
|
||||||
|
|
||||||
|
if (!string.Equals(stringValue, transformedString))
|
||||||
|
{
|
||||||
|
return ReplaceNodeValue(options, transformedString);
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonValue.Create(stringValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode? WalkNode(ITransformerContext transformerContext, ReplaceNodeOptions options, JsonNode? node, object model)
|
||||||
|
{
|
||||||
|
switch (node)
|
||||||
|
{
|
||||||
|
case JsonObject jsonObject:
|
||||||
|
foreach (var property in jsonObject.ToArray())
|
||||||
|
{
|
||||||
|
jsonObject[property.Key] = WalkNode(transformerContext, options, property.Value, model);
|
||||||
|
}
|
||||||
|
return jsonObject;
|
||||||
|
|
||||||
|
case JsonArray jsonArray:
|
||||||
|
for (var i = 0; i < jsonArray.Count; i++)
|
||||||
|
{
|
||||||
|
jsonArray[i] = WalkNode(transformerContext, options, jsonArray[i], model);
|
||||||
|
}
|
||||||
|
return jsonArray;
|
||||||
|
|
||||||
|
case JsonValue jsonValue when jsonValue.TryGetValue<string>(out var stringValue):
|
||||||
|
if (string.IsNullOrEmpty(stringValue))
|
||||||
|
{
|
||||||
|
return jsonValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var transformed = transformerContext.ParseAndEvaluate(stringValue!, model);
|
||||||
|
return !Equals(stringValue, transformed) ? ReplaceNodeValue(options, transformed) ?? jsonValue : jsonValue;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonNode? ReplaceNodeValue(ReplaceNodeOptions options, object? transformedValue)
|
||||||
|
{
|
||||||
|
switch (transformedValue)
|
||||||
|
{
|
||||||
|
case JsonNode jsonNode:
|
||||||
|
return CloneNode(jsonNode);
|
||||||
|
|
||||||
|
case string transformedString:
|
||||||
|
var (isConvertedFromString, convertedValueFromString) = TryConvert(options, transformedString);
|
||||||
|
return isConvertedFromString
|
||||||
|
? JsonSerializer.SerializeToNode(convertedValueFromString)
|
||||||
|
: ParseAsJsonObject(transformedString);
|
||||||
|
|
||||||
|
case WireMockList<string> strings:
|
||||||
|
switch (strings.Count)
|
||||||
|
{
|
||||||
|
case 1:
|
||||||
|
return ParseAsJsonObject(strings[0]);
|
||||||
|
|
||||||
|
case > 1:
|
||||||
|
return JsonSerializer.SerializeToNode(strings.ToArray());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case { }:
|
||||||
|
var (isConverted, convertedValue) = TryConvert(options, transformedValue);
|
||||||
|
if (isConverted)
|
||||||
|
{
|
||||||
|
return JsonSerializer.SerializeToNode(convertedValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JsonNode? CloneNode(JsonNode? node)
|
||||||
|
{
|
||||||
|
#if NET8_0_OR_GREATER
|
||||||
|
return node?.DeepClone();
|
||||||
|
#else
|
||||||
|
return node == null ? null : JsonNode.Parse(node.ToJsonString());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (bool IsConverted, object ConvertedValue) TryConvert(ReplaceNodeOptions options, object value)
|
||||||
|
{
|
||||||
|
var valueAsString = value as string;
|
||||||
|
|
||||||
|
if (options == ReplaceNodeOptions.Evaluate)
|
||||||
|
{
|
||||||
|
if (valueAsString != null && WrappedString.TryDecode(valueAsString, out var decoded))
|
||||||
|
{
|
||||||
|
return (true, decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (false, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (valueAsString != null)
|
||||||
|
{
|
||||||
|
return WrappedString.TryDecode(valueAsString, out var decoded)
|
||||||
|
? (true, decoded)
|
||||||
|
: NewtonsoftJsonBodyTransformer.TryConvertToKnownType(valueAsString);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (false, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
<PackageReference Include="AnyOf" Version="0.5.0.1" />
|
<PackageReference Include="AnyOf" Version="0.5.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
|
||||||
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.10.0" />
|
<PackageReference Include="JsonConverter.Newtonsoft.Json" Version="0.10.0" />
|
||||||
|
<PackageReference Include="JsonConverter.System.Text.Json" Version="0.10.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
// Copyright © WireMock.Net
|
||||||
|
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
|
using Moq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using WireMock.Handlers;
|
||||||
|
using WireMock.Settings;
|
||||||
|
using WireMock.Transformers;
|
||||||
|
using WireMock.Types;
|
||||||
|
using WireMock.Util;
|
||||||
|
|
||||||
|
namespace WireMock.Net.Tests.Transformers;
|
||||||
|
|
||||||
|
public class JsonBodyTransformerTests
|
||||||
|
{
|
||||||
|
public static TheoryData<JsonBodyTransformerTestContext> Transformers
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new TheoryData<JsonBodyTransformerTestContext>
|
||||||
|
{
|
||||||
|
new JsonBodyTransformerTestContext(
|
||||||
|
() => new NewtonsoftJsonBodyTransformer(new WireMockServerSettings()),
|
||||||
|
JObject.Parse,
|
||||||
|
body => ((JToken)body).ToString(Formatting.None)),
|
||||||
|
|
||||||
|
new JsonBodyTransformerTestContext(
|
||||||
|
() => new SystemTextJsonBodyTransformer(new WireMockServerSettings()),
|
||||||
|
json => JsonNode.Parse(json)!,
|
||||||
|
body => ((JsonNode)body).ToJsonString())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Transformers))]
|
||||||
|
public void TransformBodyAsJson_Replaces_String_Value_And_Preserves_Original(JsonBodyTransformerTestContext testContext)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var transformer = testContext.CreateTransformer();
|
||||||
|
var originalJson = testContext.ParseJson("{\"value\":\"{{number}}\"}");
|
||||||
|
var bodyData = new BodyData
|
||||||
|
{
|
||||||
|
Encoding = Encoding.UTF8,
|
||||||
|
DetectedBodyType = BodyType.Json,
|
||||||
|
DetectedBodyTypeFromContentType = BodyType.Json,
|
||||||
|
ProtoBufMessageType = "My.Message",
|
||||||
|
BodyAsJson = originalJson
|
||||||
|
};
|
||||||
|
|
||||||
|
var transformerContext = new FakeTransformerContext(
|
||||||
|
text => text,
|
||||||
|
text => text == "{{number}}" ? "123" : text);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = transformer.TransformBodyAsJson(transformerContext, ReplaceNodeOptions.EvaluateAndTryToConvert, new { }, bodyData);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.Encoding.Should().Be(Encoding.UTF8);
|
||||||
|
result.DetectedBodyType.Should().Be(BodyType.Json);
|
||||||
|
result.DetectedBodyTypeFromContentType.Should().Be(BodyType.Json);
|
||||||
|
result.ProtoBufMessageType.Should().Be("My.Message");
|
||||||
|
result.BodyAsJson.Should().NotBeNull();
|
||||||
|
testContext.SerializeJson(result.BodyAsJson).Should().Be("{\"value\":123}");
|
||||||
|
testContext.SerializeJson(originalJson).Should().Be("{\"value\":\"{{number}}\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Transformers))]
|
||||||
|
public void TransformBodyAsJson_With_String_Body_Replaces_Single_Node_With_Object(JsonBodyTransformerTestContext testContext)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var transformer = testContext.CreateTransformer();
|
||||||
|
var bodyData = new BodyData
|
||||||
|
{
|
||||||
|
DetectedBodyType = BodyType.Json,
|
||||||
|
BodyAsJson = "{{json}}"
|
||||||
|
};
|
||||||
|
|
||||||
|
var transformerContext = new FakeTransformerContext(
|
||||||
|
text => text == "{{json}}" ? "{\"name\":\"test\"}" : text,
|
||||||
|
text => text);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = transformer.TransformBodyAsJson(transformerContext, ReplaceNodeOptions.EvaluateAndTryToConvert, new { }, bodyData);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.BodyAsJson.Should().NotBeNull();
|
||||||
|
testContext.SerializeJson(result.BodyAsJson).Should().Be("{\"name\":\"test\"}");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(Transformers))]
|
||||||
|
public void TransformBodyAsJson_Replaces_String_Value_With_WireMockList_As_Array(JsonBodyTransformerTestContext testContext)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var transformer = testContext.CreateTransformer();
|
||||||
|
var bodyData = new BodyData
|
||||||
|
{
|
||||||
|
DetectedBodyType = BodyType.Json,
|
||||||
|
BodyAsJson = testContext.ParseJson("{\"values\":\"{{list}}\"}")
|
||||||
|
};
|
||||||
|
|
||||||
|
var transformerContext = new FakeTransformerContext(
|
||||||
|
text => text,
|
||||||
|
text => text == "{{list}}" ? new WireMockList<string>(new[] { "a", "b" }) : text);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = transformer.TransformBodyAsJson(transformerContext, ReplaceNodeOptions.EvaluateAndTryToConvert, new { }, bodyData);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
result.BodyAsJson.Should().NotBeNull();
|
||||||
|
testContext.SerializeJson(result.BodyAsJson).Should().Be("{\"values\":[\"a\",\"b\"]}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class JsonBodyTransformerTestContext
|
||||||
|
{
|
||||||
|
private readonly Func<IJsonBodyTransformer> _createTransformer;
|
||||||
|
private readonly Func<string, object> _parseJson;
|
||||||
|
private readonly Func<object, string> _serializeJson;
|
||||||
|
|
||||||
|
public JsonBodyTransformerTestContext(
|
||||||
|
Func<IJsonBodyTransformer> createTransformer,
|
||||||
|
Func<string, object> parseJson,
|
||||||
|
Func<object, string> serializeJson)
|
||||||
|
{
|
||||||
|
_createTransformer = createTransformer;
|
||||||
|
_parseJson = parseJson;
|
||||||
|
_serializeJson = serializeJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IJsonBodyTransformer CreateTransformer()
|
||||||
|
{
|
||||||
|
return _createTransformer();
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ParseJson(string json)
|
||||||
|
{
|
||||||
|
return _parseJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string SerializeJson(object body)
|
||||||
|
{
|
||||||
|
return _serializeJson(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class FakeTransformerContext : ITransformerContext
|
||||||
|
{
|
||||||
|
private readonly Func<string, string> _parseAndRender;
|
||||||
|
private readonly Func<string, object> _parseAndEvaluate;
|
||||||
|
|
||||||
|
public FakeTransformerContext(
|
||||||
|
Func<string, string> parseAndRender,
|
||||||
|
Func<string, object> parseAndEvaluate)
|
||||||
|
{
|
||||||
|
_parseAndRender = parseAndRender;
|
||||||
|
_parseAndEvaluate = parseAndEvaluate;
|
||||||
|
FileSystemHandler = Mock.Of<IFileSystemHandler>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public IFileSystemHandler FileSystemHandler { get; private set; }
|
||||||
|
|
||||||
|
public string ParseAndRender(string text, object model)
|
||||||
|
{
|
||||||
|
return _parseAndRender(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ParseAndEvaluate(string text, object model)
|
||||||
|
{
|
||||||
|
return _parseAndEvaluate(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -76,7 +76,6 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Moq" Version="4.20.72" />
|
<PackageReference Include="Moq" Version="4.20.72" />
|
||||||
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
|
<PackageReference Include="SimMetrics.Net" Version="1.0.5" />
|
||||||
<PackageReference Include="JsonConverter.System.Text.Json" Version="0.10.0" />
|
|
||||||
<PackageReference Include="Google.Protobuf" Version="3.33.5" />
|
<PackageReference Include="Google.Protobuf" Version="3.33.5" />
|
||||||
<PackageReference Include="Grpc.Net.Client" Version="2.76.0" />
|
<PackageReference Include="Grpc.Net.Client" Version="2.76.0" />
|
||||||
<PackageReference Include="Grpc.Tools" Version="2.78.0">
|
<PackageReference Include="Grpc.Tools" Version="2.78.0">
|
||||||
|
|||||||
Reference in New Issue
Block a user