Skip to content

Commit

Permalink
Add new CreateHttpApiRequest with TActionResponse (#85)
Browse files Browse the repository at this point in the history
Fix CodeQL alerts
Test
  • Loading branch information
Sergio1192 authored May 20, 2023
1 parent 15ea0b2 commit e204431
Show file tree
Hide file tree
Showing 5 changed files with 255 additions and 206 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: "CodeQL"

on:
push:
branches: [ "master" ]
branches: [ "master", "develop" ]
pull_request:
branches: [ "master", "develop" ]
workflow_dispatch:
Expand Down
196 changes: 191 additions & 5 deletions src/Acheve.TestHost/Routing/UriDiscover.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,73 @@
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<ITokenizer> _tokenizers = new List<ITokenizer>()
private static readonly IEnumerable<ITokenizer> _tokenizers = new List<ITokenizer>()
{
new PrimitiveParameterActionTokenizer(),
new EnumerableParameterActionTokenizer(),
new ComplexParameterActionTokenizer(),
new DefaultConventionalTokenizer()
};

public static string Discover<TController>(TestServerAction action, object tokenValues)
public static RequestBuilder CreateHttpApiRequest<TController>(TestServer server,
LambdaExpression actionSelector,
object tokenValues = null,
RequestContentOptions contentOptions = null)
where TController : class
{
if (!IsController<TController>())
{
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<TController>(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<TController>(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<TController>(TestServerAction action, object tokenValues)
where TController : class
{
//at this moment only the first route is considered..
Expand Down Expand Up @@ -50,7 +102,7 @@ public static string Discover<TController>(TestServerAction action, object token
$"{controllerTemplate}/{template}{queryStringTemplate}";
}

static TestServerTokenCollection AddTokens<TController>(TestServerAction action, object tokenValues)
private static TestServerTokenCollection AddTokens<TController>(TestServerAction action, object tokenValues)
where TController : class
{
var dictionaryTokenValues = new Dictionary<string, string>();
Expand All @@ -71,7 +123,8 @@ static TestServerTokenCollection AddTokens<TController>(TestServerAction action,

return testServerTokens;
}
static bool IsTildeOverride(string template, out string overrideTemplate)

private static bool IsTildeOverride(string template, out string overrideTemplate)
{
const string TILDE = "~";

Expand All @@ -85,4 +138,137 @@ static bool IsTildeOverride(string template, out string overrideTemplate)

return isTildeOverride;
}

private static bool IsController<TController>()
{
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<TController>(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()));
}
}
}
Loading

0 comments on commit e204431

Please sign in to comment.