From 651326a61c2945d1dd28fa4a2c60cd16eb4708a7 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Tue, 2 Aug 2022 10:40:18 +0200 Subject: [PATCH 01/23] If the method is abstract and virtual is a correct method (#72) Tests to check abstract and virtual methods --- src/Acheve.TestHost/TestServerExtensions.cs | 2 +- .../Routing/Builders/AbstractController.cs | 24 ++++++++++ .../ImplementationAbstractController.cs | 20 ++++++++ .../Routing/TestServerExtensionsTests.cs | 46 +++++++++++++++++++ 4 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 tests/UnitTests/Acheve.TestHost/Routing/Builders/AbstractController.cs create mode 100644 tests/UnitTests/Acheve.TestHost/Routing/Builders/ImplementationAbstractController.cs diff --git a/src/Acheve.TestHost/TestServerExtensions.cs b/src/Acheve.TestHost/TestServerExtensions.cs index dc3b969..b4bacba 100644 --- a/src/Acheve.TestHost/TestServerExtensions.cs +++ b/src/Acheve.TestHost/TestServerExtensions.cs @@ -122,7 +122,7 @@ private static bool IsValidActionMethod(MethodInfo methodInfo) return false; } - if (methodInfo.IsAbstract) + if (methodInfo.IsAbstract && !methodInfo.IsVirtual) { return false; } diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Builders/AbstractController.cs b/tests/UnitTests/Acheve.TestHost/Routing/Builders/AbstractController.cs new file mode 100644 index 0000000..0875b33 --- /dev/null +++ b/tests/UnitTests/Acheve.TestHost/Routing/Builders/AbstractController.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Mvc; + +namespace UnitTests.Acheve.TestHost.Builders +{ + [Route("api/[controller]")] + [ApiController] + public abstract class AbstractController : ControllerBase + { + [HttpGet(nameof(VirtualMethod))] + public virtual IActionResult VirtualMethod() + { + return Ok(); + } + + [HttpGet(nameof(Virtual2Method))] + public virtual IActionResult Virtual2Method() + { + return Ok(); + } + + [HttpGet(nameof(AbstractMethod))] + public abstract IActionResult AbstractMethod(); + } +} diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Builders/ImplementationAbstractController.cs b/tests/UnitTests/Acheve.TestHost/Routing/Builders/ImplementationAbstractController.cs new file mode 100644 index 0000000..2205e0c --- /dev/null +++ b/tests/UnitTests/Acheve.TestHost/Routing/Builders/ImplementationAbstractController.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Mvc; + +namespace UnitTests.Acheve.TestHost.Builders +{ + public class ImplementationAbstractController : AbstractController + { + public const string VIRTUAL_METHOD_RESULT = "from implementation"; + + public override IActionResult AbstractMethod() + { + return Ok(); + } + + [HttpGet(nameof(VirtualMethod))] + public override IActionResult VirtualMethod() + { + return Ok(VIRTUAL_METHOD_RESULT); + } + } +} diff --git a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs index 21f62f7..61a24a4 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs @@ -1673,6 +1673,52 @@ public async Task create_request_and_add_parameter_when_you_have_path_parameter( response.Should().Be(JsonSerializer.Serialize(new { id1 = id1.ToString(), id2 = id2.ToString() })); } + [Fact] + public async Task create_request_from_implemented_abstract_method() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var request = server.CreateHttpApiRequest(controller => controller.AbstractMethod()); + + var responseMessage = await request.GetAsync(); + + responseMessage.EnsureSuccessStatusCode(); + responseMessage.IsSuccessStatusCode.Should().BeTrue(); + } + + [Fact] + public async Task create_request_from_overrided_virtual_method() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var request = server.CreateHttpApiRequest(controller => controller.VirtualMethod()); + + var responseMessage = await request.GetAsync(); + + responseMessage.EnsureSuccessStatusCode(); + var content = await responseMessage.Content.ReadAsStringAsync(); + content.Should().Be(ImplementationAbstractController.VIRTUAL_METHOD_RESULT); + } + + [Fact] + public async Task create_request_from_not_overrided_virtual_method() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var request = server.CreateHttpApiRequest(controller => controller.Virtual2Method()); + + var responseMessage = await request.GetAsync(); + + responseMessage.EnsureSuccessStatusCode(); + responseMessage.IsSuccessStatusCode.Should().BeTrue(); + } + private class PrivateNonControllerClass { public int SomeAction() From f65be9c2869b1903e5041ad7923fd1b24ea4663a Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Tue, 13 Sep 2022 19:33:58 +0200 Subject: [PATCH 02/23] Fix enumerable parameter (#74) --- .../PrimitiveParameterActionTokenizer.cs | 19 ++++++------------- .../Routing/Tokenizers/TypeExtensions.cs | 15 ++++++++++++++- src/Acheve.TestHost/TestServerExtensions.cs | 5 ++--- .../Routing/Builders/BugsController.cs | 8 ++++++++ .../Routing/TestServerExtensionsTests.cs | 19 +++++++++++++++++++ 5 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs b/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs index 2de578c..4736442 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Mvc; using System; +using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; @@ -19,8 +20,9 @@ public void AddTokens(TestServerAction action, TestServerTokenColle if (!IgnoreHeader(parameters[i])) { var tokenName = parameters[i].Name.ToLowerInvariant(); + var parameterType = parameters[i].ParameterType; - if (parameters[i].ParameterType.IsPrimitiveType()) + if (parameterType.IsPrimitiveType()) { var tokenValue = action.ArgumentValues.Any(x => x.Key == i) ? action.ArgumentValues[i].Instance : null; @@ -29,14 +31,13 @@ public void AddTokens(TestServerAction action, TestServerTokenColle tokens.AddToken(tokenName, tokenValue.ToString(), isConventional: false); } } - else if (parameters[i].ParameterType.IsArray - && IsPrimitiveType(parameters[i].ParameterType.GetElementType())) + else if (parameterType.IsEnumerable() + && parameterType.GetEnumerableElementType().IsPrimitiveType()) { var arrayValues = (Array)action.ArgumentValues[i].Instance; if (arrayValues != null - && arrayValues.Length != 0 - ) + && arrayValues.Length != 0) { var tokenValue = GetTokenValue(arrayValues, tokenName); tokens.AddToken(tokenName, tokenValue, isConventional: false); @@ -58,14 +59,6 @@ private bool IgnoreHeader(ParameterInfo parameter) return false; } - private bool IsPrimitiveType(Type type) - { - return type.IsPrimitive - || type == typeof(string) - || type == typeof(decimal) - || type == typeof(Guid); - } - private string GetTokenValue(Array array, string tokenName) { var list = new List(); diff --git a/src/Acheve.TestHost/Routing/Tokenizers/TypeExtensions.cs b/src/Acheve.TestHost/Routing/Tokenizers/TypeExtensions.cs index e45adf5..bb95861 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/TypeExtensions.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/TypeExtensions.cs @@ -1,4 +1,7 @@ -namespace System +using System.Collections; +using System.Linq; + +namespace System { internal static class TypeExtensions { @@ -11,5 +14,15 @@ internal static bool IsPrimitiveType(this Type typeToInspect) || type == typeof(decimal) || type == typeof(Guid); } + + internal static bool IsEnumerable(this Type typeToInspect) + { + return typeof(IEnumerable).IsAssignableFrom(typeToInspect); + } + + internal static Type GetEnumerableElementType(this Type typeToInspect) + { + return typeToInspect.GetElementType() ?? typeToInspect.GetGenericArguments().FirstOrDefault(); + } } } \ No newline at end of file diff --git a/src/Acheve.TestHost/TestServerExtensions.cs b/src/Acheve.TestHost/TestServerExtensions.cs index b4bacba..8b8d1da 100644 --- a/src/Acheve.TestHost/TestServerExtensions.cs +++ b/src/Acheve.TestHost/TestServerExtensions.cs @@ -47,10 +47,9 @@ public static RequestBuilder CreateHttpApiRequest(this TestServer s throw new InvalidOperationException($"The action selector is not a valid action for MVC Controller."); } - //the uri discover use only attribute route conventions.. + //the uri discover use only attribute route conventions. - var validUri = UriDiscover.Discover( - action, tokenValues); + var validUri = UriDiscover.Discover(action, tokenValues); var requestBuilder = server.CreateRequest(validUri); diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs b/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs index f6d9b6f..c70603f 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs @@ -1,5 +1,7 @@ using Microsoft.AspNetCore.Mvc; using System; +using System.Collections; +using System.Collections.Generic; using UnitTests.Acheve.TestHost.Routing.Models; namespace UnitTests.Acheve.TestHost.Builders @@ -62,5 +64,11 @@ public ActionResult GetWithSeveralColon(int param1, int param2) { return Ok($"{param1}/{param2}"); } + + [HttpGet(nameof(GetWithListParam))] + public ActionResult> GetWithListParam([FromQuery] IEnumerable param) + { + return Ok(param); + } } } \ No newline at end of file diff --git a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs index 61a24a4..52224f7 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs @@ -1719,6 +1719,25 @@ public async Task create_request_from_not_overrided_virtual_method() responseMessage.IsSuccessStatusCode.Should().BeTrue(); } + [Fact] + public async Task Create_request_with_list_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var param = new string[] { "one", "two" }; + + var request = server.CreateHttpApiRequest(controller => controller.GetWithListParam(param)); + + var responseMessage = await request.GetAsync(); + + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.ReadContentAsAsync>(); + + response.Should().BeEquivalentTo(param); + } + private class PrivateNonControllerClass { public int SomeAction() From f817b67cbacc16ad8823f522fb611959f6396d31 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Thu, 15 Sep 2022 19:34:33 +0200 Subject: [PATCH 03/23] Add RemoveQueryParameter extension method (#75) Move some tests --- README.md | 15 ++ .../RequestBuilderExtensions.cs | 40 ++++- .../Routing/RequestBuilderExtensionsTests.cs | 159 ++++++++++++++++++ .../Routing/TestServerExtensionsTests.cs | 62 ------- 4 files changed, 210 insertions(+), 66 deletions(-) create mode 100644 tests/UnitTests/Acheve.TestHost/Routing/RequestBuilderExtensionsTests.cs diff --git a/README.md b/README.md index cc530d1..69d8bc1 100644 --- a/README.md +++ b/README.md @@ -221,6 +221,21 @@ public async Task MyFirstTest() } ``` +## About removin query parameters + +The package has a *RequestBuilder* extension to remove a query parameter: *RemoveQueryParameter*. + +```csharp +[Fact] +public async Task MyFirstTest() +{ + ... + var request = server.CreateHttpApiRequest(controller => controller.MyMethod(myFirstParameter)) + .RemoveQueryParameter(nameof(myFirstParameter)); + ... +} +``` + ## Improving assertions in API responses The package has HttpResponseMessage extension to help us assert the response. diff --git a/src/Acheve.TestHost/RequestBuilderExtensions.cs b/src/Acheve.TestHost/RequestBuilderExtensions.cs index 3a7523b..844558d 100644 --- a/src/Acheve.TestHost/RequestBuilderExtensions.cs +++ b/src/Acheve.TestHost/RequestBuilderExtensions.cs @@ -1,8 +1,9 @@ -using System; -using Acheve.TestHost; +using Acheve.TestHost; +using Microsoft.Net.Http.Headers; +using System; using System.Collections.Generic; using System.Security.Claims; -using Microsoft.Net.Http.Headers; +using System.Text.RegularExpressions; namespace Microsoft.AspNetCore.TestHost { @@ -97,11 +98,42 @@ public static RequestBuilder AddQueryParameter(this RequestBuilder requestBui var separatoChar = '?'; if (configure.RequestUri.ToString().Contains(separatoChar)) separatoChar = '&'; - + configure.RequestUri = new Uri($"{configure.RequestUri}{separatoChar}{Uri.EscapeDataString(name)}={Uri.EscapeDataString(value.ToString())}", UriKind.Relative); }); return requestBuilder; } + + /// + /// Remove the given parameter from the request + /// + /// The requestBuilder instance + /// Parameter name + /// RequestBuilder instance + public static RequestBuilder RemoveQueryParameter(this RequestBuilder requestBuilder, string name) + { + requestBuilder.And(configure => + { + var regexOptions = RegexOptions.Compiled | RegexOptions.IgnoreCase; + var isTheLastParameter = new Regex(@$"[?|&]{name}=[^&]+$", regexOptions); + var isTheFistParamaeterAndHasOtherParamaters = new Regex(@$"[?]{name}=[^&]+[&]", regexOptions); + var isTheMiddleParameter = new Regex(@$"[&]{name}=[^&]+[&]", regexOptions); + + var newUri = configure.RequestUri.ToString() + .ReplaceRegex(isTheLastParameter, string.Empty) + .ReplaceRegex(isTheFistParamaeterAndHasOtherParamaters, "?") + .ReplaceRegex(isTheMiddleParameter, "&"); + + configure.RequestUri = new Uri(newUri, UriKind.Relative); + }); + + return requestBuilder; + } + + private static string ReplaceRegex(this string value, Regex regex, string replacement) + { + return regex.Replace(value, replacement); + } } } diff --git a/tests/UnitTests/Acheve.TestHost/Routing/RequestBuilderExtensionsTests.cs b/tests/UnitTests/Acheve.TestHost/Routing/RequestBuilderExtensionsTests.cs new file mode 100644 index 0000000..bfdcb4e --- /dev/null +++ b/tests/UnitTests/Acheve.TestHost/Routing/RequestBuilderExtensionsTests.cs @@ -0,0 +1,159 @@ +using FluentAssertions; +using Microsoft.AspNetCore.TestHost; +using System; +using System.Text.Json; +using System.Threading.Tasks; +using UnitTests.Acheve.TestHost.Builders; +using Xunit; + +namespace UnitTests.Acheve.TestHost.Routing +{ + public class RequestBuilderExtensionsTests + { + public const string BASE_PATH = "api/values/"; + + [Fact] + public async Task Create_request_and_add_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var id = new Random().Next(1, 100); + + var request = server.CreateHttpApiRequest(controller => controller.GetParameterFromRequestQuery()) + .AddQueryParameter(nameof(id), id); + + var responseMessage = await request.GetAsync(); + + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.Content.ReadAsStringAsync(); + + response.Should().Be(id.ToString()); + } + + [Fact] + public async Task Create_request_and_add_additional_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var id1 = new Random().Next(1, 100); + var id2 = new Random().Next(1, 100); + + var request = server.CreateHttpApiRequest(controller => controller.GetAdditionalParameterFromRequestQuery(id1)) + .AddQueryParameter(nameof(id2), id2); + + var responseMessage = await request.GetAsync(); + + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.Content.ReadAsStringAsync(); + + response.Should().Be(JsonSerializer.Serialize(new { id1 = id1.ToString(), id2 = id2.ToString() })); + } + + [Fact] + public async Task Create_request_and_add_parameter_when_you_have_path_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var id1 = new Random().Next(1, 100); + var id2 = new Random().Next(1, 100); + + var request = server.CreateHttpApiRequest(controller => controller.GetAdditionalParameterFromRequestQueryAndPath(id1)) + .AddQueryParameter(nameof(id2), id2); + + var responseMessage = await request.GetAsync(); + + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.Content.ReadAsStringAsync(); + + response.Should().Be(JsonSerializer.Serialize(new { id1 = id1.ToString(), id2 = id2.ToString() })); + } + + [Fact] + public void Remove_parameter_when_you_have_one_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + const string PARAMETER_TO_REMOVE = "parameter1"; + const string URL = BASE_PATH + "?" + PARAMETER_TO_REMOVE + "=valueParameter1"; + + var requestBuilder = server.CreateRequest(URL) + .RemoveQueryParameter(PARAMETER_TO_REMOVE); + + var requestUrl = requestBuilder.GetRequest().RequestUri.ToString(); + + const string EXPECTED_URL = BASE_PATH; + requestUrl.Should().Be(EXPECTED_URL); + } + + [Fact] + public void Remove_last_parameter_when_you_have_two_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + const string PARAMETER_TO_REMOVE = "parameter2"; + const string URL_FIRST_PART = "?parameter1=valueParameter1"; + const string URL = BASE_PATH + URL_FIRST_PART + "&" + PARAMETER_TO_REMOVE + "=valueParameter2"; + + var requestBuilder = server.CreateRequest(URL) + .RemoveQueryParameter(PARAMETER_TO_REMOVE); + + var requestUrl = requestBuilder.GetRequest().RequestUri.ToString(); + + const string EXPECTED_URL = BASE_PATH + URL_FIRST_PART; + requestUrl.Should().Be(EXPECTED_URL); + } + + [Fact] + public void Remove_first_parameter_when_you_have_two_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + const string PARAMETER_TO_REMOVE = "parameter1"; + const string URL_LAST_PART = "parameter2=valueParameter2"; + const string URL = BASE_PATH + "?" + PARAMETER_TO_REMOVE + "=valueParameter1&" + URL_LAST_PART; + + var requestBuilder = server.CreateRequest(URL) + .RemoveQueryParameter(PARAMETER_TO_REMOVE); + + var requestUrl = requestBuilder.GetRequest().RequestUri.ToString(); + + const string EXPECTED_URL = BASE_PATH + "?" + URL_LAST_PART; + requestUrl.Should().Be(EXPECTED_URL); + } + + [Theory] + [InlineData("?parameter1=valueParameter1&", "parameter2=valueParameter2")] + [InlineData("?parameter1=valueParameter1&", "parameter2=valueParameter2¶meter3=valueParametere")] + [InlineData("?parameter1=valueParameter1¶meter2=valueParameter2&", "parameter3=valueParameter3")] + [InlineData("?parameter1=valueParameter1¶meter2=valueParameter2&", "parameter3=valueParameter3¶meter4=valueParameter4")] + public void Remove_parameter_when_is_beetween_parameters(string urlFirstPart, string urlLastPart) + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + const string PARAMETER_TO_REMOVE = "parameterA"; + string url = BASE_PATH + urlFirstPart + PARAMETER_TO_REMOVE + "=valueParameterA&" + urlLastPart; + + var requestBuilder = server.CreateRequest(url) + .RemoveQueryParameter(PARAMETER_TO_REMOVE); + + var requestUrl = requestBuilder.GetRequest().RequestUri.ToString(); + + string expectedUrl = BASE_PATH + urlFirstPart + urlLastPart; + requestUrl.Should().Be(expectedUrl); + } + } +} diff --git a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs index 52224f7..bff7098 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs @@ -1611,68 +1611,6 @@ public async Task create_request_supporting_template_with_serveral_colon() response.Should().NotBeNull().And.Be($"{param1}/{param2}"); } - [Fact] - public async Task create_request_and_add_parameter() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); - - var id = new Random().Next(1, 100); - - var request = server.CreateHttpApiRequest(controller => controller.GetParameterFromRequestQuery()) - .AddQueryParameter(nameof(id), id); - - var responseMessage = await request.GetAsync(); - - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.Content.ReadAsStringAsync(); - - response.Should().Be(id.ToString()); - } - - [Fact] - public async Task create_request_and_add_additional_parameter() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); - - var id1 = new Random().Next(1, 100); - var id2 = new Random().Next(1, 100); - - var request = server.CreateHttpApiRequest(controller => controller.GetAdditionalParameterFromRequestQuery(id1)) - .AddQueryParameter(nameof(id2), id2); - - var responseMessage = await request.GetAsync(); - - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.Content.ReadAsStringAsync(); - - response.Should().Be(JsonSerializer.Serialize(new { id1 = id1.ToString(), id2 = id2.ToString() })); - } - - [Fact] - public async Task create_request_and_add_parameter_when_you_have_path_parameter() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); - - var id1 = new Random().Next(1, 100); - var id2 = new Random().Next(1, 100); - - var request = server.CreateHttpApiRequest(controller => controller.GetAdditionalParameterFromRequestQueryAndPath(id1)) - .AddQueryParameter(nameof(id2), id2); - - var responseMessage = await request.GetAsync(); - - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.Content.ReadAsStringAsync(); - - response.Should().Be(JsonSerializer.Serialize(new { id1 = id1.ToString(), id2 = id2.ToString() })); - } - [Fact] public async Task create_request_from_implemented_abstract_method() { From c2f870d08bc23fd15a49a0ff777bb912a2262f79 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Wed, 21 Sep 2022 14:59:31 +0200 Subject: [PATCH 04/23] CancellationTOken always is a query paramater (#76) --- .../Routing/TestServerAction.cs | 8 +++- .../Routing/Builders/ValuesV5Controller.cs | 23 +++++++++++ .../Routing/TestServerExtensionsTests.cs | 39 +++++++++++++++++++ 3 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/Acheve.TestHost/Routing/TestServerAction.cs b/src/Acheve.TestHost/Routing/TestServerAction.cs index 80234da..c51d31a 100644 --- a/src/Acheve.TestHost/Routing/TestServerAction.cs +++ b/src/Acheve.TestHost/Routing/TestServerAction.cs @@ -27,7 +27,7 @@ public void AddArgument(int order, Expression expression, bool activeBodyApiCont var isFromHeader = argument.GetCustomAttributes().Any(); var isFromRoute = argument.GetCustomAttributes().Any(); - bool isPrimitive = argument.ParameterType.IsPrimitive || argument.ParameterType.Equals(typeof(string)); + bool isPrimitive = argument.ParameterType.IsPrimitiveType(); bool hasNoAttributes = !isFromBody && !isFromForm && !isFromHeader && !isFromRoute; if (activeBodyApiController && hasNoAttributes && !isPrimitive) @@ -35,6 +35,12 @@ public void AddArgument(int order, Expression expression, bool activeBodyApiCont isFromBody = true; } + var isCancellationToken = argument.ParameterType == typeof(System.Threading.CancellationToken); + if (isCancellationToken) + { + isFromBody = isFromForm = isFromHeader = false; + } + if (!ArgumentValues.ContainsKey(order)) { if (IsNullable(argument.ParameterType)) diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs b/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs index d5df147..abdda17 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using System.Threading; namespace UnitTests.Acheve.TestHost.Builders { @@ -44,5 +45,27 @@ public IActionResult Patch1(int id, Pagination pagination1) return Ok(); } + + [HttpGet(nameof(GetWithCancellationToken))] + public ActionResult GetWithCancellationToken([FromQuery] string value, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return BadRequest(); + } + + return Ok(value); + } + + [HttpPost(nameof(PostWithCancellationToken))] + public ActionResult PostWithCancellationToken([FromBody] string value, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return BadRequest(); + } + + return Ok(value); + } } } diff --git a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs index bff7098..34c92fb 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs @@ -7,6 +7,7 @@ using System.Net; using System.Net.Http; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using UnitTests.Acheve.TestHost.Builders; using UnitTests.Acheve.TestHost.Routing.Models; @@ -1676,6 +1677,44 @@ public async Task Create_request_with_list_parameter() response.Should().BeEquivalentTo(param); } + [Fact] + public async Task Create_get_request_with_cancellation_token_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var param = "one"; + var source = new CancellationTokenSource(); + var token = source.Token; + + var request = server.CreateHttpApiRequest(controller => controller.GetWithCancellationToken(param, token)); + var responseMessage = await request.GetAsync(); + + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.Content.ReadAsStringAsync(); + response.Should().Be(param); + } + + [Fact] + public async Task Create_post_request_with_cancellation_token_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var param = "one"; + var source = new CancellationTokenSource(); + var token = source.Token; + + var request = server.CreateHttpApiRequest(controller => controller.PostWithCancellationToken(param, token)); + var responseMessage = await request.PostAsync(); + + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.Content.ReadAsStringAsync(); + response.Should().Be(param); + } + private class PrivateNonControllerClass { public int SomeAction() From 706ab470e3b80523e123dfbb2975d35fbd5085d5 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Wed, 21 Sep 2022 15:01:24 +0200 Subject: [PATCH 05/23] Version 3.4.0 (#77) --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 92fa907..2d9ff8f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ - 3.3.0 + 3.4.0 Apache-2.0 http://github.com/xabaril/Acheve.TestHost From 387b500514c54439d76e5c5f689d18558b5e47d4 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Tue, 4 Oct 2022 16:43:28 +0200 Subject: [PATCH 06/23] Action convection name (#79) * Action convection name --- .../DefaultConventionalTokenizer.cs | 14 +-- .../Routing/Builders/BugsController.cs | 7 ++ .../Routing/TestServerExtensionsTests.cs | 89 +++++++++++-------- 3 files changed, 67 insertions(+), 43 deletions(-) diff --git a/src/Acheve.TestHost/Routing/Tokenizers/DefaultConventionalTokenizer.cs b/src/Acheve.TestHost/Routing/Tokenizers/DefaultConventionalTokenizer.cs index dc6ef6b..3bc03c3 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/DefaultConventionalTokenizer.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/DefaultConventionalTokenizer.cs @@ -1,6 +1,4 @@ -using System; - -namespace Acheve.TestHost.Routing.Tokenizers +namespace Acheve.TestHost.Routing.Tokenizers { class DefaultConventionalTokenizer : ITokenizer @@ -11,14 +9,20 @@ public void AddTokens(TestServerAction action, TestServerTokenColle const string ControllerTypeNameSuffix = "Controller"; const string controller_key = "controller"; - if (!tokens.ContainsToken(controller_key)) { var controllerName = typeof(TController).Name - .Replace(ControllerTypeNameSuffix, String.Empty); + .Replace(ControllerTypeNameSuffix, string.Empty) + .ToLower(); tokens.AddToken(controller_key, controllerName, isConventional: true); } + + const string action_key = "action"; + if (!tokens.ContainsToken(action_key)) + { + tokens.AddToken(action_key, action.MethodInfo.Name.ToLower(), isConventional: true); + } } } } diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs b/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs index c70603f..3cdd7a6 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs @@ -70,5 +70,12 @@ public ActionResult> GetWithListParam([FromQuery] IEnumerabl { return Ok(param); } + + [HttpGet] + [Route("[action]")] + public ActionResult> ActionNameInRoute() + { + return Ok(); + } } } \ No newline at end of file diff --git a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs index 34c92fb..7939294 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs @@ -19,9 +19,7 @@ public class create_api_request_should { public const string BASE_PATH = "api/"; public const string BASE_PATH_BUGS = BASE_PATH + "bugs"; - public const string BASE_PATH_BUGS_CONTROLLER_NAME = BASE_PATH + "Bugs"; public const string BASE_PATH_VALUES = BASE_PATH + "values"; - public const string BASE_PATH_VALUES_CONTROLLER_NAME = BASE_PATH + "Values"; [Fact] public void throw_when_controller_is_not_a_valid_controller() @@ -76,7 +74,7 @@ public void create_valid_request_for_primitive_parameter_action() controller => controller.Get(0)); request.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}?id=0"); + .Should().Be($"{BASE_PATH_VALUES}?id=0"); } [Fact] @@ -90,7 +88,7 @@ public void create_valid_request_for_string_as_primitive_parameter_tokenizer_act controller => controller.GetStringAsParameter("unai")); request.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/stringasprimitive?value=unai"); + .Should().Be($"{BASE_PATH_VALUES}/stringasprimitive?value=unai"); } [Fact] @@ -104,7 +102,7 @@ public void create_valid_request_for_string_as_primitive_parameter_tokenizer_act controller => controller.GetStringAsParameter("Uppercase")); request.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/stringasprimitive?value=Uppercase"); + .Should().Be($"{BASE_PATH_VALUES}/stringasprimitive?value=Uppercase"); } [Fact] @@ -118,7 +116,7 @@ public void create_valid_request_for_string_as_decimal_parameter_tokenizer_actio controller => controller.GetDecimalAsParameter(2m)); request.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/decimalasprimitive?value=2"); + .Should().Be($"{BASE_PATH_VALUES}/decimalasprimitive?value=2"); } [Fact] @@ -155,16 +153,16 @@ public void create_valid_request_using_verbs_templates() controller => controller.Delete2(3)); requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname?id=0"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname?id=0"); requestPut.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname?id=1"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname?id=1"); requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname?id=2"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname?id=2"); requestDelete.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname?id=3"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname?id=3"); } [Fact] @@ -187,16 +185,16 @@ public void create_valid_request_using_verbs_and_parameters() controller => controller.Delete3(3)); requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname/0"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/0"); requestPut.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname/1"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/1"); requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname/2"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/2"); requestDelete.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname/3"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/3"); } [Fact] @@ -219,16 +217,16 @@ public void create_valid_request_using_verbs_and_extra_parameters() controller => controller.Delete4(3), new { version = "v1" }); requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname/v1/0"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/v1/0"); requestPut.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname/v1/1"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/v1/1"); requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname/v1/2"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/v1/2"); requestDelete.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname/v1/3"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/v1/3"); } [Fact] @@ -242,7 +240,7 @@ public void create_valid_request_using_verbs_and_extra_parameters_with_different controller => controller.Get4(0), new { Version = "v1" }); requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname/v1/0"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/v1/0"); } [Fact] @@ -265,16 +263,16 @@ public void create_valid_request_using_route_templates() controller => controller.Delete5(0)); requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overrideroutetemplatemethodname?id=0"); + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname?id=0"); requestPut.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overrideroutetemplatemethodname?id=0"); + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname?id=0"); requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overrideroutetemplatemethodname?id=0"); + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname?id=0"); requestDelete.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overrideroutetemplatemethodname?id=0"); + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname?id=0"); } [Fact] @@ -297,16 +295,16 @@ public void create_valid_request_using_route_templates_with_parameters_and_extra controller => controller.Delete6(3), new { version = "v1" }); requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overrideroutetemplatemethodname/v1/0"); + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/v1/0"); requestPut.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overrideroutetemplatemethodname/v1/1"); + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/v1/1"); requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overrideroutetemplatemethodname/v1/2"); + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/v1/2"); requestDelete.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overrideroutetemplatemethodname/v1/3"); + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/v1/3"); } [Fact] @@ -329,16 +327,16 @@ public void create_valid_request_using_route_templates_and_parameters_with_route controller => controller.Delete7(3)); requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overrideroutetemplatemethodname/0"); + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/0"); requestPut.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overrideroutetemplatemethodname/1"); + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/1"); requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overrideroutetemplatemethodname/2"); + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/2"); requestDelete.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overrideroutetemplatemethodname/3"); + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/3"); } [Fact] @@ -361,16 +359,16 @@ public void create_valid_request_using_verbs_and_parameters_with_route_constrain controller => controller.Delete8(3)); requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname/0"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/0"); requestPut.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname/1"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/1"); requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname/2"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/2"); requestDelete.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES_CONTROLLER_NAME}/overridemethodname/3"); + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/3"); } [Fact] @@ -1273,7 +1271,7 @@ public void create_request_supporting_guid_types_on_parameters_and_numbes_on_par contentOptions: new NotIncludeContent()); request.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_BUGS_CONTROLLER_NAME}/prm1/{guidValue}"); + .Should().Be($"{BASE_PATH_BUGS}/prm1/{guidValue}"); } [Fact] @@ -1395,7 +1393,7 @@ public void create_valid_request_supporting_underdash_on_router_params() contentOptions: new NotIncludeContent()); request.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_BUGS_CONTROLLER_NAME}/{guid}/10"); + .Should().Be($"{BASE_PATH_BUGS}/{guid}/10"); } [Fact] @@ -1564,7 +1562,7 @@ public void create_request_supporting_send_method_on_client_http() contentOptions: new NotIncludeContent()); request.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_BUGS_CONTROLLER_NAME}/prm1/{guid}"); + .Should().Be($"{BASE_PATH_BUGS}/prm1/{guid}"); } [Fact] @@ -1715,6 +1713,21 @@ public async Task Create_post_request_with_cancellation_token_parameter() response.Should().Be(param); } + [Fact] + public async Task Create_request_with_action_name_in_route() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var request = server.CreateHttpApiRequest(controller => controller.ActionNameInRoute()); + + request.GetConfiguredAddress().Should().Be($"{BASE_PATH_BUGS}/{nameof(BugsController.ActionNameInRoute)}".ToLower()); + + var responseMessage = await request.GetAsync(); + responseMessage.EnsureSuccessStatusCode(); + } + private class PrivateNonControllerClass { public int SomeAction() From 994cb8fd607cff2a3fb7b7e5f7c6870e3b737cf1 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Wed, 22 Feb 2023 18:42:40 +0100 Subject: [PATCH 07/23] Create get request when it has an object with some list --- src/Acheve.TestHost/Acheve.TestHost.csproj | 5 - .../ComplexParameterActionTokenizer.cs | 81 +- .../EnumerableParameterActionTokenizer.cs | 46 + .../PrimitiveParameterActionTokenizer.cs | 80 +- .../Routing/Tokenizers/TypeExtensions.cs | 37 +- src/Acheve.TestHost/Routing/UriDiscover.cs | 133 +- .../Routing/Builders/BugsController.cs | 140 +- .../Routing/Models/ParamWithList.cs | 8 + .../Routing/TestServerExtensionsTests.cs | 2666 +++++++++-------- 9 files changed, 1641 insertions(+), 1555 deletions(-) create mode 100644 src/Acheve.TestHost/Routing/Tokenizers/EnumerableParameterActionTokenizer.cs create mode 100644 tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs diff --git a/src/Acheve.TestHost/Acheve.TestHost.csproj b/src/Acheve.TestHost/Acheve.TestHost.csproj index e69405c..40fed40 100644 --- a/src/Acheve.TestHost/Acheve.TestHost.csproj +++ b/src/Acheve.TestHost/Acheve.TestHost.csproj @@ -3,11 +3,6 @@ $(NetCoreTargetVersion) - - - - - diff --git a/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs b/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs index 2612011..d1b5520 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs @@ -1,58 +1,69 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using System; +using System.Collections; using System.Linq; using System.Reflection; -namespace Acheve.TestHost.Routing.Tokenizers +namespace Acheve.TestHost.Routing.Tokenizers; + +class ComplexParameterActionTokenizer + : ITokenizer { - class ComplexParameterActionTokenizer - : ITokenizer + public void AddTokens(TestServerAction action, TestServerTokenCollection tokens) where TController : class { - public void AddTokens(TestServerAction action, TestServerTokenCollection tokens) where TController : class + var parameters = action.MethodInfo.GetParameters(); + + for (int i = 0; i < parameters.Length; i++) { - var parameters = action.MethodInfo.GetParameters(); + var type = parameters[i].ParameterType; + var instance = action.ArgumentValues.Any(x => x.Key == i) ? action.ArgumentValues[i].Instance : null; - for (int i = 0; i < parameters.Length; i++) + if (instance == null || type.IsPrimitiveType() || IgnoreBind(parameters[i])) { - var type = parameters[i].ParameterType; - var instance = action.ArgumentValues.Any(x => x.Key == i) ? action.ArgumentValues[i].Instance : null; + continue; + } - if (instance != null && !type.IsPrimitiveType()) + foreach (var property in type.GetProperties()) + { + var value = property.GetValue(instance); + if (value == null) + { + continue; + } + + var tokenName = property.Name.ToLowerInvariant(); + string tokenValue; + if (property.PropertyType.IsEnumerable() && property.PropertyType.GetEnumerableElementType().IsPrimitiveType()) + { + tokenValue = EnumerableParameterActionTokenizer.GetTokenValue((IList)value, tokenName); + } + else { - if (!IgnoreBind(parameters[i])) - { - foreach (var property in type.GetProperties()) - { - var tokenName = property.Name.ToLowerInvariant(); - var value = property.GetValue(instance); - - if (value != null) - { - tokens.AddToken(tokenName, value.ToString(), isConventional: false); - } - } - } + tokenValue = value.ToString(); } + + tokens.AddToken(tokenName, tokenValue, isConventional: false); } } + } - bool IgnoreBind(ParameterInfo parameter) - { - var attributes = parameter.GetCustomAttributes(false); + static bool IgnoreBind(ParameterInfo parameter) + { + var attributes = parameter.GetCustomAttributes(false); - foreach (var attribute in attributes) + foreach (var attribute in attributes) + { + switch (attribute) { - switch (attribute) - { - case FromBodyAttribute body: return true; - case FromFormAttribute form: return true; - case BindNeverAttribute bind: return true; - default: continue; - } + case FromBodyAttribute: + case FromFormAttribute: + case BindNeverAttribute: + return true; + default: continue; } - - return false; } + + return false; } } diff --git a/src/Acheve.TestHost/Routing/Tokenizers/EnumerableParameterActionTokenizer.cs b/src/Acheve.TestHost/Routing/Tokenizers/EnumerableParameterActionTokenizer.cs new file mode 100644 index 0000000..f3dc9fa --- /dev/null +++ b/src/Acheve.TestHost/Routing/Tokenizers/EnumerableParameterActionTokenizer.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Acheve.TestHost.Routing.Tokenizers; + +internal class EnumerableParameterActionTokenizer + : ITokenizer +{ + public void AddTokens(TestServerAction action, TestServerTokenCollection tokens) + where TController : class + { + var parameters = action.MethodInfo.GetParameters(); + + for (var i = 0; i < parameters.Length; i++) + { + var parameterType = parameters[i].ParameterType; + if (!parameterType.IsEnumerable() || !parameterType.GetEnumerableElementType().IsPrimitiveType()) + { + continue; + } + + var arrayValues = (Array)action.ArgumentValues[i].Instance; + if (arrayValues == null || arrayValues.Length == 0) + { + continue; + } + + var tokenName = parameters[i].Name.ToLowerInvariant(); + var tokenValue = GetTokenValue(arrayValues, tokenName); + tokens.AddToken(tokenName, tokenValue, isConventional: false); + } + } + + public static string GetTokenValue(IList array, string tokenName) + { + var list = new List(); + + foreach (var element in array) + { + list.Add(element.ToString()); + } + + return string.Join($"&{tokenName}=", list); + } +} \ No newline at end of file diff --git a/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs b/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs index 4736442..5f637cd 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs @@ -1,74 +1,46 @@ using Microsoft.AspNetCore.Mvc; using System; -using System.Collections; -using System.Collections.Generic; using System.Linq; using System.Reflection; -namespace Acheve.TestHost.Routing.Tokenizers +namespace Acheve.TestHost.Routing.Tokenizers; + +internal class PrimitiveParameterActionTokenizer + : ITokenizer { - internal class PrimitiveParameterActionTokenizer - : ITokenizer + public void AddTokens(TestServerAction action, TestServerTokenCollection tokens) + where TController : class { - public void AddTokens(TestServerAction action, TestServerTokenCollection tokens) - where TController : class - { - var parameters = action.MethodInfo.GetParameters(); + ParameterInfo[] parameters = action.MethodInfo.GetParameters(); - for (var i = 0; i < parameters.Length; i++) + for (int i = 0; i < parameters.Length; i++) + { + if (IgnoreHeader(parameters[i])) { - if (!IgnoreHeader(parameters[i])) - { - var tokenName = parameters[i].Name.ToLowerInvariant(); - var parameterType = parameters[i].ParameterType; - - if (parameterType.IsPrimitiveType()) - { - var tokenValue = action.ArgumentValues.Any(x => x.Key == i) ? action.ArgumentValues[i].Instance : null; - - if (tokenValue != null) - { - tokens.AddToken(tokenName, tokenValue.ToString(), isConventional: false); - } - } - else if (parameterType.IsEnumerable() - && parameterType.GetEnumerableElementType().IsPrimitiveType()) - { - var arrayValues = (Array)action.ArgumentValues[i].Instance; - - if (arrayValues != null - && arrayValues.Length != 0) - { - var tokenValue = GetTokenValue(arrayValues, tokenName); - tokens.AddToken(tokenName, tokenValue, isConventional: false); - } - } - } + continue; } - } - - private bool IgnoreHeader(ParameterInfo parameter) - { - var attributes = parameter.GetCustomAttributes(false); - if (attributes.Any(a => a.GetType() == typeof(FromHeaderAttribute))) + Type parameterType = parameters[i].ParameterType; + if (!parameterType.IsPrimitiveType()) { - return true; + continue; } - return false; - } - - private string GetTokenValue(Array array, string tokenName) - { - var list = new List(); - - foreach (var element in array) + object tokenValue = action.ArgumentValues.Any(x => x.Key == i) ? action.ArgumentValues[i].Instance : null; + if (tokenValue == null) { - list.Add(element.ToString()); + continue; } - return string.Join($"&{tokenName}=", list); + string tokenName = parameters[i].Name.ToLowerInvariant(); + tokens.AddToken(tokenName, tokenValue.ToString(), isConventional: false); } } + + private static bool IgnoreHeader(ParameterInfo parameter) + { + object[] attributes = parameter.GetCustomAttributes(false); + + return attributes.Any(a => a.GetType() == typeof(FromHeaderAttribute)); + } } \ No newline at end of file diff --git a/src/Acheve.TestHost/Routing/Tokenizers/TypeExtensions.cs b/src/Acheve.TestHost/Routing/Tokenizers/TypeExtensions.cs index bb95861..91836e9 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/TypeExtensions.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/TypeExtensions.cs @@ -1,28 +1,27 @@ using System.Collections; using System.Linq; -namespace System +namespace System; + +internal static class TypeExtensions { - internal static class TypeExtensions + internal static bool IsPrimitiveType(this Type typeToInspect) { - internal static bool IsPrimitiveType(this Type typeToInspect) - { - var type = Nullable.GetUnderlyingType(typeToInspect) ?? typeToInspect; + var type = Nullable.GetUnderlyingType(typeToInspect) ?? typeToInspect; + + return type.IsPrimitive + || type == typeof(string) + || type == typeof(decimal) + || type == typeof(Guid); + } - return type.IsPrimitive - || type == typeof(string) - || type == typeof(decimal) - || type == typeof(Guid); - } + internal static bool IsEnumerable(this Type typeToInspect) + { + return typeof(IEnumerable).IsAssignableFrom(typeToInspect) && typeToInspect != typeof(string); + } - internal static bool IsEnumerable(this Type typeToInspect) - { - return typeof(IEnumerable).IsAssignableFrom(typeToInspect); - } - - internal static Type GetEnumerableElementType(this Type typeToInspect) - { - return typeToInspect.GetElementType() ?? typeToInspect.GetGenericArguments().FirstOrDefault(); - } + internal static Type GetEnumerableElementType(this Type typeToInspect) + { + return typeToInspect.GetElementType() ?? typeToInspect.GetGenericArguments().FirstOrDefault(); } } \ No newline at end of file diff --git a/src/Acheve.TestHost/Routing/UriDiscover.cs b/src/Acheve.TestHost/Routing/UriDiscover.cs index e705716..97b3ee2 100644 --- a/src/Acheve.TestHost/Routing/UriDiscover.cs +++ b/src/Acheve.TestHost/Routing/UriDiscover.cs @@ -3,96 +3,91 @@ using System.Collections.Generic; using System.Linq; -namespace Acheve.TestHost.Routing +namespace Acheve.TestHost.Routing; + +static class UriDiscover { - static class UriDiscover + static readonly IEnumerable _tokenizers = new List() { - static IEnumerable _tokenizers; - - static UriDiscover() - { - _tokenizers = new List() - { - new PrimitiveParameterActionTokenizer(), - new ComplexParameterActionTokenizer(), - new DefaultConventionalTokenizer() - }; - } - - public static string Discover(TestServerAction action,object tokenValues) - where TController:class - { - //at this moment only the first route is considered.. + new PrimitiveParameterActionTokenizer(), + new EnumerableParameterActionTokenizer(), + new ComplexParameterActionTokenizer(), + new DefaultConventionalTokenizer() + }; + + public static string Discover(TestServerAction action, object tokenValues) + where TController : class + { + //at this moment only the first route is considered.. - var testServerTokens = AddTokens(action, tokenValues); + var testServerTokens = AddTokens(action, tokenValues); - var controllerTemplate = new AttributeControllerRouteTemplates() - .GetTemplates(action, testServerTokens) - .FirstOrDefault(); + var controllerTemplate = new AttributeControllerRouteTemplates() + .GetTemplates(action, testServerTokens) + .FirstOrDefault(); - var verbsTemplate = new AttributeVerbsTemplate() - .GetTemplates(action, testServerTokens) - .FirstOrDefault(); + var verbsTemplate = new AttributeVerbsTemplate() + .GetTemplates(action, testServerTokens) + .FirstOrDefault(); - var routeTemplate = new AttributeRouteTemplates() - .GetTemplates(action, testServerTokens) - .FirstOrDefault(); + var routeTemplate = new AttributeRouteTemplates() + .GetTemplates(action, testServerTokens) + .FirstOrDefault(); - var queryStringTemplate = new QueryStringTemplate() - .GetTemplates(action, testServerTokens) - .FirstOrDefault(); + var queryStringTemplate = new QueryStringTemplate() + .GetTemplates(action, testServerTokens) + .FirstOrDefault(); - var template = (verbsTemplate ?? routeTemplate); + var template = (verbsTemplate ?? routeTemplate); - if (template != null) + if (template != null) + { + if (IsTildeOverride(template, out string overrideTemplate)) { - if (IsTildeOverride(template, out string overrideTemplate)) - { - return $"{overrideTemplate}{queryStringTemplate}"; - } - else - { - return $"{controllerTemplate}/{template}{queryStringTemplate}"; - } + return $"{overrideTemplate}{queryStringTemplate}"; } - - return $"{controllerTemplate}{queryStringTemplate}"; - } - - static TestServerTokenCollection AddTokens(TestServerAction action, object tokenValues) - where TController : class - { - var dictionaryTokenValues = new Dictionary(); - - if (tokenValues != null) + else { - dictionaryTokenValues = tokenValues.GetType() - .GetProperties() - .ToDictionary(p => p.Name.ToLowerInvariant(), p => p.GetValue(tokenValues).ToString()); + return $"{controllerTemplate}/{template}{queryStringTemplate}"; } + } - var testServerTokens = TestServerTokenCollection.FromDictionary(dictionaryTokenValues); + return $"{controllerTemplate}{queryStringTemplate}"; + } - foreach (var tokeniker in _tokenizers) - { - tokeniker.AddTokens(action, testServerTokens); - } + static TestServerTokenCollection AddTokens(TestServerAction action, object tokenValues) + where TController : class + { + var dictionaryTokenValues = new Dictionary(); - return testServerTokens; + if (tokenValues != null) + { + dictionaryTokenValues = tokenValues.GetType() + .GetProperties() + .ToDictionary(p => p.Name.ToLowerInvariant(), p => p.GetValue(tokenValues).ToString()); } - static bool IsTildeOverride(string template, out string overrideTemplate) + + var testServerTokens = TestServerTokenCollection.FromDictionary(dictionaryTokenValues); + + foreach (var tokeniker in _tokenizers) { - const string TILDE = "~"; + tokeniker.AddTokens(action, testServerTokens); + } - overrideTemplate = null; - var isTildeOverride = template.StartsWith(TILDE); + return testServerTokens; + } + static bool IsTildeOverride(string template, out string overrideTemplate) + { + const string TILDE = "~"; - if (isTildeOverride) - { - overrideTemplate = template.Substring(2); // remove ~/ - } + overrideTemplate = null; + var isTildeOverride = template.StartsWith(TILDE); - return isTildeOverride; + if (isTildeOverride) + { + overrideTemplate = template.Substring(2); // remove ~/ } + + return isTildeOverride; } } diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs b/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs index 3cdd7a6..a6c62c3 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs @@ -1,81 +1,97 @@ using Microsoft.AspNetCore.Mvc; using System; -using System.Collections; using System.Collections.Generic; using UnitTests.Acheve.TestHost.Routing.Models; -namespace UnitTests.Acheve.TestHost.Builders +namespace UnitTests.Acheve.TestHost.Builders; + +[Route("api/[controller]")] +[ApiController] +public class BugsController + : ControllerBase { - [Route("api/[controller]")] - [ApiController] - public class BugsController - : ControllerBase + [HttpGet("{param1}/{param2}")] + public IActionResult GuidSupport(string param1, Guid param2) + { + return Ok(); + } + + [HttpGet("{param_1:guid}/{param_2:int}")] + public IActionResult UnderDashSupport(Guid param_1, int param_2) { - [HttpGet("{param1}/{param2}")] - public IActionResult GuidSupport(string param1, Guid param2) - { - return Ok(); - } + return Ok(); + } - [HttpGet("{param_1:guid}/{param_2:int}")] - public IActionResult UnderDashSupport(Guid param_1, int param_2) - { - return Ok(); - } + [HttpGet("nullableQueryParams")] + public ActionResult NullableQueryParams(bool? param1, Guid? param2) + { + return Ok(new NullableQueryParamsResponse { Param1 = param1, Param2 = param2 }); + } - [HttpGet("nullableQueryParams")] - public ActionResult NullableQueryParams(bool? param1, Guid? param2) - { - return Ok(new NullableQueryParamsResponse { Param1 = param1, Param2 = param2 }); - } + [HttpGet("arrayGuid")] + public ActionResult GuidArraySupport([FromQuery] Guid[] param1) + { + return Ok(param1); + } + + [HttpGet("arrayInt")] + public ActionResult IntArraySupport([FromQuery] int[] param1) + { + return Ok(param1); + } + + [HttpGet("arrayString")] + public ActionResult StringArraySupport([FromQuery] string[] param1) + { + return Ok(param1); + } - [HttpGet("arrayGuid")] - public ActionResult GuidArraySupport([FromQuery] Guid[] param1) - { - return Ok(param1); - } + [HttpGet("arrayPerson")] + public ActionResult PersonArraySupport([FromQuery] Person[] param1) + { + return Ok(param1); + } - [HttpGet("arrayInt")] - public ActionResult IntArraySupport([FromQuery] int[] param1) - { - return Ok(param1); - } + [HttpPost("{test_id:guid}")] + public ActionResult AllowRouterAndBodyParams([FromRoute] Guid test_id, [FromBody] Person person) + { + return Ok(new RouterAndBodyParamsResponse { TestId = test_id, Person = person }); + } - [HttpGet("arrayString")] - public ActionResult StringArraySupport([FromQuery] string[] param1) - { - return Ok(param1); - } + [HttpGet("{param1:int:min(1)}/params/{param2:int:min(1)}")] + public ActionResult GetWithSeveralColon(int param1, int param2) + { + return Ok($"{param1}/{param2}"); + } - [HttpGet("arrayPerson")] - public ActionResult PersonArraySupport([FromQuery] Person[] param1) - { - return Ok(param1); - } + [HttpGet(nameof(GetWithListParam))] + public ActionResult> GetWithListParam([FromQuery] IEnumerable param) + { + return Ok(param); + } - [HttpPost("{test_id:guid}")] - public ActionResult AllowRouterAndBodyParams([FromRoute] Guid test_id, [FromBody] Person person) - { - return Ok(new RouterAndBodyParamsResponse { TestId = test_id, Person = person }); - } + [HttpGet] + [Route("[action]")] + public ActionResult> ActionNameInRoute() + { + return Ok(); + } - [HttpGet("{param1:int:min(1)}/params/{param2:int:min(1)}")] - public ActionResult GetWithSeveralColon(int param1, int param2) - { - return Ok($"{param1}/{param2}"); - } + [HttpGet(nameof(GetWithObjectWithList))] + public ActionResult GetWithObjectWithList([FromQuery] ParamWithList param) + { + return Ok(param); + } - [HttpGet(nameof(GetWithListParam))] - public ActionResult> GetWithListParam([FromQuery] IEnumerable param) - { - return Ok(param); - } + [HttpPost(nameof(PostWithListParam))] + public ActionResult> PostWithListParam([FromBody] IEnumerable param) + { + return Ok(param); + } - [HttpGet] - [Route("[action]")] - public ActionResult> ActionNameInRoute() - { - return Ok(); - } + [HttpPost(nameof(PostWithObjectWithList))] + public ActionResult PostWithObjectWithList([FromBody] ParamWithList param) + { + return Ok(param); } } \ No newline at end of file diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs b/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs new file mode 100644 index 0000000..2269bb5 --- /dev/null +++ b/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs @@ -0,0 +1,8 @@ +using System.Collections.Generic; + +namespace UnitTests.Acheve.TestHost.Routing.Models; + +public class ParamWithList +{ + public IEnumerable Values { get; set; } +} diff --git a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs index 7939294..4dea50a 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs @@ -13,1760 +13,1804 @@ using UnitTests.Acheve.TestHost.Routing.Models; using Xunit; -namespace UnitTests.Acheve.TestHost.Routing +namespace UnitTests.Acheve.TestHost.Routing; + +public class CreateApiRequestShould { - public class create_api_request_should + public const string BASE_PATH = "api/"; + public const string BASE_PATH_BUGS = BASE_PATH + "bugs"; + public const string BASE_PATH_VALUES = BASE_PATH + "values"; + + [Fact] + public void Throw_when_controller_is_not_a_valid_controller() { - public const string BASE_PATH = "api/"; - public const string BASE_PATH_BUGS = BASE_PATH + "bugs"; - public const string BASE_PATH_VALUES = BASE_PATH + "values"; + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void throw_when_controller_is_not_a_valid_controller() + Assert.Throws(() => + { + server.CreateHttpApiRequest(controller => controller.SomeAction()); + }); + Assert.Throws(() => + { + server.CreateHttpApiRequest(controller => controller.SomeAction()); + }); + Assert.Throws(() => + { + server.CreateHttpApiRequest(controller => controller.SomeAction()); + }); + Assert.Throws(() => { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + server.CreateHttpApiRequest(controller => controller.SomeAction()); + }); + Assert.Throws(() => + { + server.CreateHttpApiRequest(controller => controller.SomeAction()); + }); + } - Assert.Throws(() => - { - server.CreateHttpApiRequest(controller => controller.SomeAction()); - }); - Assert.Throws(() => - { - server.CreateHttpApiRequest(controller => controller.SomeAction()); - }); - Assert.Throws(() => - { - server.CreateHttpApiRequest(controller => controller.SomeAction()); - }); - Assert.Throws(() => - { - server.CreateHttpApiRequest(controller => controller.SomeAction()); - }); - Assert.Throws(() => - { - server.CreateHttpApiRequest(controller => controller.SomeAction()); - }); - } + [Fact] + public void Throw_when_the_action_selector_is_null() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void throw_when_the_action_selector_is_null() + Assert.Throws(() => { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + server.CreateHttpApiRequest(null); + }); + } - Assert.Throws(() => - { - server.CreateHttpApiRequest(null); - }); - } + [Fact] + public void Create_valid_request_for_primitive_parameter_action() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_for_primitive_parameter_action() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var request = server.CreateHttpApiRequest( + controller => controller.Get(0)); - var request = server.CreateHttpApiRequest( - controller => controller.Get(0)); + request.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}?id=0"); + } - request.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}?id=0"); - } + [Fact] + public void Create_valid_request_for_string_as_primitive_parameter_tokenizer_action() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_for_string_as_primitive_parameter_tokenizer_action() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var request = server.CreateHttpApiRequest( + controller => controller.GetStringAsParameter("unai")); - var request = server.CreateHttpApiRequest( - controller => controller.GetStringAsParameter("unai")); + request.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/stringasprimitive?value=unai"); + } - request.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/stringasprimitive?value=unai"); - } + [Fact] + public void Create_valid_request_for_string_as_primitive_parameter_tokenizer_action_with_case() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_for_string_as_primitive_parameter_tokenizer_action_with_case() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var request = server.CreateHttpApiRequest( + controller => controller.GetStringAsParameter("Uppercase")); - var request = server.CreateHttpApiRequest( - controller => controller.GetStringAsParameter("Uppercase")); + request.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/stringasprimitive?value=Uppercase"); + } - request.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/stringasprimitive?value=Uppercase"); - } + [Fact] + public void Create_valid_request_for_string_as_decimal_parameter_tokenizer_action() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_for_string_as_decimal_parameter_tokenizer_action() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var request = server.CreateHttpApiRequest( + controller => controller.GetDecimalAsParameter(2m)); - var request = server.CreateHttpApiRequest( - controller => controller.GetDecimalAsParameter(2m)); + request.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/decimalasprimitive?value=2"); + } - request.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/decimalasprimitive?value=2"); - } + [Fact] + public void Create_valid_request_for_conventional_action_with_extra_parameters() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_for_conventional_action_with_extra_parameters() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var request = server.CreateHttpApiRequest( + controller => controller.Get(0), new { Version = "v1" }); - var request = server.CreateHttpApiRequest( - controller => controller.Get(0), new { Version = "v1" }); + request.GetConfiguredAddress() + .Should().Be("api/v1/values?id=0"); + } - request.GetConfiguredAddress() - .Should().Be("api/v1/values?id=0"); - } + [Fact] + public void Create_valid_request_using_verbs_templates() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_verbs_templates() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var requestGet = server.CreateHttpApiRequest( + controller => controller.Get2(0)); - var requestGet = server.CreateHttpApiRequest( - controller => controller.Get2(0)); + var requestPut = server.CreateHttpApiRequest( + controller => controller.Put2(1)); - var requestPut = server.CreateHttpApiRequest( - controller => controller.Put2(1)); + var requestPost = server.CreateHttpApiRequest( + controller => controller.Post2(2)); - var requestPost = server.CreateHttpApiRequest( - controller => controller.Post2(2)); + var requestDelete = server.CreateHttpApiRequest( + controller => controller.Delete2(3)); - var requestDelete = server.CreateHttpApiRequest( - controller => controller.Delete2(3)); + requestGet.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname?id=0"); - requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname?id=0"); + requestPut.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname?id=1"); - requestPut.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname?id=1"); + requestPost.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname?id=2"); - requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname?id=2"); + requestDelete.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname?id=3"); + } - requestDelete.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname?id=3"); - } + [Fact] + public void Create_valid_request_using_verbs_and_parameters() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_verbs_and_parameters() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var requestGet = server.CreateHttpApiRequest( + controller => controller.Get3(0)); - var requestGet = server.CreateHttpApiRequest( - controller => controller.Get3(0)); + var requestPut = server.CreateHttpApiRequest( + controller => controller.Put3(1)); - var requestPut = server.CreateHttpApiRequest( - controller => controller.Put3(1)); + var requestPost = server.CreateHttpApiRequest( + controller => controller.Post3(2)); - var requestPost = server.CreateHttpApiRequest( - controller => controller.Post3(2)); + var requestDelete = server.CreateHttpApiRequest( + controller => controller.Delete3(3)); - var requestDelete = server.CreateHttpApiRequest( - controller => controller.Delete3(3)); + requestGet.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/0"); - requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/0"); + requestPut.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/1"); - requestPut.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/1"); + requestPost.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/2"); - requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/2"); + requestDelete.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/3"); + } - requestDelete.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/3"); - } + [Fact] + public void Create_valid_request_using_verbs_and_extra_parameters() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_verbs_and_extra_parameters() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var requestGet = server.CreateHttpApiRequest( + controller => controller.Get4(0), new { version = "v1" }); - var requestGet = server.CreateHttpApiRequest( - controller => controller.Get4(0), new { version = "v1" }); + var requestPut = server.CreateHttpApiRequest( + controller => controller.Put4(1), new { version = "v1" }); - var requestPut = server.CreateHttpApiRequest( - controller => controller.Put4(1), new { version = "v1" }); + var requestPost = server.CreateHttpApiRequest( + controller => controller.Post4(2), new { version = "v1" }); - var requestPost = server.CreateHttpApiRequest( - controller => controller.Post4(2), new { version = "v1" }); + var requestDelete = server.CreateHttpApiRequest( + controller => controller.Delete4(3), new { version = "v1" }); - var requestDelete = server.CreateHttpApiRequest( - controller => controller.Delete4(3), new { version = "v1" }); + requestGet.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/v1/0"); - requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/v1/0"); + requestPut.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/v1/1"); - requestPut.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/v1/1"); + requestPost.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/v1/2"); - requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/v1/2"); + requestDelete.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/v1/3"); + } - requestDelete.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/v1/3"); - } + [Fact] + public void Create_valid_request_using_verbs_and_extra_parameters_with_different_case() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_verbs_and_extra_parameters_with_different_case() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var requestGet = server.CreateHttpApiRequest( + controller => controller.Get4(0), new { Version = "v1" }); - var requestGet = server.CreateHttpApiRequest( - controller => controller.Get4(0), new { Version = "v1" }); + requestGet.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/v1/0"); + } - requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/v1/0"); - } + [Fact] + public void Create_valid_request_using_route_templates() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_route_templates() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var requestGet = server.CreateHttpApiRequest( + controller => controller.Get5(0)); - var requestGet = server.CreateHttpApiRequest( - controller => controller.Get5(0)); + var requestPut = server.CreateHttpApiRequest( + controller => controller.Put5(0)); - var requestPut = server.CreateHttpApiRequest( - controller => controller.Put5(0)); + var requestPost = server.CreateHttpApiRequest( + controller => controller.Post5(0)); - var requestPost = server.CreateHttpApiRequest( - controller => controller.Post5(0)); + var requestDelete = server.CreateHttpApiRequest( + controller => controller.Delete5(0)); - var requestDelete = server.CreateHttpApiRequest( - controller => controller.Delete5(0)); + requestGet.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname?id=0"); - requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname?id=0"); + requestPut.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname?id=0"); - requestPut.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname?id=0"); + requestPost.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname?id=0"); - requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname?id=0"); + requestDelete.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname?id=0"); + } - requestDelete.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname?id=0"); - } + [Fact] + public void Create_valid_request_using_route_templates_with_parameters_and_extra_parameters() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_route_templates_with_parameters_and_extra_parameters() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var requestGet = server.CreateHttpApiRequest( + controller => controller.Get6(0), new { version = "v1" }); - var requestGet = server.CreateHttpApiRequest( - controller => controller.Get6(0), new { version = "v1" }); + var requestPut = server.CreateHttpApiRequest( + controller => controller.Put6(1), new { version = "v1" }); - var requestPut = server.CreateHttpApiRequest( - controller => controller.Put6(1), new { version = "v1" }); + var requestPost = server.CreateHttpApiRequest( + controller => controller.Post6(2), new { version = "v1" }); - var requestPost = server.CreateHttpApiRequest( - controller => controller.Post6(2), new { version = "v1" }); + var requestDelete = server.CreateHttpApiRequest( + controller => controller.Delete6(3), new { version = "v1" }); - var requestDelete = server.CreateHttpApiRequest( - controller => controller.Delete6(3), new { version = "v1" }); + requestGet.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/v1/0"); - requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/v1/0"); + requestPut.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/v1/1"); - requestPut.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/v1/1"); + requestPost.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/v1/2"); - requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/v1/2"); + requestDelete.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/v1/3"); + } - requestDelete.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/v1/3"); - } + [Fact] + public void Create_valid_request_using_route_templates_and_parameters_with_route_constraints() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_route_templates_and_parameters_with_route_constraints() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var requestGet = server.CreateHttpApiRequest( + controller => controller.Get7(0)); - var requestGet = server.CreateHttpApiRequest( - controller => controller.Get7(0)); + var requestPut = server.CreateHttpApiRequest( + controller => controller.Put7(1)); - var requestPut = server.CreateHttpApiRequest( - controller => controller.Put7(1)); + var requestPost = server.CreateHttpApiRequest( + controller => controller.Post7(2)); - var requestPost = server.CreateHttpApiRequest( - controller => controller.Post7(2)); + var requestDelete = server.CreateHttpApiRequest( + controller => controller.Delete7(3)); - var requestDelete = server.CreateHttpApiRequest( - controller => controller.Delete7(3)); + requestGet.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/0"); - requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/0"); + requestPut.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/1"); - requestPut.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/1"); + requestPost.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/2"); - requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/2"); + requestDelete.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/3"); + } - requestDelete.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overrideroutetemplatemethodname/3"); - } + [Fact] + public void Create_valid_request_using_verbs_and_parameters_with_route_constraints() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_verbs_and_parameters_with_route_constraints() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var requestGet = server.CreateHttpApiRequest( + controller => controller.Get8(0)); - var requestGet = server.CreateHttpApiRequest( - controller => controller.Get8(0)); + var requestPut = server.CreateHttpApiRequest( + controller => controller.Put8(1)); - var requestPut = server.CreateHttpApiRequest( - controller => controller.Put8(1)); + var requestPost = server.CreateHttpApiRequest( + controller => controller.Post8(2)); - var requestPost = server.CreateHttpApiRequest( - controller => controller.Post8(2)); + var requestDelete = server.CreateHttpApiRequest( + controller => controller.Delete8(3)); - var requestDelete = server.CreateHttpApiRequest( - controller => controller.Delete8(3)); + requestGet.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/0"); - requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/0"); + requestPut.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/1"); - requestPut.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/1"); + requestPost.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/2"); - requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/2"); + requestDelete.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/3"); + } - requestDelete.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/overridemethodname/3"); - } + [Fact] + public void Create_valid_request_using_verbs_when_parameters_are_not_primitives() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_verbs_when_parameters_are_not_primitives() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var requestGet = server.CreateHttpApiRequest( + controller => controller.Get2(complexParameter)); - var requestGet = server.CreateHttpApiRequest( - controller => controller.Get2(complexParameter)); + requestGet.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/get2/1/10"); + } - requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/get2/1/10"); - } + [Fact] + public void Create_valid_request_using_verbs_when_parameters_are_not_primitives_with_null_properties() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_verbs_when_parameters_are_not_primitives_with_null_properties() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageIndex = 1 - }; + var requestGet = server.CreateHttpApiRequest( + controller => controller.Get2(complexParameter)); - var requestGet = server.CreateHttpApiRequest( - controller => controller.Get2(complexParameter)); + requestGet.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/get2/1"); + } - requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/get2/1"); - } + [Fact] + public void Create_valid_request_using_verbs_and_query_string_when_parameters_are_not_primitives() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_verbs_and_query_string_when_parameters_are_not_primitives() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var requestGet = server.CreateHttpApiRequest( + controller => controller.Get1(complexParameter)); - var requestGet = server.CreateHttpApiRequest( - controller => controller.Get1(complexParameter)); + requestGet.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/get1?pageindex=1&pagecount=10"); + } - requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/get1?pageindex=1&pagecount=10"); - } + [Fact] + public void Create_valid_request_using_verbs_and_query_string_when_parameters_are_not_primitives_with_null_properties() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_verbs_and_query_string_when_parameters_are_not_primitives_with_null_properties() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageIndex = 1 - }; + var requestGet = server.CreateHttpApiRequest( + controller => controller.Get1(complexParameter)); - var requestGet = server.CreateHttpApiRequest( - controller => controller.Get1(complexParameter)); + requestGet.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/get1?pageindex=1"); + } - requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/get1?pageindex=1"); - } + [Fact] + public void Create_valid_request_using_route_when_parameters_are_not_primitives() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_route_when_parameters_are_not_primitives() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var requestGet = server.CreateHttpApiRequest( + controller => controller.Get4(complexParameter)); - var requestGet = server.CreateHttpApiRequest( - controller => controller.Get4(complexParameter)); + requestGet.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/get4/1/10"); + } - requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/get4/1/10"); - } + [Fact] + public void Create_valid_request_using_verbs_query_string_and_from_header() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_verbs_query_string_and_from_header() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var header = "HeaderCustom"; + var numberHeader = 1; - var header = "HeaderCustom"; - var numberHeader = 1; + var request = server.CreateHttpApiRequest( + controller => controller.Get5(header, complexParameter)); - var request = server.CreateHttpApiRequest( - controller => controller.Get5(header, complexParameter)); + var requestMultipleHeader = server.CreateHttpApiRequest( + controller => controller.Get6(header, numberHeader, complexParameter)); - var requestMultipleHeader = server.CreateHttpApiRequest( - controller => controller.Get6(header, numberHeader, complexParameter)); + var requestOnlyHeader = server.CreateHttpApiRequest( + controller => controller.Get7(header)); - var requestOnlyHeader = server.CreateHttpApiRequest( - controller => controller.Get7(header)); + request.GetRequest().Headers.GetValues("custom").First().Should().Be(header); + request.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/get5?pageindex=1&pagecount=10"); - request.GetRequest().Headers.GetValues("custom").First().Should().Be(header); - request.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/get5?pageindex=1&pagecount=10"); + requestMultipleHeader.GetRequest().Headers.GetValues("custom1").First().Should().Be(header); + requestMultipleHeader.GetRequest().Headers.GetValues("custom2").First().Should().Be(numberHeader.ToString()); + requestMultipleHeader.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/get6?pageindex=1&pagecount=10"); - requestMultipleHeader.GetRequest().Headers.GetValues("custom1").First().Should().Be(header); - requestMultipleHeader.GetRequest().Headers.GetValues("custom2").First().Should().Be(numberHeader.ToString()); - requestMultipleHeader.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/get6?pageindex=1&pagecount=10"); + requestOnlyHeader.GetRequest().Headers.GetValues("custom").First().Should().Be(header); + requestOnlyHeader.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/get7"); + } - requestOnlyHeader.GetRequest().Headers.GetValues("custom").First().Should().Be(header); - requestOnlyHeader.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/get7"); - } + [Fact] + public void Create_valid_request_using_route_when_parameters_are_not_primitives_with_null_properties() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_route_when_parameters_are_not_primitives_with_null_properties() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageIndex = 1 - }; + var requestGet = server.CreateHttpApiRequest( + controller => controller.Get4(complexParameter)); - var requestGet = server.CreateHttpApiRequest( - controller => controller.Get4(complexParameter)); + requestGet.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/get4/1"); + } - requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/get4/1"); - } + [Fact] + public void Create_valid_request_using_route_and_query_string_when_parameters_are_not_primitives() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_route_and_query_string_when_parameters_are_not_primitives() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var requestGet = server.CreateHttpApiRequest( + controller => controller.Get3(complexParameter)); - var requestGet = server.CreateHttpApiRequest( - controller => controller.Get3(complexParameter)); + requestGet.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/get3?pageindex=1&pagecount=10"); + } - requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/get3?pageindex=1&pagecount=10"); - } + [Fact] + public void Create_valid_request_using_route_and_query_string_when_parameters_are_not_primitives_with_null_properties() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_route_and_query_string_when_parameters_are_not_primitives_with_null_properties() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageIndex = 1 - }; + var requestGet = server.CreateHttpApiRequest( + controller => controller.Get3(complexParameter)); - var requestGet = server.CreateHttpApiRequest( - controller => controller.Get3(complexParameter)); + requestGet.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/get3?pageindex=1"); + } - requestGet.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/get3?pageindex=1"); - } + [Fact] + public void Create_valid_request_using_from_body_complex_arguments() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_from_body_complex_arguments() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var requestPost = server.CreateHttpApiRequest( + controller => controller.Post1(complexParameter)); - var requestPost = server.CreateHttpApiRequest( - controller => controller.Post1(complexParameter)); + requestPost.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/"); + } - requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/"); - } + [Fact] + public void Create_valid_request_using_from_body_complex_arguments_with_null_properties() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_from_body_complex_arguments_with_null_properties() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageIndex = 1 - }; + var requestPost = server.CreateHttpApiRequest( + controller => controller.Post1(complexParameter)); - var requestPost = server.CreateHttpApiRequest( - controller => controller.Post1(complexParameter)); + requestPost.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/"); + } - requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/"); - } + [Fact] + public void Create_valid_request_using_from_form_complex_arguments() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_from_form_complex_arguments() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var requestPost = server.CreateHttpApiRequest( + controller => controller.Post3(complexParameter), + tokenValues: null, + new IncludeContentAsFormUrlEncoded()); - var requestPost = server.CreateHttpApiRequest( - controller => controller.Post3(complexParameter), - tokenValues: null, - new IncludeContentAsFormUrlEncoded()); + requestPost.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/post3"); + } - requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/post3"); - } + [Fact] + public void Create_valid_request_using_from_form_complex_arguments_with_null_properties() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_from_form_complex_arguments_with_null_properties() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageIndex = 1 - }; + var requestPost = server.CreateHttpApiRequest( + controller => controller.Post3(complexParameter), + tokenValues: null, + new IncludeContentAsFormUrlEncoded()); - var requestPost = server.CreateHttpApiRequest( - controller => controller.Post3(complexParameter), - tokenValues: null, - new IncludeContentAsFormUrlEncoded()); + requestPost.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/post3"); + } - requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/post3"); - } + [Fact] + public void Create_valid_request_using_from_body_complex_arguments_and_primitive_query_parameters() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_from_body_complex_arguments_and_primitive_query_parameters() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var requestPost = server.CreateHttpApiRequest( + controller => controller.Post2(2, complexParameter)); - var requestPost = server.CreateHttpApiRequest( - controller => controller.Post2(2, complexParameter)); + requestPost.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/post/2"); + } - requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/post/2"); - } + [Fact] + public void Create_valid_request_using_from_body_complex_arguments_and_primitive_query_parameters_with_null_properties() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_from_body_complex_arguments_and_primitive_query_parameters_with_null_properties() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageIndex = 1 - }; + var requestPost = server.CreateHttpApiRequest( + controller => controller.Post2(2, complexParameter)); - var requestPost = server.CreateHttpApiRequest( - controller => controller.Post2(2, complexParameter)); + requestPost.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/post/2"); + } - requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/post/2"); - } + [Fact] + public void Create_valid_request_using_from_form_complex_arguments_and_primitive_query_parameters() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_from_form_complex_arguments_and_primitive_query_parameters() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var requestPost = server.CreateHttpApiRequest( + controller => controller.Post4(2, complexParameter), + tokenValues: null, + new IncludeContentAsFormUrlEncoded()); - var requestPost = server.CreateHttpApiRequest( - controller => controller.Post4(2, complexParameter), - tokenValues: null, - new IncludeContentAsFormUrlEncoded()); + requestPost.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/post4/2"); + } - requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/post4/2"); - } + [Fact] + public void Create_valid_request_using_from_header_primitive_arguments() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_from_header_primitive_arguments() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var header = "HeaderCustom"; - var header = "HeaderCustom"; + var requestPost = server.CreateHttpApiRequest( + controller => controller.Post5(header)); + requestPost.GetRequest().Headers.GetValues("custom").First().Should().Be(header); + requestPost.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/post5"); + } - var requestPost = server.CreateHttpApiRequest( - controller => controller.Post5(header)); - requestPost.GetRequest().Headers.GetValues("custom").First().Should().Be(header); - requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/post5"); - } + [Fact] + public void Create_valid_request_using_from_header_primitive_arguments_and_from_body_complex_arguments() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_from_header_primitive_arguments_and_from_body_complex_arguments() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; + var header = "HeaderCustom"; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; - var header = "HeaderCustom"; + var requestPost2 = server.CreateHttpApiRequest( + controller => controller.Post6(header, complexParameter)); - var requestPost2 = server.CreateHttpApiRequest( - controller => controller.Post6(header, complexParameter)); + requestPost2.GetRequest().Headers.GetValues("custom").First().Should().Be(header); + requestPost2.GetConfiguredAddress().Should().Be($"{BASE_PATH_VALUES}/post6"); + } - requestPost2.GetRequest().Headers.GetValues("custom").First().Should().Be(header); - requestPost2.GetConfiguredAddress().Should().Be($"{BASE_PATH_VALUES}/post6"); - } + [Fact] + public void Create_valid_request_using_from_form_complex_arguments_and_primitive_query_parameters_wit_null_properties() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_using_from_form_complex_arguments_and_primitive_query_parameters_wit_null_properties() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() + PageIndex = 1 + }; + + var requestPost = server.CreateHttpApiRequest( + controller => controller.Post4(2, complexParameter), + tokenValues: null, + new IncludeContentAsFormUrlEncoded()); + + requestPost.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_VALUES}/post4/2"); + } + + [Fact] + public void Create_valid_request_when_action_use_tilde_to_override_controller_route() + { + var server = new TestServerBuilder() .UseDefaultStartup() .Build(); - var complexParameter = new Pagination() - { - PageIndex = 1 - }; + var requestPost = server.CreateHttpApiRequest( + controller => controller.Get1(1)); - var requestPost = server.CreateHttpApiRequest( - controller => controller.Post4(2, complexParameter), - tokenValues: null, - new IncludeContentAsFormUrlEncoded()); + requestPost.GetConfiguredAddress() + .Should().Be("get1/1"); - requestPost.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_VALUES}/post4/2"); - } + requestPost = server.CreateHttpApiRequest( + controller => controller.Post1(2)); + + requestPost.GetConfiguredAddress() + .Should().Be("post1/2"); - //[Fact] - //public void create_valid_request_using_from_body_complex_arguments_and_complex_query_parameters() - //{ - // var server = new TestServerBuilder() - // .UseDefaultStartup() - // .Build(); + requestPost = server.CreateHttpApiRequest( + controller => controller.Put1(3)); - // var complexParameter = new Pagination() - // { - // PageCount = 10, - // PageIndex = 1 - // }; + requestPost.GetConfiguredAddress() + .Should().Be("put1/3"); - // var requestPost = server.CreateHttpApiRequest( - // controller => controller.Post3(2, complexParameter, complexParameter)); + requestPost = server.CreateHttpApiRequest( + controller => controller.Delete1(4)); - // requestPost.GetConfiguredAddress() - // .Should().Be($"{BASE_PATH_VALUES}/post/2/1/10"); - //} + requestPost.GetConfiguredAddress() + .Should().Be("delete1/4"); - [Fact] - public void create_valid_request_when_action_use_tilde_to_override_controller_route() + var pagination = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageIndex = 1, + PageCount = 2 + }; - var requestPost = server.CreateHttpApiRequest( - controller => controller.Get1(1)); + requestPost = server.CreateHttpApiRequest( + controller => controller.Get2(1, pagination)); - requestPost.GetConfiguredAddress() - .Should().Be("get1/1"); + requestPost.GetConfiguredAddress() + .Should().Be("get2/1?pageindex=1&pagecount=2"); - requestPost = server.CreateHttpApiRequest( - controller => controller.Post1(2)); + requestPost = server.CreateHttpApiRequest( + controller => controller.Post2(1, pagination)); - requestPost.GetConfiguredAddress() - .Should().Be("post1/2"); + requestPost.GetConfiguredAddress() + .Should().Be("post2/1?pageindex=1&pagecount=2"); - requestPost = server.CreateHttpApiRequest( - controller => controller.Put1(3)); + requestPost = server.CreateHttpApiRequest( + controller => controller.Put2(1, pagination)); - requestPost.GetConfiguredAddress() - .Should().Be("put1/3"); + requestPost.GetConfiguredAddress() + .Should().Be("put2/1?pageindex=1&pagecount=2"); - requestPost = server.CreateHttpApiRequest( - controller => controller.Delete1(4)); + requestPost = server.CreateHttpApiRequest( + controller => controller.Delete2(1, pagination)); - requestPost.GetConfiguredAddress() - .Should().Be("delete1/4"); + requestPost.GetConfiguredAddress() + .Should().Be("delete2/1?pageindex=1&pagecount=2"); - var pagination = new Pagination() - { - PageIndex = 1, - PageCount = 2 - }; + requestPost = server.CreateHttpApiRequest( + controller => controller.Get3(1, pagination)); - requestPost = server.CreateHttpApiRequest( - controller => controller.Get2(1, pagination)); + requestPost.GetConfiguredAddress() + .Should().Be("get3/1?pageindex=1&pagecount=2"); - requestPost.GetConfiguredAddress() - .Should().Be("get2/1?pageindex=1&pagecount=2"); + requestPost = server.CreateHttpApiRequest( + controller => controller.Post3(1, pagination)); - requestPost = server.CreateHttpApiRequest( - controller => controller.Post2(1, pagination)); + requestPost.GetConfiguredAddress() + .Should().Be("post3/1?pageindex=1&pagecount=2"); - requestPost.GetConfiguredAddress() - .Should().Be("post2/1?pageindex=1&pagecount=2"); + requestPost = server.CreateHttpApiRequest( + controller => controller.Put3(1, pagination)); - requestPost = server.CreateHttpApiRequest( - controller => controller.Put2(1, pagination)); + requestPost.GetConfiguredAddress() + .Should().Be("put3/1?pageindex=1&pagecount=2"); - requestPost.GetConfiguredAddress() - .Should().Be("put2/1?pageindex=1&pagecount=2"); + requestPost = server.CreateHttpApiRequest( + controller => controller.Delete3(1, pagination)); - requestPost = server.CreateHttpApiRequest( - controller => controller.Delete2(1, pagination)); + requestPost.GetConfiguredAddress() + .Should().Be("delete3/1?pageindex=1&pagecount=2"); + } - requestPost.GetConfiguredAddress() - .Should().Be("delete2/1?pageindex=1&pagecount=2"); + [Fact] + public void Create_valid_request_when_action_use_tilde_to_override_controller_route_with_null_properties() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - requestPost = server.CreateHttpApiRequest( - controller => controller.Get3(1, pagination)); + var requestPost = server.CreateHttpApiRequest( + controller => controller.Get1(1)); - requestPost.GetConfiguredAddress() - .Should().Be("get3/1?pageindex=1&pagecount=2"); + requestPost.GetConfiguredAddress() + .Should().Be("get1/1"); - requestPost = server.CreateHttpApiRequest( - controller => controller.Post3(1, pagination)); + requestPost = server.CreateHttpApiRequest( + controller => controller.Post1(2)); - requestPost.GetConfiguredAddress() - .Should().Be("post3/1?pageindex=1&pagecount=2"); + requestPost.GetConfiguredAddress() + .Should().Be("post1/2"); - requestPost = server.CreateHttpApiRequest( - controller => controller.Put3(1, pagination)); + requestPost = server.CreateHttpApiRequest( + controller => controller.Put1(3)); - requestPost.GetConfiguredAddress() - .Should().Be("put3/1?pageindex=1&pagecount=2"); + requestPost.GetConfiguredAddress() + .Should().Be("put1/3"); - requestPost = server.CreateHttpApiRequest( - controller => controller.Delete3(1, pagination)); + requestPost = server.CreateHttpApiRequest( + controller => controller.Delete1(4)); - requestPost.GetConfiguredAddress() - .Should().Be("delete3/1?pageindex=1&pagecount=2"); - } + requestPost.GetConfiguredAddress() + .Should().Be("delete1/4"); - [Fact] - public void create_valid_request_when_action_use_tilde_to_override_controller_route_with_null_properties() + var pagination = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageIndex = 1 + }; - var requestPost = server.CreateHttpApiRequest( - controller => controller.Get1(1)); + requestPost = server.CreateHttpApiRequest( + controller => controller.Get2(1, pagination)); - requestPost.GetConfiguredAddress() - .Should().Be("get1/1"); + requestPost.GetConfiguredAddress() + .Should().Be("get2/1?pageindex=1"); - requestPost = server.CreateHttpApiRequest( - controller => controller.Post1(2)); + requestPost = server.CreateHttpApiRequest( + controller => controller.Post2(1, pagination)); - requestPost.GetConfiguredAddress() - .Should().Be("post1/2"); + requestPost.GetConfiguredAddress() + .Should().Be("post2/1?pageindex=1"); - requestPost = server.CreateHttpApiRequest( - controller => controller.Put1(3)); + requestPost = server.CreateHttpApiRequest( + controller => controller.Put2(1, pagination)); - requestPost.GetConfiguredAddress() - .Should().Be("put1/3"); + requestPost.GetConfiguredAddress() + .Should().Be("put2/1?pageindex=1"); - requestPost = server.CreateHttpApiRequest( - controller => controller.Delete1(4)); + requestPost = server.CreateHttpApiRequest( + controller => controller.Delete2(1, pagination)); - requestPost.GetConfiguredAddress() - .Should().Be("delete1/4"); - - var pagination = new Pagination() - { - PageIndex = 1 - }; + requestPost.GetConfiguredAddress() + .Should().Be("delete2/1?pageindex=1"); - requestPost = server.CreateHttpApiRequest( - controller => controller.Get2(1, pagination)); + requestPost = server.CreateHttpApiRequest( + controller => controller.Get3(1, pagination)); - requestPost.GetConfiguredAddress() - .Should().Be("get2/1?pageindex=1"); + requestPost.GetConfiguredAddress() + .Should().Be("get3/1?pageindex=1"); - requestPost = server.CreateHttpApiRequest( - controller => controller.Post2(1, pagination)); + requestPost = server.CreateHttpApiRequest( + controller => controller.Post3(1, pagination)); - requestPost.GetConfiguredAddress() - .Should().Be("post2/1?pageindex=1"); + requestPost.GetConfiguredAddress() + .Should().Be("post3/1?pageindex=1"); - requestPost = server.CreateHttpApiRequest( - controller => controller.Put2(1, pagination)); + requestPost = server.CreateHttpApiRequest( + controller => controller.Put3(1, pagination)); - requestPost.GetConfiguredAddress() - .Should().Be("put2/1?pageindex=1"); + requestPost.GetConfiguredAddress() + .Should().Be("put3/1?pageindex=1"); - requestPost = server.CreateHttpApiRequest( - controller => controller.Delete2(1, pagination)); + requestPost = server.CreateHttpApiRequest( + controller => controller.Delete3(1, pagination)); - requestPost.GetConfiguredAddress() - .Should().Be("delete2/1?pageindex=1"); - - requestPost = server.CreateHttpApiRequest( - controller => controller.Get3(1, pagination)); - - requestPost.GetConfiguredAddress() - .Should().Be("get3/1?pageindex=1"); + requestPost.GetConfiguredAddress() + .Should().Be("delete3/1?pageindex=1"); + } - requestPost = server.CreateHttpApiRequest( - controller => controller.Post3(1, pagination)); + [Fact] + public async Task Create_request_including_fromBody_argument_as_content_as_default_behavior() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - requestPost.GetConfiguredAddress() - .Should().Be("post3/1?pageindex=1"); + var complexParameter = new Pagination() + { + PageCount = 10, + PageIndex = 1 + }; - requestPost = server.CreateHttpApiRequest( - controller => controller.Put3(1, pagination)); + var request = server.CreateHttpApiRequest( + controller => controller.Post2(2, complexParameter), + tokenValues: null, + new IncludeContentAsJson()); - requestPost.GetConfiguredAddress() - .Should().Be("put3/1?pageindex=1"); + var response = await request.PostAsync(); - requestPost = server.CreateHttpApiRequest( - controller => controller.Delete3(1, pagination)); + await response.IsSuccessStatusCodeOrThrow(); + response.IsSuccessStatusCode.Should().BeTrue(); + } - requestPost.GetConfiguredAddress() - .Should().Be("delete3/1?pageindex=1"); - } + [Fact] + public async Task Create_request_including_fromBody_argument_as_content_as_default_behavior_with_null_properties() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public async Task create_request_including_fromBody_argument_as_content_as_default_behavior() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var request = server.CreateHttpApiRequest( + controller => controller.Post2(2, complexParameter), + tokenValues: null, + new IncludeContentAsJson()); - var request = server.CreateHttpApiRequest( - controller => controller.Post2(2, complexParameter), - tokenValues: null, - new IncludeContentAsJson()); + var response = await request.PostAsync(); - var response = await request.PostAsync(); + await response.IsSuccessStatusCodeOrThrow(); + response.IsSuccessStatusCode.Should().BeTrue(); + } - await response.IsSuccessStatusCodeOrThrow(); - } + [Fact] + public async Task Create_request_including_fromBody_argument_as_content_configured_explicitly() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public async Task create_request_including_fromBody_argument_as_content_as_default_behavior_with_null_properties() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageIndex = 1 - }; + var request = server.CreateHttpApiRequest( + actionSelector: controller => controller.Post2(2, complexParameter), + tokenValues: null, + contentOptions: new IncludeContentAsJson()); - var request = server.CreateHttpApiRequest( - controller => controller.Post2(2, complexParameter), - tokenValues: null, - new IncludeContentAsJson()); + var response = await request.PostAsync(); - var response = await request.PostAsync(); + await response.IsSuccessStatusCodeOrThrow(); + response.IsSuccessStatusCode.Should().BeTrue(); + } - await response.IsSuccessStatusCodeOrThrow(); - } + [Fact] + public async Task Create_request_including_fromBody_argument_as_content_configured_explicitly_with_null_properties() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public async Task create_request_including_fromBody_argument_as_content_configured_explicitly() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var request = server.CreateHttpApiRequest( + actionSelector: controller => controller.Post2(2, complexParameter), + tokenValues: null, + contentOptions: new IncludeContentAsJson()); - var request = server.CreateHttpApiRequest( - actionSelector: controller => controller.Post2(2, complexParameter), - tokenValues: null, - contentOptions: new IncludeContentAsJson()); + var response = await request.PostAsync(); - var response = await request.PostAsync(); + await response.IsSuccessStatusCodeOrThrow(); + response.IsSuccessStatusCode.Should().BeTrue(); + } - await response.IsSuccessStatusCodeOrThrow(); - } + [Fact] + public async Task Create_request_including_fromForm_argument_as_content_as_default_behavior() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public async Task create_request_including_fromBody_argument_as_content_configured_explicitly_with_null_properties() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageIndex = 1 - }; + var request = server.CreateHttpApiRequest( + controller => controller.Post4(2, complexParameter), + tokenValues: null, + new IncludeContentAsFormUrlEncoded()); - var request = server.CreateHttpApiRequest( - actionSelector: controller => controller.Post2(2, complexParameter), - tokenValues: null, - contentOptions: new IncludeContentAsJson()); + var response = await request.PostAsync(); - var response = await request.PostAsync(); + await response.IsSuccessStatusCodeOrThrow(); + response.IsSuccessStatusCode.Should().BeTrue(); + } - await response.IsSuccessStatusCodeOrThrow(); - } + [Fact] + public async Task Create_request_including_fromForm_argument_as_content_as_default_behavior_with_null_properties() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public async Task create_request_including_fromForm_argument_as_content_as_default_behavior() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var request = server.CreateHttpApiRequest( + controller => controller.Post4(2, complexParameter), + tokenValues: null, + new IncludeContentAsFormUrlEncoded()); - var request = server.CreateHttpApiRequest( - controller => controller.Post4(2, complexParameter), - tokenValues: null, - new IncludeContentAsFormUrlEncoded()); + var response = await request.PostAsync(); - var response = await request.PostAsync(); + await response.IsSuccessStatusCodeOrThrow(); + response.IsSuccessStatusCode.Should().BeTrue(); + } - await response.IsSuccessStatusCodeOrThrow(); - } + [Fact] + public async Task Create_request_including_fromForm_argument_as_content_configured_explicitly() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public async Task create_request_including_fromForm_argument_as_content_as_default_behavior_with_null_properties() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageIndex = 1 - }; + var request = server.CreateHttpApiRequest( + actionSelector: controller => controller.Post4(2, complexParameter), + tokenValues: null, + contentOptions: new IncludeContentAsFormUrlEncoded()); - var request = server.CreateHttpApiRequest( - controller => controller.Post4(2, complexParameter), - tokenValues: null, - new IncludeContentAsFormUrlEncoded()); + var response = await request.PostAsync(); - var response = await request.PostAsync(); + await response.IsSuccessStatusCodeOrThrow(); + response.IsSuccessStatusCode.Should().BeTrue(); + } - await response.IsSuccessStatusCodeOrThrow(); - } + [Fact] + public async Task Create_request_including_fromForm_argument_as_content_configured_explicitly_with_null_properties() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public async Task create_request_including_fromForm_argument_as_content_configured_explicitly() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var request = server.CreateHttpApiRequest( + actionSelector: controller => controller.Post4(2, complexParameter), + tokenValues: null, + contentOptions: new IncludeContentAsFormUrlEncoded()); - var request = server.CreateHttpApiRequest( - actionSelector: controller => controller.Post4(2, complexParameter), - tokenValues: null, - contentOptions: new IncludeContentAsFormUrlEncoded()); + var response = await request.PostAsync(); - var response = await request.PostAsync(); + await response.IsSuccessStatusCodeOrThrow(); + response.IsSuccessStatusCode.Should().BeTrue(); + } - await response.IsSuccessStatusCodeOrThrow(); - } + [Fact] + public async Task Create_request_including_fromForm_argument_as_content_with_complex_object() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public async Task create_request_including_fromForm_argument_as_content_configured_explicitly_with_null_properties() + var complexObject = new ComplexObject() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); - - var complexParameter = new Pagination() + BoolNullableParameter = true, + BoolParameter = true, + ComplexParameter = new Complex() { - PageIndex = 1 - }; - - var request = server.CreateHttpApiRequest( - actionSelector: controller => controller.Post4(2, complexParameter), - tokenValues: null, - contentOptions: new IncludeContentAsFormUrlEncoded()); - - var response = await request.PostAsync(); + Pagination = new Pagination() + { + PageCount = 10, + PageIndex = 1 + }, + LongNullableParameter = 1, + LongParameter = 1 + }, + IntNullableParameter = 1, + IntParameter = 1, + StringParameter = "Test", + DateTimeParameter = DateTime.Now + }; + + var request = server.CreateHttpApiRequest( + controller => controller.Post(complexObject), + tokenValues: null, + new IncludeContentAsFormUrlEncoded()); + + var response = await request.PostAsync(); + + await response.IsSuccessStatusCodeOrThrow(); + } - await response.IsSuccessStatusCodeOrThrow(); - } + [Fact] + public async Task Create_request_including_fromForm_argument_as_content_with_complex_object_with_null_properties() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public async Task create_request_including_fromForm_argument_as_content_with_complex_object() + var complexObject = new ComplexObject() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); - - var complexObject = new ComplexObject() + BoolNullableParameter = true, + BoolParameter = true, + ComplexParameter = new Complex() { - BoolNullableParameter = true, - BoolParameter = true, - ComplexParameter = new Complex() + Pagination = new Pagination() { - Pagination = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }, - LongNullableParameter = 1, - LongParameter = 1 + PageIndex = 1 }, - IntNullableParameter = 1, - IntParameter = 1, - StringParameter = "Test", - DateTimeParameter = DateTime.Now - }; + LongNullableParameter = 1, + LongParameter = 1 + }, + IntNullableParameter = 1, + IntParameter = 1, + StringParameter = "Test", + DateTimeParameter = DateTime.Now + }; + + var request = server.CreateHttpApiRequest( + controller => controller.Post(complexObject), + tokenValues: null, + new IncludeContentAsFormUrlEncoded()); + + var response = await request.PostAsync(); + + await response.IsSuccessStatusCodeOrThrow(); + } - var request = server.CreateHttpApiRequest( - controller => controller.Post(complexObject), - tokenValues: null, - new IncludeContentAsFormUrlEncoded()); + [Fact] + public async Task Create_request_not_adding_fromBody_argument_as_content() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - var response = await request.PostAsync(); + var complexParameter = new Pagination() + { + PageCount = 10, + PageIndex = 1 + }; - await response.IsSuccessStatusCodeOrThrow(); - } + var request = server.CreateHttpApiRequest( + actionSelector: controller => controller.Post2(2, complexParameter), + tokenValues: null, + contentOptions: new NotIncludeContent()); - [Fact] - public async Task create_request_including_fromForm_argument_as_content_with_complex_object_with_null_properties() - { - var server = new TestServerBuilder() + var response = await request.PostAsync(); + + response.StatusCode.Should().Be(HttpStatusCode.UnsupportedMediaType); + } + + [Fact] + public async Task Create_request_not_adding_fromBody_argument_as_content_with_null_properties() + { + var server = new TestServerBuilder() .UseDefaultStartup() .Build(); - var complexObject = new ComplexObject() - { - BoolNullableParameter = true, - BoolParameter = true, - ComplexParameter = new Complex() - { - Pagination = new Pagination() - { - PageIndex = 1 - }, - LongNullableParameter = 1, - LongParameter = 1 - }, - IntNullableParameter = 1, - IntParameter = 1, - StringParameter = "Test", - DateTimeParameter = DateTime.Now - }; + var complexParameter = new Pagination() + { + PageIndex = 1 + }; - var request = server.CreateHttpApiRequest( - controller => controller.Post(complexObject), - tokenValues: null, - new IncludeContentAsFormUrlEncoded()); + var request = server.CreateHttpApiRequest( + actionSelector: controller => controller.Post2(2, complexParameter), + tokenValues: null, + contentOptions: new NotIncludeContent()); - var response = await request.PostAsync(); + var response = await request.PostAsync(); - await response.IsSuccessStatusCodeOrThrow(); - } + response.StatusCode.Should().Be(HttpStatusCode.UnsupportedMediaType); + } - [Fact] - public async Task create_request_not_adding_fromBody_argument_as_content() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + [Fact] + public void Create_request_supporting_guid_types_on_parameters_and_numbes_on_parameters_names() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var guidValue = Guid.NewGuid(); - var request = server.CreateHttpApiRequest( - actionSelector: controller => controller.Post2(2, complexParameter), - tokenValues: null, - contentOptions: new NotIncludeContent()); + var request = server.CreateHttpApiRequest( + actionSelector: controller => controller.GuidSupport("prm1", guidValue), + tokenValues: null, + contentOptions: new NotIncludeContent()); - var response = await request.PostAsync(); + request.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_BUGS}/prm1/{guidValue}"); + } - response.StatusCode.Should().Be(HttpStatusCode.UnsupportedMediaType); - } + [Fact] + public void Create_valid_request_without_using_frombody_with_apicontroller_attribute() + { + var server = new TestServerBuilder().UseDefaultStartup() + .Build(); - [Fact] - public async Task create_request_not_adding_fromBody_argument_as_content_with_null_properties() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageIndex = 1 - }; + var requestPost1 = server.CreateHttpApiRequest(controller => controller.Post1(complexParameter)); - var request = server.CreateHttpApiRequest( - actionSelector: controller => controller.Post2(2, complexParameter), - tokenValues: null, - contentOptions: new NotIncludeContent()); - - var response = await request.PostAsync(); + string body = requestPost1.GetRequest().Content.ReadAsStringAsync().Result; + JsonSerializer.Deserialize(body).PageIndex.Should().Be(complexParameter.PageIndex); + JsonSerializer.Deserialize(body).PageCount.Should().Be(complexParameter.PageCount); + } - response.StatusCode.Should().Be(HttpStatusCode.UnsupportedMediaType); - } + [Fact] + public void Create_valid_request_without_using_frombody_with_apicontroller_attribute_and_route_parameter() + { + var server = new TestServerBuilder().UseDefaultStartup() + .Build(); - [Fact] - public void create_request_supporting_guid_types_on_parameters_and_numbes_on_parameters_names() + var complexParameter = new Pagination() { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var guidValue = Guid.NewGuid(); + var requestPost2 = server.CreateHttpApiRequest(controller => controller.Post2(1, complexParameter)); - var request = server.CreateHttpApiRequest( - actionSelector: controller => controller.GuidSupport("prm1", guidValue), - tokenValues: null, - contentOptions: new NotIncludeContent()); + string body = requestPost2.GetRequest().Content.ReadAsStringAsync().Result; + JsonSerializer.Deserialize(body).PageIndex.Should().Be(complexParameter.PageIndex); + JsonSerializer.Deserialize(body).PageCount.Should().Be(complexParameter.PageCount); + requestPost2.GetConfiguredAddress().StartsWith($"{BASE_PATH_VALUES}/1").Should().Be(true); + } - request.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_BUGS}/prm1/{guidValue}"); - } + [Fact] + public void Create_valid_request_without_using_frombody_with_apicontroller_and_string_parameter_in_route() + { + var server = new TestServerBuilder().UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_without_using_frombody_with_apicontroller_attribute() + var complexParameter = new Pagination() { - var server = new TestServerBuilder().UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + string id = "A1"; - var requestPost1 = server.CreateHttpApiRequest(controller => controller.Post1(complexParameter)); + var requestPost3 = server.CreateHttpApiRequest(controller => controller.Post3($"{id}", complexParameter)); - string body = requestPost1.GetRequest().Content.ReadAsStringAsync().Result; - JsonSerializer.Deserialize(body).PageIndex.Should().Be(complexParameter.PageIndex); - JsonSerializer.Deserialize(body).PageCount.Should().Be(complexParameter.PageCount); - } + string body = requestPost3.GetRequest().Content.ReadAsStringAsync().Result; + JsonSerializer.Deserialize(body).PageIndex.Should().Be(complexParameter.PageIndex); + JsonSerializer.Deserialize(body).PageCount.Should().Be(complexParameter.PageCount); + requestPost3.GetConfiguredAddress().StartsWith($"{BASE_PATH_VALUES}/{id}").Should().Be(true); + } - [Fact] - public void create_valid_request_without_using_frombody_with_apicontroller_attribute_and_route_parameter() + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Create_valid_request_without_using_frombody_with_apicontroller_and_string_parameter_with_invalid_value(string id) + { + var server = new TestServerBuilder().UseDefaultStartup() + .Build(); + + var complexParameter = new Pagination() { - var server = new TestServerBuilder().UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var requestPost3 = server.CreateHttpApiRequest(controller => controller.Post3($"{id}", complexParameter)); - var requestPost2 = server.CreateHttpApiRequest(controller => controller.Post2(1, complexParameter)); + string body = requestPost3.GetRequest().Content.ReadAsStringAsync().Result; + JsonSerializer.Deserialize(body).PageIndex.Should().Be(complexParameter.PageIndex); + JsonSerializer.Deserialize(body).PageCount.Should().Be(complexParameter.PageCount); + requestPost3.GetConfiguredAddress().StartsWith($"{BASE_PATH_VALUES}/").Should().Be(true); + } - string body = requestPost2.GetRequest().Content.ReadAsStringAsync().Result; - JsonSerializer.Deserialize(body).PageIndex.Should().Be(complexParameter.PageIndex); - JsonSerializer.Deserialize(body).PageCount.Should().Be(complexParameter.PageCount); - requestPost2.GetConfiguredAddress().StartsWith($"{BASE_PATH_VALUES}/1").Should().Be(true); - } + [Fact] + public void Create_valid_request_of_patch_without_using_frombody_with_apicontroller_attribute_and_route_parameter() + { + var server = new TestServerBuilder().UseDefaultStartup() + .Build(); - [Fact] - public void create_valid_request_without_using_frombody_with_apicontroller_and_string_parameter_in_route() + var complexParameter = new Pagination() { - var server = new TestServerBuilder().UseDefaultStartup() - .Build(); + PageCount = 10, + PageIndex = 1 + }; - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var requestPost2 = server.CreateHttpApiRequest(controller => controller.Patch1(1, complexParameter)); - string id = "A1"; + string body = requestPost2.GetRequest().Content.ReadAsStringAsync().Result; + JsonSerializer.Deserialize(body).PageIndex.Should().Be(complexParameter.PageIndex); + JsonSerializer.Deserialize(body).PageCount.Should().Be(complexParameter.PageCount); + requestPost2.GetConfiguredAddress().StartsWith($"{BASE_PATH_VALUES}/1").Should().Be(true); + } - var requestPost3 = server.CreateHttpApiRequest(controller => controller.Post3($"{id}", complexParameter)); + [Fact] + public void Create_valid_request_supporting_underdash_on_router_params() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - string body = requestPost3.GetRequest().Content.ReadAsStringAsync().Result; - JsonSerializer.Deserialize(body).PageIndex.Should().Be(complexParameter.PageIndex); - JsonSerializer.Deserialize(body).PageCount.Should().Be(complexParameter.PageCount); - requestPost3.GetConfiguredAddress().StartsWith($"{BASE_PATH_VALUES}/{id}").Should().Be(true); - } + var guid = Guid.NewGuid(); - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void create_valid_request_without_using_frombody_with_apicontroller_and_string_parameter_with_invalid_value(string id) - { - var server = new TestServerBuilder().UseDefaultStartup() - .Build(); + var request = server.CreateHttpApiRequest( + actionSelector: controller => controller.UnderDashSupport(guid, 10), + tokenValues: null, + contentOptions: new NotIncludeContent()); - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + request.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_BUGS}/{guid}/10"); + } - var requestPost3 = server.CreateHttpApiRequest(controller => controller.Post3($"{id}", complexParameter)); + [Fact] + public async Task Create_valid_request_supporting_nullable_params_on_query() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - string body = requestPost3.GetRequest().Content.ReadAsStringAsync().Result; - JsonSerializer.Deserialize(body).PageIndex.Should().Be(complexParameter.PageIndex); - JsonSerializer.Deserialize(body).PageCount.Should().Be(complexParameter.PageCount); - requestPost3.GetConfiguredAddress().StartsWith($"{BASE_PATH_VALUES}/").Should().Be(true); - } + var guid = Guid.NewGuid(); - [Fact] - public void create_valid_request_of_patch_without_using_frombody_with_apicontroller_attribute_and_route_parameter() - { - var server = new TestServerBuilder().UseDefaultStartup() - .Build(); + var request = server.CreateHttpApiRequest( + actionSelector: controller => controller.NullableQueryParams(null, guid), + tokenValues: null, + contentOptions: new NotIncludeContent()); - var complexParameter = new Pagination() - { - PageCount = 10, - PageIndex = 1 - }; + var responseMessage = await request.GetAsync(); - var requestPost2 = server.CreateHttpApiRequest(controller => controller.Patch1(1, complexParameter)); + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.ReadContentAsAsync(); - string body = requestPost2.GetRequest().Content.ReadAsStringAsync().Result; - JsonSerializer.Deserialize(body).PageIndex.Should().Be(complexParameter.PageIndex); - JsonSerializer.Deserialize(body).PageCount.Should().Be(complexParameter.PageCount); - requestPost2.GetConfiguredAddress().StartsWith($"{BASE_PATH_VALUES}/1").Should().Be(true); - } + response.Param1.Should().Be(null); + response.Param2.Should().Be(guid); + } - [Fact] - public void create_valid_request_supporting_underdash_on_router_params() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + [Fact] + public async Task Create_request_supporting_guid_array_types_on_parameters() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - var guid = Guid.NewGuid(); + var guidList = new List { + Guid.NewGuid(), + Guid.NewGuid(), + Guid.NewGuid(), + }; - var request = server.CreateHttpApiRequest( - actionSelector: controller => controller.UnderDashSupport(guid, 10), - tokenValues: null, - contentOptions: new NotIncludeContent()); + var array = guidList.ToArray(); - request.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_BUGS}/{guid}/10"); - } + var request = server.CreateHttpApiRequest( + actionSelector: controller => controller.GuidArraySupport(array), + tokenValues: null, + contentOptions: new NotIncludeContent()); - [Fact] - public async Task create_valid_request_supporting_nullable_params_on_query() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var responseMessage = await request.GetAsync(); - var guid = Guid.NewGuid(); + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.ReadContentAsAsync(); - var request = server.CreateHttpApiRequest( - actionSelector: controller => controller.NullableQueryParams(null, guid), - tokenValues: null, - contentOptions: new NotIncludeContent()); + response.Should().NotBeNull(); + response.Should().HaveCount(3); + } - var responseMessage = await request.GetAsync(); + [Fact] + public async Task Create_request_supporting_int_array_types_on_parameters() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.ReadContentAsAsync(); + int[] array = { 1, 3, 5, 7, 9 }; - response.Param1.Should().Be(null); - response.Param2.Should().Be(guid); - } + var request = server.CreateHttpApiRequest( + actionSelector: controller => controller.IntArraySupport(array), + tokenValues: null, + contentOptions: new NotIncludeContent()); - [Fact] - public async Task create_request_supporting_guid_array_types_on_parameters() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var responseMessage = await request.GetAsync(); + + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.ReadContentAsAsync(); + + response.Should().NotBeNull(); + response.Should().HaveCount(5); + } - var guidList = new List { - Guid.NewGuid(), - Guid.NewGuid(), - Guid.NewGuid(), - }; + [Fact] + public async Task Create_request_not_supporting_class_array_types_on_parameters() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - var array = guidList.ToArray(); + var array = new Person[] { + new Person { FirstName = "john", LastName = "walter" }, + new Person { FirstName = "john2", LastName = "walter2" } + }; - var request = server.CreateHttpApiRequest( - actionSelector: controller => controller.GuidArraySupport(array), - tokenValues: null, - contentOptions: new NotIncludeContent()); + var request = server.CreateHttpApiRequest( + actionSelector: controller => controller.PersonArraySupport(array), + tokenValues: null, + contentOptions: new NotIncludeContent()); - var responseMessage = await request.GetAsync(); + var responseMessage = await request.GetAsync(); - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.ReadContentAsAsync(); + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.ReadContentAsAsync(); - response.Should().NotBeNull(); - response.Count().Should().Be(3); - } + response.Should().NotBeNull(); + response.Should().HaveCount(0); + } - [Fact] - public async Task create_request_supporting_int_array_types_on_parameters() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + [Fact] + public async Task Create_request_supporting_string_array_types_on_parameters() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - int[] array = { 1, 3, 5, 7, 9 }; + string[] array = { "one", "two", "three" }; - var request = server.CreateHttpApiRequest( - actionSelector: controller => controller.IntArraySupport(array), - tokenValues: null, - contentOptions: new NotIncludeContent()); + var request = server.CreateHttpApiRequest( + actionSelector: controller => controller.StringArraySupport(array), + tokenValues: null, + contentOptions: new NotIncludeContent()); - var responseMessage = await request.GetAsync(); + var responseMessage = await request.GetAsync(); - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.ReadContentAsAsync(); + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.ReadContentAsAsync(); - response.Should().NotBeNull(); - response.Count().Should().Be(5); - } + response.Should().NotBeNull(); + response.Should().HaveCount(3); + } - [Fact] - public async Task create_request_not_supporting_class_array_types_on_parameters() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + [Fact] + public async Task Create_request_supporting_guid_array_types_on_parameters_seding_method() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - var array = new Person[] { - new Person { FirstName = "john", LastName = "walter" }, - new Person { FirstName = "john2", LastName = "walter2" } - }; + var guidList = new List { + Guid.NewGuid(), + Guid.NewGuid(), + Guid.NewGuid(), + }; - var request = server.CreateHttpApiRequest( - actionSelector: controller => controller.PersonArraySupport(array), - tokenValues: null, - contentOptions: new NotIncludeContent()); + var request = server.CreateHttpApiRequest( + actionSelector: controller => controller.GuidArraySupport(guidList.ToArray()), + tokenValues: null, + contentOptions: new NotIncludeContent()); - var responseMessage = await request.GetAsync(); + var responseMessage = await request.GetAsync(); - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.ReadContentAsAsync(); + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.ReadContentAsAsync(); - response.Should().NotBeNull(); - response.Count().Should().Be(0); - } + response.Should().NotBeNull(); + response.Should().HaveCount(3); + } - [Fact] - public async Task create_request_supporting_string_array_types_on_parameters() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + [Fact] + public void Create_request_supporting_send_method_on_client_http() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - string[] array = { "one", "two", "three" }; + var guid = Guid.NewGuid().ToString(); - var request = server.CreateHttpApiRequest( - actionSelector: controller => controller.StringArraySupport(array), - tokenValues: null, - contentOptions: new NotIncludeContent()); + var request = server.CreateHttpApiRequest( + actionSelector: controller => controller.GuidSupport("prm1", Guid.Parse(guid)), + tokenValues: null, + contentOptions: new NotIncludeContent()); - var responseMessage = await request.GetAsync(); + request.GetConfiguredAddress() + .Should().Be($"{BASE_PATH_BUGS}/prm1/{guid}"); + } - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.ReadContentAsAsync(); + [Fact] + public async Task Create_request_supporting_router_and_body_params() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - response.Should().NotBeNull(); - response.Count().Should().Be(3); - } + var guid = Guid.NewGuid(); + var person = new Person { FirstName = "john", LastName = "walter" }; - [Fact] - public async Task create_request_supporting_guid_array_types_on_parameters_seding_method() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var request = server.CreateHttpApiRequest( + actionSelector: controller => controller.AllowRouterAndBodyParams(guid, person), + tokenValues: null); - var guidList = new List { - Guid.NewGuid(), - Guid.NewGuid(), - Guid.NewGuid(), - }; + var responseMessage = await request.PostAsync(); - var request = server.CreateHttpApiRequest( - actionSelector: controller => controller.GuidArraySupport(guidList.ToArray()), - tokenValues: null, - contentOptions: new NotIncludeContent()); + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.ReadContentAsAsync(); - var responseMessage = await request.GetAsync(); + response.Should().NotBeNull(); + response.TestId.Should().Be(guid); + response.Person.Should().NotBeNull(); + response.Person.FirstName.Should().Be(person.FirstName); + response.Person.LastName.Should().Be(person.LastName); + } - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.ReadContentAsAsync(); + [Fact] + public async Task Create_request_supporting_template_with_serveral_colon() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + const int param1 = 1; + const int param2 = 2; - response.Should().NotBeNull(); - response.Count().Should().Be(3); - } + var request = server.CreateHttpApiRequest(controller => controller.GetWithSeveralColon(param1, param2)); - [Fact] - public void create_request_supporting_send_method_on_client_http() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var responseMessage = await request.GetAsync(); - var guid = Guid.NewGuid().ToString(); + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.Content.ReadAsStringAsync(); - var request = server.CreateHttpApiRequest( - actionSelector: controller => controller.GuidSupport("prm1", Guid.Parse(guid)), - tokenValues: null, - contentOptions: new NotIncludeContent()); + response.Should().NotBeNull().And.Be($"{param1}/{param2}"); + } - request.GetConfiguredAddress() - .Should().Be($"{BASE_PATH_BUGS}/prm1/{guid}"); - } + [Fact] + public async Task Create_request_from_implemented_abstract_method() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public async Task create_request_supporting_router_and_body_params() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var request = server.CreateHttpApiRequest(controller => controller.AbstractMethod()); - var guid = Guid.NewGuid(); - var person = new Person { FirstName = "john", LastName = "walter" }; + var responseMessage = await request.GetAsync(); - var request = server.CreateHttpApiRequest( - actionSelector: controller => controller.AllowRouterAndBodyParams(guid, person), - tokenValues: null); + responseMessage.EnsureSuccessStatusCode(); + responseMessage.IsSuccessStatusCode.Should().BeTrue(); + } - var responseMessage = await request.PostAsync(); + [Fact] + public async Task Create_request_from_overrided_virtual_method() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.ReadContentAsAsync(); + var request = server.CreateHttpApiRequest(controller => controller.VirtualMethod()); - response.Should().NotBeNull(); - response.TestId.Should().Be(guid); - response.Person.Should().NotBeNull(); - response.Person.FirstName.Should().Be(person.FirstName); - response.Person.LastName.Should().Be(person.LastName); - } + var responseMessage = await request.GetAsync(); - [Fact] - public async Task create_request_supporting_template_with_serveral_colon() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); - const int param1 = 1; - const int param2 = 2; + responseMessage.EnsureSuccessStatusCode(); + var content = await responseMessage.Content.ReadAsStringAsync(); + content.Should().Be(ImplementationAbstractController.VIRTUAL_METHOD_RESULT); + } - var request = server.CreateHttpApiRequest(controller => controller.GetWithSeveralColon(param1, param2)); + [Fact] + public async Task Create_request_from_not_overrided_virtual_method() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - var responseMessage = await request.GetAsync(); + var request = server.CreateHttpApiRequest(controller => controller.Virtual2Method()); - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.Content.ReadAsStringAsync(); + var responseMessage = await request.GetAsync(); - response.Should().NotBeNull().And.Be($"{param1}/{param2}"); - } + responseMessage.EnsureSuccessStatusCode(); + responseMessage.IsSuccessStatusCode.Should().BeTrue(); + } - [Fact] - public async Task create_request_from_implemented_abstract_method() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + [Fact] + public async Task Create_request_with_list_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - var request = server.CreateHttpApiRequest(controller => controller.AbstractMethod()); + var param = new string[] { "one", "two" }; - var responseMessage = await request.GetAsync(); + var request = server.CreateHttpApiRequest(controller => controller.GetWithListParam(param)); - responseMessage.EnsureSuccessStatusCode(); - responseMessage.IsSuccessStatusCode.Should().BeTrue(); - } + var responseMessage = await request.GetAsync(); - [Fact] - public async Task create_request_from_overrided_virtual_method() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.ReadContentAsAsync>(); - var request = server.CreateHttpApiRequest(controller => controller.VirtualMethod()); + response.Should().BeEquivalentTo(param); + } - var responseMessage = await request.GetAsync(); + [Fact] + public async Task Create_get_request_with_cancellation_token_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - responseMessage.EnsureSuccessStatusCode(); - var content = await responseMessage.Content.ReadAsStringAsync(); - content.Should().Be(ImplementationAbstractController.VIRTUAL_METHOD_RESULT); - } + var param = "one"; + var source = new CancellationTokenSource(); + var token = source.Token; - [Fact] - public async Task create_request_from_not_overrided_virtual_method() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var request = server.CreateHttpApiRequest(controller => controller.GetWithCancellationToken(param, token)); + var responseMessage = await request.GetAsync(); - var request = server.CreateHttpApiRequest(controller => controller.Virtual2Method()); + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.Content.ReadAsStringAsync(); + response.Should().Be(param); + } - var responseMessage = await request.GetAsync(); + [Fact] + public async Task Create_post_request_with_cancellation_token_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - responseMessage.EnsureSuccessStatusCode(); - responseMessage.IsSuccessStatusCode.Should().BeTrue(); - } + var param = "one"; + var source = new CancellationTokenSource(); + var token = source.Token; - [Fact] - public async Task Create_request_with_list_parameter() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var request = server.CreateHttpApiRequest(controller => controller.PostWithCancellationToken(param, token)); + var responseMessage = await request.PostAsync(); - var param = new string[] { "one", "two" }; + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.Content.ReadAsStringAsync(); + response.Should().Be(param); + } - var request = server.CreateHttpApiRequest(controller => controller.GetWithListParam(param)); + [Fact] + public async Task Create_request_with_action_name_in_route() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - var responseMessage = await request.GetAsync(); + var request = server.CreateHttpApiRequest(controller => controller.ActionNameInRoute()); - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.ReadContentAsAsync>(); + request.GetConfiguredAddress().Should().Be($"{BASE_PATH_BUGS}/{nameof(BugsController.ActionNameInRoute)}".ToLower()); - response.Should().BeEquivalentTo(param); - } + var responseMessage = await request.GetAsync(); + responseMessage.EnsureSuccessStatusCode(); + } - [Fact] - public async Task Create_get_request_with_cancellation_token_parameter() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); - - var param = "one"; - var source = new CancellationTokenSource(); - var token = source.Token; - - var request = server.CreateHttpApiRequest(controller => controller.GetWithCancellationToken(param, token)); - var responseMessage = await request.GetAsync(); - - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.Content.ReadAsStringAsync(); - response.Should().Be(param); - } + [Fact] + public async Task Create_get_request_with_object_with_list() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public async Task Create_post_request_with_cancellation_token_parameter() + var model = new ParamWithList { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + Values = new List() { "one", "two" } + }; - var param = "one"; - var source = new CancellationTokenSource(); - var token = source.Token; + var request = server.CreateHttpApiRequest(controller => controller.GetWithObjectWithList(model)); + var responseMessage = await request.GetAsync(); - var request = server.CreateHttpApiRequest(controller => controller.PostWithCancellationToken(param, token)); - var responseMessage = await request.PostAsync(); + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.ReadContentAsAsync(); + response.Values.Should().Equal(model.Values); + } - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.Content.ReadAsStringAsync(); - response.Should().Be(param); - } + [Fact] + public async Task Create_post_request_with_object_with_list() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public async Task Create_request_with_action_name_in_route() + var model = new ParamWithList { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + Values = new List() { "one", "two" } + }; - var request = server.CreateHttpApiRequest(controller => controller.ActionNameInRoute()); + var request = server.CreateHttpApiRequest(controller => controller.PostWithObjectWithList(model)); + var responseMessage = await request.PostAsync(); - request.GetConfiguredAddress().Should().Be($"{BASE_PATH_BUGS}/{nameof(BugsController.ActionNameInRoute)}".ToLower()); + await responseMessage.IsSuccessStatusCodeOrThrow(); + var response = await responseMessage.ReadContentAsAsync(); + response.Values.Should().Equal(model.Values); + } - var responseMessage = await request.GetAsync(); - responseMessage.EnsureSuccessStatusCode(); - } + [Fact] + public async Task Create_post_request_with_list() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var model = new string[] { "one", "two" }; + + var request = server.CreateHttpApiRequest(controller => controller.PostWithListParam(model)); + var responseMessage = await request.PostAsync(); - private class PrivateNonControllerClass + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.ReadContentAsAsync>(); + response.Should().Equal(model); + } + + private class PrivateNonControllerClass + { + public int SomeAction() { - public int SomeAction() - { - return 0; - } + return 0; } + } - public class PublicNonControllerClass + public class PublicNonControllerClass + { + public int SomeAction() { - public int SomeAction() - { - return 0; - } + return 0; } + } - [NonController()] - public class WithNonControllerAttributeNonControllerClass + [NonController()] + public class WithNonControllerAttributeNonControllerClass + { + public int SomeAction() { - public int SomeAction() - { - return 0; - } + return 0; } + } - public class AbstractNonControllerClass + public class AbstractNonControllerClass + { + public int SomeAction() { - public int SomeAction() - { - return 0; - } + return 0; } + } - public class WithInvalidSufixNonControllerClass + public class WithInvalidSufixNonControllerClass + { + public int SomeAction() { - public int SomeAction() - { - return 0; - } + return 0; } } } \ No newline at end of file From 99f0c8a7470011412e734e3c83198039354804c2 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Wed, 1 Mar 2023 18:23:40 +0100 Subject: [PATCH 08/23] DateTime as a primitive type but using custom formatter (#81) --- .../ComplexParameterActionTokenizer.cs | 2 +- .../EnumerableParameterActionTokenizer.cs | 6 +- .../PrimitiveParameterActionTokenizer.cs | 29 ++- .../Routing/Tokenizers/TypeExtensions.cs | 8 +- .../Routing/Builders/BugsController.cs | 48 +++-- .../Routing/Models/ParamWithList.cs | 17 +- .../Routing/TestServerExtensionsTests.cs | 184 +++++++++++++++++- 7 files changed, 247 insertions(+), 47 deletions(-) diff --git a/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs b/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs index d1b5520..b575429 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs @@ -40,7 +40,7 @@ public void AddTokens(TestServerAction action, TestServerTokenColle } else { - tokenValue = value.ToString(); + tokenValue = PrimitiveParameterActionTokenizer.PrimitiveValueToString(value); } tokens.AddToken(tokenName, tokenValue, isConventional: false); diff --git a/src/Acheve.TestHost/Routing/Tokenizers/EnumerableParameterActionTokenizer.cs b/src/Acheve.TestHost/Routing/Tokenizers/EnumerableParameterActionTokenizer.cs index f3dc9fa..f440790 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/EnumerableParameterActionTokenizer.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/EnumerableParameterActionTokenizer.cs @@ -20,8 +20,8 @@ public void AddTokens(TestServerAction action, TestServerTokenColle continue; } - var arrayValues = (Array)action.ArgumentValues[i].Instance; - if (arrayValues == null || arrayValues.Length == 0) + var arrayValues = (IList)action.ArgumentValues[i].Instance; + if (arrayValues == null || arrayValues.Count == 0) { continue; } @@ -38,7 +38,7 @@ public static string GetTokenValue(IList array, string tokenName) foreach (var element in array) { - list.Add(element.ToString()); + list.Add(PrimitiveParameterActionTokenizer.PrimitiveValueToString(element)); } return string.Join($"&{tokenName}=", list); diff --git a/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs b/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs index 5f637cd..4455167 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs @@ -15,12 +15,13 @@ public void AddTokens(TestServerAction action, TestServerTokenColle for (int i = 0; i < parameters.Length; i++) { - if (IgnoreHeader(parameters[i])) + var parameter = parameters[i]; + if (IgnoreHeader(parameter)) { continue; } - Type parameterType = parameters[i].ParameterType; + Type parameterType = parameter.ParameterType; if (!parameterType.IsPrimitiveType()) { continue; @@ -32,15 +33,25 @@ public void AddTokens(TestServerAction action, TestServerTokenColle continue; } - string tokenName = parameters[i].Name.ToLowerInvariant(); - tokens.AddToken(tokenName, tokenValue.ToString(), isConventional: false); + string tokenName = parameter.Name.ToLowerInvariant(); + if (parameterType.IsDateTime()) + { + tokens.AddToken(tokenName, PrimitiveValueToString(tokenValue), isConventional: false); + } + else + { + tokens.AddToken(tokenName, PrimitiveValueToString(tokenValue), isConventional: false); + } } } - private static bool IgnoreHeader(ParameterInfo parameter) - { - object[] attributes = parameter.GetCustomAttributes(false); + public static string PrimitiveValueToString(object value) + => value.GetType().IsDateTime() ? + ((DateTime)value).ToString("yyyy/MM/ddTHH:mm:ss.fff") : + value.ToString(); - return attributes.Any(a => a.GetType() == typeof(FromHeaderAttribute)); - } + private static bool IgnoreHeader(ParameterInfo parameter) + => parameter + .GetCustomAttributes(false) + .Any(a => a.GetType() == typeof(FromHeaderAttribute)); } \ No newline at end of file diff --git a/src/Acheve.TestHost/Routing/Tokenizers/TypeExtensions.cs b/src/Acheve.TestHost/Routing/Tokenizers/TypeExtensions.cs index 91836e9..73e8792 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/TypeExtensions.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/TypeExtensions.cs @@ -12,7 +12,13 @@ internal static bool IsPrimitiveType(this Type typeToInspect) return type.IsPrimitive || type == typeof(string) || type == typeof(decimal) - || type == typeof(Guid); + || type == typeof(Guid) + || type.IsDateTime(); + } + + internal static bool IsDateTime(this Type typeToInspect) + { + return typeToInspect == typeof(DateTime); } internal static bool IsEnumerable(this Type typeToInspect) diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs b/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs index a6c62c3..fe3ab40 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs @@ -47,7 +47,7 @@ public ActionResult StringArraySupport([FromQuery] string[] param1) } [HttpGet("arrayPerson")] - public ActionResult PersonArraySupport([FromQuery] Person[] param1) + public ActionResult PersonArraySupport([FromQuery] Person[] param1) { return Ok(param1); } @@ -64,12 +64,6 @@ public ActionResult GetWithSeveralColon(int param1, int param2) return Ok($"{param1}/{param2}"); } - [HttpGet(nameof(GetWithListParam))] - public ActionResult> GetWithListParam([FromQuery] IEnumerable param) - { - return Ok(param); - } - [HttpGet] [Route("[action]")] public ActionResult> ActionNameInRoute() @@ -77,21 +71,35 @@ public ActionResult> ActionNameInRoute() return Ok(); } - [HttpGet(nameof(GetWithObjectWithList))] - public ActionResult GetWithObjectWithList([FromQuery] ParamWithList param) - { - return Ok(param); - } + [HttpGet(nameof(GetWithObject))] + public ActionResult GetWithObject([FromQuery] ParamWithSeveralTypes param) + => Ok(param); + + [HttpPost(nameof(PostWithObject))] + public ActionResult PostWithObject([FromBody] ParamWithSeveralTypes param) + => Ok(param); + + [HttpGet(nameof(GetWithListParam))] + public ActionResult> GetWithListParam([FromQuery] IEnumerable param) + => Ok(param); [HttpPost(nameof(PostWithListParam))] public ActionResult> PostWithListParam([FromBody] IEnumerable param) - { - return Ok(param); - } + => Ok(param); - [HttpPost(nameof(PostWithObjectWithList))] - public ActionResult PostWithObjectWithList([FromBody] ParamWithList param) - { - return Ok(param); - } + [HttpGet(nameof(GetWithDatetimeParam))] + public ActionResult GetWithDatetimeParam([FromQuery] DateTime param) + => Ok(param); + + [HttpPost(nameof(PostWithDatetimeParam))] + public ActionResult PostWithDatetimeParam([FromBody] DateTime param) + => Ok(param); + + [HttpGet(nameof(GetWithDatetimeListParam))] + public ActionResult GetWithDatetimeListParam([FromQuery] IEnumerable param) + => Ok(param); + + [HttpPost(nameof(PostWithDatetimeListParam))] + public ActionResult PostWithDatetimeListParam([FromBody] IEnumerable param) + => Ok(param); } \ No newline at end of file diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs b/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs index 2269bb5..eb7b941 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs @@ -1,8 +1,19 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace UnitTests.Acheve.TestHost.Routing.Models; -public class ParamWithList +public class ParamWithSeveralTypes { - public IEnumerable Values { get; set; } + public string StringValue { get; set; } + public int IntValue { get; set; } + public double DoubleValue { get; set; } + public bool BooleanValue { get; set; } + public DateTime DateTimeValue { get; set; } + + public IEnumerable StringValues { get; set; } + public IEnumerable IntValues { get; set; } + public IEnumerable DoubleValues { get; set; } + public IEnumerable BooleanValues { get; set; } + public IEnumerable DateTimeValues { get; set; } } diff --git a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs index 4dea50a..a9349e7 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs @@ -1723,17 +1723,17 @@ public async Task Create_get_request_with_object_with_list() .UseDefaultStartup() .Build(); - var model = new ParamWithList + var model = new ParamWithSeveralTypes { - Values = new List() { "one", "two" } + StringValues = new List() { "one", "two" } }; - var request = server.CreateHttpApiRequest(controller => controller.GetWithObjectWithList(model)); + var request = server.CreateHttpApiRequest(controller => controller.GetWithObject(model)); var responseMessage = await request.GetAsync(); responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.ReadContentAsAsync(); - response.Values.Should().Equal(model.Values); + var response = await responseMessage.ReadContentAsAsync(); + response.StringValues.Should().Equal(model.StringValues); } [Fact] @@ -1743,17 +1743,17 @@ public async Task Create_post_request_with_object_with_list() .UseDefaultStartup() .Build(); - var model = new ParamWithList + var model = new ParamWithSeveralTypes { - Values = new List() { "one", "two" } + StringValues = new List() { "one", "two" } }; - var request = server.CreateHttpApiRequest(controller => controller.PostWithObjectWithList(model)); + var request = server.CreateHttpApiRequest(controller => controller.PostWithObject(model)); var responseMessage = await request.PostAsync(); await responseMessage.IsSuccessStatusCodeOrThrow(); - var response = await responseMessage.ReadContentAsAsync(); - response.Values.Should().Equal(model.Values); + var response = await responseMessage.ReadContentAsAsync(); + response.StringValues.Should().Equal(model.StringValues); } [Fact] @@ -1773,6 +1773,170 @@ public async Task Create_post_request_with_list() response.Should().Equal(model); } + [Fact] + public async Task Create_get_request_with_datetime() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var model = new DateTime(2023, 03, 01); + + var request = server.CreateHttpApiRequest(controller => controller.GetWithDatetimeParam(model)); + var responseMessage = await request.GetAsync(); + + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.ReadContentAsAsync(); + response.Should().Be(model); + } + + [Fact] + public async Task Create_get_request_with_datetime_in_object() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var model = new ParamWithSeveralTypes + { + DateTimeValue = new DateTime(2023, 03, 01) + }; + + var request = server.CreateHttpApiRequest(controller => controller.GetWithObject(model)); + var responseMessage = await request.GetAsync(); + + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.ReadContentAsAsync(); + response.DateTimeValue.Should().Be(model.DateTimeValue); + } + + [Fact] + public async Task Create_get_request_with_datetime_list_in_object() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var model = new ParamWithSeveralTypes + { + DateTimeValues = new List + { + new DateTime(2023, 03, 01), + new DateTime(2023, 05, 04) + } + }; + + var request = server.CreateHttpApiRequest(controller => controller.GetWithObject(model)); + var responseMessage = await request.GetAsync(); + + await responseMessage.IsSuccessStatusCodeOrThrow(); + var response = await responseMessage.ReadContentAsAsync(); + response.DateTimeValues.Should().Equal(model.DateTimeValues); + } + + [Fact] + public async Task Create_get_request_with_datetime_list() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var model = new List + { + new DateTime(2023, 03, 01), + new DateTime(2023, 05, 04) + }; + + var request = server.CreateHttpApiRequest(controller => controller.GetWithDatetimeListParam(model)); + var responseMessage = await request.GetAsync(); + + await responseMessage.IsSuccessStatusCodeOrThrow(); + var response = await responseMessage.ReadContentAsAsync>(); + response.Should().Equal(model); + } + + [Fact] + public async Task Create_post_request_with_datetime() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var model = new DateTime(2023, 03, 01); + + var request = server.CreateHttpApiRequest(controller => controller.PostWithDatetimeParam(model)); + var responseMessage = await request.PostAsync(); + + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.ReadContentAsAsync(); + response.Should().Be(model); + } + + [Fact] + public async Task Create_post_request_with_datetime_in_object() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var model = new ParamWithSeveralTypes + { + DateTimeValue = new DateTime(2023, 03, 01) + }; + + var request = server.CreateHttpApiRequest(controller => controller.PostWithObject(model)); + var responseMessage = await request.PostAsync(); + + await responseMessage.IsSuccessStatusCodeOrThrow(); + var response = await responseMessage.ReadContentAsAsync(); + response.DateTimeValue.Should().Be(model.DateTimeValue); + } + + [Fact] + public async Task Create_post_request_with_datetime_list_in_object() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var model = new ParamWithSeveralTypes + { + DateTimeValues = new List + { + new DateTime(2023, 03, 01), + new DateTime(2023, 05, 04) + } + }; + + var request = server.CreateHttpApiRequest(controller => controller.PostWithObject(model)); + var responseMessage = await request.PostAsync(); + + await responseMessage.IsSuccessStatusCodeOrThrow(); + var response = await responseMessage.ReadContentAsAsync(); + response.DateTimeValues.Should().Equal(model.DateTimeValues); + } + + [Fact] + public async Task Create_post_request_with_datetime_list() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var model = new List + { + new DateTime(2023, 03, 01), + new DateTime(2023, 05, 04) + }; + + var request = server.CreateHttpApiRequest(controller => controller.PostWithDatetimeListParam(model)); + var responseMessage = await request.PostAsync(); + + await responseMessage.IsSuccessStatusCodeOrThrow(); + var response = await responseMessage.ReadContentAsAsync>(); + response.Should().Equal(model); + } + private class PrivateNonControllerClass { public int SomeAction() From ed138ab2e4691dcb71dc145d8125af7bb7f0ed71 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Sun, 12 Mar 2023 17:28:51 +0100 Subject: [PATCH 09/23] Fixing codeQL alerts (#82) --- .../IncludeContentAsFormUrlEncoded.cs | 93 +++++++++---------- .../Tokenizers/TestServerTokenCollection.cs | 89 ++++++++---------- src/Acheve.TestHost/Routing/UriDiscover.cs | 17 ++-- .../Security/DefaultClaimsEncoder.cs | 53 ++++++----- .../Routing/TestServerExtensionsTests.cs | 4 +- 5 files changed, 117 insertions(+), 139 deletions(-) diff --git a/src/Acheve.TestHost/IncludeContentAsFormUrlEncoded.cs b/src/Acheve.TestHost/IncludeContentAsFormUrlEncoded.cs index 1e1d773..717ab72 100644 --- a/src/Acheve.TestHost/IncludeContentAsFormUrlEncoded.cs +++ b/src/Acheve.TestHost/IncludeContentAsFormUrlEncoded.cs @@ -5,63 +5,62 @@ using System.Linq; using System.Net.Http; -namespace Microsoft.AspNetCore.TestHost -{ - /// - /// An implementation of that includes - /// the [FromForm] parameter as - /// - public class IncludeContentAsFormUrlEncoded : RequestContentOptions - { - /// - public override bool IncludeFromBodyAsContent => false; - - /// - public override bool IncludeFromFormAsContent => true; +namespace Microsoft.AspNetCore.TestHost; - /// - public override Func ContentBuilder => - content => new FormUrlEncodedContent(ToKeyValue(content)); +/// +/// An implementation of that includes +/// the [FromForm] parameter as +/// +public class IncludeContentAsFormUrlEncoded : RequestContentOptions +{ + /// + public override bool IncludeFromBodyAsContent => false; - private IDictionary ToKeyValue(object metaToken) - { - if (metaToken == null) - { - return null; - } + /// + public override bool IncludeFromFormAsContent => true; - if (!(metaToken is JToken token)) - { - return ToKeyValue(JObject.FromObject(metaToken)); - } + /// + public override Func ContentBuilder => + content => new FormUrlEncodedContent(ToKeyValue(content)); - if (token.HasValues) - { - var contentData = new Dictionary(); - foreach (var child in token.Children().ToList()) - { - var childContent = ToKeyValue(child); - if (childContent != null) - { - contentData = contentData.Concat(childContent) - .ToDictionary(k => k.Key, v => v.Value); - } - } + private IDictionary ToKeyValue(object metaToken) + { + if (metaToken is null) + { + return null; + } - return contentData; - } + if (metaToken is not JToken token) + { + return ToKeyValue(JObject.FromObject(metaToken)); + } - var jValue = token as JValue; - if (jValue?.Value == null) + if (token.HasValues) + { + var contentData = new Dictionary(); + var childrenContent = token.Children() + .AsEnumerable() + .Select(ToKeyValue) + .Where(childrenContent => childrenContent is not null); + foreach (var childContent in childrenContent) { - return null; + contentData = contentData.Concat(childContent) + .ToDictionary(k => k.Key, v => v.Value); } - var value = jValue?.Type == JTokenType.Date ? - jValue?.ToString("o", CultureInfo.InvariantCulture) : - jValue?.ToString(CultureInfo.InvariantCulture); + return contentData; + } - return new Dictionary { { token.Path, value } }; + var jValue = token as JValue; + if (jValue?.Value == null) + { + return null; } + + var value = jValue?.Type == JTokenType.Date ? + jValue?.ToString("o", CultureInfo.InvariantCulture) : + jValue?.ToString(CultureInfo.InvariantCulture); + + return new Dictionary { { token.Path, value } }; } } diff --git a/src/Acheve.TestHost/Routing/Tokenizers/TestServerTokenCollection.cs b/src/Acheve.TestHost/Routing/Tokenizers/TestServerTokenCollection.cs index f6dced7..91a6e5b 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/TestServerTokenCollection.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/TestServerTokenCollection.cs @@ -1,71 +1,56 @@ using System.Collections.Generic; using System.Linq; -namespace Acheve.TestHost.Routing.Tokenizers +namespace Acheve.TestHost.Routing.Tokenizers; + +public class TestServerTokenCollection { - public class TestServerTokenCollection + private readonly Dictionary _activeTokens = new(); + + public bool ContainsToken(string tokenName) { - Dictionary _activeTokens; + return _activeTokens.ContainsKey(tokenName); + } - public TestServerTokenCollection() + public void AddToken(string tokenName, string tokenValue, bool isConventional = false) + { + if (!ContainsToken(tokenName)) { - _activeTokens = new Dictionary(); + _activeTokens.Add(tokenName, + new TestServerToken(tokenName, tokenValue, isConventional)); } + } - public bool ContainsToken(string tokenName) - { - return _activeTokens.ContainsKey(tokenName); - } + public TestServerToken Find(string tokenName) + => ContainsToken(tokenName) ? _activeTokens[tokenName] : default; - public void AddToken(string tokenName, string tokenValue, bool isConventional = false) - { - if (!ContainsToken(tokenName)) - { - _activeTokens.Add(tokenName, - new TestServerToken(tokenName, tokenValue, isConventional)); - } - } + public IEnumerable GetConventionalTokens() + { + return _activeTokens.Values + .Where(token => token.IsConventional); + } - public TestServerToken Find(string tokenName) - { - if (ContainsToken(tokenName)) - { - return _activeTokens[tokenName]; - } - else - { - return default(TestServerToken); - } - } + public IEnumerable GetNonConventionalTokens() + { + return _activeTokens.Values + .Where(token => !token.IsConventional); + } - public IEnumerable GetConventionalTokens() - { - return _activeTokens.Values - .Where(token => token.IsConventional); - } + public IEnumerable GetUnusedTokens() + { + return _activeTokens.Values + .Where(token => !token.IsConventional && !token.Used); + } - public IEnumerable GetNonConventionalTokens() - { - return _activeTokens.Values - .Where(token => !token.IsConventional); - } + public static TestServerTokenCollection FromDictionary(Dictionary tokenKV) + { + var tokens = new TestServerTokenCollection(); - public IEnumerable GetUnusedTokens() + foreach (var item in tokenKV) { - return _activeTokens.Values - .Where(token => !token.IsConventional && !token.Used); + tokens.AddToken(item.Key, item.Value, isConventional: false); } - public static TestServerTokenCollection FromDictionary(Dictionary tokenKV) - { - var tokens = new TestServerTokenCollection(); - - foreach (var item in tokenKV) - { - tokens.AddToken(item.Key, item.Value, isConventional: false); - } - - return tokens; - } + return tokens; } } diff --git a/src/Acheve.TestHost/Routing/UriDiscover.cs b/src/Acheve.TestHost/Routing/UriDiscover.cs index 97b3ee2..574708b 100644 --- a/src/Acheve.TestHost/Routing/UriDiscover.cs +++ b/src/Acheve.TestHost/Routing/UriDiscover.cs @@ -40,19 +40,14 @@ public static string Discover(TestServerAction action, object token var template = (verbsTemplate ?? routeTemplate); - if (template != null) + if (template is null) { - if (IsTildeOverride(template, out string overrideTemplate)) - { - return $"{overrideTemplate}{queryStringTemplate}"; - } - else - { - return $"{controllerTemplate}/{template}{queryStringTemplate}"; - } + return $"{controllerTemplate}{queryStringTemplate}"; } - return $"{controllerTemplate}{queryStringTemplate}"; + return IsTildeOverride(template, out string overrideTemplate) ? + $"{overrideTemplate}{queryStringTemplate}" : + $"{controllerTemplate}/{template}{queryStringTemplate}"; } static TestServerTokenCollection AddTokens(TestServerAction action, object tokenValues) @@ -85,7 +80,7 @@ static bool IsTildeOverride(string template, out string overrideTemplate) if (isTildeOverride) { - overrideTemplate = template.Substring(2); // remove ~/ + overrideTemplate = template[2..]; // remove ~/ } return isTildeOverride; diff --git a/src/Acheve.TestHost/Security/DefaultClaimsEncoder.cs b/src/Acheve.TestHost/Security/DefaultClaimsEncoder.cs index d8618db..6c1548f 100644 --- a/src/Acheve.TestHost/Security/DefaultClaimsEncoder.cs +++ b/src/Acheve.TestHost/Security/DefaultClaimsEncoder.cs @@ -4,41 +4,40 @@ using System.Linq; using System.Security.Claims; -namespace Acheve.TestHost +namespace Acheve.TestHost; + +internal static class DefautClaimsEncoder { - internal static class DefautClaimsEncoder + public static string Encode(IEnumerable claims) { - public static string Encode(IEnumerable claims) - { - var ticket = new AuthenticationTicket( - principal: new ClaimsPrincipal( - new ClaimsIdentity(claims)), - authenticationScheme: "TestServer"); + var ticket = new AuthenticationTicket( + principal: new ClaimsPrincipal( + new ClaimsIdentity(claims)), + authenticationScheme: "TestServer"); - var serializer = new TicketSerializer(); - var bytes = serializer.Serialize(ticket); + var serializer = new TicketSerializer(); + var bytes = serializer.Serialize(ticket); - return Convert.ToBase64String(bytes); - } + return Convert.ToBase64String(bytes); + } - public static IEnumerable Decode(string encodedValue) + public static IEnumerable Decode(string encodedValue) + { + if (string.IsNullOrEmpty(encodedValue)) { - if (string.IsNullOrEmpty(encodedValue)) - { - return Enumerable.Empty(); - } + return Enumerable.Empty(); + } - var serializer = new TicketSerializer(); - try - { - var ticket = serializer.Deserialize(Convert.FromBase64String(encodedValue)); + var serializer = new TicketSerializer(); + try + { + var ticket = serializer.Deserialize(Convert.FromBase64String(encodedValue)); - return ticket.Principal.Claims; - } - catch (Exception) - { - return Enumerable.Empty(); - } + return ticket.Principal.Claims; + } + catch + { + return Enumerable.Empty(); } } } diff --git a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs index a9349e7..de7acbb 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs @@ -1671,7 +1671,7 @@ public async Task Create_get_request_with_cancellation_token_parameter() .Build(); var param = "one"; - var source = new CancellationTokenSource(); + using var source = new CancellationTokenSource(); var token = source.Token; var request = server.CreateHttpApiRequest(controller => controller.GetWithCancellationToken(param, token)); @@ -1690,7 +1690,7 @@ public async Task Create_post_request_with_cancellation_token_parameter() .Build(); var param = "one"; - var source = new CancellationTokenSource(); + using var source = new CancellationTokenSource(); var token = source.Token; var request = server.CreateHttpApiRequest(controller => controller.PostWithCancellationToken(param, token)); From de91f824ee17ae09cd3d973f1648ea346825d3c9 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Sun, 19 Mar 2023 17:57:46 +0100 Subject: [PATCH 10/23] feature: Get url (#83) --- .../RequestBuilderExtensions.cs | 232 +++++++++-------- .../Routing/RequestBuilderExtensionsTests.cs | 233 ++++++++++-------- 2 files changed, 247 insertions(+), 218 deletions(-) diff --git a/src/Acheve.TestHost/RequestBuilderExtensions.cs b/src/Acheve.TestHost/RequestBuilderExtensions.cs index 844558d..7878385 100644 --- a/src/Acheve.TestHost/RequestBuilderExtensions.cs +++ b/src/Acheve.TestHost/RequestBuilderExtensions.cs @@ -5,135 +5,149 @@ using System.Security.Claims; using System.Text.RegularExpressions; -namespace Microsoft.AspNetCore.TestHost +namespace Microsoft.AspNetCore.TestHost; + +public static class RequestBuilderExtensions { - public static class RequestBuilderExtensions + /// + /// Adds an Authentication header to the with the provided claims using + /// "TestServer" as authentication scheme. + /// + /// The requestBuilder instance + /// The claims collection that represents the user identity + /// + public static RequestBuilder WithIdentity(this RequestBuilder requestBuilder, IEnumerable claims) + { + return requestBuilder.WithIdentity( + claims, + TestServerDefaults.AuthenticationScheme); + } + + /// + /// Adds an Authentication header to the with the provided claims + /// and authentication scheme. + /// + /// The requestBuilder instance + /// The claims collection that represents the user identity + /// The authentication scheme + /// + public static RequestBuilder WithIdentity(this RequestBuilder requestBuilder, IEnumerable claims, string scheme) { - /// - /// Adds an Authentication header to the with the provided claims using - /// "TestServer" as authentication scheme. - /// - /// The requestBuilder instance - /// The claims collection that represents the user identity - /// - public static RequestBuilder WithIdentity(this RequestBuilder requestBuilder, IEnumerable claims) + if (string.IsNullOrWhiteSpace(scheme)) { - return requestBuilder.WithIdentity( - claims, - TestServerDefaults.AuthenticationScheme); + throw new ArgumentNullException(nameof(scheme)); } - /// - /// Adds an Authentication header to the with the provided claims - /// and authentication scheme. - /// - /// The requestBuilder instance - /// The claims collection that represents the user identity - /// The authentication scheme - /// - public static RequestBuilder WithIdentity(this RequestBuilder requestBuilder, IEnumerable claims, string scheme) - { - if (string.IsNullOrWhiteSpace(scheme)) - { - throw new ArgumentNullException(nameof(scheme)); - } + var headerName = AuthenticationHeaderHelper.GetHeaderName(scheme); - var headerName = AuthenticationHeaderHelper.GetHeaderName(scheme); + requestBuilder.AddHeader( + headerName, + $"{scheme} {DefautClaimsEncoder.Encode(claims)}"); - requestBuilder.AddHeader( - headerName, - $"{scheme} {DefautClaimsEncoder.Encode(claims)}"); + return requestBuilder; + } - return requestBuilder; - } + /// + /// Get the header string for the provided claims + /// + /// The requestBuilder instance + /// The claims collection that represents the user identity + /// + public static string GetHeaderForIdentity(this RequestBuilder requestBuilder, IEnumerable claims) + { + return requestBuilder.GetHeaderForIdentity( + claims, + TestServerDefaults.AuthenticationScheme); + } - /// - /// Get the header string for the provided claims - /// - /// The requestBuilder instance - /// The claims collection that represents the user identity - /// - public static string GetHeaderForIdentity(this RequestBuilder requestBuilder, IEnumerable claims) + /// + /// Get the header string for the provided claims + /// + /// The requestBuilder instance + /// The claims collection that represents the user identity + /// The authentication scheme + /// + public static string GetHeaderForIdentity(this RequestBuilder _, IEnumerable claims, string scheme) + { + if (string.IsNullOrWhiteSpace(scheme)) { - return requestBuilder.GetHeaderForIdentity( - claims, - TestServerDefaults.AuthenticationScheme); + throw new ArgumentNullException(nameof(scheme)); } - /// - /// Get the header string for the provided claims - /// - /// The requestBuilder instance - /// The claims collection that represents the user identity - /// The authentication scheme - /// - public static string GetHeaderForIdentity(this RequestBuilder requestBuilder, IEnumerable claims, string scheme) - { - if (string.IsNullOrWhiteSpace(scheme)) - { - throw new ArgumentNullException(nameof(scheme)); - } - - var headerName = AuthenticationHeaderHelper.GetHeaderName(scheme); + var headerName = AuthenticationHeaderHelper.GetHeaderName(scheme); - var header = new NameValueHeaderValue( - headerName, - $"{TestServerDefaults.AuthenticationScheme} {DefautClaimsEncoder.Encode(claims)}"); + var header = new NameValueHeaderValue( + headerName, + $"{TestServerDefaults.AuthenticationScheme} {DefautClaimsEncoder.Encode(claims)}"); - return header.ToString(); - } + return header.ToString(); + } - /// - /// Add the given parameter and value to the request - /// - /// Type of the value to be casted to string - /// The requestBuilder instance - /// Parameter name - /// Parameter value - /// RequestBuilder instance - public static RequestBuilder AddQueryParameter(this RequestBuilder requestBuilder, string name, T value) + /// + /// Add the given parameter and value to the request + /// + /// Type of the value to be casted to string + /// The requestBuilder instance + /// Parameter name + /// Parameter value + /// RequestBuilder instance + public static RequestBuilder AddQueryParameter(this RequestBuilder requestBuilder, string name, T value) + { + requestBuilder.And(configure => { - requestBuilder.And(configure => - { - var separatoChar = '?'; - if (configure.RequestUri.ToString().Contains(separatoChar)) - separatoChar = '&'; + var separatoChar = '?'; + if (configure.RequestUri.ToString().Contains(separatoChar)) + separatoChar = '&'; - configure.RequestUri = new Uri($"{configure.RequestUri}{separatoChar}{Uri.EscapeDataString(name)}={Uri.EscapeDataString(value.ToString())}", UriKind.Relative); - }); + configure.RequestUri = new Uri($"{configure.RequestUri}{separatoChar}{Uri.EscapeDataString(name)}={Uri.EscapeDataString(value.ToString())}", UriKind.Relative); + }); - return requestBuilder; - } + return requestBuilder; + } - /// - /// Remove the given parameter from the request - /// - /// The requestBuilder instance - /// Parameter name - /// RequestBuilder instance - public static RequestBuilder RemoveQueryParameter(this RequestBuilder requestBuilder, string name) + /// + /// Remove the given parameter from the request + /// + /// The requestBuilder instance + /// Parameter name + /// RequestBuilder instance + public static RequestBuilder RemoveQueryParameter(this RequestBuilder requestBuilder, string name) + { + requestBuilder.And(configure => { - requestBuilder.And(configure => - { - var regexOptions = RegexOptions.Compiled | RegexOptions.IgnoreCase; - var isTheLastParameter = new Regex(@$"[?|&]{name}=[^&]+$", regexOptions); - var isTheFistParamaeterAndHasOtherParamaters = new Regex(@$"[?]{name}=[^&]+[&]", regexOptions); - var isTheMiddleParameter = new Regex(@$"[&]{name}=[^&]+[&]", regexOptions); - - var newUri = configure.RequestUri.ToString() - .ReplaceRegex(isTheLastParameter, string.Empty) - .ReplaceRegex(isTheFistParamaeterAndHasOtherParamaters, "?") - .ReplaceRegex(isTheMiddleParameter, "&"); - - configure.RequestUri = new Uri(newUri, UriKind.Relative); - }); - - return requestBuilder; - } + var regexOptions = RegexOptions.Compiled | RegexOptions.IgnoreCase; + var isTheLastParameter = new Regex(@$"[?|&]{name}=[^&]+$", regexOptions); + var isTheFistParamaeterAndHasOtherParamaters = new Regex(@$"[?]{name}=[^&]+[&]", regexOptions); + var isTheMiddleParameter = new Regex(@$"[&]{name}=[^&]+[&]", regexOptions); - private static string ReplaceRegex(this string value, Regex regex, string replacement) - { - return regex.Replace(value, replacement); - } + var newUri = configure.RequestUri.ToString() + .ReplaceRegex(isTheLastParameter, string.Empty) + .ReplaceRegex(isTheFistParamaeterAndHasOtherParamaters, "?") + .ReplaceRegex(isTheMiddleParameter, "&"); + + configure.RequestUri = new Uri(newUri, UriKind.Relative); + }); + + return requestBuilder; + } + + /// + /// Get url from the HttpRequest + /// + /// The requestBuilder instance + /// Url + public static string GetUrl(this RequestBuilder requestBuilder) + { + string url = null; + requestBuilder.And( + configure => url = configure.RequestUri.ToString() + ); + + return url; + } + + private static string ReplaceRegex(this string value, Regex regex, string replacement) + { + return regex.Replace(value, replacement); } } diff --git a/tests/UnitTests/Acheve.TestHost/Routing/RequestBuilderExtensionsTests.cs b/tests/UnitTests/Acheve.TestHost/Routing/RequestBuilderExtensionsTests.cs index bfdcb4e..76f857c 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/RequestBuilderExtensionsTests.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/RequestBuilderExtensionsTests.cs @@ -6,154 +6,169 @@ using UnitTests.Acheve.TestHost.Builders; using Xunit; -namespace UnitTests.Acheve.TestHost.Routing +namespace UnitTests.Acheve.TestHost.Routing; + +public class RequestBuilderExtensionsTests { - public class RequestBuilderExtensionsTests + public const string BASE_PATH = "api/values/"; + + [Fact] + public async Task Create_request_and_add_parameter() { - public const string BASE_PATH = "api/values/"; + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public async Task Create_request_and_add_parameter() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var id = new Random().Next(1, 100); - var id = new Random().Next(1, 100); + var request = server.CreateHttpApiRequest(controller => controller.GetParameterFromRequestQuery()) + .AddQueryParameter(nameof(id), id); - var request = server.CreateHttpApiRequest(controller => controller.GetParameterFromRequestQuery()) - .AddQueryParameter(nameof(id), id); + var responseMessage = await request.GetAsync(); - var responseMessage = await request.GetAsync(); + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.Content.ReadAsStringAsync(); - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.Content.ReadAsStringAsync(); + response.Should().Be(id.ToString()); + } - response.Should().Be(id.ToString()); - } + [Fact] + public async Task Create_request_and_add_additional_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public async Task Create_request_and_add_additional_parameter() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var id1 = new Random().Next(1, 100); + var id2 = new Random().Next(1, 100); - var id1 = new Random().Next(1, 100); - var id2 = new Random().Next(1, 100); + var request = server.CreateHttpApiRequest(controller => controller.GetAdditionalParameterFromRequestQuery(id1)) + .AddQueryParameter(nameof(id2), id2); - var request = server.CreateHttpApiRequest(controller => controller.GetAdditionalParameterFromRequestQuery(id1)) - .AddQueryParameter(nameof(id2), id2); + var responseMessage = await request.GetAsync(); - var responseMessage = await request.GetAsync(); + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.Content.ReadAsStringAsync(); - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.Content.ReadAsStringAsync(); + response.Should().Be(JsonSerializer.Serialize(new { id1 = id1.ToString(), id2 = id2.ToString() })); + } - response.Should().Be(JsonSerializer.Serialize(new { id1 = id1.ToString(), id2 = id2.ToString() })); - } + [Fact] + public async Task Create_request_and_add_parameter_when_you_have_path_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public async Task Create_request_and_add_parameter_when_you_have_path_parameter() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var id1 = new Random().Next(1, 100); + var id2 = new Random().Next(1, 100); - var id1 = new Random().Next(1, 100); - var id2 = new Random().Next(1, 100); + var request = server.CreateHttpApiRequest(controller => controller.GetAdditionalParameterFromRequestQueryAndPath(id1)) + .AddQueryParameter(nameof(id2), id2); - var request = server.CreateHttpApiRequest(controller => controller.GetAdditionalParameterFromRequestQueryAndPath(id1)) - .AddQueryParameter(nameof(id2), id2); + var responseMessage = await request.GetAsync(); - var responseMessage = await request.GetAsync(); + responseMessage.EnsureSuccessStatusCode(); + var response = await responseMessage.Content.ReadAsStringAsync(); - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.Content.ReadAsStringAsync(); + response.Should().Be(JsonSerializer.Serialize(new { id1 = id1.ToString(), id2 = id2.ToString() })); + } - response.Should().Be(JsonSerializer.Serialize(new { id1 = id1.ToString(), id2 = id2.ToString() })); - } + [Fact] + public void Remove_parameter_when_you_have_one_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void Remove_parameter_when_you_have_one_parameter() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + const string PARAMETER_TO_REMOVE = "parameter1"; + const string URL = BASE_PATH + "?" + PARAMETER_TO_REMOVE + "=valueParameter1"; - const string PARAMETER_TO_REMOVE = "parameter1"; - const string URL = BASE_PATH + "?" + PARAMETER_TO_REMOVE + "=valueParameter1"; + var requestBuilder = server.CreateRequest(URL) + .RemoveQueryParameter(PARAMETER_TO_REMOVE); - var requestBuilder = server.CreateRequest(URL) - .RemoveQueryParameter(PARAMETER_TO_REMOVE); + var requestUrl = requestBuilder.GetRequest().RequestUri.ToString(); - var requestUrl = requestBuilder.GetRequest().RequestUri.ToString(); + const string EXPECTED_URL = BASE_PATH; + requestUrl.Should().Be(EXPECTED_URL); + } - const string EXPECTED_URL = BASE_PATH; - requestUrl.Should().Be(EXPECTED_URL); - } + [Fact] + public void Remove_last_parameter_when_you_have_two_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void Remove_last_parameter_when_you_have_two_parameter() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + const string PARAMETER_TO_REMOVE = "parameter2"; + const string URL_FIRST_PART = "?parameter1=valueParameter1"; + const string URL = BASE_PATH + URL_FIRST_PART + "&" + PARAMETER_TO_REMOVE + "=valueParameter2"; - const string PARAMETER_TO_REMOVE = "parameter2"; - const string URL_FIRST_PART = "?parameter1=valueParameter1"; - const string URL = BASE_PATH + URL_FIRST_PART + "&" + PARAMETER_TO_REMOVE + "=valueParameter2"; + var requestBuilder = server.CreateRequest(URL) + .RemoveQueryParameter(PARAMETER_TO_REMOVE); - var requestBuilder = server.CreateRequest(URL) - .RemoveQueryParameter(PARAMETER_TO_REMOVE); + var requestUrl = requestBuilder.GetRequest().RequestUri.ToString(); - var requestUrl = requestBuilder.GetRequest().RequestUri.ToString(); + const string EXPECTED_URL = BASE_PATH + URL_FIRST_PART; + requestUrl.Should().Be(EXPECTED_URL); + } - const string EXPECTED_URL = BASE_PATH + URL_FIRST_PART; - requestUrl.Should().Be(EXPECTED_URL); - } + [Fact] + public void Remove_first_parameter_when_you_have_two_parameter() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - [Fact] - public void Remove_first_parameter_when_you_have_two_parameter() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + const string PARAMETER_TO_REMOVE = "parameter1"; + const string URL_LAST_PART = "parameter2=valueParameter2"; + const string URL = BASE_PATH + "?" + PARAMETER_TO_REMOVE + "=valueParameter1&" + URL_LAST_PART; - const string PARAMETER_TO_REMOVE = "parameter1"; - const string URL_LAST_PART = "parameter2=valueParameter2"; - const string URL = BASE_PATH + "?" + PARAMETER_TO_REMOVE + "=valueParameter1&" + URL_LAST_PART; + var requestBuilder = server.CreateRequest(URL) + .RemoveQueryParameter(PARAMETER_TO_REMOVE); - var requestBuilder = server.CreateRequest(URL) - .RemoveQueryParameter(PARAMETER_TO_REMOVE); + var requestUrl = requestBuilder.GetRequest().RequestUri.ToString(); - var requestUrl = requestBuilder.GetRequest().RequestUri.ToString(); + const string EXPECTED_URL = BASE_PATH + "?" + URL_LAST_PART; + requestUrl.Should().Be(EXPECTED_URL); + } + + [Theory] + [InlineData("?parameter1=valueParameter1&", "parameter2=valueParameter2")] + [InlineData("?parameter1=valueParameter1&", "parameter2=valueParameter2¶meter3=valueParametere")] + [InlineData("?parameter1=valueParameter1¶meter2=valueParameter2&", "parameter3=valueParameter3")] + [InlineData("?parameter1=valueParameter1¶meter2=valueParameter2&", "parameter3=valueParameter3¶meter4=valueParameter4")] + public void Remove_parameter_when_is_beetween_parameters(string urlFirstPart, string urlLastPart) + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - const string EXPECTED_URL = BASE_PATH + "?" + URL_LAST_PART; - requestUrl.Should().Be(EXPECTED_URL); - } + const string PARAMETER_TO_REMOVE = "parameterA"; + string url = BASE_PATH + urlFirstPart + PARAMETER_TO_REMOVE + "=valueParameterA&" + urlLastPart; - [Theory] - [InlineData("?parameter1=valueParameter1&", "parameter2=valueParameter2")] - [InlineData("?parameter1=valueParameter1&", "parameter2=valueParameter2¶meter3=valueParametere")] - [InlineData("?parameter1=valueParameter1¶meter2=valueParameter2&", "parameter3=valueParameter3")] - [InlineData("?parameter1=valueParameter1¶meter2=valueParameter2&", "parameter3=valueParameter3¶meter4=valueParameter4")] - public void Remove_parameter_when_is_beetween_parameters(string urlFirstPart, string urlLastPart) - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); + var requestBuilder = server.CreateRequest(url) + .RemoveQueryParameter(PARAMETER_TO_REMOVE); - const string PARAMETER_TO_REMOVE = "parameterA"; - string url = BASE_PATH + urlFirstPart + PARAMETER_TO_REMOVE + "=valueParameterA&" + urlLastPart; + var requestUrl = requestBuilder.GetRequest().RequestUri.ToString(); + + string expectedUrl = BASE_PATH + urlFirstPart + urlLastPart; + requestUrl.Should().Be(expectedUrl); + } + + [Fact] + public void Create_request_and_get_url() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); - var requestBuilder = server.CreateRequest(url) - .RemoveQueryParameter(PARAMETER_TO_REMOVE); + var id = new Random().Next(1, 100); - var requestUrl = requestBuilder.GetRequest().RequestUri.ToString(); + var request = server.CreateHttpApiRequest(controller => controller.GetParameterFromRequestQuery()) + .AddQueryParameter(nameof(id), id); + var uri = request.GetUrl(); - string expectedUrl = BASE_PATH + urlFirstPart + urlLastPart; - requestUrl.Should().Be(expectedUrl); - } + uri.Should().Be($"api/values/getparameterfromrequestquery?id={id}"); } } From 15ea0b25a1f904c7cb408713e4acb4dc635988ea Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Sat, 20 May 2023 17:49:23 +0200 Subject: [PATCH 11/23] In IncludeContentAsFormUrlEncoded and PrimitiveParameterActionTokenizer explicit conversion for floats (#84) Create random ParamWithSeveralTypes More tests --- .../IncludeContentAsFormUrlEncoded.cs | 12 +- .../ComplexParameterActionTokenizer.cs | 13 +- .../PrimitiveParameterActionTokenizer.cs | 21 +-- src/Acheve.TestHost/TestServerExtensions.cs | 6 +- .../Routing/Builders/BugsController.cs | 18 +- .../Routing/Models/ParamWithList.cs | 68 +++++++- .../Routing/TestServerExtensionsTests.cs | 159 ++++-------------- 7 files changed, 139 insertions(+), 158 deletions(-) diff --git a/src/Acheve.TestHost/IncludeContentAsFormUrlEncoded.cs b/src/Acheve.TestHost/IncludeContentAsFormUrlEncoded.cs index 717ab72..4a7f7da 100644 --- a/src/Acheve.TestHost/IncludeContentAsFormUrlEncoded.cs +++ b/src/Acheve.TestHost/IncludeContentAsFormUrlEncoded.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Globalization; @@ -57,9 +58,12 @@ private IDictionary ToKeyValue(object metaToken) return null; } - var value = jValue?.Type == JTokenType.Date ? - jValue?.ToString("o", CultureInfo.InvariantCulture) : - jValue?.ToString(CultureInfo.InvariantCulture); + var value = jValue?.Type switch + { + JTokenType.Date => jValue?.ToString("o", CultureInfo.InvariantCulture), + JTokenType.Float => jValue?.ToString(), + _ => jValue?.ToString(CultureInfo.InvariantCulture) + }; return new Dictionary { { token.Path, value } }; } diff --git a/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs b/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs index b575429..6a88c57 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs @@ -33,15 +33,10 @@ public void AddTokens(TestServerAction action, TestServerTokenColle } var tokenName = property.Name.ToLowerInvariant(); - string tokenValue; - if (property.PropertyType.IsEnumerable() && property.PropertyType.GetEnumerableElementType().IsPrimitiveType()) - { - tokenValue = EnumerableParameterActionTokenizer.GetTokenValue((IList)value, tokenName); - } - else - { - tokenValue = PrimitiveParameterActionTokenizer.PrimitiveValueToString(value); - } + string tokenValue = + property.PropertyType.IsEnumerable() && property.PropertyType.GetEnumerableElementType().IsPrimitiveType() ? + EnumerableParameterActionTokenizer.GetTokenValue((IList)value, tokenName) : + PrimitiveParameterActionTokenizer.PrimitiveValueToString(value); tokens.AddToken(tokenName, tokenValue, isConventional: false); } diff --git a/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs b/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs index 4455167..1a140ed 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; using System; using System.Linq; using System.Reflection; @@ -34,21 +35,17 @@ public void AddTokens(TestServerAction action, TestServerTokenColle } string tokenName = parameter.Name.ToLowerInvariant(); - if (parameterType.IsDateTime()) - { - tokens.AddToken(tokenName, PrimitiveValueToString(tokenValue), isConventional: false); - } - else - { - tokens.AddToken(tokenName, PrimitiveValueToString(tokenValue), isConventional: false); - } + tokens.AddToken(tokenName, PrimitiveValueToString(tokenValue), isConventional: false); } } - public static string PrimitiveValueToString(object value) - => value.GetType().IsDateTime() ? - ((DateTime)value).ToString("yyyy/MM/ddTHH:mm:ss.fff") : - value.ToString(); + public static string PrimitiveValueToString(T value) + => value switch + { + DateTime dateTimeValue => dateTimeValue.ToString("yyyy/MM/ddTHH:mm:ss.fff"), + double doubleValue => JsonConvert.SerializeObject(doubleValue), + _ => value.ToString() + }; private static bool IgnoreHeader(ParameterInfo parameter) => parameter diff --git a/src/Acheve.TestHost/TestServerExtensions.cs b/src/Acheve.TestHost/TestServerExtensions.cs index 8b8d1da..7820f2f 100644 --- a/src/Acheve.TestHost/TestServerExtensions.cs +++ b/src/Acheve.TestHost/TestServerExtensions.cs @@ -37,9 +37,6 @@ public static RequestBuilder CreateHttpApiRequest(this TestServer s throw new ArgumentNullException(nameof(actionSelector)); } - // Include content as Json by default - contentOptions = contentOptions ?? new IncludeContentAsJson(); - var action = GetTestServerAction(actionSelector); if (!IsValidActionMethod(action.MethodInfo)) @@ -53,6 +50,9 @@ public static RequestBuilder CreateHttpApiRequest(this TestServer s var requestBuilder = server.CreateRequest(validUri); + // Include content as Json by default + contentOptions ??= action.ArgumentValues.Values.Any(a => a.IsFromForm) ? new IncludeContentAsFormUrlEncoded() : new IncludeContentAsJson(); + if (contentOptions.IncludeFromBodyAsContent) { AddFromBodyArgumentsToRequestBody(requestBuilder, action, contentOptions); diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs b/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs index fe3ab40..f010ebe 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs @@ -66,18 +66,22 @@ public ActionResult GetWithSeveralColon(int param1, int param2) [HttpGet] [Route("[action]")] - public ActionResult> ActionNameInRoute() + public IActionResult ActionNameInRoute() { return Ok(); } [HttpGet(nameof(GetWithObject))] - public ActionResult GetWithObject([FromQuery] ParamWithSeveralTypes param) + public ActionResult GetWithObject([FromQuery] ParamWithSeveralTypes param) => Ok(param); [HttpPost(nameof(PostWithObject))] - public ActionResult PostWithObject([FromBody] ParamWithSeveralTypes param) - => Ok(param); + public ActionResult PostWithObject([FromBody] ParamWithSeveralTypes param) + => Ok(param); + + [HttpPost(nameof(PostWithObjectFromForm))] + public ActionResult PostWithObjectFromForm([FromForm] ParamWithSeveralTypes param) + => Ok(param); [HttpGet(nameof(GetWithListParam))] public ActionResult> GetWithListParam([FromQuery] IEnumerable param) @@ -88,7 +92,7 @@ public ActionResult> PostWithListParam([FromBody] IEnumerabl => Ok(param); [HttpGet(nameof(GetWithDatetimeParam))] - public ActionResult GetWithDatetimeParam([FromQuery] DateTime param) + public ActionResult GetWithDatetimeParam([FromQuery] DateTime param) => Ok(param); [HttpPost(nameof(PostWithDatetimeParam))] @@ -96,10 +100,10 @@ public ActionResult PostWithDatetimeParam([FromBody] DateTime param) => Ok(param); [HttpGet(nameof(GetWithDatetimeListParam))] - public ActionResult GetWithDatetimeListParam([FromQuery] IEnumerable param) + public ActionResult> GetWithDatetimeListParam([FromQuery] IEnumerable param) => Ok(param); [HttpPost(nameof(PostWithDatetimeListParam))] - public ActionResult PostWithDatetimeListParam([FromBody] IEnumerable param) + public ActionResult> PostWithDatetimeListParam([FromBody] IEnumerable param) => Ok(param); } \ No newline at end of file diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs b/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs index eb7b941..95ce0fe 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; +using System.Linq; namespace UnitTests.Acheve.TestHost.Routing.Models; -public class ParamWithSeveralTypes +public sealed class ParamWithSeveralTypes : IEquatable { public string StringValue { get; set; } public int IntValue { get; set; } @@ -16,4 +17,69 @@ public class ParamWithSeveralTypes public IEnumerable DoubleValues { get; set; } public IEnumerable BooleanValues { get; set; } public IEnumerable DateTimeValues { get; set; } + + public static ParamWithSeveralTypes CreateRandom() + { + var random = new Random(); + + var getRandomEnumerable = () => Enumerable.Repeat(0, random.Next(1, 10)); + var getRandomString = () => Guid.NewGuid().ToString(); + var getRandomInt = () => random.Next(); + var getRandomDouble = () => random.NextDouble(); + var getRandomBool = () => random.Next() % 2 == 0; + var getRandomDateTime = () => new DateTime( + random.Next(1900, 2900), + random.Next(1, 12), + random.Next(1, 28), + random.Next(23), + random.Next(59), + random.Next(59), + random.Next(999)); + + return new() { + StringValue = getRandomString(), + IntValue = getRandomInt(), + DoubleValue = getRandomDouble(), + BooleanValue = getRandomBool(), + DateTimeValue = getRandomDateTime(), + + StringValues = getRandomEnumerable().Select(_ => getRandomString()).ToList(), + IntValues = getRandomEnumerable().Select(_ => getRandomInt()).ToList(), + DoubleValues = getRandomEnumerable().Select(_ => getRandomDouble()).ToList(), + BooleanValues = getRandomEnumerable().Select(_ => getRandomBool()).ToList(), + DateTimeValues = getRandomEnumerable().Select(_ => getRandomDateTime()).ToList(), + }; + } + + public bool Equals(ParamWithSeveralTypes other) + => other != null + && StringValue == other.StringValue + && IntValue == other.IntValue + && DoubleValue == other.DoubleValue + && BooleanValue == other.BooleanValue + && DateTimeValue == other.DateTimeValue + && StringValues.SequenceEqual(other.StringValues) + && IntValues.SequenceEqual(other.IntValues) + && DoubleValues.SequenceEqual(other.DoubleValues) + && BooleanValues.SequenceEqual(other.BooleanValues) + && DateTimeValues.SequenceEqual(other.DateTimeValues); + + public override bool Equals(object obj) + => Equals(obj as ParamWithSeveralTypes); + + public override int GetHashCode() + { + var hash = new HashCode(); + hash.Add(StringValue); + hash.Add(IntValue); + hash.Add(DoubleValue); + hash.Add(BooleanValue); + hash.Add(DateTimeValue); + hash.Add(StringValues); + hash.Add(IntValues); + hash.Add(DoubleValues); + hash.Add(BooleanValues); + hash.Add(DateTimeValues); + return hash.ToHashCode(); + } } diff --git a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs index de7acbb..5a67e17 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs @@ -1644,25 +1644,6 @@ public async Task Create_request_from_not_overrided_virtual_method() responseMessage.IsSuccessStatusCode.Should().BeTrue(); } - [Fact] - public async Task Create_request_with_list_parameter() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); - - var param = new string[] { "one", "two" }; - - var request = server.CreateHttpApiRequest(controller => controller.GetWithListParam(param)); - - var responseMessage = await request.GetAsync(); - - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.ReadContentAsAsync>(); - - response.Should().BeEquivalentTo(param); - } - [Fact] public async Task Create_get_request_with_cancellation_token_parameter() { @@ -1717,53 +1698,32 @@ public async Task Create_request_with_action_name_in_route() } [Fact] - public async Task Create_get_request_with_object_with_list() + public async Task Create_get_request_with_string_list_from_query() { var server = new TestServerBuilder() .UseDefaultStartup() .Build(); - var model = new ParamWithSeveralTypes - { - StringValues = new List() { "one", "two" } - }; + var param = ParamWithSeveralTypes.CreateRandom().StringValues; + + var request = server.CreateHttpApiRequest(controller => controller.GetWithListParam(param)); - var request = server.CreateHttpApiRequest(controller => controller.GetWithObject(model)); var responseMessage = await request.GetAsync(); responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.ReadContentAsAsync(); - response.StringValues.Should().Equal(model.StringValues); - } - - [Fact] - public async Task Create_post_request_with_object_with_list() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); - - var model = new ParamWithSeveralTypes - { - StringValues = new List() { "one", "two" } - }; - - var request = server.CreateHttpApiRequest(controller => controller.PostWithObject(model)); - var responseMessage = await request.PostAsync(); + var response = await responseMessage.ReadContentAsAsync>(); - await responseMessage.IsSuccessStatusCodeOrThrow(); - var response = await responseMessage.ReadContentAsAsync(); - response.StringValues.Should().Equal(model.StringValues); + response.Should().BeEquivalentTo(param); } [Fact] - public async Task Create_post_request_with_list() + public async Task Create_post_request_with_string_list_from_body() { var server = new TestServerBuilder() .UseDefaultStartup() .Build(); - var model = new string[] { "one", "two" }; + var model = ParamWithSeveralTypes.CreateRandom().StringValues; var request = server.CreateHttpApiRequest(controller => controller.PostWithListParam(model)); var responseMessage = await request.PostAsync(); @@ -1774,13 +1734,13 @@ public async Task Create_post_request_with_list() } [Fact] - public async Task Create_get_request_with_datetime() + public async Task Create_get_request_with_datetime_from_query() { var server = new TestServerBuilder() .UseDefaultStartup() .Build(); - var model = new DateTime(2023, 03, 01); + var model = ParamWithSeveralTypes.CreateRandom().DateTimeValue; var request = server.CreateHttpApiRequest(controller => controller.GetWithDatetimeParam(model)); var responseMessage = await request.GetAsync(); @@ -1791,61 +1751,30 @@ public async Task Create_get_request_with_datetime() } [Fact] - public async Task Create_get_request_with_datetime_in_object() + public async Task Create_post_request_with_datetime_from_body() { var server = new TestServerBuilder() .UseDefaultStartup() .Build(); - var model = new ParamWithSeveralTypes - { - DateTimeValue = new DateTime(2023, 03, 01) - }; + var model = ParamWithSeveralTypes.CreateRandom().DateTimeValue; - var request = server.CreateHttpApiRequest(controller => controller.GetWithObject(model)); - var responseMessage = await request.GetAsync(); + var request = server.CreateHttpApiRequest(controller => controller.PostWithDatetimeParam(model)); + var responseMessage = await request.PostAsync(); responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.ReadContentAsAsync(); - response.DateTimeValue.Should().Be(model.DateTimeValue); - } - - [Fact] - public async Task Create_get_request_with_datetime_list_in_object() - { - var server = new TestServerBuilder() - .UseDefaultStartup() - .Build(); - - var model = new ParamWithSeveralTypes - { - DateTimeValues = new List - { - new DateTime(2023, 03, 01), - new DateTime(2023, 05, 04) - } - }; - - var request = server.CreateHttpApiRequest(controller => controller.GetWithObject(model)); - var responseMessage = await request.GetAsync(); - - await responseMessage.IsSuccessStatusCodeOrThrow(); - var response = await responseMessage.ReadContentAsAsync(); - response.DateTimeValues.Should().Equal(model.DateTimeValues); + var response = await responseMessage.ReadContentAsAsync(); + response.Should().Be(model); } [Fact] - public async Task Create_get_request_with_datetime_list() + public async Task Create_get_request_with_datetime_list_from_query() { var server = new TestServerBuilder() .UseDefaultStartup() .Build(); - var model = new List - { - new DateTime(2023, 03, 01), - new DateTime(2023, 05, 04) - }; + var model = ParamWithSeveralTypes.CreateRandom().DateTimeValues; var request = server.CreateHttpApiRequest(controller => controller.GetWithDatetimeListParam(model)); var responseMessage = await request.GetAsync(); @@ -1856,85 +1785,71 @@ public async Task Create_get_request_with_datetime_list() } [Fact] - public async Task Create_post_request_with_datetime() + public async Task Create_post_request_with_datetime_list_from_body() { var server = new TestServerBuilder() .UseDefaultStartup() .Build(); - var model = new DateTime(2023, 03, 01); + var model = ParamWithSeveralTypes.CreateRandom().DateTimeValues; - var request = server.CreateHttpApiRequest(controller => controller.PostWithDatetimeParam(model)); + var request = server.CreateHttpApiRequest(controller => controller.PostWithDatetimeListParam(model)); var responseMessage = await request.PostAsync(); - responseMessage.EnsureSuccessStatusCode(); - var response = await responseMessage.ReadContentAsAsync(); - response.Should().Be(model); + await responseMessage.IsSuccessStatusCodeOrThrow(); + var response = await responseMessage.ReadContentAsAsync>(); + response.Should().Equal(model); } [Fact] - public async Task Create_post_request_with_datetime_in_object() + public async Task Create_get_request_with_object_from_query() { var server = new TestServerBuilder() .UseDefaultStartup() .Build(); - var model = new ParamWithSeveralTypes - { - DateTimeValue = new DateTime(2023, 03, 01) - }; + var model = ParamWithSeveralTypes.CreateRandom(); - var request = server.CreateHttpApiRequest(controller => controller.PostWithObject(model)); - var responseMessage = await request.PostAsync(); + var request = server.CreateHttpApiRequest(controller => controller.GetWithObject(model)); + var responseMessage = await request.GetAsync(); await responseMessage.IsSuccessStatusCodeOrThrow(); var response = await responseMessage.ReadContentAsAsync(); - response.DateTimeValue.Should().Be(model.DateTimeValue); + response.Should().Be(model); } [Fact] - public async Task Create_post_request_with_datetime_list_in_object() + public async Task Create_post_request_with_object_from_body() { var server = new TestServerBuilder() .UseDefaultStartup() .Build(); - var model = new ParamWithSeveralTypes - { - DateTimeValues = new List - { - new DateTime(2023, 03, 01), - new DateTime(2023, 05, 04) - } - }; + var model = ParamWithSeveralTypes.CreateRandom(); var request = server.CreateHttpApiRequest(controller => controller.PostWithObject(model)); var responseMessage = await request.PostAsync(); await responseMessage.IsSuccessStatusCodeOrThrow(); var response = await responseMessage.ReadContentAsAsync(); - response.DateTimeValues.Should().Equal(model.DateTimeValues); + response.Should().Be(model); } [Fact] - public async Task Create_post_request_with_datetime_list() + public async Task Create_post_request_with_object_from_form() { var server = new TestServerBuilder() .UseDefaultStartup() .Build(); - var model = new List - { - new DateTime(2023, 03, 01), - new DateTime(2023, 05, 04) - }; + var model = ParamWithSeveralTypes.CreateRandom(); - var request = server.CreateHttpApiRequest(controller => controller.PostWithDatetimeListParam(model)); + var request = server.CreateHttpApiRequest(controller => controller.PostWithObjectFromForm(model)); var responseMessage = await request.PostAsync(); await responseMessage.IsSuccessStatusCodeOrThrow(); - var response = await responseMessage.ReadContentAsAsync>(); - response.Should().Equal(model); + var response = await responseMessage.ReadContentAsAsync(); + response.Should().Be(model); } private class PrivateNonControllerClass From e20443139d904c37eb3a06e315423e2f149a13e7 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Sat, 20 May 2023 18:22:20 +0200 Subject: [PATCH 12/23] Add new CreateHttpApiRequest with TActionResponse (#85) Fix CodeQL alerts Test --- .github/workflows/codeql-analysis.yml | 2 +- src/Acheve.TestHost/Routing/UriDiscover.cs | 196 +++++++++++++- src/Acheve.TestHost/TestServerExtensions.cs | 239 +++--------------- .../Routing/Models/ParamWithList.cs | 5 +- .../Routing/TestServerExtensionsTests.cs | 19 ++ 5 files changed, 255 insertions(+), 206 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index adb7021..f41fbef 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -2,7 +2,7 @@ name: "CodeQL" on: push: - branches: [ "master" ] + branches: [ "master", "develop" ] pull_request: branches: [ "master", "develop" ] workflow_dispatch: diff --git a/src/Acheve.TestHost/Routing/UriDiscover.cs b/src/Acheve.TestHost/Routing/UriDiscover.cs index 574708b..03a640a 100644 --- a/src/Acheve.TestHost/Routing/UriDiscover.cs +++ b/src/Acheve.TestHost/Routing/UriDiscover.cs @@ -1,13 +1,18 @@ using Acheve.TestHost.Routing.AttributeTemplates; using Acheve.TestHost.Routing.Tokenizers; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.TestHost; +using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; +using System.Reflection; namespace Acheve.TestHost.Routing; -static class UriDiscover +internal static class UriDiscover { - static readonly IEnumerable _tokenizers = new List() + private static readonly IEnumerable _tokenizers = new List() { new PrimitiveParameterActionTokenizer(), new EnumerableParameterActionTokenizer(), @@ -15,7 +20,54 @@ static class UriDiscover new DefaultConventionalTokenizer() }; - public static string Discover(TestServerAction action, object tokenValues) + public static RequestBuilder CreateHttpApiRequest(TestServer server, + LambdaExpression actionSelector, + object tokenValues = null, + RequestContentOptions contentOptions = null) + where TController : class + { + if (!IsController()) + { + throw new InvalidOperationException($"The type {typeof(TController).FullName} is not a valid MVC controller."); + } + + if (actionSelector == null) + { + throw new ArgumentNullException(nameof(actionSelector)); + } + + var action = GetTestServerAction(actionSelector); + + if (!IsValidActionMethod(action.MethodInfo)) + { + throw new InvalidOperationException($"The action selector is not a valid action for MVC Controller."); + } + + //the uri discover use only attribute route conventions. + + var validUri = UriDiscover.Discover(action, tokenValues); + + var requestBuilder = server.CreateRequest(validUri); + + // Include content as Json by default + contentOptions ??= action.ArgumentValues.Values.Any(a => a.IsFromForm) ? new IncludeContentAsFormUrlEncoded() : new IncludeContentAsJson(); + + if (contentOptions.IncludeFromBodyAsContent) + { + AddFromBodyArgumentsToRequestBody(requestBuilder, action, contentOptions); + } + + if (contentOptions.IncludeFromFormAsContent) + { + AddFromFormArgumentsToRequestForm(requestBuilder, action, contentOptions); + } + + AddFromHeaderArgumentsToRequestForm(requestBuilder, action); + + return requestBuilder; + } + + private static string Discover(TestServerAction action, object tokenValues) where TController : class { //at this moment only the first route is considered.. @@ -50,7 +102,7 @@ public static string Discover(TestServerAction action, object token $"{controllerTemplate}/{template}{queryStringTemplate}"; } - static TestServerTokenCollection AddTokens(TestServerAction action, object tokenValues) + private static TestServerTokenCollection AddTokens(TestServerAction action, object tokenValues) where TController : class { var dictionaryTokenValues = new Dictionary(); @@ -71,7 +123,8 @@ static TestServerTokenCollection AddTokens(TestServerAction action, return testServerTokens; } - static bool IsTildeOverride(string template, out string overrideTemplate) + + private static bool IsTildeOverride(string template, out string overrideTemplate) { const string TILDE = "~"; @@ -85,4 +138,137 @@ static bool IsTildeOverride(string template, out string overrideTemplate) return isTildeOverride; } + + private static bool IsController() + { + const string ControllerTypeNameSuffix = "Controller"; + + var typeInfo = typeof(TController); + + if (!typeInfo.IsClass) + { + return false; + } + + if (typeInfo.IsAbstract) + { + return false; + } + + if (!typeInfo.IsPublic) + { + return false; + } + + if (typeInfo.ContainsGenericParameters) + { + return false; + } + + if (typeInfo.IsDefined(typeof(NonControllerAttribute))) + { + return false; + } + + if (!typeInfo.Name.EndsWith(ControllerTypeNameSuffix, StringComparison.OrdinalIgnoreCase) && + + !typeInfo.IsDefined(typeof(ControllerAttribute))) + { + return false; + } + + return true; + } + + private static bool IsValidActionMethod(MethodInfo methodInfo) + { + if (!methodInfo.IsPublic) + { + return false; + } + + if (methodInfo.IsStatic) + { + return false; + } + + if (methodInfo.IsAbstract && !methodInfo.IsVirtual) + { + return false; + } + + if (methodInfo.IsDefined(typeof(NonActionAttribute))) + { + return false; + } + + + return true; + } + + private static TestServerAction GetTestServerAction(LambdaExpression actionSelector) + { + if (actionSelector.NodeType != ExpressionType.Lambda) + { + throw new InvalidOperationException($"The action selector is not a valid lambda expression"); + } + + var methodCall = (MethodCallExpression)actionSelector.Body; + + var action = new TestServerAction(methodCall.Method); + bool haveAttributeApiController = typeof(TController).GetTypeInfo().GetCustomAttribute(typeof(ApiControllerAttribute)) != null; + bool isGetOrDelete = action.MethodInfo.GetCustomAttributes().FirstOrDefault(attr => attr.GetType() == typeof(HttpGetAttribute) + || attr.GetType() == typeof(HttpDeleteAttribute)) != null; + + var index = 0; + + foreach (var item in methodCall.Arguments) + { + action.AddArgument(index, item, haveAttributeApiController && !isGetOrDelete); + + ++index; + } + + return action; + } + + private static void AddFromBodyArgumentsToRequestBody( + RequestBuilder requestBuilder, + TestServerAction action, + RequestContentOptions contentOptions) + { + var fromBodyArgument = action.ArgumentValues.Values.SingleOrDefault(x => x.IsFromBody); + + if (fromBodyArgument != null) + { + requestBuilder.And(x => x.Content = + contentOptions.ContentBuilder(fromBodyArgument.Instance)); + } + } + + private static void AddFromFormArgumentsToRequestForm( + RequestBuilder requestBuilder, + TestServerAction action, + RequestContentOptions contentOptions) + { + var fromFormArgument = action.ArgumentValues.Values.SingleOrDefault(x => x.IsFromForm); + + if (fromFormArgument != null) + { + requestBuilder.And(x => x.Content = + contentOptions.ContentBuilder(fromFormArgument.Instance)); + } + } + + private static void AddFromHeaderArgumentsToRequestForm( + RequestBuilder requestBuilder, + TestServerAction action) + { + var fromHeaderArguments = action.ArgumentValues.Values.Where(x => x.IsFromHeader); + + foreach (var fromHeaderArgument in fromHeaderArguments) + { + requestBuilder.And(x => x.Headers.Add(fromHeaderArgument.HeaderName, fromHeaderArgument.Instance.ToString())); + } + } } diff --git a/src/Acheve.TestHost/TestServerExtensions.cs b/src/Acheve.TestHost/TestServerExtensions.cs index 7820f2f..63bd6a1 100644 --- a/src/Acheve.TestHost/TestServerExtensions.cs +++ b/src/Acheve.TestHost/TestServerExtensions.cs @@ -1,204 +1,47 @@ using Acheve.TestHost.Routing; -using Microsoft.AspNetCore.Mvc; using System; -using System.Linq; using System.Linq.Expressions; -using System.Reflection; -namespace Microsoft.AspNetCore.TestHost -{ - public static class TestServerExtensions - { - /// - /// Create a configured automatically - /// with the generated uri from the . - /// At this moment this method only resolver HTTP API using ASP.NET CORE Attribute Routing - /// - /// The controller to use - /// The TestServer - /// The action selector used to discover the uri - /// The optional token values used to create the uri - /// Determines if [FromBody] arguments are included as request content. - /// By default they are included as application/json content - /// - public static RequestBuilder CreateHttpApiRequest(this TestServer server, - Expression> actionSelector, - object tokenValues = null, - RequestContentOptions contentOptions = null) - where TController : class - { - if (!IsController()) - { - throw new InvalidOperationException($"The type {typeof(TController).FullName} is not a valid MVC controller."); - } - - if (actionSelector == null) - { - throw new ArgumentNullException(nameof(actionSelector)); - } - - var action = GetTestServerAction(actionSelector); - - if (!IsValidActionMethod(action.MethodInfo)) - { - throw new InvalidOperationException($"The action selector is not a valid action for MVC Controller."); - } - - //the uri discover use only attribute route conventions. - - var validUri = UriDiscover.Discover(action, tokenValues); - - var requestBuilder = server.CreateRequest(validUri); - - // Include content as Json by default - contentOptions ??= action.ArgumentValues.Values.Any(a => a.IsFromForm) ? new IncludeContentAsFormUrlEncoded() : new IncludeContentAsJson(); - - if (contentOptions.IncludeFromBodyAsContent) - { - AddFromBodyArgumentsToRequestBody(requestBuilder, action, contentOptions); - } - - if (contentOptions.IncludeFromFormAsContent) - { - AddFromFormArgumentsToRequestForm(requestBuilder, action, contentOptions); - } - - AddFromHeaderArgumentsToRequestForm(requestBuilder, action); - - return requestBuilder; - } - - private static bool IsController() - { - const string ControllerTypeNameSuffix = "Controller"; - - var typeInfo = typeof(TController); - - if (!typeInfo.IsClass) - { - return false; - } - - if (typeInfo.IsAbstract) - { - return false; - } - - if (!typeInfo.IsPublic) - { - return false; - } - - if (typeInfo.ContainsGenericParameters) - { - return false; - } - - if (typeInfo.IsDefined(typeof(NonControllerAttribute))) - { - return false; - } - - if (!typeInfo.Name.EndsWith(ControllerTypeNameSuffix, StringComparison.OrdinalIgnoreCase) && - - !typeInfo.IsDefined(typeof(ControllerAttribute))) - { - return false; - } +namespace Microsoft.AspNetCore.TestHost; - return true; - } - - private static bool IsValidActionMethod(MethodInfo methodInfo) - { - if (!methodInfo.IsPublic) - { - return false; - } - - if (methodInfo.IsStatic) - { - return false; - } - - if (methodInfo.IsAbstract && !methodInfo.IsVirtual) - { - return false; - } - - if (methodInfo.IsDefined(typeof(NonActionAttribute))) - { - return false; - } - - - return true; - } - - private static TestServerAction GetTestServerAction(Expression> actionSelector) - { - if (actionSelector.NodeType != ExpressionType.Lambda) - { - throw new InvalidOperationException($"The action selector is not a valid lambda expression"); - } - - var methodCall = (MethodCallExpression)actionSelector.Body; - - var action = new TestServerAction(methodCall.Method); - bool haveAttributeApiController = typeof(TController).GetTypeInfo().GetCustomAttribute(typeof(ApiControllerAttribute)) != null; - bool isGetOrDelete = action.MethodInfo.GetCustomAttributes().FirstOrDefault(attr => attr.GetType() == typeof(HttpGetAttribute) - || attr.GetType() == typeof(HttpDeleteAttribute)) != null; - - var index = 0; - - foreach (var item in methodCall.Arguments) - { - action.AddArgument(index, item, haveAttributeApiController && !isGetOrDelete); - - ++index; - } - - return action; - } - - private static void AddFromBodyArgumentsToRequestBody( - RequestBuilder requestBuilder, - TestServerAction action, - RequestContentOptions contentOptions) - { - var fromBodyArgument = action.ArgumentValues.Values.SingleOrDefault(x => x.IsFromBody); - - if (fromBodyArgument != null) - { - requestBuilder.And(x => x.Content = - contentOptions.ContentBuilder(fromBodyArgument.Instance)); - } - } - - private static void AddFromFormArgumentsToRequestForm( - RequestBuilder requestBuilder, - TestServerAction action, - RequestContentOptions contentOptions) - { - var fromFormArgument = action.ArgumentValues.Values.SingleOrDefault(x => x.IsFromForm); - - if (fromFormArgument != null) - { - requestBuilder.And(x => x.Content = - contentOptions.ContentBuilder(fromFormArgument.Instance)); - } - } - - private static void AddFromHeaderArgumentsToRequestForm( - RequestBuilder requestBuilder, - TestServerAction action) - { - var fromHeaderArguments = action.ArgumentValues.Values.Where(x => x.IsFromHeader); - - foreach (var fromHeaderArgument in fromHeaderArguments) - { - requestBuilder.And(x => x.Headers.Add(fromHeaderArgument.HeaderName, fromHeaderArgument.Instance.ToString())); - } - } - } +public static class TestServerExtensions +{ + /// + /// Create a configured automatically + /// with the generated uri from the . + /// At this moment this method only resolver HTTP API using ASP.NET CORE Attribute Routing + /// + /// The controller to use + /// The TestServer + /// The action selector used to discover the uri + /// The optional token values used to create the uri + /// Determines if [FromBody] arguments are included as request content. + /// By default they are included as application/json content + /// RequestBuilder configured automatically + public static RequestBuilder CreateHttpApiRequest(this TestServer server, + Expression> actionSelector, + object tokenValues = null, + RequestContentOptions contentOptions = null) + where TController : class + => UriDiscover.CreateHttpApiRequest(server, actionSelector, tokenValues, contentOptions); + + /// + /// Create a configured automatically + /// with the generated uri from the . + /// At this moment this method only resolver HTTP API using ASP.NET CORE Attribute Routing + /// + /// The controller to use + /// actionSelector response type + /// The TestServer + /// The action selector used to discover the uri + /// The optional token values used to create the uri + /// Determines if [FromBody] arguments are included as request content. + /// By default they are included as application/json content + /// RequestBuilder configured automatically + public static RequestBuilder CreateHttpApiRequest(this TestServer server, + Expression> actionSelector, + object tokenValues = null, + RequestContentOptions contentOptions = null) + where TController : class + => UriDiscover.CreateHttpApiRequest(server, actionSelector, tokenValues, contentOptions); } diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs b/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs index 95ce0fe..a8a1702 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs @@ -36,7 +36,8 @@ public static ParamWithSeveralTypes CreateRandom() random.Next(59), random.Next(999)); - return new() { + return new() + { StringValue = getRandomString(), IntValue = getRandomInt(), DoubleValue = getRandomDouble(), @@ -55,7 +56,7 @@ public bool Equals(ParamWithSeveralTypes other) => other != null && StringValue == other.StringValue && IntValue == other.IntValue - && DoubleValue == other.DoubleValue + && (DoubleValue - other.DoubleValue) < 0.0001 && BooleanValue == other.BooleanValue && DateTimeValue == other.DateTimeValue && StringValues.SequenceEqual(other.StringValues) diff --git a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs index 5a67e17..bc25a36 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs @@ -1852,6 +1852,25 @@ public async Task Create_post_request_with_object_from_form() response.Should().Be(model); } + [Fact] + public async Task Create_get_request_with_object_from_query_setting_action_response_type() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var model = ParamWithSeveralTypes.CreateRandom(); + + var request = server.CreateHttpApiRequest>( + controller => controller.GetWithObject(model) + ); + var responseMessage = await request.GetAsync(); + + await responseMessage.IsSuccessStatusCodeOrThrow(); + var response = await responseMessage.ReadContentAsAsync(); + response.Should().Be(model); + } + private class PrivateNonControllerClass { public int SomeAction() From 92af8db04e2b400e70d4bdeb7382346b4859f6d9 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Sat, 20 May 2023 19:05:18 +0200 Subject: [PATCH 13/23] Add net7.0 Target Version (#86) * Add net7.0 Target Version Update nugets * add net 7 in workflows * update global.json * remove extra builds ci.yml --- .github/workflows/ci.yml | 9 ++---- .github/workflows/nuget.yml | 1 + Directory.Build.props | 2 +- Directory.Packages.props | 63 ++++++++++++++++++------------------- global.json | 6 ++-- 5 files changed, 38 insertions(+), 43 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index faef6dd..9cd19a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,13 +26,10 @@ jobs: 3.1.417 5.0.101 6.0.300 + 7.0.302 - - name: Build .NET 3.1 - run: dotnet build -c $BUILD_CONFIG --framework netcoreapp3.1 - - name: Build .NET 5.0 - run: dotnet build -c $BUILD_CONFIG --framework net5.0 - - name: Build .NET 6.0 - run: dotnet build -c $BUILD_CONFIG --framework net6.0 + - name: Build + run: dotnet build -c $BUILD_CONFIG - name: Test run: dotnet test -c $BUILD_CONFIG --no-build \ No newline at end of file diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index e1cbab7..10de10e 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -21,6 +21,7 @@ jobs: 3.1.417 5.0.101 6.0.300 + 7.0.302 - name: Build run: dotnet build -c $BUILD_CONFIG diff --git a/Directory.Build.props b/Directory.Build.props index 2d9ff8f..bd1872a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@  - netcoreapp3.1;net5.0;net6.0 + netcoreapp3.1;net5.0;net6.0;net7.0 latest true diff --git a/Directory.Packages.props b/Directory.Packages.props index f89b0bb..608ec0f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,35 +1,32 @@  - - 3.1.25 - - - - 5.0.17 - - - - 6.0.5 - - - - - - - - - - - - - - - - - - - - - - - + + 3.1.32 + + + 5.0.17 + + + 6.0.16 + + + 7.0.5 + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/global.json b/global.json index 58d7ecc..fb292b5 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { - "projects": [ "src", "test", "samples" ], + "projects": ["src", "test", "samples"], "sdk": { - "version": "6.0.300", + "version": "6.0.000", "rollForward": "latestMajor" } -} \ No newline at end of file +} From 85a2b9cc008b1e8331a47b265a4f0d2853a06603 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Sun, 21 May 2023 17:31:35 +0200 Subject: [PATCH 14/23] ReadContentAsAsync allows string type (#87) Tests for HttpResponseMessageExtensions --- .../HttpResponseMessageExtensions.cs | 54 +++++++------- .../IncludeContentAsFormUrlEncoded.cs | 3 +- .../Routing/Builders/BugsController.cs | 2 +- .../Routing/Builders/ValuesV5Controller.cs | 16 +++- .../HttpResponseMessageExtensionsTests.cs | 74 +++++++++++++++++++ 5 files changed, 119 insertions(+), 30 deletions(-) create mode 100644 tests/UnitTests/Acheve.TestHost/Routing/HttpResponseMessageExtensionsTests.cs diff --git a/src/Acheve.TestHost/HttpResponseMessageExtensions.cs b/src/Acheve.TestHost/HttpResponseMessageExtensions.cs index 601e1de..b280faf 100644 --- a/src/Acheve.TestHost/HttpResponseMessageExtensions.cs +++ b/src/Acheve.TestHost/HttpResponseMessageExtensions.cs @@ -1,38 +1,42 @@ using Newtonsoft.Json; using System.Threading.Tasks; -namespace System.Net.Http +namespace System.Net.Http; + +public static class HttpResponseMessageExtensions { - public static class HttpResponseMessageExtensions + /// + /// Try to extract the error message in the response content in case the response status code is not success. + /// + /// The HttpResponseMessage instance + /// + public static async Task IsSuccessStatusCodeOrThrow(this HttpResponseMessage response) { - /// - /// Try to extract the error message in the response content in case the response status code is not success. - /// - /// The httpResponseMessage instance - /// - public static async Task IsSuccessStatusCodeOrThrow(this HttpResponseMessage response) + if (response.IsSuccessStatusCode) { - if (response.IsSuccessStatusCode) - { - return; - } + return; + } - var content = await response.Content.ReadAsStringAsync(); + var content = await response.Content.ReadAsStringAsync(); - throw new Exception($"Response status does not indicate success: {response.StatusCode:D} ({response.StatusCode}); \r\n{content}"); - } + throw new Exception($"Response status does not indicate success: {response.StatusCode:D} ({response.StatusCode}); \r\n{content}"); + } - /// - /// Read HttpResponseMessage and convert to T Class - /// - /// Class - /// The httpResponseMessage instance - /// T class object - public static async Task ReadContentAsAsync(this HttpResponseMessage responseMessage) - { - var json = await responseMessage.Content.ReadAsStringAsync(); + /// + /// Read HttpResponseMessage and convert to T Class + /// + /// Class type or primitive type + /// The HttpResponseMessage instance + /// T class object + public static async Task ReadContentAsAsync(this HttpResponseMessage responseMessage) + { + var content = await responseMessage.Content.ReadAsStringAsync(); - return JsonConvert.DeserializeObject(json); + if (typeof(T) == typeof(string)) + { + content = $"\"{content}\""; } + + return JsonConvert.DeserializeObject(content); } } \ No newline at end of file diff --git a/src/Acheve.TestHost/IncludeContentAsFormUrlEncoded.cs b/src/Acheve.TestHost/IncludeContentAsFormUrlEncoded.cs index 4a7f7da..40f21b1 100644 --- a/src/Acheve.TestHost/IncludeContentAsFormUrlEncoded.cs +++ b/src/Acheve.TestHost/IncludeContentAsFormUrlEncoded.cs @@ -1,5 +1,4 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Globalization; diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs b/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs index f010ebe..22ba79d 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/Builders/BugsController.cs @@ -101,7 +101,7 @@ public ActionResult PostWithDatetimeParam([FromBody] DateTime param) [HttpGet(nameof(GetWithDatetimeListParam))] public ActionResult> GetWithDatetimeListParam([FromQuery] IEnumerable param) - => Ok(param); + => Ok(param); [HttpPost(nameof(PostWithDatetimeListParam))] public ActionResult> PostWithDatetimeListParam([FromBody] IEnumerable param) diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs b/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs index abdda17..9c51546 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs @@ -45,7 +45,7 @@ public IActionResult Patch1(int id, Pagination pagination1) return Ok(); } - + [HttpGet(nameof(GetWithCancellationToken))] public ActionResult GetWithCancellationToken([FromQuery] string value, CancellationToken cancellationToken) { @@ -64,8 +64,20 @@ public ActionResult PostWithCancellationToken([FromBody] string value, C { return BadRequest(); } - + return Ok(value); } + + [HttpGet(nameof(GetBadRequest))] + public IActionResult GetBadRequest() + { + return BadRequest(); + } + + [HttpGet(nameof(GetOk))] + public IActionResult GetOk() + { + return Ok(); + } } } diff --git a/tests/UnitTests/Acheve.TestHost/Routing/HttpResponseMessageExtensionsTests.cs b/tests/UnitTests/Acheve.TestHost/Routing/HttpResponseMessageExtensionsTests.cs new file mode 100644 index 0000000..1de3d2d --- /dev/null +++ b/tests/UnitTests/Acheve.TestHost/Routing/HttpResponseMessageExtensionsTests.cs @@ -0,0 +1,74 @@ +using FluentAssertions; +using Microsoft.AspNetCore.TestHost; +using System; +using System.Net.Http; +using System.Threading.Tasks; +using UnitTests.Acheve.TestHost.Builders; +using UnitTests.Acheve.TestHost.Routing.Models; +using Xunit; + +namespace UnitTests.Acheve.TestHost.Routing; + +public class HttpResponseMessageExtensionsTests +{ + [Fact] + public async Task IsSuccessStatusCodeOrThrow_throw_exception() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var request = server.CreateHttpApiRequest(controller => controller.GetBadRequest()); + var responseMessage = await request.GetAsync(); + + var isSuccessFunc = () => responseMessage.IsSuccessStatusCodeOrThrow(); + await isSuccessFunc.Should().ThrowAsync(); + } + + [Fact] + public async Task IsSuccessStatusCodeOrThrow_not_throw_exception() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var request = server.CreateHttpApiRequest(controller => controller.GetOk()); + var responseMessage = await request.GetAsync(); + + var isSuccessFunc = () => responseMessage.IsSuccessStatusCodeOrThrow(); + await isSuccessFunc.Should().NotThrowAsync(); + } + + [Fact] + public async Task ReadContentAsAsync_return_complex_object() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var model = ParamWithSeveralTypes.CreateRandom(); + var request = server.CreateHttpApiRequest(controller => controller.GetWithObject(model)); + var responseMessage = await request.GetAsync(); + + var response = await responseMessage.ReadContentAsAsync(); + + response.Should().Be(model); + } + + [Fact] + public async Task ReadContentAsAsync_return_string() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var param1 = 1; + var param2 = 1; + var request = server.CreateHttpApiRequest(controller => controller.GetWithSeveralColon(param1, param2)); + var responseMessage = await request.GetAsync(); + + var response = await responseMessage.ReadContentAsAsync(); + + response.Should().Be($"{param1}/{param2}"); + } +} From e18e22f2c7566cc9b1dcd4f2b2f7d8c4673d5e51 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Mon, 22 May 2023 08:17:44 +0200 Subject: [PATCH 15/23] Allow send a IFormFile (#88) * Allow IFormFile Refactor IncludeContentAsFormUrlEncoded Remove Newtonsoft.Json dependecy * Update documentation * Refactor GivenFile --- README.md | 78 ++++++---- src/Acheve.TestHost/Acheve.TestHost.csproj | 1 - .../AuthenticationBuilderExtensions.cs | 0 .../{ => Extensions}/HttpClientExtensions.cs | 0 .../HttpResponseMessageExtensions.cs | 9 +- .../RequestBuilderExtensions.cs | 0 .../{ => Extensions}/TestServerExtensions.cs | 12 ++ .../IncludeContentAsFormUrlEncoded.cs | 69 -------- .../IncludeContentAsFormUrlEncoded.cs | 85 ++++++++++ .../IncludeContentAsJson.cs | 8 +- .../{ => RequestContent}/NotIncludeContent.cs | 0 .../RequestContentOptions.cs | 0 .../Routing/TestServerAction.cs | 12 +- .../ComplexParameterActionTokenizer.cs | 10 +- .../PrimitiveParameterActionTokenizer.cs | 7 +- src/Acheve.TestHost/Routing/UriDiscover.cs | 2 +- .../Routing/Builders/ValuesV4Controller.cs | 129 ++++++++------- .../Routing/Builders/ValuesV5Controller.cs | 147 +++++++++++------- .../Routing/Models/ParamWithFile.cs | 20 +++ ...amWithList.cs => ParamWithSeveralTypes.cs} | 0 .../Routing/TestServerExtensionsTests.cs | 49 +++++- 21 files changed, 389 insertions(+), 249 deletions(-) rename src/Acheve.TestHost/{ => Extensions}/AuthenticationBuilderExtensions.cs (100%) rename src/Acheve.TestHost/{ => Extensions}/HttpClientExtensions.cs (100%) rename src/Acheve.TestHost/{ => Extensions}/HttpResponseMessageExtensions.cs (86%) rename src/Acheve.TestHost/{ => Extensions}/RequestBuilderExtensions.cs (100%) rename src/Acheve.TestHost/{ => Extensions}/TestServerExtensions.cs (87%) delete mode 100644 src/Acheve.TestHost/IncludeContentAsFormUrlEncoded.cs create mode 100644 src/Acheve.TestHost/RequestContent/IncludeContentAsFormUrlEncoded.cs rename src/Acheve.TestHost/{ => RequestContent}/IncludeContentAsJson.cs (88%) rename src/Acheve.TestHost/{ => RequestContent}/NotIncludeContent.cs (100%) rename src/Acheve.TestHost/{ => RequestContent}/RequestContentOptions.cs (100%) create mode 100644 tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithFile.cs rename tests/UnitTests/Acheve.TestHost/Routing/Models/{ParamWithList.cs => ParamWithSeveralTypes.cs} (100%) diff --git a/README.md b/README.md index 69d8bc1..9c8e9f9 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ [![Build status](https://github.com/Xabaril/Acheve.TestHost/actions/workflows/nuget.yml/badge.svg)](https://github.com/Xabaril/Acheve.TestHost/actions/workflows/nuget.yml/badge.svg) [![NuGet](https://img.shields.io/nuget/v/acheve.testhost.svg)](https://www.nuget.org/packages/acheve.testhost/) -NuGet package to improve AspNetCore TestServer experiences +NuGet package to improve AspNetCore TestServer experiences Unit testing your Mvc controllers is not enough to verify the correctness of your WebApi. Are the filters working? Is the correct status code sent when that condition is reached? Is the user authorized to request that endpoint? -The NuGet package [Microsoft.AspNetCore.TestHost](https://www.nuget.org/packages/Microsoft.AspNetCore.TestHost/) allows you to create an in memory server that exposes an HttpClient to be able to send request to the server. All in memory, all in the same process. Fast. It's the best way to create integration tests in your Mvc application. But at this moment this library has some gaps that *Acheve* try to fill. +The NuGet package [Microsoft.AspNetCore.TestHost](https://www.nuget.org/packages/Microsoft.AspNetCore.TestHost/) allows you to create an in memory server that exposes an HttpClient to be able to send request to the server. All in memory, all in the same process. Fast. It's the best way to create integration tests in your Mvc application. But at this moment this library has some gaps that _Acheve_ try to fill. ## Get started @@ -66,29 +66,29 @@ the claims for authenticated calls to the WebApi. In the TestServer startup class you shoud incude the authentication service and add the .Net Core new AUthentication middleware: - ```csharp - public class TestStartup - { - public void ConfigureServices(IServiceCollection services) - { - services.AddAuthentication(options => - { - options.DefaultScheme = TestServerDefaults.AuthenticationScheme; - }) - .AddTestServer(); - - var mvcCoreBuilder = services.AddMvcCore(); - ApiConfiguration.ConfigureCoreMvc(mvcCoreBuilder); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app) - { - app.UseAuthentication(); - - app.UseMvcWithDefaultRoute(); - } - } +```csharp + public class TestStartup + { + public void ConfigureServices(IServiceCollection services) + { + services.AddAuthentication(options => + { + options.DefaultScheme = TestServerDefaults.AuthenticationScheme; + }) + .AddTestServer(); + + var mvcCoreBuilder = services.AddMvcCore(); + ApiConfiguration.ConfigureCoreMvc(mvcCoreBuilder); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app) + { + app.UseAuthentication(); + + app.UseMvcWithDefaultRoute(); + } + } ``` And in your tests you can use an HttpClient with default credentials or build @@ -178,7 +178,7 @@ var response = await _server.CreateRequest("api/values/public") In general, in our tests a new simple API class is created to hide this uri and improve the code. ```csharp - // some code on tests + // some code on tests var response = await _server.CreateRequest(Api.Values.Public) .GetAsync(); @@ -199,7 +199,7 @@ The main problems on this approach are: 1. If any route convention is changed all integration test will fail. 1. If you refactor any parameter order the integration test will fail. -With *Acheve* you can create the uri dynamically using the attribute routing directly from your controllers. +With _Acheve_ you can create the uri dynamically using the attribute routing directly from your controllers. ```csharp var response = await _server.CreateHttpApiRequest(controller=>controller.PublicValues()) @@ -208,7 +208,7 @@ var response = await _server.CreateHttpApiRequest(controller=> ## About adding extra query parameters -The package has a *RequestBuilder* extension to add a new query parameter: *AddQueryParameter*. +The package has a _RequestBuilder_ extension to add a new query parameter: _AddQueryParameter_. ```csharp [Fact] @@ -221,9 +221,9 @@ public async Task MyFirstTest() } ``` -## About removin query parameters +## About removing query parameters -The package has a *RequestBuilder* extension to remove a query parameter: *RemoveQueryParameter*. +The package has a _RequestBuilder_ extension to remove a query parameter: _RemoveQueryParameter_. ```csharp [Fact] @@ -236,12 +236,26 @@ public async Task MyFirstTest() } ``` +## About sending files + +The package has a _TestServer_ extension to get a test file: _GivenFile_. + +```csharp +[Fact] +public async Task MyFirstTest() +{ + ... + var file = server.GivenFile(); + ... +} +``` + ## Improving assertions in API responses The package has HttpResponseMessage extension to help us assert the response. -- *IsSuccessStatusCodeOrThrow*: Throw exception with the response content in the message. -- *ReadContentAsAsync*: Read the response content and cast it. +- _IsSuccessStatusCodeOrThrow_: Throw exception with the response content in the message. +- _ReadContentAsAsync_: Read the response content and cast it. ```csharp [Fact] diff --git a/src/Acheve.TestHost/Acheve.TestHost.csproj b/src/Acheve.TestHost/Acheve.TestHost.csproj index 40fed40..a1d0bd1 100644 --- a/src/Acheve.TestHost/Acheve.TestHost.csproj +++ b/src/Acheve.TestHost/Acheve.TestHost.csproj @@ -10,7 +10,6 @@ - diff --git a/src/Acheve.TestHost/AuthenticationBuilderExtensions.cs b/src/Acheve.TestHost/Extensions/AuthenticationBuilderExtensions.cs similarity index 100% rename from src/Acheve.TestHost/AuthenticationBuilderExtensions.cs rename to src/Acheve.TestHost/Extensions/AuthenticationBuilderExtensions.cs diff --git a/src/Acheve.TestHost/HttpClientExtensions.cs b/src/Acheve.TestHost/Extensions/HttpClientExtensions.cs similarity index 100% rename from src/Acheve.TestHost/HttpClientExtensions.cs rename to src/Acheve.TestHost/Extensions/HttpClientExtensions.cs diff --git a/src/Acheve.TestHost/HttpResponseMessageExtensions.cs b/src/Acheve.TestHost/Extensions/HttpResponseMessageExtensions.cs similarity index 86% rename from src/Acheve.TestHost/HttpResponseMessageExtensions.cs rename to src/Acheve.TestHost/Extensions/HttpResponseMessageExtensions.cs index b280faf..d427507 100644 --- a/src/Acheve.TestHost/HttpResponseMessageExtensions.cs +++ b/src/Acheve.TestHost/Extensions/HttpResponseMessageExtensions.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using System.Text.Json; using System.Threading.Tasks; namespace System.Net.Http; @@ -37,6 +37,11 @@ public static async Task ReadContentAsAsync(this HttpResponseMessage respo content = $"\"{content}\""; } - return JsonConvert.DeserializeObject(content); + var json = JsonSerializer.Deserialize(content, new JsonSerializerOptions() + { + PropertyNameCaseInsensitive = true + }); + + return json; } } \ No newline at end of file diff --git a/src/Acheve.TestHost/RequestBuilderExtensions.cs b/src/Acheve.TestHost/Extensions/RequestBuilderExtensions.cs similarity index 100% rename from src/Acheve.TestHost/RequestBuilderExtensions.cs rename to src/Acheve.TestHost/Extensions/RequestBuilderExtensions.cs diff --git a/src/Acheve.TestHost/TestServerExtensions.cs b/src/Acheve.TestHost/Extensions/TestServerExtensions.cs similarity index 87% rename from src/Acheve.TestHost/TestServerExtensions.cs rename to src/Acheve.TestHost/Extensions/TestServerExtensions.cs index 63bd6a1..105f69a 100644 --- a/src/Acheve.TestHost/TestServerExtensions.cs +++ b/src/Acheve.TestHost/Extensions/TestServerExtensions.cs @@ -1,6 +1,9 @@ using Acheve.TestHost.Routing; +using Microsoft.AspNetCore.Http; using System; +using System.IO; using System.Linq.Expressions; +using System.Text; namespace Microsoft.AspNetCore.TestHost; @@ -44,4 +47,13 @@ public static RequestBuilder CreateHttpApiRequest( RequestContentOptions contentOptions = null) where TController : class => UriDiscover.CreateHttpApiRequest(server, actionSelector, tokenValues, contentOptions); + + public static IFormFile GivenFile(this TestServer _, string parameterName = "file", string filename = "test.txt", string content = "test") + { + var stream = new MemoryStream(Encoding.UTF8.GetBytes(content)); + + IFormFile file = new FormFile(stream, 0, stream.Length, parameterName, filename); + + return file; + } } diff --git a/src/Acheve.TestHost/IncludeContentAsFormUrlEncoded.cs b/src/Acheve.TestHost/IncludeContentAsFormUrlEncoded.cs deleted file mode 100644 index 40f21b1..0000000 --- a/src/Acheve.TestHost/IncludeContentAsFormUrlEncoded.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Net.Http; - -namespace Microsoft.AspNetCore.TestHost; - -/// -/// An implementation of that includes -/// the [FromForm] parameter as -/// -public class IncludeContentAsFormUrlEncoded : RequestContentOptions -{ - /// - public override bool IncludeFromBodyAsContent => false; - - /// - public override bool IncludeFromFormAsContent => true; - - /// - public override Func ContentBuilder => - content => new FormUrlEncodedContent(ToKeyValue(content)); - - private IDictionary ToKeyValue(object metaToken) - { - if (metaToken is null) - { - return null; - } - - if (metaToken is not JToken token) - { - return ToKeyValue(JObject.FromObject(metaToken)); - } - - if (token.HasValues) - { - var contentData = new Dictionary(); - var childrenContent = token.Children() - .AsEnumerable() - .Select(ToKeyValue) - .Where(childrenContent => childrenContent is not null); - foreach (var childContent in childrenContent) - { - contentData = contentData.Concat(childContent) - .ToDictionary(k => k.Key, v => v.Value); - } - - return contentData; - } - - var jValue = token as JValue; - if (jValue?.Value == null) - { - return null; - } - - var value = jValue?.Type switch - { - JTokenType.Date => jValue?.ToString("o", CultureInfo.InvariantCulture), - JTokenType.Float => jValue?.ToString(), - _ => jValue?.ToString(CultureInfo.InvariantCulture) - }; - - return new Dictionary { { token.Path, value } }; - } -} diff --git a/src/Acheve.TestHost/RequestContent/IncludeContentAsFormUrlEncoded.cs b/src/Acheve.TestHost/RequestContent/IncludeContentAsFormUrlEncoded.cs new file mode 100644 index 0000000..e019c5c --- /dev/null +++ b/src/Acheve.TestHost/RequestContent/IncludeContentAsFormUrlEncoded.cs @@ -0,0 +1,85 @@ +using Microsoft.AspNetCore.Http; +using System; +using System.Collections; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading; + +namespace Microsoft.AspNetCore.TestHost; + +/// +/// An implementation of that includes +/// the [FromForm] parameter as +/// +public class IncludeContentAsFormUrlEncoded : RequestContentOptions +{ + /// + public override bool IncludeFromBodyAsContent => false; + + /// + public override bool IncludeFromFormAsContent => true; + + /// + public override Func ContentBuilder => + content => GetMultipartFormDataContent(content); + + private MultipartFormDataContent GetMultipartFormDataContent(object data) + { + if (data is null) + { + return new MultipartFormDataContent(); + } + + var multipartContent = new MultipartFormDataContent(); + AddToMultipartFormDataContent(multipartContent, data); + + return multipartContent; + } + + private void AddToMultipartFormDataContent(MultipartFormDataContent multipartContent, object data, string propertyName = null) + { + switch (data) + { + case CancellationToken: + break; + case IFormFile file: + using (var ms = new MemoryStream()) + { + file.CopyTo(ms); + var fileContent = new ByteArrayContent(ms.ToArray()); + + multipartContent.Add(fileContent, file.Name, file.FileName); + } + break; + case object when data.GetType().IsPrimitiveType(): + multipartContent.Add(new StringContent(PrimitiveValueToString(data)), propertyName); + break; + case object when data.GetType().IsEnumerable(): + foreach (var item in (IList)data) + { + AddToMultipartFormDataContent(multipartContent, item, propertyName); + } + break; + default: + data.GetType().GetProperties().ToList().ForEach(p => + { + var pName = string.IsNullOrEmpty(propertyName) ? p.Name : $"{propertyName}.{p.Name}"; + var value = p.GetValue(data); + if (value is not null) + { + AddToMultipartFormDataContent(multipartContent, value, pName); + } + }); + break; + } + } + + private static string PrimitiveValueToString(T value) + => value switch + { + (DateTime or DateTimeOffset) and IFormattable dateTimeValue => dateTimeValue.ToString("o", CultureInfo.InvariantCulture), + _ => value.ToString() + }; +} diff --git a/src/Acheve.TestHost/IncludeContentAsJson.cs b/src/Acheve.TestHost/RequestContent/IncludeContentAsJson.cs similarity index 88% rename from src/Acheve.TestHost/IncludeContentAsJson.cs rename to src/Acheve.TestHost/RequestContent/IncludeContentAsJson.cs index 7a767b9..338ebcc 100644 --- a/src/Acheve.TestHost/IncludeContentAsJson.cs +++ b/src/Acheve.TestHost/RequestContent/IncludeContentAsJson.cs @@ -1,7 +1,7 @@ -using Newtonsoft.Json; -using System; +using System; using System.Net.Http; using System.Text; +using System.Text.Json; namespace Microsoft.AspNetCore.TestHost { @@ -13,14 +13,14 @@ public class IncludeContentAsJson : RequestContentOptions { /// public override bool IncludeFromBodyAsContent => true; - + /// public override bool IncludeFromFormAsContent => false; /// public override Func ContentBuilder => content => new StringContent( - JsonConvert.SerializeObject(content), + JsonSerializer.Serialize(content), Encoding.UTF8, "application/json"); } diff --git a/src/Acheve.TestHost/NotIncludeContent.cs b/src/Acheve.TestHost/RequestContent/NotIncludeContent.cs similarity index 100% rename from src/Acheve.TestHost/NotIncludeContent.cs rename to src/Acheve.TestHost/RequestContent/NotIncludeContent.cs diff --git a/src/Acheve.TestHost/RequestContentOptions.cs b/src/Acheve.TestHost/RequestContent/RequestContentOptions.cs similarity index 100% rename from src/Acheve.TestHost/RequestContentOptions.cs rename to src/Acheve.TestHost/RequestContent/RequestContentOptions.cs diff --git a/src/Acheve.TestHost/Routing/TestServerAction.cs b/src/Acheve.TestHost/Routing/TestServerAction.cs index c51d31a..3cc6243 100644 --- a/src/Acheve.TestHost/Routing/TestServerAction.cs +++ b/src/Acheve.TestHost/Routing/TestServerAction.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Linq; @@ -35,12 +36,17 @@ public void AddArgument(int order, Expression expression, bool activeBodyApiCont isFromBody = true; } - var isCancellationToken = argument.ParameterType == typeof(System.Threading.CancellationToken); - if (isCancellationToken) + if (argument.ParameterType == typeof(System.Threading.CancellationToken)) { isFromBody = isFromForm = isFromHeader = false; } + if (argument.ParameterType == typeof(IFormFile)) + { + isFromForm = true; + isFromBody = isFromHeader = false; + } + if (!ArgumentValues.ContainsKey(order)) { if (IsNullable(argument.ParameterType)) diff --git a/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs b/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs index 6a88c57..b2fa0b5 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs @@ -17,9 +17,10 @@ public void AddTokens(TestServerAction action, TestServerTokenColle for (int i = 0; i < parameters.Length; i++) { var type = parameters[i].ParameterType; - var instance = action.ArgumentValues.Any(x => x.Key == i) ? action.ArgumentValues[i].Instance : null; + var argument = action.ArgumentValues.Any(x => x.Key == i) ? action.ArgumentValues[i] : null; + var instance = argument?.Instance; - if (instance == null || type.IsPrimitiveType() || IgnoreBind(parameters[i])) + if (instance is null || type.IsPrimitiveType() || IgnoreBind(parameters[i]) || !IsQueryOrRouteParameter(argument)) { continue; } @@ -43,7 +44,7 @@ public void AddTokens(TestServerAction action, TestServerTokenColle } } - static bool IgnoreBind(ParameterInfo parameter) + private static bool IgnoreBind(ParameterInfo parameter) { var attributes = parameter.GetCustomAttributes(false); @@ -61,4 +62,7 @@ static bool IgnoreBind(ParameterInfo parameter) return false; } + + private static bool IsQueryOrRouteParameter(TestServerArgument argument) + => !(argument.IsFromHeader || argument.IsFromForm || argument.IsFromBody); } diff --git a/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs b/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs index 1a140ed..bf3f671 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs @@ -1,8 +1,9 @@ using Microsoft.AspNetCore.Mvc; -using Newtonsoft.Json; using System; +using System.Globalization; using System.Linq; using System.Reflection; +using System.Text.Json; namespace Acheve.TestHost.Routing.Tokenizers; @@ -42,8 +43,8 @@ public void AddTokens(TestServerAction action, TestServerTokenColle public static string PrimitiveValueToString(T value) => value switch { - DateTime dateTimeValue => dateTimeValue.ToString("yyyy/MM/ddTHH:mm:ss.fff"), - double doubleValue => JsonConvert.SerializeObject(doubleValue), + (DateTime or DateTimeOffset) and IFormattable dateTimeValue => dateTimeValue.ToString("o", CultureInfo.InvariantCulture), + float or double or long or decimal => JsonSerializer.Serialize(value), _ => value.ToString() }; diff --git a/src/Acheve.TestHost/Routing/UriDiscover.cs b/src/Acheve.TestHost/Routing/UriDiscover.cs index 03a640a..4a96346 100644 --- a/src/Acheve.TestHost/Routing/UriDiscover.cs +++ b/src/Acheve.TestHost/Routing/UriDiscover.cs @@ -45,7 +45,7 @@ public static RequestBuilder CreateHttpApiRequest(TestServer server //the uri discover use only attribute route conventions. - var validUri = UriDiscover.Discover(action, tokenValues); + var validUri = Discover(action, tokenValues); var requestBuilder = server.CreateRequest(validUri); diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV4Controller.cs b/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV4Controller.cs index a85b59e..8f05545 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV4Controller.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV4Controller.cs @@ -1,83 +1,82 @@ using Microsoft.AspNetCore.Mvc; -namespace UnitTests.Acheve.TestHost.Builders +namespace UnitTests.Acheve.TestHost.Builders; + +[Route("api/values")] +[ApiController] +public class ValuesV4Controller + : ControllerBase { - [Route("api/values")] - [ApiController] - public class ValuesV4Controller - : ControllerBase + [HttpGet("~/get1/{index}")] + public IActionResult Get1(int index) { - [HttpGet("~/get1/{index}")] - public IActionResult Get1(int index) - { - return Ok(); - } + return Ok(); + } - [HttpPost("~/post1/{index}")] - public IActionResult Post1(int index) - { - return Ok(); - } + [HttpPost("~/post1/{index}")] + public IActionResult Post1(int index) + { + return Ok(); + } - [HttpPost("~/put1/{index}")] - public IActionResult Put1(int index) - { - return Ok(); - } + [HttpPut("~/put1/{index}")] + public IActionResult Put1(int index) + { + return Ok(); + } - [HttpPost("~/delete1/{index}")] - public IActionResult Delete1(int index) - { - return Ok(); - } + [HttpDelete("~/delete1/{index}")] + public IActionResult Delete1(int index) + { + return Ok(); + } - [HttpGet("~/get2/{index}")] - public IActionResult Get2(int index,Pagination pagination) - { - return Ok(); - } + [HttpGet("~/get2/{index}")] + public IActionResult Get2(int index, Pagination pagination) + { + return Ok(); + } - [HttpPost("~/post2/{index}")] - public IActionResult Post2(int index, Pagination pagination) - { - return Ok(); - } + [HttpPost("~/post2/{index}")] + public IActionResult Post2(int index, Pagination pagination) + { + return Ok(); + } - [HttpPost("~/put2/{index}")] - public IActionResult Put2(int index, Pagination pagination) - { - return Ok(); - } + [HttpPut("~/put2/{index}")] + public IActionResult Put2(int index, Pagination pagination) + { + return Ok(); + } - [HttpPost("~/delete2/{index}")] - public IActionResult Delete2(int index, Pagination pagination) - { - return Ok(); - } + [HttpDelete("~/delete2/{index}")] + public IActionResult Delete2(int index, Pagination pagination) + { + return Ok(); + } - [Route("~/get3/{index}"),HttpGet] - public IActionResult Get3(int index, Pagination pagination) - { - return Ok(); - } + [Route("~/get3/{index}"), HttpGet] + public IActionResult Get3(int index, Pagination pagination) + { + return Ok(); + } - [Route("~/post3/{index}"),HttpPost] - public IActionResult Post3(int index, Pagination pagination) - { - return Ok(); - } + [Route("~/post3/{index}"), HttpPost] + public IActionResult Post3(int index, Pagination pagination) + { + return Ok(); + } - [Route("~/put3/{index}"),HttpPut] - public IActionResult Put3(int index, Pagination pagination) - { - return Ok(); - } + [Route("~/put3/{index}"), HttpPut] + public IActionResult Put3(int index, Pagination pagination) + { + return Ok(); + } - [Route("~/delete3/{index}"),HttpPost] - public IActionResult Delete3(int index, Pagination pagination) - { - return Ok(); - } + [Route("~/delete3/{index}"), HttpDelete] + public IActionResult Delete3(int index, Pagination pagination) + { + return Ok(); } } diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs b/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs index 9c51546..5b747f9 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs @@ -1,83 +1,114 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System.IO; using System.Threading; +using System.Threading.Tasks; +using UnitTests.Acheve.TestHost.Routing.Models; -namespace UnitTests.Acheve.TestHost.Builders +namespace UnitTests.Acheve.TestHost.Builders; + +[Route("api/values")] +[ApiController] +public class ValuesV5Controller : ControllerBase { - [Route("api/values")] - [ApiController] - public class ValuesV5Controller : ControllerBase + [HttpPost] + public IActionResult Post1(Pagination pagination) + { + return Ok(); + } + + [HttpPost("{id:int}")] + public IActionResult Post2(int id, Pagination pagination1) { - [HttpPost] - public IActionResult Post1(Pagination pagination) + if (pagination1 == null) { - return Ok(); + return BadRequest(); } - [HttpPost("{id:int}")] - public IActionResult Post2(int id, Pagination pagination1) - { - if (pagination1 == null) - { - return BadRequest(); - } + return Ok(); + } - return Ok(); + [HttpPost("{id}")] + public IActionResult Post3(string id, Pagination pagination1) + { + if (pagination1 == null || string.IsNullOrWhiteSpace(id)) + { + return BadRequest(); } - [HttpPost("{id}")] - public IActionResult Post3(string id, Pagination pagination1) - { - if (pagination1 == null || string.IsNullOrWhiteSpace(id)) - { - return BadRequest(); - } + return Ok(); + } - return Ok(); + [HttpPatch("{id:int}")] + public IActionResult Patch1(int id, Pagination pagination1) + { + if (pagination1 == null) + { + return BadRequest(); } - [HttpPatch("{id:int}")] - public IActionResult Patch1(int id, Pagination pagination1) - { - if (pagination1 == null) - { - return BadRequest(); - } + return Ok(); + } - return Ok(); + [HttpGet(nameof(GetWithCancellationToken))] + public ActionResult GetWithCancellationToken([FromQuery] string value, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return BadRequest(); } - [HttpGet(nameof(GetWithCancellationToken))] - public ActionResult GetWithCancellationToken([FromQuery] string value, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return BadRequest(); - } + return Ok(value); + } - return Ok(value); + [HttpPost(nameof(PostWithCancellationToken))] + public ActionResult PostWithCancellationToken([FromBody] string value, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return BadRequest(); } - [HttpPost(nameof(PostWithCancellationToken))] - public ActionResult PostWithCancellationToken([FromBody] string value, CancellationToken cancellationToken) - { - if (cancellationToken.IsCancellationRequested) - { - return BadRequest(); - } + return Ok(value); + } - return Ok(value); - } + [HttpGet(nameof(GetBadRequest))] + public IActionResult GetBadRequest() + { + return BadRequest(); + } - [HttpGet(nameof(GetBadRequest))] - public IActionResult GetBadRequest() - { + [HttpGet(nameof(GetOk))] + public IActionResult GetOk() + { + return Ok(); + } + + [HttpPost(nameof(PostFile))] + public async Task> PostFile(IFormFile file) + { + if (file is null) return BadRequest(); - } - [HttpGet(nameof(GetOk))] - public IActionResult GetOk() - { - return Ok(); - } + var content = await ReadFormFile(file); + + return Ok(content); + } + + [HttpPost(nameof(PostObjectWithFile))] + public async Task> PostObjectWithFile([FromForm] ParamWithFile model) + { + if (model is null) + return BadRequest(); + + var content = await ReadFormFile(model.File); + + return Ok($"{model.Id}+{content}"); + } + + private static async Task ReadFormFile(IFormFile file) + { + using var reader = new StreamReader(file.OpenReadStream()); + return await reader.ReadToEndAsync(); } } diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithFile.cs b/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithFile.cs new file mode 100644 index 0000000..c052242 --- /dev/null +++ b/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithFile.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Http; +using System; + +namespace UnitTests.Acheve.TestHost.Routing.Models; + +public class ParamWithFile +{ + public int Id { get; set; } + public IFormFile File { get; set; } + + public ParamWithFile() { } + + public ParamWithFile(IFormFile file) + { + var random = new Random(); + + Id = random.Next(); + File = file; + } +} diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs b/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithSeveralTypes.cs similarity index 100% rename from tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithList.cs rename to tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithSeveralTypes.cs diff --git a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs index bc25a36..607e853 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs @@ -814,13 +814,13 @@ public void Create_valid_request_when_action_use_tilde_to_override_controller_ro controller => controller.Post2(1, pagination)); requestPost.GetConfiguredAddress() - .Should().Be("post2/1?pageindex=1&pagecount=2"); + .Should().Be("post2/1"); requestPost = server.CreateHttpApiRequest( controller => controller.Put2(1, pagination)); requestPost.GetConfiguredAddress() - .Should().Be("put2/1?pageindex=1&pagecount=2"); + .Should().Be("put2/1"); requestPost = server.CreateHttpApiRequest( controller => controller.Delete2(1, pagination)); @@ -838,13 +838,13 @@ public void Create_valid_request_when_action_use_tilde_to_override_controller_ro controller => controller.Post3(1, pagination)); requestPost.GetConfiguredAddress() - .Should().Be("post3/1?pageindex=1&pagecount=2"); + .Should().Be("post3/1"); requestPost = server.CreateHttpApiRequest( controller => controller.Put3(1, pagination)); requestPost.GetConfiguredAddress() - .Should().Be("put3/1?pageindex=1&pagecount=2"); + .Should().Be("put3/1"); requestPost = server.CreateHttpApiRequest( controller => controller.Delete3(1, pagination)); @@ -899,13 +899,13 @@ public void Create_valid_request_when_action_use_tilde_to_override_controller_ro controller => controller.Post2(1, pagination)); requestPost.GetConfiguredAddress() - .Should().Be("post2/1?pageindex=1"); + .Should().Be("post2/1"); requestPost = server.CreateHttpApiRequest( controller => controller.Put2(1, pagination)); requestPost.GetConfiguredAddress() - .Should().Be("put2/1?pageindex=1"); + .Should().Be("put2/1"); requestPost = server.CreateHttpApiRequest( controller => controller.Delete2(1, pagination)); @@ -923,13 +923,13 @@ public void Create_valid_request_when_action_use_tilde_to_override_controller_ro controller => controller.Post3(1, pagination)); requestPost.GetConfiguredAddress() - .Should().Be("post3/1?pageindex=1"); + .Should().Be("post3/1"); requestPost = server.CreateHttpApiRequest( controller => controller.Put3(1, pagination)); requestPost.GetConfiguredAddress() - .Should().Be("put3/1?pageindex=1"); + .Should().Be("put3/1"); requestPost = server.CreateHttpApiRequest( controller => controller.Delete3(1, pagination)); @@ -1871,6 +1871,39 @@ public async Task Create_get_request_with_object_from_query_setting_action_respo response.Should().Be(model); } + [Fact] + public async Task Create_post_request_with_file() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var model = server.GivenFile(content: nameof(Create_post_request_with_file)); + var request = server.CreateHttpApiRequest(controller => controller.PostFile(model)); + var responseMessage = await request.PostAsync(); + + await responseMessage.IsSuccessStatusCodeOrThrow(); + var content = await responseMessage.Content.ReadAsStringAsync(); + content.Should().Be(nameof(Create_post_request_with_file)); + } + + [Fact] + public async Task Create_post_request_with_object_with_file() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var file = server.GivenFile(content: nameof(Create_post_request_with_file)); + var model = new ParamWithFile(file); + var request = server.CreateHttpApiRequest(controller => controller.PostObjectWithFile(model)); + var responseMessage = await request.PostAsync(); + + await responseMessage.IsSuccessStatusCodeOrThrow(); + var content = await responseMessage.Content.ReadAsStringAsync(); + content.Should().Be($"{model.Id}+{nameof(Create_post_request_with_file)}"); + } + private class PrivateNonControllerClass { public int SomeAction() From d5aa032e8535ba6615b2302937dffb2de74793ef Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Mon, 22 May 2023 08:28:57 +0200 Subject: [PATCH 16/23] Update version to 3.5.0 (#89) --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index bd1872a..3f7e2bb 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ - 3.4.0 + 3.5.0 Apache-2.0 http://github.com/xabaril/Acheve.TestHost From 2018abc9fc1eb786ae3c483b98390865f7c36cf4 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Mon, 22 May 2023 09:00:08 +0200 Subject: [PATCH 17/23] Add DateTimeOffset to IsDateTime function (#91) Move TypeExtensions --- .../{Routing/Tokenizers => Extensions}/TypeExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/Acheve.TestHost/{Routing/Tokenizers => Extensions}/TypeExtensions.cs (90%) diff --git a/src/Acheve.TestHost/Routing/Tokenizers/TypeExtensions.cs b/src/Acheve.TestHost/Extensions/TypeExtensions.cs similarity index 90% rename from src/Acheve.TestHost/Routing/Tokenizers/TypeExtensions.cs rename to src/Acheve.TestHost/Extensions/TypeExtensions.cs index 73e8792..6464836 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/TypeExtensions.cs +++ b/src/Acheve.TestHost/Extensions/TypeExtensions.cs @@ -18,7 +18,7 @@ internal static bool IsPrimitiveType(this Type typeToInspect) internal static bool IsDateTime(this Type typeToInspect) { - return typeToInspect == typeof(DateTime); + return typeToInspect == typeof(DateTime) || typeToInspect == typeof(DateTimeOffset); } internal static bool IsEnumerable(this Type typeToInspect) From 0f613c29568c730d48d25de34901e6381633c8d1 Mon Sep 17 00:00:00 2001 From: Sergio Date: Thu, 25 May 2023 13:19:23 +0200 Subject: [PATCH 18/23] Allow dispatch workflows manually --- .github/workflows/ci.yml | 1 + .github/workflows/nuget.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9cd19a7..7bb99da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,7 @@ on: branches: - master - develop + workflow_dispatch: jobs: build: diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index 10de10e..775b937 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -3,6 +3,7 @@ on: push: tags: - v* + workflow_dispatch: jobs: build: From 316d22c1c6d113cd96f2c5eecdd75e440ebb3361 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Fri, 3 May 2024 11:27:56 +0200 Subject: [PATCH 19/23] Update to Net8 (#92) --- .github/workflows/ci.yml | 9 ++++----- .github/workflows/nuget.yml | 7 +++---- Directory.Build.props | 2 +- Directory.Packages.props | 25 +++++++++++-------------- global.json | 2 +- 5 files changed, 20 insertions(+), 25 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7bb99da..be21861 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,13 +24,12 @@ jobs: uses: actions/setup-dotnet@v2 with: dotnet-version: | - 3.1.417 - 5.0.101 - 6.0.300 - 7.0.302 + 6.0.421 + 7.0.408 + 8.0.204 - name: Build run: dotnet build -c $BUILD_CONFIG - name: Test - run: dotnet test -c $BUILD_CONFIG --no-build \ No newline at end of file + run: dotnet test -c $BUILD_CONFIG --no-build diff --git a/.github/workflows/nuget.yml b/.github/workflows/nuget.yml index 775b937..f4455d7 100644 --- a/.github/workflows/nuget.yml +++ b/.github/workflows/nuget.yml @@ -19,10 +19,9 @@ jobs: uses: actions/setup-dotnet@v2 with: dotnet-version: | - 3.1.417 - 5.0.101 - 6.0.300 - 7.0.302 + 6.0.421 + 7.0.408 + 8.0.204 - name: Build run: dotnet build -c $BUILD_CONFIG diff --git a/Directory.Build.props b/Directory.Build.props index 3f7e2bb..f23c521 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@  - netcoreapp3.1;net5.0;net6.0;net7.0 + net6.0;net7.0;net8.0 latest true diff --git a/Directory.Packages.props b/Directory.Packages.props index 608ec0f..63bca32 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,15 +1,12 @@  - - 3.1.32 - - - 5.0.17 - - 6.0.16 + 6.0.29 - 7.0.5 + 7.0.18 + + + 8.0.4 @@ -20,13 +17,13 @@ - - - - - + + + + + - + \ No newline at end of file diff --git a/global.json b/global.json index fb292b5..e0311ae 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "projects": ["src", "test", "samples"], "sdk": { - "version": "6.0.000", + "version": "8.0.000", "rollForward": "latestMajor" } } From afb09ea3b35ffe07233580dcaee71736285f9b26 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Mon, 13 May 2024 17:02:24 +0200 Subject: [PATCH 20/23] Different froms in object (#93) * Change to use TestServerArgumentFromType * fix parameter names order * Change Tokenizers to use argument instead of parameters * Fix warnings * Only apply canBeObjectWithMultipleFroms in Net 8 or greater --- .../Routing/TestServerAction.cs | 129 ++++++++++++++---- .../Routing/TestServerArgument.cs | 42 ++++-- .../ComplexParameterActionTokenizer.cs | 14 +- .../EnumerableParameterActionTokenizer.cs | 10 +- .../PrimitiveParameterActionTokenizer.cs | 24 ++-- src/Acheve.TestHost/Routing/UriDiscover.cs | 12 +- .../Security/TestServerHandler.cs | 12 +- .../Routing/Builders/ValuesV5Controller.cs | 8 ++ .../Routing/Models/ParamWithDifferentFroms.cs | 18 +++ .../Routing/TestServerExtensionsTests.cs | 70 ++++++++-- 10 files changed, 247 insertions(+), 92 deletions(-) create mode 100644 tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithDifferentFroms.cs diff --git a/src/Acheve.TestHost/Routing/TestServerAction.cs b/src/Acheve.TestHost/Routing/TestServerAction.cs index 3cc6243..859a2b9 100644 --- a/src/Acheve.TestHost/Routing/TestServerAction.cs +++ b/src/Acheve.TestHost/Routing/TestServerAction.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; using System; using System.Collections.Generic; using System.Linq; @@ -23,29 +24,7 @@ public TestServerAction(MethodInfo methodInfo) public void AddArgument(int order, Expression expression, bool activeBodyApiController) { var argument = MethodInfo.GetParameters()[order]; - var isFromBody = argument.GetCustomAttributes().Any(); - var isFromForm = argument.GetCustomAttributes().Any(); - var isFromHeader = argument.GetCustomAttributes().Any(); - var isFromRoute = argument.GetCustomAttributes().Any(); - - bool isPrimitive = argument.ParameterType.IsPrimitiveType(); - bool hasNoAttributes = !isFromBody && !isFromForm && !isFromHeader && !isFromRoute; - - if (activeBodyApiController && hasNoAttributes && !isPrimitive) - { - isFromBody = true; - } - - if (argument.ParameterType == typeof(System.Threading.CancellationToken)) - { - isFromBody = isFromForm = isFromHeader = false; - } - - if (argument.ParameterType == typeof(IFormFile)) - { - isFromForm = true; - isFromBody = isFromHeader = false; - } + var (fromType, canBeObjectWithMultipleFroms, isNeverBind) = GetIsFrom(argument, activeBodyApiController, value => value.ParameterType, value => value.GetCustomAttributes()); if (!ArgumentValues.ContainsKey(order)) { @@ -55,7 +34,7 @@ public void AddArgument(int order, Expression expression, bool activeBodyApiCont if (expressionValue != null) { - ArgumentValues.Add(order, new TestServerArgument(expressionValue.ToString(), isFromBody, isFromForm, isFromHeader, argument.Name)); + ArgumentValues.Add(order, new TestServerArgument(expressionValue.ToString(), fromType, isNeverBind, argument.ParameterType, argument.Name)); } } else @@ -64,7 +43,7 @@ public void AddArgument(int order, Expression expression, bool activeBodyApiCont { case ConstantExpression constant: { - ArgumentValues.Add(order, new TestServerArgument(constant.Value?.ToString(), isFromBody, isFromForm, isFromHeader, argument.Name)); + ArgumentValues.Add(order, new TestServerArgument(constant.Value?.ToString(), fromType, isNeverBind, argument.ParameterType, argument.Name)); } break; @@ -73,15 +52,14 @@ public void AddArgument(int order, Expression expression, bool activeBodyApiCont var instance = Expression.Lambda(member) .Compile() .DynamicInvoke(); - - ArgumentValues.Add(order, new TestServerArgument(instance, isFromBody, isFromForm, isFromHeader, argument.Name)); + AddArgumentValues(order, instance, argument.Name, fromType, isNeverBind, argument.ParameterType, canBeObjectWithMultipleFroms); } break; case MethodCallExpression method: { var instance = Expression.Lambda(method).Compile().DynamicInvoke(); - ArgumentValues.Add(order, new TestServerArgument(instance, isFromBody, isFromForm, isFromHeader, argument.Name)); + AddArgumentValues(order, instance, argument.Name, fromType, isNeverBind, argument.ParameterType, canBeObjectWithMultipleFroms); } break; @@ -91,6 +69,99 @@ public void AddArgument(int order, Expression expression, bool activeBodyApiCont } } - private bool IsNullable(Type type) => Nullable.GetUnderlyingType(type) != null; + private static bool IsNullable(Type type) => Nullable.GetUnderlyingType(type) != null; + + private void AddArgumentValues(int order, object value, string argumentName, + TestServerArgumentFromType fromType, bool isNeverBind, Type type, bool canBeObjectWithMultipleFroms) + { + if (canBeObjectWithMultipleFroms) + { + var properties = value.GetType().GetProperties(); + var isObjectWithMultipleFroms = properties + .SelectMany(p => p.GetCustomAttributes()) + .Select(a => a.GetType()) + .Any(a => a == typeof(FromBodyAttribute) || a == typeof(FromRouteAttribute) || a == typeof(FromHeaderAttribute) || a == typeof(FromQueryAttribute)); + if (isObjectWithMultipleFroms) + { + foreach (var property in properties) + { + (fromType, canBeObjectWithMultipleFroms, var isNeverBindProp) = GetIsFrom(property, false, value => value.PropertyType, value => value.GetCustomAttributes()); + argumentName = property.Name; + var propertyValue = property.GetValue(value); + + ArgumentValues.Add(order, new TestServerArgument(propertyValue, fromType, isNeverBind || isNeverBindProp, property.PropertyType, argumentName)); + order++; + } + + return; + } + } + + ArgumentValues.Add(order, new TestServerArgument(value, fromType, isNeverBind, type, argumentName)); + } + + private (TestServerArgumentFromType fromType, bool canBeObjectWithMultipleFroms, bool neverBind) GetIsFrom(T value, bool activeBodyApiController, Func getTypeFunc, Func> getAttributesFunc) + { + var fromType = TestServerArgumentFromType.None; + var type = getTypeFunc(value); + var attributes = getAttributesFunc(value); + + var isFromBody = attributes.Any(a => a is FromBodyAttribute); + var isFromForm = attributes.Any(a => a is FromFormAttribute); + var isFromHeader = attributes.Any(a => a is FromHeaderAttribute); + var isFromRoute = attributes.Any(a => a is FromRouteAttribute); + var isFromQuery = attributes.Any(a => a is FromQueryAttribute); + var isBindNever = attributes.Any(a => a is BindNeverAttribute); + + if (isFromBody) + { + fromType |= TestServerArgumentFromType.Body; + } + if (isFromForm) + { + fromType |= TestServerArgumentFromType.Form; + } + if (isFromHeader) + { + fromType |= TestServerArgumentFromType.Header; + } + if (isFromRoute) + { + fromType |= TestServerArgumentFromType.Route; + } + if (isFromQuery) + { + fromType |= TestServerArgumentFromType.Query; + } + + bool isPrimitive = type.IsPrimitiveType(); + bool hasNoAttributes = fromType == TestServerArgumentFromType.None; + bool canBeObjectWithMultipleFroms = false; + if (hasNoAttributes && !isPrimitive) + { +#if NET8_0_OR_GREATER + canBeObjectWithMultipleFroms = MethodInfo.GetParameters().Length == 1; +#else + canBeObjectWithMultipleFroms = false; +#endif + + if (activeBodyApiController) + { + fromType = TestServerArgumentFromType.Body; + } + } + + if (type == typeof(System.Threading.CancellationToken)) + { + fromType = TestServerArgumentFromType.None; + } + + if (type == typeof(IFormFile)) + { + fromType = TestServerArgumentFromType.Form; + } + + return (fromType, canBeObjectWithMultipleFroms, isBindNever); + } } } \ No newline at end of file diff --git a/src/Acheve.TestHost/Routing/TestServerArgument.cs b/src/Acheve.TestHost/Routing/TestServerArgument.cs index 5ac6069..b3b1dc1 100644 --- a/src/Acheve.TestHost/Routing/TestServerArgument.cs +++ b/src/Acheve.TestHost/Routing/TestServerArgument.cs @@ -1,26 +1,38 @@ -namespace Acheve.TestHost.Routing +using System; + +namespace Acheve.TestHost.Routing { + [Flags] + public enum TestServerArgumentFromType + { + None = 0, + Body = 1, + Form = 2, + Header = 4, + Query = 8, + Route = 32, + } + public class TestServerArgument { public TestServerArgument( object instance, - bool isFromBody = false, - bool isFromForm = false, - bool isFromHeader = false, - string headerName = null) + TestServerArgumentFromType fromType, + bool neverBind, + Type type, + string name) { Instance = instance; - IsFromBody = isFromBody; - IsFromForm = isFromForm; - IsFromHeader = isFromHeader; - HeaderName = isFromHeader ? headerName : null; + FromType = fromType; + Name = name; + NeverBind = neverBind; + Type = type; } - public object Instance { get; private set; } - - public bool IsFromBody { get; private set; } - public bool IsFromForm { get; private set; } - public bool IsFromHeader { get; private set; } - public string HeaderName { get; private set; } + public object Instance { get; } + public TestServerArgumentFromType FromType { get; } + public string Name { get; } + public bool NeverBind { get; } + public Type Type { get; } } } \ No newline at end of file diff --git a/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs b/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs index b2fa0b5..6f1e680 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/ComplexParameterActionTokenizer.cs @@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; using System; using System.Collections; -using System.Linq; using System.Reflection; namespace Acheve.TestHost.Routing.Tokenizers; @@ -12,15 +11,12 @@ class ComplexParameterActionTokenizer { public void AddTokens(TestServerAction action, TestServerTokenCollection tokens) where TController : class { - var parameters = action.MethodInfo.GetParameters(); - - for (int i = 0; i < parameters.Length; i++) + foreach (var argument in action.ArgumentValues.Values) { - var type = parameters[i].ParameterType; - var argument = action.ArgumentValues.Any(x => x.Key == i) ? action.ArgumentValues[i] : null; - var instance = argument?.Instance; + var type = argument.Type; + var instance = argument.Instance; - if (instance is null || type.IsPrimitiveType() || IgnoreBind(parameters[i]) || !IsQueryOrRouteParameter(argument)) + if (instance is null || type.IsPrimitiveType() || argument.NeverBind || !IsQueryOrRouteParameter(argument)) { continue; } @@ -64,5 +60,5 @@ private static bool IgnoreBind(ParameterInfo parameter) } private static bool IsQueryOrRouteParameter(TestServerArgument argument) - => !(argument.IsFromHeader || argument.IsFromForm || argument.IsFromBody); + => argument.FromType == TestServerArgumentFromType.None || argument.FromType.HasFlag(TestServerArgumentFromType.Query) || argument.FromType.HasFlag(TestServerArgumentFromType.Route); } diff --git a/src/Acheve.TestHost/Routing/Tokenizers/EnumerableParameterActionTokenizer.cs b/src/Acheve.TestHost/Routing/Tokenizers/EnumerableParameterActionTokenizer.cs index f440790..abfd72f 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/EnumerableParameterActionTokenizer.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/EnumerableParameterActionTokenizer.cs @@ -10,23 +10,21 @@ internal class EnumerableParameterActionTokenizer public void AddTokens(TestServerAction action, TestServerTokenCollection tokens) where TController : class { - var parameters = action.MethodInfo.GetParameters(); - - for (var i = 0; i < parameters.Length; i++) + foreach (var argument in action.ArgumentValues.Values) { - var parameterType = parameters[i].ParameterType; + var parameterType = argument.Type; if (!parameterType.IsEnumerable() || !parameterType.GetEnumerableElementType().IsPrimitiveType()) { continue; } - var arrayValues = (IList)action.ArgumentValues[i].Instance; + var arrayValues = (IList)argument.Instance; if (arrayValues == null || arrayValues.Count == 0) { continue; } - var tokenName = parameters[i].Name.ToLowerInvariant(); + var tokenName = argument.Name.ToLowerInvariant(); var tokenValue = GetTokenValue(arrayValues, tokenName); tokens.AddToken(tokenName, tokenValue, isConventional: false); } diff --git a/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs b/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs index bf3f671..eed5899 100644 --- a/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs +++ b/src/Acheve.TestHost/Routing/Tokenizers/PrimitiveParameterActionTokenizer.cs @@ -1,8 +1,5 @@ -using Microsoft.AspNetCore.Mvc; -using System; +using System; using System.Globalization; -using System.Linq; -using System.Reflection; using System.Text.Json; namespace Acheve.TestHost.Routing.Tokenizers; @@ -13,29 +10,26 @@ internal class PrimitiveParameterActionTokenizer public void AddTokens(TestServerAction action, TestServerTokenCollection tokens) where TController : class { - ParameterInfo[] parameters = action.MethodInfo.GetParameters(); - - for (int i = 0; i < parameters.Length; i++) + foreach (var argument in action.ArgumentValues.Values) { - var parameter = parameters[i]; - if (IgnoreHeader(parameter)) + if (IgnoreHeader(argument)) { continue; } - Type parameterType = parameter.ParameterType; + Type parameterType = argument.Type; if (!parameterType.IsPrimitiveType()) { continue; } - object tokenValue = action.ArgumentValues.Any(x => x.Key == i) ? action.ArgumentValues[i].Instance : null; + object tokenValue = argument.Instance; if (tokenValue == null) { continue; } - string tokenName = parameter.Name.ToLowerInvariant(); + string tokenName = argument.Name.ToLowerInvariant(); tokens.AddToken(tokenName, PrimitiveValueToString(tokenValue), isConventional: false); } } @@ -48,8 +42,6 @@ public static string PrimitiveValueToString(T value) _ => value.ToString() }; - private static bool IgnoreHeader(ParameterInfo parameter) - => parameter - .GetCustomAttributes(false) - .Any(a => a.GetType() == typeof(FromHeaderAttribute)); + private static bool IgnoreHeader(TestServerArgument parameter) + => parameter.FromType.HasFlag(TestServerArgumentFromType.Header); } \ No newline at end of file diff --git a/src/Acheve.TestHost/Routing/UriDiscover.cs b/src/Acheve.TestHost/Routing/UriDiscover.cs index 4a96346..f9d83db 100644 --- a/src/Acheve.TestHost/Routing/UriDiscover.cs +++ b/src/Acheve.TestHost/Routing/UriDiscover.cs @@ -50,7 +50,9 @@ public static RequestBuilder CreateHttpApiRequest(TestServer server var requestBuilder = server.CreateRequest(validUri); // Include content as Json by default - contentOptions ??= action.ArgumentValues.Values.Any(a => a.IsFromForm) ? new IncludeContentAsFormUrlEncoded() : new IncludeContentAsJson(); + contentOptions ??= action.ArgumentValues.Values.Any(a => a.FromType.HasFlag(TestServerArgumentFromType.Form)) + ? new IncludeContentAsFormUrlEncoded() + : new IncludeContentAsJson(); if (contentOptions.IncludeFromBodyAsContent) { @@ -237,7 +239,7 @@ private static void AddFromBodyArgumentsToRequestBody( TestServerAction action, RequestContentOptions contentOptions) { - var fromBodyArgument = action.ArgumentValues.Values.SingleOrDefault(x => x.IsFromBody); + var fromBodyArgument = action.ArgumentValues.Values.SingleOrDefault(x => x.FromType.HasFlag(TestServerArgumentFromType.Body)); if (fromBodyArgument != null) { @@ -251,7 +253,7 @@ private static void AddFromFormArgumentsToRequestForm( TestServerAction action, RequestContentOptions contentOptions) { - var fromFormArgument = action.ArgumentValues.Values.SingleOrDefault(x => x.IsFromForm); + var fromFormArgument = action.ArgumentValues.Values.SingleOrDefault(x => x.FromType.HasFlag(TestServerArgumentFromType.Form)); if (fromFormArgument != null) { @@ -264,11 +266,11 @@ private static void AddFromHeaderArgumentsToRequestForm( RequestBuilder requestBuilder, TestServerAction action) { - var fromHeaderArguments = action.ArgumentValues.Values.Where(x => x.IsFromHeader); + var fromHeaderArguments = action.ArgumentValues.Values.Where(x => x.FromType.HasFlag(TestServerArgumentFromType.Header)); foreach (var fromHeaderArgument in fromHeaderArguments) { - requestBuilder.And(x => x.Headers.Add(fromHeaderArgument.HeaderName, fromHeaderArgument.Instance.ToString())); + requestBuilder.And(x => x.Headers.Add(fromHeaderArgument.Name, fromHeaderArgument.Instance.ToString())); } } } diff --git a/src/Acheve.TestHost/Security/TestServerHandler.cs b/src/Acheve.TestHost/Security/TestServerHandler.cs index ec0c1c5..8759c9e 100644 --- a/src/Acheve.TestHost/Security/TestServerHandler.cs +++ b/src/Acheve.TestHost/Security/TestServerHandler.cs @@ -13,14 +13,22 @@ namespace Acheve.TestHost { public class TestServerHandler : AuthenticationHandler { +#if NET8_0_OR_GREATER + public TestServerHandler( + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder) + : base(options, logger, encoder) + { } +#else public TestServerHandler( IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) - { - } + { } +#endif protected new TestServerEvents Events { diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs b/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs index 5b747f9..87c3437 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs @@ -111,4 +111,12 @@ private static async Task ReadFormFile(IFormFile file) using var reader = new StreamReader(file.OpenReadStream()); return await reader.ReadToEndAsync(); } + + [HttpPost($"{nameof(PostWithDifferentFroms)}/{{{nameof(ParamWithDifferentFroms.ParamFromRoute)}}}")] + public ActionResult PostWithDifferentFroms(ParamWithDifferentFroms request) + => Ok(request); + + [HttpPut($"{nameof(PutWithDifferentFroms)}/{{{nameof(ParamWithDifferentFroms.ParamFromRoute)}}}")] + public ActionResult PutWithDifferentFroms(ParamWithDifferentFroms request) + => Ok(request); } diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithDifferentFroms.cs b/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithDifferentFroms.cs new file mode 100644 index 0000000..81e08f0 --- /dev/null +++ b/tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithDifferentFroms.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc; + +namespace UnitTests.Acheve.TestHost.Routing.Models; + +public class ParamWithDifferentFroms +{ + [FromRoute] + public string ParamFromRoute { get; set; } + + [FromQuery] + public string ParamFromQuery { get; set; } + + [FromHeader] + public string ParamFromHeader { get; set; } + + [FromBody] + public ParamWithSeveralTypes ParamFromBody { get; set; } +} diff --git a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs index 607e853..e319f82 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/TestServerExtensionsTests.cs @@ -1263,7 +1263,7 @@ public void Create_request_supporting_guid_types_on_parameters_and_numbes_on_par } [Fact] - public void Create_valid_request_without_using_frombody_with_apicontroller_attribute() + public async Task Create_valid_request_without_using_frombody_with_apicontroller_attribute() { var server = new TestServerBuilder().UseDefaultStartup() .Build(); @@ -1276,13 +1276,13 @@ public void Create_valid_request_without_using_frombody_with_apicontroller_attri var requestPost1 = server.CreateHttpApiRequest(controller => controller.Post1(complexParameter)); - string body = requestPost1.GetRequest().Content.ReadAsStringAsync().Result; + string body = await requestPost1.GetRequest().Content.ReadAsStringAsync(); JsonSerializer.Deserialize(body).PageIndex.Should().Be(complexParameter.PageIndex); JsonSerializer.Deserialize(body).PageCount.Should().Be(complexParameter.PageCount); } [Fact] - public void Create_valid_request_without_using_frombody_with_apicontroller_attribute_and_route_parameter() + public async Task Create_valid_request_without_using_frombody_with_apicontroller_attribute_and_route_parameter() { var server = new TestServerBuilder().UseDefaultStartup() .Build(); @@ -1295,14 +1295,14 @@ public void Create_valid_request_without_using_frombody_with_apicontroller_attri var requestPost2 = server.CreateHttpApiRequest(controller => controller.Post2(1, complexParameter)); - string body = requestPost2.GetRequest().Content.ReadAsStringAsync().Result; + string body = await requestPost2.GetRequest().Content.ReadAsStringAsync(); JsonSerializer.Deserialize(body).PageIndex.Should().Be(complexParameter.PageIndex); JsonSerializer.Deserialize(body).PageCount.Should().Be(complexParameter.PageCount); requestPost2.GetConfiguredAddress().StartsWith($"{BASE_PATH_VALUES}/1").Should().Be(true); } [Fact] - public void Create_valid_request_without_using_frombody_with_apicontroller_and_string_parameter_in_route() + public async Task Create_valid_request_without_using_frombody_with_apicontroller_and_string_parameter_in_route() { var server = new TestServerBuilder().UseDefaultStartup() .Build(); @@ -1317,7 +1317,7 @@ public void Create_valid_request_without_using_frombody_with_apicontroller_and_s var requestPost3 = server.CreateHttpApiRequest(controller => controller.Post3($"{id}", complexParameter)); - string body = requestPost3.GetRequest().Content.ReadAsStringAsync().Result; + string body = await requestPost3.GetRequest().Content.ReadAsStringAsync(); JsonSerializer.Deserialize(body).PageIndex.Should().Be(complexParameter.PageIndex); JsonSerializer.Deserialize(body).PageCount.Should().Be(complexParameter.PageCount); requestPost3.GetConfiguredAddress().StartsWith($"{BASE_PATH_VALUES}/{id}").Should().Be(true); @@ -1327,7 +1327,7 @@ public void Create_valid_request_without_using_frombody_with_apicontroller_and_s [InlineData(null)] [InlineData("")] [InlineData(" ")] - public void Create_valid_request_without_using_frombody_with_apicontroller_and_string_parameter_with_invalid_value(string id) + public async Task Create_valid_request_without_using_frombody_with_apicontroller_and_string_parameter_with_invalid_value(string id) { var server = new TestServerBuilder().UseDefaultStartup() .Build(); @@ -1340,14 +1340,14 @@ public void Create_valid_request_without_using_frombody_with_apicontroller_and_s var requestPost3 = server.CreateHttpApiRequest(controller => controller.Post3($"{id}", complexParameter)); - string body = requestPost3.GetRequest().Content.ReadAsStringAsync().Result; + string body = await requestPost3.GetRequest().Content.ReadAsStringAsync(); JsonSerializer.Deserialize(body).PageIndex.Should().Be(complexParameter.PageIndex); JsonSerializer.Deserialize(body).PageCount.Should().Be(complexParameter.PageCount); requestPost3.GetConfiguredAddress().StartsWith($"{BASE_PATH_VALUES}/").Should().Be(true); } [Fact] - public void Create_valid_request_of_patch_without_using_frombody_with_apicontroller_attribute_and_route_parameter() + public async Task Create_valid_request_of_patch_without_using_frombody_with_apicontroller_attribute_and_route_parameter() { var server = new TestServerBuilder().UseDefaultStartup() .Build(); @@ -1360,7 +1360,7 @@ public void Create_valid_request_of_patch_without_using_frombody_with_apicontrol var requestPost2 = server.CreateHttpApiRequest(controller => controller.Patch1(1, complexParameter)); - string body = requestPost2.GetRequest().Content.ReadAsStringAsync().Result; + string body = await requestPost2.GetRequest().Content.ReadAsStringAsync(); JsonSerializer.Deserialize(body).PageIndex.Should().Be(complexParameter.PageIndex); JsonSerializer.Deserialize(body).PageCount.Should().Be(complexParameter.PageCount); requestPost2.GetConfiguredAddress().StartsWith($"{BASE_PATH_VALUES}/1").Should().Be(true); @@ -1904,6 +1904,56 @@ public async Task Create_post_request_with_object_with_file() content.Should().Be($"{model.Id}+{nameof(Create_post_request_with_file)}"); } + [Fact] + public async Task Create_post_request_with_object_with_different_froms() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var model = new ParamWithDifferentFroms() + { + ParamFromBody = ParamWithSeveralTypes.CreateRandom(), + ParamFromQuery = "fromQuery", + ParamFromRoute = "fromRoute", + ParamFromHeader = "fromHeader" + }; + var request = server.CreateHttpApiRequest(controller => controller.PostWithDifferentFroms(model)); + var responseMessage = await request.PostAsync(); + + await responseMessage.IsSuccessStatusCodeOrThrow(); + var content = await responseMessage.ReadContentAsAsync(); + content.ParamFromHeader.Should().Be(model.ParamFromHeader); + content.ParamFromQuery.Should().Be(model.ParamFromQuery); + content.ParamFromRoute.Should().Be(model.ParamFromRoute); + content.ParamFromBody.Should().Be(model.ParamFromBody); + } + + [Fact] + public async Task Create_put_request_with_object_with_different_froms() + { + var server = new TestServerBuilder() + .UseDefaultStartup() + .Build(); + + var model = new ParamWithDifferentFroms() + { + ParamFromBody = ParamWithSeveralTypes.CreateRandom(), + ParamFromQuery = "fromQuery", + ParamFromRoute = "fromRoute", + ParamFromHeader = "fromHeader" + }; + var request = server.CreateHttpApiRequest(controller => controller.PutWithDifferentFroms(model)); + var responseMessage = await request.SendAsync("PUT"); + + await responseMessage.IsSuccessStatusCodeOrThrow(); + var content = await responseMessage.ReadContentAsAsync(); + content.ParamFromHeader.Should().Be(model.ParamFromHeader); + content.ParamFromQuery.Should().Be(model.ParamFromQuery); + content.ParamFromRoute.Should().Be(model.ParamFromRoute); + content.ParamFromBody.Should().Be(model.ParamFromBody); + } + private class PrivateNonControllerClass { public int SomeAction() From 97f4d69eed0da82f08a04a13b1881dfdeb17c765 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Mon, 13 May 2024 17:05:52 +0200 Subject: [PATCH 21/23] Version 4.0.0 (#94) --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index f23c521..b886dd6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ - 3.5.0 + 4.0.0 Apache-2.0 http://github.com/xabaril/Acheve.TestHost From d2fef41057d81879ea0c52706e10f659d662478a Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Tue, 14 May 2024 08:30:21 +0200 Subject: [PATCH 22/23] Fix IsDateTime (#96) --- src/Acheve.TestHost/Extensions/TypeExtensions.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Acheve.TestHost/Extensions/TypeExtensions.cs b/src/Acheve.TestHost/Extensions/TypeExtensions.cs index 6464836..6a29ed6 100644 --- a/src/Acheve.TestHost/Extensions/TypeExtensions.cs +++ b/src/Acheve.TestHost/Extensions/TypeExtensions.cs @@ -18,7 +18,11 @@ internal static bool IsPrimitiveType(this Type typeToInspect) internal static bool IsDateTime(this Type typeToInspect) { - return typeToInspect == typeof(DateTime) || typeToInspect == typeof(DateTimeOffset); + return typeToInspect == typeof(DateTime) || + typeToInspect == typeof(DateTimeOffset) || + typeToInspect == typeof(DateOnly) || + typeToInspect == typeof(TimeSpan) || + typeToInspect == typeof(TimeOnly); } internal static bool IsEnumerable(this Type typeToInspect) From df4b7017cc6c3d2a57ee42cae973e3103d9b59cf Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Tue, 11 Jun 2024 09:15:29 +0200 Subject: [PATCH 23/23] Send ContentType and ContentDisposition (#98) --- .../Extensions/TestServerExtensions.cs | 13 +++++++++++-- .../IncludeContentAsFormUrlEncoded.cs | 14 +++++++------- .../Routing/Builders/ValuesV5Controller.cs | 6 ++++++ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/Acheve.TestHost/Extensions/TestServerExtensions.cs b/src/Acheve.TestHost/Extensions/TestServerExtensions.cs index 105f69a..bb283e7 100644 --- a/src/Acheve.TestHost/Extensions/TestServerExtensions.cs +++ b/src/Acheve.TestHost/Extensions/TestServerExtensions.cs @@ -1,8 +1,10 @@ using Acheve.TestHost.Routing; using Microsoft.AspNetCore.Http; +using Microsoft.Net.Http.Headers; using System; using System.IO; using System.Linq.Expressions; +using System.Net.Mime; using System.Text; namespace Microsoft.AspNetCore.TestHost; @@ -48,11 +50,18 @@ public static RequestBuilder CreateHttpApiRequest( where TController : class => UriDiscover.CreateHttpApiRequest(server, actionSelector, tokenValues, contentOptions); - public static IFormFile GivenFile(this TestServer _, string parameterName = "file", string filename = "test.txt", string content = "test") + public static IFormFile GivenFile(this TestServer _, string parameterName = "file", string filename = "test.txt", string content = "test", string contentType = MediaTypeNames.Text.Plain) { var stream = new MemoryStream(Encoding.UTF8.GetBytes(content)); - IFormFile file = new FormFile(stream, 0, stream.Length, parameterName, filename); + IFormFile file = new FormFile(stream, 0, stream.Length, parameterName, filename) + { + Headers = new HeaderDictionary() + { + [HeaderNames.ContentType] = contentType + }, + ContentType = contentType + }; return file; } diff --git a/src/Acheve.TestHost/RequestContent/IncludeContentAsFormUrlEncoded.cs b/src/Acheve.TestHost/RequestContent/IncludeContentAsFormUrlEncoded.cs index e019c5c..845c989 100644 --- a/src/Acheve.TestHost/RequestContent/IncludeContentAsFormUrlEncoded.cs +++ b/src/Acheve.TestHost/RequestContent/IncludeContentAsFormUrlEncoded.cs @@ -2,9 +2,9 @@ using System; using System.Collections; using System.Globalization; -using System.IO; using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading; namespace Microsoft.AspNetCore.TestHost; @@ -45,13 +45,13 @@ private void AddToMultipartFormDataContent(MultipartFormDataContent multipartCon case CancellationToken: break; case IFormFile file: - using (var ms = new MemoryStream()) - { - file.CopyTo(ms); - var fileContent = new ByteArrayContent(ms.ToArray()); + var fileContent = new StreamContent(file.OpenReadStream()); + if (!string.IsNullOrEmpty(file.ContentType)) + fileContent.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType); + if (!string.IsNullOrEmpty(file.ContentDisposition)) + fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue(file.ContentDisposition); - multipartContent.Add(fileContent, file.Name, file.FileName); - } + multipartContent.Add(fileContent, file.Name, file.FileName); break; case object when data.GetType().IsPrimitiveType(): multipartContent.Add(new StringContent(PrimitiveValueToString(data)), propertyName); diff --git a/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs b/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs index 87c3437..05d3d09 100644 --- a/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs +++ b/tests/UnitTests/Acheve.TestHost/Routing/Builders/ValuesV5Controller.cs @@ -1,5 +1,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using System; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -108,6 +109,11 @@ public async Task> PostObjectWithFile([FromForm] ParamWithF private static async Task ReadFormFile(IFormFile file) { + if (string.IsNullOrEmpty(file.FileName) || string.IsNullOrEmpty(file.Name) || string.IsNullOrEmpty(file.ContentType)) + { + throw new ArgumentException("Error in file", nameof(file)); + } + using var reader = new StreamReader(file.OpenReadStream()); return await reader.ReadToEndAsync(); }