From 7ad0a5ea844b1625e757701b4ce750d51b75b429 Mon Sep 17 00:00:00 2001 From: Sergio <7523246+Sergio1192@users.noreply.github.com> Date: Tue, 11 Jun 2024 10:20:00 +0200 Subject: [PATCH] Develop into master (#97) * If the method is abstract and virtual is a correct method (#72) Tests to check abstract and virtual methods * Fix enumerable parameter (#74) * Add RemoveQueryParameter extension method (#75) Move some tests * CancellationTOken always is a query paramater (#76) * Version 3.4.0 (#77) * Action convection name (#79) * Action convection name * Create get request when it has an object with some list * DateTime as a primitive type but using custom formatter (#81) * Fixing codeQL alerts (#82) * feature: Get url (#83) * In IncludeContentAsFormUrlEncoded and PrimitiveParameterActionTokenizer explicit conversion for floats (#84) Create random ParamWithSeveralTypes More tests * Add new CreateHttpApiRequest with TActionResponse (#85) Fix CodeQL alerts Test * 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 * ReadContentAsAsync allows string type (#87) Tests for HttpResponseMessageExtensions * Allow send a IFormFile (#88) * Allow IFormFile Refactor IncludeContentAsFormUrlEncoded Remove Newtonsoft.Json dependecy * Update documentation * Refactor GivenFile * Update version to 3.5.0 (#89) * Add DateTimeOffset to IsDateTime function (#91) Move TypeExtensions * Allow dispatch workflows manually * Update to Net8 (#92) * 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 * Version 4.0.0 (#94) * Fix IsDateTime (#96) * Send ContentType and ContentDisposition (#98) --- .github/workflows/ci.yml | 9 +- .github/workflows/nuget.yml | 7 +- Directory.Build.props | 4 +- Directory.Packages.props | 25 ++-- global.json | 2 +- .../Extensions/TestServerExtensions.cs | 13 +- .../Extensions/TypeExtensions.cs | 6 +- .../IncludeContentAsFormUrlEncoded.cs | 14 +- .../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 | 14 ++ .../Routing/Models/ParamWithDifferentFroms.cs | 18 +++ .../Routing/TestServerExtensionsTests.cs | 70 ++++++++-- 18 files changed, 297 insertions(+), 128 deletions(-) create mode 100644 tests/UnitTests/Acheve.TestHost/Routing/Models/ParamWithDifferentFroms.cs 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..b886dd6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,12 +1,12 @@  - netcoreapp3.1;net5.0;net6.0;net7.0 + net6.0;net7.0;net8.0 latest true - 3.5.0 + 4.0.0 Apache-2.0 http://github.com/xabaril/Acheve.TestHost 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" } } 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/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) 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/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..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,7 +109,20 @@ 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(); } + + [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()