diff --git a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs
index e2daeea7..5fa993c5 100644
--- a/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs
+++ b/examples/WireMock.Net.Console.Net452.Classic/MainApp.cs
@@ -1,19 +1,17 @@
-using Newtonsoft.Json;
-using HandlebarsDotNet;
using System;
-using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
+using System.Threading.Tasks;
+using Newtonsoft.Json;
using WireMock.Logging;
using WireMock.Matchers;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
using WireMock.Settings;
-using WireMock.Util;
-using System.Threading.Tasks;
using WireMock.Types;
+using WireMock.Util;
namespace WireMock.Net.ConsoleApplication
{
@@ -359,7 +357,7 @@ namespace WireMock.Net.ConsoleApplication
.WithHeader("Transformed-Postman-Token", "token is {{request.headers.Postman-Token}}")
.WithHeader("xyz_{{request.headers.Postman-Token}}", "token is {{request.headers.Postman-Token}}")
.WithBody(@"{""msg"": ""Hello world CATCH-ALL on /*, {{request.path}}, add={{Math.Add request.query.start.[0] 42}} bykey={{request.query.start}}, bykey={{request.query.stop}}, byidx0={{request.query.stop.[0]}}, byidx1={{request.query.stop.[1]}}"" }")
- .WithTransformer(TransformerType.Handlebars)
+ .WithTransformer(TransformerType.Handlebars, true, ReplaceNodeOptions.None)
.WithDelay(TimeSpan.FromMilliseconds(100))
);
diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/ResponseModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/ResponseModel.cs
index c3acc208..f9cbc218 100644
--- a/src/WireMock.Net.Abstractions/Admin/Mappings/ResponseModel.cs
+++ b/src/WireMock.Net.Abstractions/Admin/Mappings/ResponseModel.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
namespace WireMock.Admin.Mappings
{
@@ -64,10 +64,15 @@ namespace WireMock.Admin.Mappings
public string TransformerType { get; set; }
///
- /// Use the Handlerbars transformer for the content from the referenced BodyAsFile.
+ /// Use the Handlebars transformer for the content from the referenced BodyAsFile.
///
public bool? UseTransformerForBodyAsFile { get; set; }
+ ///
+ /// The ReplaceNodeOptions to use when transforming a JSON node.
+ ///
+ public string TransformerReplaceNodeOptions { get; set; }
+
///
/// Gets or sets the headers.
///
diff --git a/src/WireMock.Net.Abstractions/Admin/Mappings/WebhookRequestModel.cs b/src/WireMock.Net.Abstractions/Admin/Mappings/WebhookRequestModel.cs
index c8e7e3b7..d74a1775 100644
--- a/src/WireMock.Net.Abstractions/Admin/Mappings/WebhookRequestModel.cs
+++ b/src/WireMock.Net.Abstractions/Admin/Mappings/WebhookRequestModel.cs
@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
+using WireMock.Types;
namespace WireMock.Admin.Mappings
{
@@ -42,5 +43,10 @@ namespace WireMock.Admin.Mappings
/// Gets the type of the transformer.
///
public string TransformerType { get; set; }
+
+ ///
+ /// The ReplaceNodeOptions to use when transforming a JSON node.
+ ///
+ public string TransformerReplaceNodeOptions { get; set; }
}
}
\ No newline at end of file
diff --git a/src/WireMock.Net.Abstractions/Models/IWebhook.cs b/src/WireMock.Net.Abstractions/Models/IWebhook.cs
index 67174d9e..a4838c55 100644
--- a/src/WireMock.Net.Abstractions/Models/IWebhook.cs
+++ b/src/WireMock.Net.Abstractions/Models/IWebhook.cs
@@ -1,13 +1,13 @@
-namespace WireMock.Models
-{
+namespace WireMock.Models
+{
///
/// IWebhook
- ///
- public interface IWebhook
+ ///
+ public interface IWebhook
{
///
/// Request
- ///
- IWebhookRequest Request { get; set; }
- }
+ ///
+ IWebhookRequest Request { get; set; }
+ }
}
\ No newline at end of file
diff --git a/src/WireMock.Net.Abstractions/Models/IWebhookRequest.cs b/src/WireMock.Net.Abstractions/Models/IWebhookRequest.cs
index bfd4f07f..0f6e9be5 100644
--- a/src/WireMock.Net.Abstractions/Models/IWebhookRequest.cs
+++ b/src/WireMock.Net.Abstractions/Models/IWebhookRequest.cs
@@ -1,42 +1,47 @@
-using System.Collections.Generic;
-using WireMock.Types;
-using WireMock.Util;
-
-namespace WireMock.Models
-{
- ///
- /// IWebhookRequest
- ///
- public interface IWebhookRequest
- {
- ///
- /// The Webhook Url.
- ///
- string Url { get; set; }
-
- ///
- /// The method to use.
- ///
- string Method { get; set; }
-
- ///
- /// The Headers to send.
- ///
- IDictionary> Headers { get; }
-
- ///
- /// The body to send.
- ///
+using System.Collections.Generic;
+using WireMock.Types;
+using WireMock.Util;
+
+namespace WireMock.Models
+{
+ ///
+ /// IWebhookRequest
+ ///
+ public interface IWebhookRequest
+ {
+ ///
+ /// The Webhook Url.
+ ///
+ string Url { get; set; }
+
+ ///
+ /// The method to use.
+ ///
+ string Method { get; set; }
+
+ ///
+ /// The Headers to send.
+ ///
+ IDictionary> Headers { get; }
+
+ ///
+ /// The body to send.
+ ///
IBodyData BodyData { get; set; }
- ///
- /// Use Transformer.
- ///
- bool? UseTransformer { get; set; }
-
- ///
- /// The transformer type.
- ///
- TransformerType TransformerType { get; set; }
- }
+ ///
+ /// Use Transformer.
+ ///
+ bool? UseTransformer { get; set; }
+
+ ///
+ /// The transformer type.
+ ///
+ TransformerType TransformerType { get; set; }
+
+ ///
+ /// The ReplaceNodeOptions to use when transforming a JSON node.
+ ///
+ ReplaceNodeOptions TransformerReplaceNodeOptions { get; set; }
+ }
}
\ No newline at end of file
diff --git a/src/WireMock.Net.Abstractions/Types/ReplaceNodeOptions.cs b/src/WireMock.Net.Abstractions/Types/ReplaceNodeOptions.cs
new file mode 100644
index 00000000..cc70e57f
--- /dev/null
+++ b/src/WireMock.Net.Abstractions/Types/ReplaceNodeOptions.cs
@@ -0,0 +1,36 @@
+using System;
+
+namespace WireMock.Types
+{
+ ///
+ /// Flags to use when replace a JSON node using the Transformer.
+ ///
+ [Flags]
+ public enum ReplaceNodeOptions
+ {
+ ///
+ /// Default
+ ///
+ None = 0
+
+ /////
+ ///// Replace boolean string value to a real boolean value. (This is used by default to maintain backward compatibility.)
+ /////
+ //Bool = 0b00000001,
+
+ /////
+ ///// Replace integer string value to a real integer value.
+ /////
+ //Integer = 0b00000010,
+
+ /////
+ ///// Replace long string value to a real long value.
+ /////
+ //Long = 0b00000100,
+
+ /////
+ ///// Replace all string values to a real values.
+ /////
+ //All = Bool | Integer | Long
+ }
+}
\ No newline at end of file
diff --git a/src/WireMock.Net/Http/WebhookSender.cs b/src/WireMock.Net/Http/WebhookSender.cs
index fc827bf4..0a054c33 100644
--- a/src/WireMock.Net/Http/WebhookSender.cs
+++ b/src/WireMock.Net/Http/WebhookSender.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
@@ -55,7 +55,7 @@ namespace WireMock.Http
throw new NotImplementedException($"TransformerType '{request.TransformerType}' is not supported.");
}
- (bodyData, headers) = responseMessageTransformer.Transform(originalRequestMessage, originalResponseMessage, request.BodyData, request.Headers);
+ (bodyData, headers) = responseMessageTransformer.Transform(originalRequestMessage, originalResponseMessage, request.BodyData, request.Headers, request.TransformerReplaceNodeOptions);
}
else
{
diff --git a/src/WireMock.Net/Models/WebhookRequest.cs b/src/WireMock.Net/Models/WebhookRequest.cs
index 077d84fa..bd6a59e8 100644
--- a/src/WireMock.Net/Models/WebhookRequest.cs
+++ b/src/WireMock.Net/Models/WebhookRequest.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using WireMock.Types;
using WireMock.Util;
@@ -26,5 +26,8 @@ namespace WireMock.Models
///
public TransformerType TransformerType { get; set; }
+
+ ///
+ public ReplaceNodeOptions TransformerReplaceNodeOptions { get; set; }
}
}
\ No newline at end of file
diff --git a/src/WireMock.Net/Owin/WireMockMiddleware.cs b/src/WireMock.Net/Owin/WireMockMiddleware.cs
index ca8f3a57..94ed7bca 100644
--- a/src/WireMock.Net/Owin/WireMockMiddleware.cs
+++ b/src/WireMock.Net/Owin/WireMockMiddleware.cs
@@ -1,13 +1,13 @@
using System;
using System.Threading.Tasks;
-using WireMock.Logging;
using System.Linq;
+using Stef.Validation;
+using WireMock.Logging;
using WireMock.Matchers;
using WireMock.Http;
using WireMock.Owin.Mappers;
using WireMock.Serialization;
using WireMock.Types;
-using Stef.Validation;
using WireMock.ResponseBuilders;
using WireMock.Settings;
#if !USE_ASPNETCORE
diff --git a/src/WireMock.Net/ResponseBuilders/ITransformResponseBuilder.cs b/src/WireMock.Net/ResponseBuilders/ITransformResponseBuilder.cs
index e1cfcaf0..b903401d 100644
--- a/src/WireMock.Net/ResponseBuilders/ITransformResponseBuilder.cs
+++ b/src/WireMock.Net/ResponseBuilders/ITransformResponseBuilder.cs
@@ -1,4 +1,4 @@
-using WireMock.Types;
+using WireMock.Types;
namespace WireMock.ResponseBuilders
{
@@ -13,7 +13,15 @@ namespace WireMock.ResponseBuilders
///
/// The .
///
- IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile = false);
+ IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile);
+
+ ///
+ /// Use the Handlebars.Net ResponseMessage transformer.
+ ///
+ ///
+ /// The .
+ ///
+ IResponseBuilder WithTransformer(ReplaceNodeOptions options);
///
/// Use a specific ResponseMessage transformer.
@@ -21,6 +29,6 @@ namespace WireMock.ResponseBuilders
///
/// The .
///
- IResponseBuilder WithTransformer(TransformerType transformerType, bool transformContentFromBodyAsFile = false);
+ IResponseBuilder WithTransformer(TransformerType transformerType = TransformerType.Handlebars, bool transformContentFromBodyAsFile = false, ReplaceNodeOptions options = ReplaceNodeOptions.None);
}
}
\ No newline at end of file
diff --git a/src/WireMock.Net/ResponseBuilders/Response.cs b/src/WireMock.Net/ResponseBuilders/Response.cs
index e8633d9d..65f9da32 100644
--- a/src/WireMock.Net/ResponseBuilders/Response.cs
+++ b/src/WireMock.Net/ResponseBuilders/Response.cs
@@ -8,6 +8,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
+using Stef.Validation;
using WireMock.Proxy;
using WireMock.ResponseProviders;
using WireMock.Settings;
@@ -16,7 +17,6 @@ using WireMock.Transformers.Handlebars;
using WireMock.Transformers.Scriban;
using WireMock.Types;
using WireMock.Util;
-using Stef.Validation;
namespace WireMock.ResponseBuilders
{
@@ -68,10 +68,15 @@ namespace WireMock.ResponseBuilders
public TransformerType TransformerType { get; private set; }
///
- /// Gets a value indicating whether to use the Handlerbars transformer for the content from the referenced BodyAsFile.
+ /// Gets a value indicating whether to use the Handlebars transformer for the content from the referenced BodyAsFile.
///
public bool UseTransformerForBodyAsFile { get; private set; }
+ ///
+ /// Gets the ReplaceNodeOptions to use when transforming a JSON node.
+ ///
+ public ReplaceNodeOptions TransformerReplaceNodeOptions { get; private set; }
+
///
/// Gets the response message.
///
@@ -330,20 +335,26 @@ namespace WireMock.ResponseBuilders
}
///
- public IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile = false)
+ public IResponseBuilder WithTransformer(bool transformContentFromBodyAsFile)
{
- UseTransformer = true;
- TransformerType = TransformerType.Handlebars;
- UseTransformerForBodyAsFile = transformContentFromBodyAsFile;
- return this;
+ return WithTransformer(TransformerType.Handlebars, transformContentFromBodyAsFile);
}
- ///
- public IResponseBuilder WithTransformer(TransformerType transformerType, bool transformContentFromBodyAsFile = false)
+ ///
+ public IResponseBuilder WithTransformer(ReplaceNodeOptions options)
+ {
+ return WithTransformer(TransformerType.Handlebars, false, options);
+ }
+
+#pragma warning disable CS1574
+ ///
+#pragma warning restore CS1574
+ public IResponseBuilder WithTransformer(TransformerType transformerType, bool transformContentFromBodyAsFile = false, ReplaceNodeOptions options = ReplaceNodeOptions.None)
{
UseTransformer = true;
TransformerType = transformerType;
UseTransformerForBodyAsFile = transformContentFromBodyAsFile;
+ TransformerReplaceNodeOptions = options;
return this;
}
@@ -458,7 +469,7 @@ namespace WireMock.ResponseBuilders
throw new NotImplementedException($"TransformerType '{TransformerType}' is not supported.");
}
- return (responseMessageTransformer.Transform(requestMessage, responseMessage, UseTransformerForBodyAsFile), null);
+ return (responseMessageTransformer.Transform(requestMessage, responseMessage, UseTransformerForBodyAsFile, TransformerReplaceNodeOptions), null);
}
if (!UseTransformer && ResponseMessage.BodyData?.BodyAsFileIsCached == true)
diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs
index ebeaaaf5..26f334fb 100644
--- a/src/WireMock.Net/Serialization/MappingConverter.cs
+++ b/src/WireMock.Net/Serialization/MappingConverter.cs
@@ -1,6 +1,6 @@
-using System;
using System.Collections.Generic;
using System.Linq;
+using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Matchers.Request;
using WireMock.RequestBuilders;
@@ -16,7 +16,7 @@ namespace WireMock.Serialization
public MappingConverter(MatcherMapper mapper)
{
- _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
+ _mapper = Guard.NotNull(mapper, nameof(mapper));
}
public MappingModel ToMappingModel(IMapping mapping)
@@ -130,6 +130,7 @@ namespace WireMock.Serialization
mappingModel.Response.UseTransformer = null;
mappingModel.Response.TransformerType = null;
mappingModel.Response.UseTransformerForBodyAsFile = null;
+ mappingModel.Response.TransformerReplaceNodeOptions = null;
mappingModel.Response.BodyEncoding = null;
mappingModel.Response.ProxyUrl = response.ProxyAndRecordSettings.Url;
mappingModel.Response.Fault = null;
@@ -150,6 +151,7 @@ namespace WireMock.Serialization
{
mappingModel.Response.UseTransformer = response.UseTransformer;
mappingModel.Response.TransformerType = response.TransformerType.ToString();
+ mappingModel.Response.TransformerReplaceNodeOptions = response.TransformerReplaceNodeOptions.ToString();
}
if (response.UseTransformerForBodyAsFile)
diff --git a/src/WireMock.Net/Serialization/WebhookMapper.cs b/src/WireMock.Net/Serialization/WebhookMapper.cs
index 08f19f14..d944e453 100644
--- a/src/WireMock.Net/Serialization/WebhookMapper.cs
+++ b/src/WireMock.Net/Serialization/WebhookMapper.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using WireMock.Admin.Mappings;
@@ -31,6 +31,13 @@ namespace WireMock.Serialization
transformerType = TransformerType.Handlebars;
}
webhook.Request.TransformerType = transformerType;
+
+ if (!Enum.TryParse(model.Request.TransformerReplaceNodeOptions, out var option))
+ {
+ option = ReplaceNodeOptions.None;
+ }
+
+ webhook.Request.TransformerReplaceNodeOptions = option;
}
IEnumerable contentTypeHeader = null;
@@ -76,7 +83,8 @@ namespace WireMock.Serialization
Method = webhook.Request.Method,
Headers = webhook.Request.Headers?.ToDictionary(x => x.Key, x => x.Value.ToString()),
UseTransformer = webhook.Request.UseTransformer,
- TransformerType = webhook.Request.UseTransformer == true ? webhook.Request.TransformerType.ToString() : null
+ TransformerType = webhook.Request.UseTransformer == true ? webhook.Request.TransformerType.ToString() : null,
+ TransformerReplaceNodeOptions = webhook.Request.TransformerReplaceNodeOptions.ToString()
}
};
@@ -93,6 +101,7 @@ namespace WireMock.Serialization
break;
default:
+ // Empty
break;
}
}
diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs
index 3ef204c3..a271a592 100644
--- a/src/WireMock.Net/Server/WireMockServer.Admin.cs
+++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
+using Stef.Validation;
using WireMock.Admin.Mappings;
using WireMock.Admin.Scenarios;
using WireMock.Admin.Settings;
@@ -24,7 +25,6 @@ using WireMock.Serialization;
using WireMock.Settings;
using WireMock.Types;
using WireMock.Util;
-using Stef.Validation;
namespace WireMock.Server
{
@@ -785,7 +785,15 @@ namespace WireMock.Server
{
transformerType = TransformerType.Handlebars;
}
- responseBuilder = responseBuilder.WithTransformer(transformerType, responseModel.UseTransformerForBodyAsFile == true);
+
+ if (!Enum.TryParse(responseModel.TransformerReplaceNodeOptions, out var option))
+ {
+ option = ReplaceNodeOptions.None;
+ }
+ responseBuilder = responseBuilder.WithTransformer(
+ transformerType,
+ responseModel.UseTransformerForBodyAsFile == true,
+ option);
}
if (!string.IsNullOrEmpty(responseModel.ProxyUrl))
diff --git a/src/WireMock.Net/Transformers/Handlebars/HandlebarsContext.cs b/src/WireMock.Net/Transformers/Handlebars/HandlebarsContext.cs
index a4e553be..10af57ac 100644
--- a/src/WireMock.Net/Transformers/Handlebars/HandlebarsContext.cs
+++ b/src/WireMock.Net/Transformers/Handlebars/HandlebarsContext.cs
@@ -1,18 +1,18 @@
-using HandlebarsDotNet;
-using WireMock.Handlers;
+using HandlebarsDotNet;
+using WireMock.Handlers;
-namespace WireMock.Transformers.Handlebars
-{
- internal class HandlebarsContext : IHandlebarsContext
- {
- public IHandlebars Handlebars { get; set; }
-
- public IFileSystemHandler FileSystemHandler { get; set; }
-
- public string ParseAndRender(string text, object model)
- {
- var template = Handlebars.Compile(text);
- return template(model);
- }
- }
+namespace WireMock.Transformers.Handlebars
+{
+ internal class HandlebarsContext : IHandlebarsContext
+ {
+ public IHandlebars Handlebars { get; set; }
+
+ public IFileSystemHandler FileSystemHandler { get; set; }
+
+ public string ParseAndRender(string text, object model)
+ {
+ var template = Handlebars.Compile(text);
+ return template(model);
+ }
+ }
}
\ No newline at end of file
diff --git a/src/WireMock.Net/Transformers/ITransformer.cs b/src/WireMock.Net/Transformers/ITransformer.cs
index ebcd3336..f1af1fc9 100644
--- a/src/WireMock.Net/Transformers/ITransformer.cs
+++ b/src/WireMock.Net/Transformers/ITransformer.cs
@@ -1,13 +1,14 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using WireMock.Types;
using WireMock.Util;
-namespace WireMock.Transformers
-{
- interface ITransformer
- {
- ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage original, bool useTransformerForBodyAsFile);
-
- (IBodyData BodyData, IDictionary> Headers) Transform(RequestMessage originalRequestMessage, ResponseMessage originalResponseMessage, IBodyData bodyData, IDictionary> headers);
- }
+namespace WireMock.Transformers
+{
+ interface ITransformer
+ {
+ ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage original, bool useTransformerForBodyAsFile, ReplaceNodeOptions options);
+
+ (IBodyData BodyData, IDictionary> Headers) Transform(RequestMessage originalRequestMessage, ResponseMessage originalResponseMessage, IBodyData bodyData, IDictionary> headers, ReplaceNodeOptions options);
+ }
}
\ No newline at end of file
diff --git a/src/WireMock.Net/Transformers/Transformer.cs b/src/WireMock.Net/Transformers/Transformer.cs
index 99857779..afb90b99 100644
--- a/src/WireMock.Net/Transformers/Transformer.cs
+++ b/src/WireMock.Net/Transformers/Transformer.cs
@@ -18,7 +18,7 @@ namespace WireMock.Transformers
_factory = factory ?? throw new ArgumentNullException(nameof(factory));
}
- public (IBodyData BodyData, IDictionary> Headers) Transform(RequestMessage originalRequestMessage, ResponseMessage originalResponseMessage, IBodyData bodyData, IDictionary> headers)
+ public (IBodyData BodyData, IDictionary> Headers) Transform(RequestMessage originalRequestMessage, ResponseMessage originalResponseMessage, IBodyData bodyData, IDictionary> headers, ReplaceNodeOptions options)
{
var transformerContext = _factory.Create();
@@ -31,13 +31,13 @@ namespace WireMock.Transformers
IBodyData newBodyData = null;
if (bodyData?.DetectedBodyType != null)
{
- newBodyData = TransformBodyData(transformerContext, model, bodyData, false);
+ newBodyData = TransformBodyData(transformerContext, options, model, bodyData, false);
}
return (newBodyData, TransformHeaders(transformerContext, model, headers));
}
- public ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage original, bool useTransformerForBodyAsFile)
+ public ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage original, bool useTransformerForBodyAsFile, ReplaceNodeOptions options)
{
var transformerContext = _factory.Create();
@@ -50,7 +50,7 @@ namespace WireMock.Transformers
if (original.BodyData?.DetectedBodyType != null)
{
- responseMessage.BodyData = TransformBodyData(transformerContext, model, original.BodyData, useTransformerForBodyAsFile);
+ responseMessage.BodyData = TransformBodyData(transformerContext, options, model, original.BodyData, useTransformerForBodyAsFile);
if (original.BodyData.DetectedBodyType == BodyType.String)
{
@@ -77,12 +77,12 @@ namespace WireMock.Transformers
return responseMessage;
}
- private static IBodyData TransformBodyData(ITransformerContext transformerContext, object model, IBodyData original, bool useTransformerForBodyAsFile)
+ private static IBodyData TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, object model, IBodyData original, bool useTransformerForBodyAsFile)
{
switch (original?.DetectedBodyType)
{
case BodyType.Json:
- return TransformBodyAsJson(transformerContext, model, original);
+ return TransformBodyAsJson(transformerContext, options, model, original);
case BodyType.File:
return TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile);
@@ -114,28 +114,28 @@ namespace WireMock.Transformers
return newHeaders;
}
- private static IBodyData TransformBodyAsJson(ITransformerContext handlebarsContext, object model, IBodyData original)
+ private static IBodyData TransformBodyAsJson(ITransformerContext handlebarsContext, ReplaceNodeOptions options, object model, IBodyData original)
{
JToken jToken;
switch (original.BodyAsJson)
{
case JObject bodyAsJObject:
jToken = bodyAsJObject.DeepClone();
- WalkNode(handlebarsContext, jToken, model);
+ WalkNode(handlebarsContext, options, jToken, model);
break;
case Array bodyAsArray:
jToken = JArray.FromObject(bodyAsArray);
- WalkNode(handlebarsContext, jToken, model);
+ WalkNode(handlebarsContext, options, jToken, model);
break;
case string bodyAsString:
- jToken = ReplaceSingleNode(handlebarsContext, bodyAsString, model);
+ jToken = ReplaceSingleNode(handlebarsContext, options, bodyAsString, model);
break;
default:
jToken = JObject.FromObject(original.BodyAsJson);
- WalkNode(handlebarsContext, jToken, model);
+ WalkNode(handlebarsContext, options, jToken, model);
break;
}
@@ -148,7 +148,7 @@ namespace WireMock.Transformers
};
}
- private static JToken ReplaceSingleNode(ITransformerContext handlebarsContext, string stringValue, object model)
+ private static JToken ReplaceSingleNode(ITransformerContext handlebarsContext, ReplaceNodeOptions options, string stringValue, object model)
{
string transformedString = handlebarsContext.ParseAndRender(stringValue, model);
@@ -158,7 +158,7 @@ namespace WireMock.Transformers
JObject dummy = JObject.Parse($"{{ \"{property}\": null }}");
JToken node = dummy[property];
- ReplaceNodeValue(node, transformedString);
+ ReplaceNodeValue(options, node, transformedString);
return dummy[property];
}
@@ -166,44 +166,47 @@ namespace WireMock.Transformers
return stringValue;
}
- private static void WalkNode(ITransformerContext handlebarsContext, JToken node, object model)
+ private static void WalkNode(ITransformerContext handlebarsContext, ReplaceNodeOptions options, JToken node, object model)
{
- if (node.Type == JTokenType.Object)
+ switch (node.Type)
{
- // In case of Object, loop all children. Do a ToArray() to avoid `Collection was modified` exceptions.
- foreach (JProperty child in node.Children().ToArray())
- {
- WalkNode(handlebarsContext, child.Value, model);
- }
- }
- else if (node.Type == JTokenType.Array)
- {
- // In case of Array, loop all items. Do a ToArray() to avoid `Collection was modified` exceptions.
- foreach (JToken child in node.Children().ToArray())
- {
- WalkNode(handlebarsContext, child, model);
- }
- }
- else if (node.Type == JTokenType.String)
- {
- // In case of string, try to transform the value.
- string stringValue = node.Value();
- if (string.IsNullOrEmpty(stringValue))
- {
- return;
- }
+ 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().ToArray())
+ {
+ WalkNode(handlebarsContext, options, child.Value, model);
+ }
+ break;
- string transformedString = handlebarsContext.ParseAndRender(stringValue, model);
- if (!string.Equals(stringValue, transformedString))
- {
- ReplaceNodeValue(node, transformedString);
- }
+ 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(handlebarsContext, options, child, model);
+ }
+ break;
+
+ case JTokenType.String:
+ // In case of string, try to transform the value.
+ string stringValue = node.Value();
+ if (string.IsNullOrEmpty(stringValue))
+ {
+ return;
+ }
+
+ string transformed = handlebarsContext.ParseAndRender(stringValue, model);
+ if (!string.Equals(stringValue, transformed))
+ {
+ ReplaceNodeValue(options, node, transformed);
+ }
+ break;
}
}
- private static void ReplaceNodeValue(JToken node, string stringValue)
+ private static void ReplaceNodeValue(ReplaceNodeOptions options, JToken node, string transformedString)
{
- if (bool.TryParse(stringValue, out bool valueAsBoolean))
+ StringUtils.TryParseQuotedString(transformedString, out var result, out _);
+ if (bool.TryParse(result, out var valueAsBoolean) || bool.TryParse(transformedString, out valueAsBoolean))
{
node.Replace(valueAsBoolean);
return;
@@ -213,12 +216,12 @@ namespace WireMock.Transformers
try
{
// Try to convert this string into a JsonObject
- value = JToken.Parse(stringValue);
+ value = JToken.Parse(transformedString);
}
catch (JsonException)
{
// Ignore JsonException and just keep string value and convert to JToken
- value = stringValue;
+ value = transformedString;
}
node.Replace(value);
@@ -248,18 +251,15 @@ namespace WireMock.Transformers
BodyAsFile = transformedBodyAsFilename
};
}
- else
- {
- string text = handlebarsContext.FileSystemHandler.ReadResponseBodyAsString(transformedBodyAsFilename);
- return new BodyData
- {
- DetectedBodyType = BodyType.String,
- DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
- BodyAsString = handlebarsContext.ParseAndRender(text, model),
- BodyAsFile = transformedBodyAsFilename
- };
- }
+ string text = handlebarsContext.FileSystemHandler.ReadResponseBodyAsString(transformedBodyAsFilename);
+ return new BodyData
+ {
+ DetectedBodyType = BodyType.String,
+ DetectedBodyTypeFromContentType = original.DetectedBodyTypeFromContentType,
+ BodyAsString = handlebarsContext.ParseAndRender(text, model),
+ BodyAsFile = transformedBodyAsFilename
+ };
}
}
}
\ No newline at end of file
diff --git a/src/WireMock.Net/Util/JsonUtils.cs b/src/WireMock.Net/Util/JsonUtils.cs
index 19626932..ec754147 100644
--- a/src/WireMock.Net/Util/JsonUtils.cs
+++ b/src/WireMock.Net/Util/JsonUtils.cs
@@ -10,6 +10,33 @@ namespace WireMock.Util
{
internal static class JsonUtils
{
+ public static bool TryParseAsComplexObject(string strInput, out JToken token)
+ {
+ token = null;
+
+ if (string.IsNullOrWhiteSpace(strInput))
+ {
+ return false;
+ }
+
+ strInput = strInput.Trim();
+ if ((!strInput.StartsWith("{") || !strInput.EndsWith("}")) && (!strInput.StartsWith("[") || !strInput.EndsWith("]")))
+ {
+ return false;
+ }
+
+ try
+ {
+ // Try to convert this string into a JToken
+ token = JToken.Parse(strInput);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
public static string Serialize(T value)
{
return JsonConvert.SerializeObject(value, JsonSerializationConstants.JsonSerializerSettingsIncludeNullValues);
diff --git a/src/WireMock.Net/Util/StringUtils.cs b/src/WireMock.Net/Util/StringUtils.cs
new file mode 100644
index 00000000..18c59853
--- /dev/null
+++ b/src/WireMock.Net/Util/StringUtils.cs
@@ -0,0 +1,42 @@
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace WireMock.Util
+{
+ internal static class StringUtils
+ {
+ public static bool TryParseQuotedString(string value, out string result, out char quote)
+ {
+ result = null;
+ quote = '\0';
+
+ if (value == null || value.Length < 2)
+ {
+ return false;
+ }
+
+ quote = value[0]; // This can be single or a double quote
+ if (quote != '"' && quote != '\'')
+ {
+ return false;
+ }
+
+ if (value.Last() != quote)
+ {
+ return false;
+ }
+
+ try
+ {
+ result = Regex.Unescape(value.Substring(1, value.Length - 2));
+ return true;
+ }
+ catch
+ {
+ // Ignore Exception, just continue and return false.
+ }
+
+ return false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj
index c4504e66..4331ecf3 100644
--- a/src/WireMock.Net/WireMock.Net.csproj
+++ b/src/WireMock.Net/WireMock.Net.csproj
@@ -83,7 +83,7 @@
-
+
diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithBodyFromFileTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithBodyFromFileTests.cs
index b1a91bf3..af990c5a 100644
--- a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithBodyFromFileTests.cs
+++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithBodyFromFileTests.cs
@@ -1,10 +1,8 @@
-using FluentAssertions;
-using System;
-using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
+using FluentAssertions;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
using WireMock.Server;
diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsRandomTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsRandomTests.cs
index 6afc2ca4..728ab790 100644
--- a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsRandomTests.cs
+++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsRandomTests.cs
@@ -7,6 +7,7 @@ using WireMock.Handlers;
using WireMock.Models;
using WireMock.ResponseBuilders;
using WireMock.Settings;
+using WireMock.Types;
using Xunit;
namespace WireMock.Net.Tests.ResponseBuilders
@@ -15,15 +16,14 @@ namespace WireMock.Net.Tests.ResponseBuilders
{
private const string ClientIp = "::1";
- private readonly Mock _filesystemHandlerMock;
private readonly WireMockServerSettings _settings = new WireMockServerSettings();
public ResponseWithHandlebarsRandomTests()
{
- _filesystemHandlerMock = new Mock(MockBehavior.Strict);
- _filesystemHandlerMock.Setup(fs => fs.ReadResponseBodyAsString(It.IsAny())).Returns("abc");
+ var filesystemHandlerMock = new Mock(MockBehavior.Strict);
+ filesystemHandlerMock.Setup(fs => fs.ReadResponseBodyAsString(It.IsAny())).Returns("abc");
- _settings.FileSystemHandler = _filesystemHandlerMock.Object;
+ _settings.FileSystemHandler = filesystemHandlerMock.Object;
}
[Fact]
@@ -73,6 +73,31 @@ namespace WireMock.Net.Tests.ResponseBuilders
Check.That(j["Value"].Type).IsEqualTo(JTokenType.Boolean);
}
+ [Theory]
+ [InlineData(ReplaceNodeOptions.None, JTokenType.Integer)]
+ //[InlineData(ReplaceNodeOptions.Bool, JTokenType.String)]
+ //[InlineData(ReplaceNodeOptions.Integer, JTokenType.Integer)]
+ //[InlineData(ReplaceNodeOptions.Bool | ReplaceNodeOptions.Integer, JTokenType.Integer)]
+ public async Task Response_ProvideResponseAsync_Handlebars_Random1_Integer(ReplaceNodeOptions options, JTokenType expected)
+ {
+ // Assign
+ var request = new RequestMessage(new UrlDetails("http://localhost:1234"), "GET", ClientIp);
+
+ var responseBuilder = Response.Create()
+ .WithBodyAsJson(new
+ {
+ Value = "{{Random Type=\"Integer\"}}"
+ })
+ .WithTransformer(options);
+
+ // Act
+ var response = await responseBuilder.ProvideResponseAsync(request, _settings).ConfigureAwait(false);
+
+ // Assert
+ JObject j = JObject.FromObject(response.Message.BodyData.BodyAsJson);
+ Check.That(j["Value"].Type).IsEqualTo(expected);
+ }
+
[Fact]
public async Task Response_ProvideResponseAsync_Handlebars_Random1_Guid()
{
diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithTransformerTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithTransformerTests.cs
index 230143f3..e8f36677 100644
--- a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithTransformerTests.cs
+++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithTransformerTests.cs
@@ -23,17 +23,16 @@ namespace WireMock.Net.Tests.ResponseBuilders
{
public class ResponseWithTransformerTests
{
- private readonly Mock _filesystemHandlerMock;
private readonly WireMockServerSettings _settings = new WireMockServerSettings();
private const string ClientIp = "::1";
public ResponseWithTransformerTests()
{
- _filesystemHandlerMock = new Mock(MockBehavior.Strict);
- _filesystemHandlerMock.Setup(fs => fs.ReadResponseBodyAsString(It.IsAny())).Returns("abc");
+ var filesystemHandlerMock = new Mock(MockBehavior.Strict);
+ filesystemHandlerMock.Setup(fs => fs.ReadResponseBodyAsString(It.IsAny())).Returns("abc");
- _settings.FileSystemHandler = _filesystemHandlerMock.Object;
+ _settings.FileSystemHandler = filesystemHandlerMock.Object;
}
[Theory]
@@ -366,7 +365,7 @@ namespace WireMock.Net.Tests.ResponseBuilders
public async Task Response_ProvideResponse_Transformer_WithBodyAsJson_ResultAsObject(TransformerType transformerType)
{
// Assign
- string jsonString = "{ \"things\": [ { \"name\": \"RequiredThing\" }, { \"name\": \"Wiremock\" } ] }";
+ string jsonString = "{ \"things\": [ { \"name\": \"RequiredThing\" }, { \"name\": \"WireMock\" } ] }";
var bodyData = new BodyData
{
BodyAsJson = JsonConvert.DeserializeObject(jsonString),
@@ -386,6 +385,108 @@ namespace WireMock.Net.Tests.ResponseBuilders
Check.That(JsonConvert.SerializeObject(response.Message.BodyData.BodyAsJson)).Equals("{\"x\":\"test /foo_object\"}");
}
+ //[Theory]
+ //[InlineData(TransformerType.Handlebars, "a")]
+ //[InlineData(TransformerType.Handlebars, "42")]
+ //[InlineData(TransformerType.Handlebars, "{")]
+ //[InlineData(TransformerType.Handlebars, "]")]
+ //[InlineData(TransformerType.Handlebars, " ")]
+ //public async Task Response_ProvideResponse_Transformer_WithBodyAsJsonWithExtraQuotes_AndSpecialOption_MakesAString_ResultAsObject(TransformerType transformerType, string text)
+ //{
+ // string jsonString = $"{{ \"x\": \"{text}\" }}";
+ // var bodyData = new BodyData
+ // {
+ // BodyAsJson = JsonConvert.DeserializeObject(jsonString),
+ // DetectedBodyType = BodyType.Json,
+ // Encoding = Encoding.UTF8
+ // };
+ // var request = new RequestMessage(new UrlDetails("http://localhost/foo_object"), "POST", ClientIp, bodyData);
+
+ // var responseBuilder = Response.Create()
+ // .WithBodyAsJson(new { text = "\"{{request.bodyAsJson.x}}\"" })
+ // .WithTransformer(transformerType, false, ReplaceNodeOptions.Default);
+
+ // // Act
+ // var response = await responseBuilder.ProvideResponseAsync(request, _settings).ConfigureAwait(false);
+
+ // // Assert
+ // JsonConvert.SerializeObject(response.Message.BodyData.BodyAsJson).Should().Be($"{{\"text\":\"{text}\"}}");
+ //}
+
+ [Theory]
+ [InlineData(TransformerType.Handlebars, "\"\"", "\"\"")]
+ [InlineData(TransformerType.Handlebars, "\"a\"", "\"a\"")]
+ [InlineData(TransformerType.Handlebars, "\" \"", "\" \"")]
+ [InlineData(TransformerType.Handlebars, "\"'\"", "\"'\"")]
+ [InlineData(TransformerType.Handlebars, "\"false\"", "false")] // bool is special
+ [InlineData(TransformerType.Handlebars, "false", "false")]
+ [InlineData(TransformerType.Handlebars, "\"true\"", "true")] // bool is special
+ [InlineData(TransformerType.Handlebars, "true", "true")]
+ [InlineData(TransformerType.Handlebars, "\"-42\"", "-42")] // todo
+ [InlineData(TransformerType.Handlebars, "-42", "-42")]
+ [InlineData(TransformerType.Handlebars, "\"2147483647\"", "2147483647")] // todo
+ [InlineData(TransformerType.Handlebars, "2147483647", "2147483647")]
+ [InlineData(TransformerType.Handlebars, "\"9223372036854775807\"", "9223372036854775807")] // todo
+ [InlineData(TransformerType.Handlebars, "9223372036854775807", "9223372036854775807")]
+ public async Task Response_ProvideResponse_Transformer_WithBodyAsJson_And_ReplaceNodeOptionsKeep(TransformerType transformerType, string value, string expected)
+ {
+ string jsonString = $"{{ \"x\": {value} }}";
+ var bodyData = new BodyData
+ {
+ BodyAsJson = JsonConvert.DeserializeObject(jsonString),
+ DetectedBodyType = BodyType.Json,
+ Encoding = Encoding.UTF8
+ };
+ var request = new RequestMessage(new UrlDetails("http://localhost/foo_object"), "POST", ClientIp, bodyData);
+
+ var responseBuilder = Response.Create()
+ .WithBodyAsJson(new { text = "{{request.bodyAsJson.x}}" })
+ .WithTransformer(transformerType, false, ReplaceNodeOptions.None);
+
+ // Act
+ var response = await responseBuilder.ProvideResponseAsync(request, _settings).ConfigureAwait(false);
+
+ // Assert
+ JsonConvert.SerializeObject(response.Message.BodyData.BodyAsJson).Should().Be($"{{\"text\":{expected}}}");
+ }
+
+ [Theory]
+ [InlineData(TransformerType.Handlebars, "\"\"", "\"\"")]
+ [InlineData(TransformerType.Handlebars, "\"a\"", "\"a\"")]
+ [InlineData(TransformerType.Handlebars, "\" \"", "\" \"")]
+ [InlineData(TransformerType.Handlebars, "\"'\"", "\"'\"")]
+ [InlineData(TransformerType.Handlebars, "\"false\"", "false")] // bool is special
+ [InlineData(TransformerType.Handlebars, "false", "false")]
+ [InlineData(TransformerType.Handlebars, "\"true\"", "true")] // bool is special
+ [InlineData(TransformerType.Handlebars, "true", "true")]
+ [InlineData(TransformerType.Handlebars, "\"-42\"", "\"-42\"")]
+ [InlineData(TransformerType.Handlebars, "-42", "\"-42\"")]
+ [InlineData(TransformerType.Handlebars, "\"2147483647\"", "\"2147483647\"")]
+ [InlineData(TransformerType.Handlebars, "2147483647", "\"2147483647\"")]
+ [InlineData(TransformerType.Handlebars, "\"9223372036854775807\"", "\"9223372036854775807\"")]
+ [InlineData(TransformerType.Handlebars, "9223372036854775807", "\"9223372036854775807\"")]
+ public async Task Response_ProvideResponse_Transformer_WithBodyAsJsonWithExtraQuotes_AlwaysMakesString(TransformerType transformerType, string value, string expected)
+ {
+ string jsonString = $"{{ \"x\": {value} }}";
+ var bodyData = new BodyData
+ {
+ BodyAsJson = JsonConvert.DeserializeObject(jsonString),
+ DetectedBodyType = BodyType.Json,
+ Encoding = Encoding.UTF8
+ };
+ var request = new RequestMessage(new UrlDetails("http://localhost/foo_object"), "POST", ClientIp, bodyData);
+
+ var responseBuilder = Response.Create()
+ .WithBodyAsJson(new { text = "\"{{request.bodyAsJson.x}}\"" })
+ .WithTransformer(transformerType);
+
+ // Act
+ var response = await responseBuilder.ProvideResponseAsync(request, _settings).ConfigureAwait(false);
+
+ // Assert
+ JsonConvert.SerializeObject(response.Message.BodyData.BodyAsJson).Should().Be($"{{\"text\":{expected}}}");
+ }
+
[Theory]
[InlineData(TransformerType.Handlebars)]
//[InlineData(TransformerType.Scriban)] Scriban cannot access dynamic Json Objects
diff --git a/test/WireMock.Net.Tests/Util/StringUtilsTests.cs b/test/WireMock.Net.Tests/Util/StringUtilsTests.cs
new file mode 100644
index 00000000..0afd2e90
--- /dev/null
+++ b/test/WireMock.Net.Tests/Util/StringUtilsTests.cs
@@ -0,0 +1,104 @@
+using FluentAssertions;
+using WireMock.Util;
+using Xunit;
+
+namespace WireMock.Net.Tests.Util
+{
+ public class StringUtilsTests
+ {
+ [Theory]
+ [InlineData("'s")]
+ [InlineData("\"s")]
+ public void StringUtils_TryParseQuotedString_With_UnexpectedUnclosedString_Returns_False(string input)
+ {
+ // Act
+ bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
+
+ // Assert
+ valid.Should().BeFalse();
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(null)]
+ [InlineData("x")]
+ public void StringUtils_TryParseQuotedString_With_InvalidStringLength_Returns_False(string input)
+ {
+ // Act
+ bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
+
+ // Assert
+ valid.Should().BeFalse();
+ }
+
+ [Theory]
+ [InlineData("xx")]
+ [InlineData(" ")]
+ public void StringUtils_TryParseQuotedString_With_InvalidStringQuoteCharacter_Returns_False(string input)
+ {
+ // Act
+ bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
+
+ // Assert
+ valid.Should().BeFalse();
+ }
+
+ [Fact]
+ public void StringUtils_TryParseQuotedString_With_UnexpectedUnrecognizedEscapeSequence_Returns_False()
+ {
+ // Arrange
+ string input = new string(new[] { '"', '\\', 'u', '?', '"' });
+
+ // Act
+ bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
+
+ // Assert
+ valid.Should().BeFalse();
+ }
+
+ [Theory]
+ [InlineData("''", "")]
+ [InlineData("'s'", "s")]
+ [InlineData("'\\\\'", "\\")]
+ [InlineData("'\\n'", "\n")]
+ public void StringUtils_TryParseQuotedString_SingleQuotedString(string input, string expectedResult)
+ {
+ // Act
+ bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
+
+ // Assert
+ valid.Should().BeTrue();
+ result.Should().Be(expectedResult);
+ quote.Should().Be('\'');
+ }
+
+ [Theory]
+ [InlineData("\"\"", "")]
+ [InlineData("\"\\\\\"", "\\")]
+ [InlineData("\"\\n\"", "\n")]
+ [InlineData("\"\\\\n\"", "\\n")]
+ [InlineData("\"\\\\new\"", "\\new")]
+ [InlineData("\"[]\"", "[]")]
+ [InlineData("\"()\"", "()")]
+ [InlineData("\"(\\\"\\\")\"", "(\"\")")]
+ [InlineData("\"/\"", "/")]
+ [InlineData("\"a\"", "a")]
+ [InlineData("\"This \\\"is\\\" a test.\"", "This \"is\" a test.")]
+ [InlineData(@"""This \""is\"" b test.""", @"This ""is"" b test.")]
+ [InlineData("\"ab\\\"cd\"", "ab\"cd")]
+ [InlineData("\"\\\"\"", "\"")]
+ [InlineData("\"\\\"\\\"\"", "\"\"")]
+ [InlineData("\"AB YZ 19 \uD800\udc05 \u00e4\"", "AB YZ 19 \uD800\udc05 \u00e4")]
+ [InlineData("\"\\\\\\\\192.168.1.1\\\\audio\\\\new\"", "\\\\192.168.1.1\\audio\\new")]
+ public void StringUtils_TryParseQuotedString_DoubleQuotedString(string input, string expectedResult)
+ {
+ // Act
+ bool valid = StringUtils.TryParseQuotedString(input, out var result, out var quote);
+
+ // Assert
+ valid.Should().BeTrue();
+ result.Should().Be(expectedResult);
+ quote.Should().Be('"');
+ }
+ }
+}
\ No newline at end of file