diff --git a/examples/WireMock.Net.Console.NET8/MainApp.cs b/examples/WireMock.Net.Console.NET8/MainApp.cs index 11a2b5e1..43fe8bb8 100644 --- a/examples/WireMock.Net.Console.NET8/MainApp.cs +++ b/examples/WireMock.Net.Console.NET8/MainApp.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Net; using Newtonsoft.Json; +using Scriban.Runtime; using WireMock.Logging; using WireMock.Matchers; using WireMock.Models; @@ -409,12 +410,26 @@ namespace WireMock.Net.ConsoleApplication // handlebarsContext.RegisterHelper(transformer.Name, transformer.Render); TODO }, + ScribanRegistrationCallback = (scriptObject, fileSystemHandler) => + { + var globals = (ScriptObject)scriptObject; + globals.Import("greet", new Func(name => $"Hello {name}")); + }, + // Uncomment below if you want to use the CustomFileSystemFileHandler // FileSystemHandler = new CustomFileSystemFileHandler() }); System.Console.WriteLine("WireMockServer listening at {0}", string.Join(",", server.Urls)); server.SetBasicAuthentication("a", "b"); + + server + .Given(Request.Create().WithPath("/scriban/greet").UsingGet()) + .RespondWith(Response.Create() + .WithBody("{{ greet 'WireMock.Net' }}") + .WithTransformer(TransformerType.Scriban) + ); + //server.SetAzureADAuthentication(Environment.GetEnvironmentVariable("WIREMOCK_AAD_TENANT")!, "api://e083d51a-01a6-446c-8ad5-0c5c7f002208"); //var http = new HttpClient(); diff --git a/src/WireMock.Net.Minimal/Settings/WireMockServerSettingsExtensions.cs b/src/WireMock.Net.Minimal/Settings/WireMockServerSettingsExtensions.cs new file mode 100644 index 00000000..7e347be9 --- /dev/null +++ b/src/WireMock.Net.Minimal/Settings/WireMockServerSettingsExtensions.cs @@ -0,0 +1,40 @@ +// Copyright © WireMock.Net + +using JetBrains.Annotations; +using Scriban.Runtime; +using Stef.Validation; +using WireMock.Handlers; + +namespace WireMock.Settings; + +/// +/// Extension methods for . +/// +public static class WireMockServerSettingsExtensions +{ + /// + /// Adds a strongly typed Scriban registration to . + /// + /// + /// Members added to the are available as globals in Scriban and Scriban-DotLiquid templates. + /// When a callback is already configured, it is kept and invoked before . + /// + /// The settings. + /// The action which registers functions, objects or variables on the . + /// The same instance. + [PublicAPI] + public static WireMockServerSettings WithScribanRegistration(this WireMockServerSettings settings, Action registrationCallback) + { + Guard.NotNull(settings); + Guard.NotNull(registrationCallback); + + var existingCallback = settings.ScribanRegistrationCallback; + settings.ScribanRegistrationCallback = (scriptObject, fileSystemHandler) => + { + existingCallback?.Invoke(scriptObject, fileSystemHandler); + registrationCallback((ScriptObject)scriptObject, fileSystemHandler); + }; + + return settings; + } +} diff --git a/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContext.cs b/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContext.cs index c54febbf..ea21cdee 100644 --- a/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContext.cs +++ b/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContext.cs @@ -1,6 +1,7 @@ // Copyright © WireMock.Net using Scriban; +using Scriban.Runtime; using Stef.Validation; using WireMock.Handlers; using WireMock.Types; @@ -9,21 +10,40 @@ namespace WireMock.Transformers.Scriban; internal class ScribanContext : ITransformerContext { + private static readonly MemberRenamerDelegate MemberRenamer = member => member.Name; + private readonly TransformerType _transformerType; + private readonly ScriptObject? _customGlobals; public IFileSystemHandler FileSystemHandler { get; } - public ScribanContext(IFileSystemHandler fileSystemHandler, TransformerType transformerType) + public ScribanContext(IFileSystemHandler fileSystemHandler, TransformerType transformerType, ScriptObject? customGlobals = null) { FileSystemHandler = Guard.NotNull(fileSystemHandler); _transformerType = transformerType; + _customGlobals = customGlobals; } public string ParseAndRender(string text, object model) { - var template = _transformerType == TransformerType.ScribanDotLiquid ? Template.ParseLiquid(text) : Template.Parse(text); + var isLiquid = _transformerType == TransformerType.ScribanDotLiquid; + var template = isLiquid ? Template.ParseLiquid(text) : Template.Parse(text); - return template.Render(model, member => member.Name); + if (_customGlobals is null) + { + return template.Render(model, MemberRenamer); + } + + // Same as Template.Render(model, MemberRenamer), but with the custom globals pushed beneath the model so that the model takes precedence. + var context = isLiquid ? new LiquidTemplateContext() : new TemplateContext(); + context.MemberRenamer = MemberRenamer; + context.PushGlobal(_customGlobals); + + var modelGlobals = new ScriptObject(); + modelGlobals.Import(model, renamer: MemberRenamer); + context.PushGlobal(modelGlobals); + + return template.Render(context); } public object? ParseAndEvaluate(string text, object model) diff --git a/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContextFactory.cs b/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContextFactory.cs index 91db290c..1a829a5a 100644 --- a/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContextFactory.cs +++ b/src/WireMock.Net.Minimal/Transformers/Scriban/ScribanContextFactory.cs @@ -1,24 +1,32 @@ // Copyright © WireMock.Net -using WireMock.Handlers; -using WireMock.Types; +using Scriban.Runtime; using Stef.Validation; +using WireMock.Settings; +using WireMock.Types; namespace WireMock.Transformers.Scriban; internal class ScribanContextFactory : ITransformerContextFactory { - private readonly IFileSystemHandler _fileSystemHandler; + private readonly WireMockServerSettings _settings; private readonly TransformerType _transformerType; - public ScribanContextFactory(IFileSystemHandler fileSystemHandler, TransformerType transformerType) + public ScribanContextFactory(WireMockServerSettings settings, TransformerType transformerType) { - _fileSystemHandler = Guard.NotNull(fileSystemHandler); + _settings = Guard.NotNull(settings); _transformerType = Guard.Condition(transformerType, t => t is TransformerType.Scriban or TransformerType.ScribanDotLiquid); } public ITransformerContext Create() { - return new ScribanContext(_fileSystemHandler, _transformerType); + ScriptObject? customGlobals = null; + if (_settings.ScribanRegistrationCallback is { } registrationCallback) + { + customGlobals = new ScriptObject(); + registrationCallback(customGlobals, _settings.FileSystemHandler); + } + + return new ScribanContext(_settings.FileSystemHandler, _transformerType, customGlobals); } } \ No newline at end of file diff --git a/src/WireMock.Net.Minimal/Transformers/TransformerFactory.cs b/src/WireMock.Net.Minimal/Transformers/TransformerFactory.cs index c398d140..7d390274 100644 --- a/src/WireMock.Net.Minimal/Transformers/TransformerFactory.cs +++ b/src/WireMock.Net.Minimal/Transformers/TransformerFactory.cs @@ -19,8 +19,8 @@ internal static class TransformerFactory case TransformerType.Scriban: case TransformerType.ScribanDotLiquid: - var factoryDotLiquid = new ScribanContextFactory(settings.FileSystemHandler, transformerType); - return new Transformer(settings, factoryDotLiquid); + var factoryScriban = new ScribanContextFactory(settings, transformerType); + return new Transformer(settings, factoryScriban); default: throw new NotSupportedException($"{nameof(TransformerType)} '{transformerType}' is not supported."); diff --git a/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs b/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs index e11f630c..941120eb 100644 --- a/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs +++ b/src/WireMock.Net.Shared/Settings/WireMockServerSettings.cs @@ -194,6 +194,20 @@ public class WireMockServerSettings [JsonIgnore] public Action? HandlebarsRegistrationCallback { get; set; } + /// + /// Action which can be used to add additional Scriban registrations (functions, objects or variables). [Optional] + /// + /// + /// The first argument is a Scriban.Runtime.ScriptObject from the Scriban.Signed package. + /// It is typed as so that this package does not depend on Scriban. + /// Members added to it are available as globals in and templates. + /// On a name clash the template model (request, response, mapping and data) takes precedence. + /// The action is invoked each time a transformer context is created. + /// + [PublicAPI] + [JsonIgnore] + public Action? ScribanRegistrationCallback { get; set; } + /// /// Allow the usage of CSharpCodeMatcher (default is not allowed). /// diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithScribanTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithScribanTests.cs index f4a64c5a..2c628c37 100644 --- a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithScribanTests.cs +++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithScribanTests.cs @@ -8,6 +8,8 @@ using WireMock.Util; using WireMock.Handlers; using Moq; using Microsoft.AspNetCore.Http; +using Newtonsoft.Json; +using Scriban.Runtime; namespace WireMock.Net.Tests.ResponseBuilders; @@ -65,5 +67,50 @@ public class ResponseWithScribanTests // Assert response.Message.BodyData.BodyAsString.Should().Be("test http://localhost/foo /foo POSt"); } + + [Theory] + [InlineData(TransformerType.Scriban)] + [InlineData(TransformerType.ScribanDotLiquid)] + public async Task Response_ProvideResponse_WithScribanRegistrationCallback_UsesCustomFunctionInBodyAndHeaders(TransformerType transformerType) + { + // Arrange + _settings.ScribanRegistrationCallback = (scriptObject, _) => + ((ScriptObject)scriptObject).Import("fake_name", new Func(() => "Jane Doe")); + + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", ClientIp); + + var responseBuilder = Response.Create() + .WithBody("{{ fake_name }} {{ request.Path }}") + .WithHeader("x-name", "{{ fake_name }}") + .WithTransformer(transformerType); + + // Act + var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, Mock.Of(), request, _settings); + + // Assert + response.Message.BodyData!.BodyAsString.Should().Be("Jane Doe /foo"); + response.Message.Headers!["x-name"].Should().ContainSingle().Which.Should().Be("Jane Doe"); + } + + [Theory] + [InlineData(TransformerType.Scriban)] + [InlineData(TransformerType.ScribanDotLiquid)] + public async Task Response_ProvideResponse_WithScribanRegistrationCallback_UsesCustomFunctionInBodyAsJson(TransformerType transformerType) + { + // Arrange + _settings.WithScribanRegistration((scriptObject, _) => scriptObject.Import("fake_name", new Func(() => "Jane Doe"))); + + var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", ClientIp); + + var responseBuilder = Response.Create() + .WithBodyAsJson(new { name = "{{ fake_name }}", path = "{{ request.Path }}" }) + .WithTransformer(transformerType); + + // Act + var response = await responseBuilder.ProvideResponseAsync(_mappingMock.Object, Mock.Of(), request, _settings); + + // Assert + JsonConvert.SerializeObject(response.Message.BodyData!.BodyAsJson).Should().Be("{\"name\":\"Jane Doe\",\"path\":\"/foo\"}"); + } } diff --git a/test/WireMock.Net.Tests/Settings/WireMockServerSettingsExtensionsTests.cs b/test/WireMock.Net.Tests/Settings/WireMockServerSettingsExtensionsTests.cs new file mode 100644 index 00000000..c1fe17bd --- /dev/null +++ b/test/WireMock.Net.Tests/Settings/WireMockServerSettingsExtensionsTests.cs @@ -0,0 +1,82 @@ +// Copyright © WireMock.Net + +using Moq; +using Scriban.Runtime; +using WireMock.Handlers; +using WireMock.Settings; + +namespace WireMock.Net.Tests.Settings; + +public class WireMockServerSettingsExtensionsTests +{ + private readonly IFileSystemHandler _fileSystemHandler = Mock.Of(); + + [Fact] + public void WithScribanRegistration_SetsCallbackWhichPassesTypedScriptObjectAndFileSystemHandler() + { + // Arrange + ScriptObject? receivedScriptObject = null; + IFileSystemHandler? receivedFileSystemHandler = null; + var settings = new WireMockServerSettings(); + var scriptObject = new ScriptObject(); + + // Act + var result = settings.WithScribanRegistration((so, fs) => + { + receivedScriptObject = so; + receivedFileSystemHandler = fs; + }); + settings.ScribanRegistrationCallback!(scriptObject, _fileSystemHandler); + + // Assert + result.Should().BeSameAs(settings); + receivedScriptObject.Should().BeSameAs(scriptObject); + receivedFileSystemHandler.Should().BeSameAs(_fileSystemHandler); + } + + [Fact] + public void WithScribanRegistration_WithExistingCallback_InvokesBothInRegistrationOrder() + { + // Arrange + var calls = new List(); + var settings = new WireMockServerSettings + { + ScribanRegistrationCallback = (_, _) => calls.Add("existing") + }; + + // Act + settings + .WithScribanRegistration((_, _) => calls.Add("first")) + .WithScribanRegistration((_, _) => calls.Add("second")); + settings.ScribanRegistrationCallback!(new ScriptObject(), _fileSystemHandler); + + // Assert + calls.Should().Equal("existing", "first", "second"); + } + + [Fact] + public void WithScribanRegistration_WithNullSettings_ThrowsArgumentNullException() + { + // Arrange + WireMockServerSettings settings = null!; + + // Act + Action act = () => settings.WithScribanRegistration((_, _) => { }); + + // Assert + act.Should().Throw(); + } + + [Fact] + public void WithScribanRegistration_WithNullCallback_ThrowsArgumentNullException() + { + // Arrange + var settings = new WireMockServerSettings(); + + // Act + Action act = () => settings.WithScribanRegistration(null!); + + // Assert + act.Should().Throw(); + } +} diff --git a/test/WireMock.Net.Tests/Transformers/Scriban/ScribanContextFactoryTests.cs b/test/WireMock.Net.Tests/Transformers/Scriban/ScribanContextFactoryTests.cs index 23aee732..52ad7b81 100644 --- a/test/WireMock.Net.Tests/Transformers/Scriban/ScribanContextFactoryTests.cs +++ b/test/WireMock.Net.Tests/Transformers/Scriban/ScribanContextFactoryTests.cs @@ -1,7 +1,9 @@ // Copyright © WireMock.Net using Moq; +using Scriban.Runtime; using WireMock.Handlers; +using WireMock.Settings; using WireMock.Transformers.Scriban; using WireMock.Types; @@ -17,23 +19,79 @@ namespace WireMock.Net.Tests.Transformers.Scriban public void Create_With_Scriban_TransformerType_Creates_ITransformerContext(TransformerType transformerType) { // Arrange - var sut = new ScribanContextFactory(_fileSystemHandlerMock.Object, transformerType); + var settings = new WireMockServerSettings + { + FileSystemHandler = _fileSystemHandlerMock.Object + }; + var sut = new ScribanContextFactory(settings, transformerType); // Act var result = sut.Create(); // Assert result.Should().NotBeNull(); + result.FileSystemHandler.Should().BeSameAs(_fileSystemHandlerMock.Object); } [Fact] public void Create_With_Invalid_TransformerType_Throws_Exception() { + // Arrange + var settings = new WireMockServerSettings + { + FileSystemHandler = _fileSystemHandlerMock.Object + }; + // Act - Action action = () => new ScribanContextFactory(_fileSystemHandlerMock.Object, TransformerType.Handlebars); + Action action = () => new ScribanContextFactory(settings, TransformerType.Handlebars); // Assert action.Should().Throw(); } + + [Theory] + [InlineData(TransformerType.Scriban)] + [InlineData(TransformerType.ScribanDotLiquid)] + public void Create_WithScribanRegistrationCallback_InvokesCallbackOnceWithScriptObjectAndFileSystemHandler(TransformerType transformerType) + { + // Arrange + var calls = new List<(object ScriptObject, IFileSystemHandler FileSystemHandler)>(); + var settings = new WireMockServerSettings + { + FileSystemHandler = _fileSystemHandlerMock.Object, + ScribanRegistrationCallback = (scriptObject, fileSystemHandler) => calls.Add((scriptObject, fileSystemHandler)) + }; + var sut = new ScribanContextFactory(settings, transformerType); + + // Act + var result = sut.Create(); + + // Assert + result.Should().NotBeNull(); + calls.Should().ContainSingle(); + calls[0].ScriptObject.Should().BeOfType(); + calls[0].FileSystemHandler.Should().BeSameAs(_fileSystemHandlerMock.Object); + } + + [Fact] + public void Create_CalledTwice_InvokesScribanRegistrationCallbackWithNewScriptObjectEachTime() + { + // Arrange + var scriptObjects = new List(); + var settings = new WireMockServerSettings + { + FileSystemHandler = _fileSystemHandlerMock.Object, + ScribanRegistrationCallback = (scriptObject, _) => scriptObjects.Add(scriptObject) + }; + var sut = new ScribanContextFactory(settings, TransformerType.Scriban); + + // Act + sut.Create(); + sut.Create(); + + // Assert + scriptObjects.Should().HaveCount(2); + scriptObjects[0].Should().NotBeSameAs(scriptObjects[1]); + } } -} \ No newline at end of file +} \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Transformers/Scriban/ScribanContextTests.cs b/test/WireMock.Net.Tests/Transformers/Scriban/ScribanContextTests.cs new file mode 100644 index 00000000..a3f08e85 --- /dev/null +++ b/test/WireMock.Net.Tests/Transformers/Scriban/ScribanContextTests.cs @@ -0,0 +1,165 @@ +// Copyright © WireMock.Net + +using Moq; +using Scriban.Runtime; +using WireMock.Handlers; +using WireMock.Models; +using WireMock.Transformers; +using WireMock.Transformers.Scriban; +using WireMock.Types; +using WireMock.Util; + +namespace WireMock.Net.Tests.Transformers.Scriban; + +public class ScribanContextTests +{ + private readonly IFileSystemHandler _fileSystemHandler = Mock.Of(); + + [Theory] + [InlineData(TransformerType.Scriban)] + [InlineData(TransformerType.ScribanDotLiquid)] + public void ParseAndRender_WithCustomFunction_RendersFunctionResult(TransformerType transformerType) + { + // Arrange + var customGlobals = new ScriptObject(); + customGlobals.Import("fake_name", new Func(() => "Jane Doe")); + var sut = new ScribanContext(_fileSystemHandler, transformerType, customGlobals); + + // Act + var result = sut.ParseAndRender("Hello {{ fake_name }} at {{ request.Path }}", CreateModel()); + + // Assert + result.Should().Be("Hello Jane Doe at /foo/bar"); + } + + [Fact] + public void ParseAndRender_Scriban_WithCustomFunctionWithArgument_RendersFunctionResult() + { + // Arrange + var customGlobals = new ScriptObject(); + customGlobals.Import("greet", new Func(name => $"Hello {name}")); + var sut = new ScribanContext(_fileSystemHandler, TransformerType.Scriban, customGlobals); + + // Act + var result = sut.ParseAndRender("{{ greet request.Method }}", CreateModel()); + + // Assert + result.Should().Be("Hello GET"); + } + + [Fact] + public void ParseAndRender_ScribanDotLiquid_WithCustomFunctionAsFilter_RendersFunctionResult() + { + // Arrange + var customGlobals = new ScriptObject(); + customGlobals.Import("greet", new Func(name => $"Hello {name}")); + var sut = new ScribanContext(_fileSystemHandler, TransformerType.ScribanDotLiquid, customGlobals); + + // Act + var result = sut.ParseAndRender("{{ request.Method | greet }}", CreateModel()); + + // Assert + result.Should().Be("Hello GET"); + } + + [Theory] + [InlineData(TransformerType.Scriban)] + [InlineData(TransformerType.ScribanDotLiquid)] + public void ParseAndRender_WithCustomGlobalNamedLikeModelMember_ModelTakesPrecedence(TransformerType transformerType) + { + // Arrange + var customGlobals = new ScriptObject(); + customGlobals.SetValue("request", "custom", false); + customGlobals.SetValue("other", "custom", false); + var sut = new ScribanContext(_fileSystemHandler, transformerType, customGlobals); + + // Act + var result = sut.ParseAndRender("{{ request.Path }} {{ other }}", CreateModel()); + + // Assert + result.Should().Be("/foo/bar custom"); + } + + [Theory] + [InlineData(TransformerType.Scriban)] + [InlineData(TransformerType.ScribanDotLiquid)] + public void ParseAndRender_WithCustomGlobalNamedLikeBuiltin_CustomGlobalTakesPrecedence(TransformerType transformerType) + { + // Arrange + var customGlobals = new ScriptObject(); + customGlobals.SetValue("date", "custom date", false); + var sut = new ScribanContext(_fileSystemHandler, transformerType, customGlobals); + + // Act + var result = sut.ParseAndRender("{{ date }}", CreateModel()); + + // Assert + result.Should().Be("custom date"); + } + + [Fact] + public void ParseAndRender_WithTemplateAssignment_DoesNotMutateCustomGlobals() + { + // Arrange + var customGlobals = new ScriptObject(); + customGlobals.SetValue("x", "custom", false); + var sut = new ScribanContext(_fileSystemHandler, TransformerType.Scriban, customGlobals); + + // Act + var first = sut.ParseAndRender("{{ x = 'assigned'; x }} {{ y = 1; y }}", CreateModel()); + var second = sut.ParseAndRender("{{ x }}", CreateModel()); + + // Assert + first.Should().Be("assigned 1"); + second.Should().Be("custom"); + customGlobals.Keys.Should().BeEquivalentTo("x"); + customGlobals["x"].Should().Be("custom"); + } + + [Theory] + [InlineData(TransformerType.Scriban, "{{ request.Url }} {{ request.Path }} {{ request.Method }}")] + [InlineData(TransformerType.Scriban, "{{ request.PathSegments[1] }} {{ request.Headers.Accept }}")] + [InlineData(TransformerType.Scriban, "{{ for s in request.PathSegments }}[{{ s }}]{{ end }}")] + [InlineData(TransformerType.Scriban, "{{ request.Body | string.upcase }} {{ data.Id }}")] + [InlineData(TransformerType.Scriban, "{{ x = 5; x * 2 }}")] + [InlineData(TransformerType.ScribanDotLiquid, "{{ request.Url }} {{ request.Path }} {{ request.Method }}")] + [InlineData(TransformerType.ScribanDotLiquid, "{% for s in request.PathSegments %}[{{ s }}]{% endfor %}")] + [InlineData(TransformerType.ScribanDotLiquid, "{{ request.Body | upcase }} {{ data.Id }}")] + [InlineData(TransformerType.ScribanDotLiquid, "{% assign x = 5 %}{{ x | times: 2 }}")] + public void ParseAndRender_WithEmptyCustomGlobals_RendersSameAsWithoutCustomGlobals(TransformerType transformerType, string template) + { + // Arrange + var model = CreateModel(); + var withoutCustomGlobals = new ScribanContext(_fileSystemHandler, transformerType); + var withCustomGlobals = new ScribanContext(_fileSystemHandler, transformerType, new ScriptObject()); + + // Act + var expected = withoutCustomGlobals.ParseAndRender(template, model); + var result = withCustomGlobals.ParseAndRender(template, model); + + // Assert + expected.Should().NotBeNullOrWhiteSpace(); + result.Should().Be(expected); + } + + private static TransformModel CreateModel() + { + var body = new BodyData + { + BodyAsString = "hello", + DetectedBodyType = BodyType.String + }; + var headers = new Dictionary + { + { "Accept", ["application/json"] } + }; + var request = new RequestMessage(new UrlDetails("http://localhost/foo/bar"), "GET", "::1", body, headers); + + return new TransformModel + { + mapping = Mock.Of(), + request = request, + data = new { Id = 42 } + }; + } +} diff --git a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs index 245940fe..7262479f 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Proxy.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Proxy.cs @@ -59,12 +59,13 @@ public class WireMockServerProxyTests public async Task WireMockServer_Proxy_AdminFalse_With_SaveMapping_Is_True_And_SaveMappingToFile_Is_False_Should_AddInternalMappingOnly() { // Assign + using var target = StartProxyTargetServer(); var cancellationToken = TestContext.Current.CancellationToken; var settings = new WireMockServerSettings { ProxyAndRecordSettings = new ProxyAndRecordSettings { - Url = "http://www.google.com", + Url = target.Url!, SaveMapping = true, SaveMappingToFile = false, ExcludedHeaders = ["Connection"] // Needed for .NET 4.5.x and 4.6.x @@ -93,12 +94,13 @@ public class WireMockServerProxyTests public async Task WireMockServer_Proxy_AdminTrue_With_SaveMapping_Is_True_And_SaveMappingToFile_Is_False_Should_AddInternalMappingOnly() { // Assign + using var target = StartProxyTargetServer(); var cancellationToken = TestContext.Current.CancellationToken; var settings = new WireMockServerSettings { ProxyAndRecordSettings = new ProxyAndRecordSettings { - Url = "http://www.google.com", + Url = target.Url!, SaveMapping = true, SaveMappingToFile = false, ExcludedHeaders = ["Connection"] // Needed for .NET 4.5.x and 4.6.x @@ -185,6 +187,7 @@ public class WireMockServerProxyTests public async Task WireMockServer_Proxy_With_SaveMapping_Is_False_And_SaveMappingToFile_Is_True_ShouldSaveMappingToFile() { // Assign + using var target = StartProxyTargetServer(); var fileSystemHandlerMock = new Mock(); fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); @@ -192,7 +195,7 @@ public class WireMockServerProxyTests { ProxyAndRecordSettings = new ProxyAndRecordSettings { - Url = "http://www.google.com", + Url = target.Url!, SaveMapping = false, SaveMappingToFile = true }, @@ -221,6 +224,7 @@ public class WireMockServerProxyTests public async Task WireMockServer_Proxy_With_SaveMappingForStatusCodePattern_Is_False_Should_Not_SaveMapping() { // Assign + using var target = StartProxyTargetServer(); var fileSystemHandlerMock = new Mock(); fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); @@ -228,7 +232,7 @@ public class WireMockServerProxyTests { ProxyAndRecordSettings = new ProxyAndRecordSettings { - Url = "http://www.google.com", + Url = target.Url!, SaveMapping = true, SaveMappingToFile = true, SaveMappingForStatusCodePattern = "999" // Just make sure that we don't want this mapping @@ -258,6 +262,7 @@ public class WireMockServerProxyTests public async Task WireMockServer_Proxy_With_DoNotSaveMappingForHttpMethod_Should_Not_SaveMapping() { // Assign + using var target = StartProxyTargetServer(); var fileSystemHandlerMock = new Mock(); fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); @@ -265,7 +270,7 @@ public class WireMockServerProxyTests { ProxyAndRecordSettings = new ProxyAndRecordSettings { - Url = "http://www.google.com", + Url = target.Url!, SaveMapping = true, SaveMappingToFile = true, SaveMappingSettings = new ProxySaveMappingSettings @@ -298,11 +303,12 @@ public class WireMockServerProxyTests public async Task WireMockServer_Proxy_Should_log_proxied_requests() { // Assign + using var target = StartProxyTargetServer(); var settings = new WireMockServerSettings { ProxyAndRecordSettings = new ProxyAndRecordSettings { - Url = "http://www.google.com", + Url = target.Url!, SaveMapping = true, SaveMappingToFile = false } @@ -328,12 +334,13 @@ public class WireMockServerProxyTests public async Task WireMockServer_Proxy_Should_proxy_responses() { // Assign + using var target = StartProxyTargetServer(); var cancellationToken = TestContext.Current.CancellationToken; string path = $"/prx_{Guid.NewGuid()}"; var server = WireMockServer.Start(); server .Given(Request.Create().WithPath(path)) - .RespondWith(Response.Create().WithProxy("http://www.google.com")); + .RespondWith(Response.Create().WithProxy(target.Url!)); // Act var requestMessage = new HttpRequestMessage @@ -349,7 +356,7 @@ public class WireMockServerProxyTests // Assert server.Mappings.Should().HaveCount(1); server.LogEntries.Should().HaveCount(1); - content.Should().Contain("google"); + content.Should().Be(ProxyTargetBody); server.Stop(); } @@ -947,6 +954,20 @@ public class WireMockServerProxyTests server.LogEntries.Should().HaveCount(1); server.Stop(); } + + private const string ProxyTargetBody = "response from proxy target"; + + private static WireMockServer StartProxyTargetServer() + { + var target = WireMockServer.Start(); + target + .Given(Request.Create().WithPath("/*")) + .RespondWith(Response.Create() + .WithStatusCode(HttpStatusCode.OK) + .WithBody(ProxyTargetBody)); + + return target; + } } diff --git a/test/WireMock.Net.Tests/WireMockServer.Settings.cs b/test/WireMock.Net.Tests/WireMockServer.Settings.cs index 4e34a03b..b9ec4921 100644 --- a/test/WireMock.Net.Tests/WireMockServer.Settings.cs +++ b/test/WireMock.Net.Tests/WireMockServer.Settings.cs @@ -1,13 +1,18 @@ // Copyright © WireMock.Net +using System.Net.Http; using Moq; +using Scriban.Runtime; using WireMock.Authentication; using WireMock.Constants; using WireMock.Logging; using WireMock.Owin; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; using WireMock.Server; using WireMock.Settings; +using WireMock.Types; namespace WireMock.Net.Tests; @@ -228,5 +233,33 @@ public class WireMockServerSettingsTests options.ActivityTracingOptions.Should().BeNull(); } #endif + + [Theory] + [InlineData(TransformerType.Scriban)] + [InlineData(TransformerType.ScribanDotLiquid)] + public async Task WireMockServer_WireMockServerSettings_ScribanRegistrationCallback_IsUsedInResponseTemplate(TransformerType transformerType) + { + // Arrange + var cancellationToken = TestContext.Current.CancellationToken; + var path = $"/scriban_{Guid.NewGuid()}"; + using var server = WireMockServer.Start(new WireMockServerSettings + { + ScribanRegistrationCallback = (scriptObject, _) => + ((ScriptObject)scriptObject).Import("fake_name", new Func(() => "Jane Doe")) + }); + + server + .Given(Request.Create().WithPath(path).UsingGet()) + .RespondWith(Response.Create() + .WithBody("{{ fake_name }} {{ request.Path }}") + .WithTransformer(transformerType) + ); + + // Act + var response = await new HttpClient().GetStringAsync(server.Url + path, cancellationToken); + + // Assert + response.Should().Be($"Jane Doe {path}"); + } } diff --git a/test/WireMock.Net.Tests/WireMockServerTests.cs b/test/WireMock.Net.Tests/WireMockServerTests.cs index 086411be..8853998a 100644 --- a/test/WireMock.Net.Tests/WireMockServerTests.cs +++ b/test/WireMock.Net.Tests/WireMockServerTests.cs @@ -640,6 +640,8 @@ public partial class WireMockServerTests(ITestOutputHelper testOutputHelper) [Fact] public async Task WireMockServer_Should_respond_to_ipv6_loopback() { + Assert.SkipUnless(Socket.OSSupportsIPv6, "IPv6 is not supported on this machine."); + // Assign var server = WireMockServer.Start();