diff --git a/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj b/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj index 06f30ea5f..451f9c483 100644 --- a/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj +++ b/src/WireMock.Net.StandAlone/WireMock.Net.StandAlone.csproj @@ -3,7 +3,8 @@ Lightweight StandAlone Http Mocking Server for .Net. WireMock.Net.StandAlone Stef Heyenrath - net451;net452;net46;netstandard1.3;netstandard2.0 + + net451;net452;net46;net461;netstandard1.3;netstandard2.0 true WireMock.Net.StandAlone WireMock.Net.StandAlone diff --git a/src/WireMock.Net/Admin/Mappings/ResponseModel.cs b/src/WireMock.Net/Admin/Mappings/ResponseModel.cs index 78ea85aab..e9c831b48 100644 --- a/src/WireMock.Net/Admin/Mappings/ResponseModel.cs +++ b/src/WireMock.Net/Admin/Mappings/ResponseModel.cs @@ -10,7 +10,7 @@ public class ResponseModel /// /// Gets or sets the HTTP status. /// - public int? StatusCode { get; set; } + public object StatusCode { get; set; } /// /// Gets or sets the body destination (SameAsSource, String or Bytes). diff --git a/src/WireMock.Net/Admin/Requests/LogResponseModel.cs b/src/WireMock.Net/Admin/Requests/LogResponseModel.cs index 8a0aeb222..3986ee56d 100644 --- a/src/WireMock.Net/Admin/Requests/LogResponseModel.cs +++ b/src/WireMock.Net/Admin/Requests/LogResponseModel.cs @@ -12,7 +12,7 @@ public class LogResponseModel /// /// Gets or sets the status code. /// - public int StatusCode { get; set; } = 200; + public object StatusCode { get; set; } = 200; /// /// Gets the headers. diff --git a/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs b/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs index 800a3e5f3..a19353e3b 100644 --- a/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs +++ b/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs @@ -79,7 +79,17 @@ public async Task MapAsync(ResponseMessage responseMessage, IResponse response) break; } - response.StatusCode = responseMessage.StatusCode; + switch (responseMessage.StatusCode) + { + case int statusCodeAsInteger: + response.StatusCode = statusCodeAsInteger; + break; + + case string statusCodeAsString: + response.StatusCode = int.Parse(statusCodeAsString); + break; + } + SetResponseHeaders(responseMessage, response); if (bytes != null) diff --git a/src/WireMock.Net/ResponseBuilders/IStatusCodeResponseBuilder.cs b/src/WireMock.Net/ResponseBuilders/IStatusCodeResponseBuilder.cs index 9b8767af6..41777664b 100644 --- a/src/WireMock.Net/ResponseBuilders/IStatusCodeResponseBuilder.cs +++ b/src/WireMock.Net/ResponseBuilders/IStatusCodeResponseBuilder.cs @@ -14,6 +14,13 @@ public interface IStatusCodeResponseBuilder : IHeadersResponseBuilder /// The . IResponseBuilder WithStatusCode(int code); + /// + /// The with status code. + /// + /// The code. + /// The . + IResponseBuilder WithStatusCode(string code); + /// /// The with status code. /// diff --git a/src/WireMock.Net/ResponseBuilders/Response.cs b/src/WireMock.Net/ResponseBuilders/Response.cs index 9d716286f..84151db10 100644 --- a/src/WireMock.Net/ResponseBuilders/Response.cs +++ b/src/WireMock.Net/ResponseBuilders/Response.cs @@ -87,11 +87,7 @@ private Response(ResponseMessage responseMessage) ResponseMessage = responseMessage; } - /// - /// The with status code. - /// - /// The code. - /// A .\ + /// [PublicAPI] public IResponseBuilder WithStatusCode(int code) { @@ -99,11 +95,15 @@ public IResponseBuilder WithStatusCode(int code) return this; } - /// - /// The with status code. - /// - /// The code. - /// A . + /// + [PublicAPI] + public IResponseBuilder WithStatusCode(string code) + { + ResponseMessage.StatusCode = code; + return this; + } + + /// [PublicAPI] public IResponseBuilder WithStatusCode(HttpStatusCode code) { diff --git a/src/WireMock.Net/ResponseMessage.cs b/src/WireMock.Net/ResponseMessage.cs index a089aa3f2..669ef1293 100644 --- a/src/WireMock.Net/ResponseMessage.cs +++ b/src/WireMock.Net/ResponseMessage.cs @@ -19,7 +19,7 @@ public class ResponseMessage /// /// Gets or sets the status code. /// - public int StatusCode { get; set; } = 200; + public object StatusCode { get; set; } = 200; /// /// Gets or sets the body. diff --git a/src/WireMock.Net/Server/FluentMockServer.Admin.cs b/src/WireMock.Net/Server/FluentMockServer.Admin.cs index 94d94e198..cf26a7f1c 100644 --- a/src/WireMock.Net/Server/FluentMockServer.Admin.cs +++ b/src/WireMock.Net/Server/FluentMockServer.Admin.cs @@ -805,9 +805,15 @@ private IResponseBuilder InitResponseBuilder(ResponseModel responseModel) return responseBuilder.WithProxy(proxyAndRecordSettings); } - if (responseModel.StatusCode.HasValue) + switch (responseModel.StatusCode) { - responseBuilder = responseBuilder.WithStatusCode(responseModel.StatusCode.Value); + case int statusCodeAsInteger: + responseBuilder = responseBuilder.WithStatusCode(statusCodeAsInteger); + break; + + case string statusCodeAsString: + responseBuilder = responseBuilder.WithStatusCode(statusCodeAsString); + break; } if (responseModel.Headers != null) diff --git a/src/WireMock.Net/Transformers/ResponseMessageTransformer.cs b/src/WireMock.Net/Transformers/ResponseMessageTransformer.cs index 58d1d5d1e..c7bc75104 100644 --- a/src/WireMock.Net/Transformers/ResponseMessageTransformer.cs +++ b/src/WireMock.Net/Transformers/ResponseMessageTransformer.cs @@ -1,4 +1,5 @@ -using JetBrains.Annotations; +using HandlebarsDotNet; +using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; @@ -24,14 +25,14 @@ public ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage { var handlebarsContext = _factory.Create(); - var responseMessage = new ResponseMessage { StatusCode = original.StatusCode }; + var responseMessage = new ResponseMessage(); var template = new { request = requestMessage }; switch (original.BodyData.DetectedBodyType) { case BodyType.Json: - TransformBodyAsJson(handlebarsContext, template, original, responseMessage); + TransformBodyAsJson(handlebarsContext.Handlebars, template, original, responseMessage); break; case BodyType.File: @@ -40,7 +41,7 @@ public ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage case BodyType.String: responseMessage.BodyOriginal = original.BodyData.BodyAsString; - TransformBodyAsString(handlebarsContext, template, original, responseMessage); + TransformBodyAsString(handlebarsContext.Handlebars, template, original, responseMessage); break; } @@ -62,29 +63,46 @@ public ResponseMessage Transform(RequestMessage requestMessage, ResponseMessage responseMessage.Headers = newHeaders; + switch (original.StatusCode) + { + case int statusCodeAsInteger: + responseMessage.StatusCode = statusCodeAsInteger; + break; + + case string statusCodeAsString: + var templateForStatusCode = handlebarsContext.Handlebars.Compile(statusCodeAsString); + responseMessage.StatusCode = templateForStatusCode(template); + break; + } + return responseMessage; } - private static void TransformBodyAsJson(IHandlebarsContext handlebarsContext, object template, ResponseMessage original, ResponseMessage responseMessage) + private static void TransformBodyAsJson(IHandlebars handlebarsContext, object template, ResponseMessage original, ResponseMessage responseMessage) { JToken jToken; switch (original.BodyData.BodyAsJson) { case JObject bodyAsJObject: jToken = bodyAsJObject.DeepClone(); + WalkNode(handlebarsContext, jToken, template); break; case Array bodyAsArray: jToken = JArray.FromObject(bodyAsArray); + WalkNode(handlebarsContext, jToken, template); + break; + + case string bodyAsString: + jToken = ReplaceSingleNode(handlebarsContext, bodyAsString, template); break; default: jToken = JObject.FromObject(original.BodyData.BodyAsJson); + WalkNode(handlebarsContext, jToken, template); break; } - WalkNode(handlebarsContext, jToken, template); - responseMessage.BodyData = new BodyData { DetectedBodyType = original.BodyData.DetectedBodyType, @@ -93,7 +111,25 @@ private static void TransformBodyAsJson(IHandlebarsContext handlebarsContext, ob }; } - private static void WalkNode(IHandlebarsContext handlebarsContext, JToken node, object context) + private static JToken ReplaceSingleNode(IHandlebars handlebarsContext, string stringValue, object context) + { + var templateForStringValue = handlebarsContext.Compile(stringValue); + string transformedString = templateForStringValue(context); + if (!string.Equals(stringValue, transformedString)) + { + const string property = "_"; + JObject dummy = JObject.Parse($"{{ \"{property}\": null }}"); + JToken node = dummy[property]; + + ReplaceNodeValue(node, transformedString); + + return dummy[property]; + } + + return stringValue; + } + + private static void WalkNode(IHandlebars handlebarsContext, JToken node, object context) { if (node.Type == JTokenType.Object) { @@ -120,7 +156,7 @@ private static void WalkNode(IHandlebarsContext handlebarsContext, JToken node, return; } - var templateForStringValue = handlebarsContext.Handlebars.Compile(stringValue); + var templateForStringValue = handlebarsContext.Compile(stringValue); string transformedString = templateForStringValue(context); if (!string.Equals(stringValue, transformedString)) { @@ -152,9 +188,9 @@ private static void ReplaceNodeValue(JToken node, string stringValue) node.Replace(value); } - private static void TransformBodyAsString(IHandlebarsContext handlebarsContext, object template, ResponseMessage original, ResponseMessage responseMessage) + private static void TransformBodyAsString(IHandlebars handlebarsContext, object template, ResponseMessage original, ResponseMessage responseMessage) { - var templateBodyAsString = handlebarsContext.Handlebars.Compile(original.BodyData.BodyAsString); + var templateBodyAsString = handlebarsContext.Compile(original.BodyData.BodyAsString); responseMessage.BodyData = new BodyData { diff --git a/src/WireMock.Net/Util/HttpStatusRangeParser.cs b/src/WireMock.Net/Util/HttpStatusRangeParser.cs index d838ea483..e0830acc6 100644 --- a/src/WireMock.Net/Util/HttpStatusRangeParser.cs +++ b/src/WireMock.Net/Util/HttpStatusRangeParser.cs @@ -10,6 +10,26 @@ namespace WireMock.Util /// internal static class HttpStatusRangeParser { + /// + /// Determines whether the specified pattern is match. + /// + /// The pattern. (Can be null, in that case it's allowed.) + /// The value. + /// is invalid. + public static bool IsMatch(string pattern, object httpStatusCode) + { + switch (httpStatusCode) + { + case int statusCodeAsInteger: + return IsMatch(pattern, statusCodeAsInteger); + + case string statusCodeAsString: + return IsMatch(pattern, int.Parse(statusCodeAsString)); + } + + return false; + } + /// /// Determines whether the specified pattern is match. /// diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj index f5d74ddd2..6518f6eeb 100644 --- a/src/WireMock.Net/WireMock.Net.csproj +++ b/src/WireMock.Net/WireMock.Net.csproj @@ -4,6 +4,7 @@ WireMock.Net Stef Heyenrath + net451;net452;net46;net461;netstandard1.3;netstandard2.0 true WireMock.Net diff --git a/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs b/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs index 26da3474b..bcb4764e8 100644 --- a/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs +++ b/test/WireMock.Net.Tests/Owin/WireMockMiddlewareTests.cs @@ -78,7 +78,7 @@ public async void WireMockMiddleware_Invoke_NoMatch() // Assert and Verify _optionsMock.Verify(o => o.Logger.Warn(It.IsAny(), It.IsAny()), Times.Once); - Expression> match = r => r.StatusCode == 404 && ((StatusModel)r.BodyData.BodyAsJson).Status == "No matching mapping found"; + Expression> match = r => (int) r.StatusCode == 404 && ((StatusModel)r.BodyData.BodyAsJson).Status == "No matching mapping found"; _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); } @@ -99,7 +99,7 @@ public async void WireMockMiddleware_Invoke_IsAdminInterface_EmptyHeaders_401() // Assert and Verify _optionsMock.Verify(o => o.Logger.Error(It.IsAny(), It.IsAny()), Times.Once); - Expression> match = r => r.StatusCode == 401; + Expression> match = r => (int) r.StatusCode == 401; _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); } @@ -120,7 +120,7 @@ public async void WireMockMiddleware_Invoke_IsAdminInterface_MissingHeader_401() // Assert and Verify _optionsMock.Verify(o => o.Logger.Error(It.IsAny(), It.IsAny()), Times.Once); - Expression> match = r => r.StatusCode == 401; + Expression> match = r => (int) r.StatusCode == 401; _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); } diff --git a/test/WireMock.Net.Tests/RequestWithPathTests.cs b/test/WireMock.Net.Tests/RequestWithPathTests.cs index 7ceaf79c5..163f8174d 100644 --- a/test/WireMock.Net.Tests/RequestWithPathTests.cs +++ b/test/WireMock.Net.Tests/RequestWithPathTests.cs @@ -13,21 +13,6 @@ public class RequestWithPathTests { private const string ClientIp = "::1"; - // [Fact] : TODO : this test fails??? - public void Request_WithPath_EncodedSpaces() - { - // Assign - var spec = Request.Create().WithPath("/path/a%20b").UsingAnyMethod(); - - // when - var body = new BodyData(); - var request = new RequestMessage(new UrlDetails("http://localhost/path/a%20b"), "GET", ClientIp, body); - - // then - var requestMatchResult = new RequestMatchResult(); - Check.That(spec.GetMatchingScore(request, requestMatchResult)).IsEqualTo(1.0); - } - [Fact] public void Request_WithPath_Spaces() { diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithWithFaultTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithFaultTests.cs similarity index 97% rename from test/WireMock.Net.Tests/ResponseBuilders/ResponseWithWithFaultTests.cs rename to test/WireMock.Net.Tests/ResponseBuilders/ResponseWithFaultTests.cs index 8dbc9415d..53c27e357 100644 --- a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithWithFaultTests.cs +++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithFaultTests.cs @@ -8,7 +8,7 @@ namespace WireMock.Net.Tests.ResponseBuilders { - public class ResponseWithWithFaultTests + public class ResponseWithFaultTests { private readonly Mock _settingsMock = new Mock(); private const string ClientIp = "::1"; diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsTests.cs index 594664c89..6a6bc5e00 100644 --- a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsTests.cs +++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithHandlebarsTests.cs @@ -24,30 +24,6 @@ public class ResponseWithHandlebarsTests private readonly Mock _settingsMock = new Mock(); private const string ClientIp = "::1"; - [Fact] - public async Task Response_ProvideResponse_Handlebars_WithBodyAsJson_ResultAsObject() - { - // Assign - string jsonString = "{ \"things\": [ { \"name\": \"RequiredThing\" }, { \"name\": \"Wiremock\" } ] }"; - 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 response = Response.Create() - .WithBodyAsJson(new { x = "test {{request.path}}" }) - .WithTransformer(); - - // Act - var responseMessage = await response.ProvideResponseAsync(request, _settingsMock.Object); - - // Assert - Check.That(JsonConvert.SerializeObject(responseMessage.BodyData.BodyAsJson)).Equals("{\"x\":\"test /foo_object\"}"); - } - [Fact] public async Task Response_ProvideResponse_Handlebars_UrlPathVerb() { @@ -195,6 +171,30 @@ public async Task Response_ProvideResponse_Handlebars_Origin_Port_Protocol_Host( Check.That(responseMessage.BodyData.BodyAsString).Equals("test http://localhost:1234 1234 http localhost"); } + [Fact] + public async Task Response_ProvideResponse_Handlebars_WithBodyAsJson_ResultAsObject() + { + // Assign + string jsonString = "{ \"things\": [ { \"name\": \"RequiredThing\" }, { \"name\": \"Wiremock\" } ] }"; + 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 response = Response.Create() + .WithBodyAsJson(new { x = "test {{request.path}}" }) + .WithTransformer(); + + // Act + var responseMessage = await response.ProvideResponseAsync(request, _settingsMock.Object); + + // Assert + Check.That(JsonConvert.SerializeObject(responseMessage.BodyData.BodyAsJson)).Equals("{\"x\":\"test /foo_object\"}"); + } + [Fact] public async Task Response_ProvideResponse_Handlebars_WithBodyAsJson_ResultAsArray() { @@ -286,5 +286,53 @@ public async Task Response_ProvideResponse_Handlebars_WithBodyAsFile_JsonPath() // Assert Check.That(responseMessage.BodyData.BodyAsFile).Equals(@"c:\1\test.json"); } + + [Fact] + public async Task Response_ProvideResponse_Handlebars_WithBodyAsJson_ResultAsNormalString() + { + // Assign + string jsonString = "{ \"name\": \"WireMock\" }"; + 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 response = Response.Create() + .WithBodyAsJson("test") + .WithTransformer(); + + // Act + var responseMessage = await response.ProvideResponseAsync(request, _settingsMock.Object); + + // Assert + Check.That(JsonConvert.SerializeObject(responseMessage.BodyData.BodyAsJson)).Equals("\"test\""); + } + + [Fact] + public async Task Response_ProvideResponse_Handlebars_WithBodyAsJson_ResultAsHandlebarsString() + { + // Assign + string jsonString = "{ \"name\": \"WireMock\" }"; + 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 response = Response.Create() + .WithBodyAsJson("{{{request.bodyAsJson}}}") + .WithTransformer(); + + // Act + var responseMessage = await response.ProvideResponseAsync(request, _settingsMock.Object); + + // Assert + Check.That(JsonConvert.SerializeObject(responseMessage.BodyData.BodyAsJson)).Equals("{\"name\":\"WireMock\"}"); + } } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithStatusCodeTests.cs b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithStatusCodeTests.cs new file mode 100644 index 000000000..de05a8a7c --- /dev/null +++ b/test/WireMock.Net.Tests/ResponseBuilders/ResponseWithStatusCodeTests.cs @@ -0,0 +1,49 @@ +using FluentAssertions; +using Moq; +using System.Net; +using System.Threading.Tasks; +using WireMock.Models; +using WireMock.ResponseBuilders; +using WireMock.Settings; +using Xunit; + +namespace WireMock.Net.Tests.ResponseBuilders +{ + public class ResponseWithStatusCodeTests + { + private readonly Mock _settingsMock = new Mock(); + private const string ClientIp = "::1"; + + [Theory] + [InlineData("201", "201")] + [InlineData(201, 201)] + [InlineData(HttpStatusCode.Created, 201)] + public async Task Response_ProvideResponse_WithStatusCode(object statusCode, object expectedStatusCode) + { + // Arrange + var request = new RequestMessage(new UrlDetails("http://localhost/fault"), "GET", ClientIp); + + // Act + var response = Response.Create(); + switch (statusCode) + { + case string statusCodeAsString: + response = response.WithStatusCode(statusCodeAsString); + break; + + case int statusCodeAInteger: + response = response.WithStatusCode(statusCodeAInteger); + break; + + case HttpStatusCode statusCodeAsEnum: + response = response.WithStatusCode(statusCodeAsEnum); + break; + } + + var responseMessage = await response.ProvideResponseAsync(request, _settingsMock.Object); + + // Assert + responseMessage.StatusCode.Should().Be(expectedStatusCode); + } + } +} diff --git a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj index 6e19cf80b..ec43cd296 100644 --- a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj +++ b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj @@ -3,6 +3,7 @@ Stef Heyenrath net452;netcoreapp2.1 + full WireMock.Net.Tests WireMock.Net.Tests @@ -42,7 +43,7 @@ - +