Skip to content

Commit

Permalink
Merge pull request tomkuijsten#98 from Jark/feature-add-constructor-a…
Browse files Browse the repository at this point in the history
…rguments-validation

Add validation around constructor args.
  • Loading branch information
tomkuijsten authored Nov 24, 2016
2 parents 0768645 + 3d16703 commit 3d830b2
Show file tree
Hide file tree
Showing 14 changed files with 220 additions and 55 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Restup.Webserver.UnitTests.TestHelpers;

namespace Restup.Webserver.UnitTests.Rest
{
[TestClass]
public class RestRouteHandlerTests_ControllerConstructor : RestRouteHandlerTests
{
private class ControllerWithOneStringParameter
{
public ControllerWithOneStringParameter(string param)
{
}
}

private class ControllerWithOneStringParameterAndOneIntegerParameter
{
public ControllerWithOneStringParameterAndOneIntegerParameter(string param, int param2)
{
}
}

private class ControllerWithPrivateConstructor
{
private ControllerWithPrivateConstructor()
{
}
}

private class ControllerWithTwoConstructors
{
public ControllerWithTwoConstructors()
{
}

public ControllerWithTwoConstructors(string param)
{
}
}

[TestMethod]
public void RegisterController_WithConstructerWithAParameterAndNoParamIsPassedIn_ThenExceptionIsThrown()
{
AssertRegisterControllerThrows<ControllerWithOneStringParameter>();
}

[TestMethod]
public void RegisterController_WithConstructerWithAStringParameterAndAnIntegerIsPassedIn_ThenExceptionIsThrown()
{
AssertRegisterControllerThrows<ControllerWithOneStringParameter>(1);
}

[TestMethod]
public void RegisterController_WithConstructerWithAParameterAndMoreParamsArePassedIn_ThenExceptionIsThrown()
{
AssertRegisterControllerThrows<ControllerWithOneStringParameter>("param1", "param2");
}

[TestMethod]
public void RegisterController_WithConstructerWithAParameterAndTheCorrectParamsArePassedIn_ThenNoExceptionIsThrown()
{
_restRouteHandler.RegisterController<ControllerWithOneStringParameter>(() => new object[] { "param1" });
Assert.IsTrue(true);
}

[TestMethod]
public void RegisterController_WithPrivateConstructer_ThenExceptionIsThrown()
{
AssertRegisterControllerThrows<ControllerWithPrivateConstructor>(() => new object[] { "param1" });
}

[TestMethod]
public void RegisterController_WithTwoConstructersAndFuncIsUsed_ThenExceptionIsThrown()
{
AssertRegisterControllerThrows<ControllerWithTwoConstructors>(() => new object[] { "param1" });
}

[TestMethod]
public void RegisterController_WithTwoConstructersAndInstantiatedObjectIsUsed_ThenNoExceptionIsThrown()
{
_restRouteHandler.RegisterController<ControllerWithTwoConstructors>("param1");
Assert.IsTrue(true);
}

[TestMethod]
public void RegisterController_WithConstructerWithTwoParametersAndLessParamsArePassedIn_ThenExceptionIsThrown()
{
AssertRegisterControllerThrows<ControllerWithOneStringParameterAndOneIntegerParameter>("param1");
}

[TestMethod]
public void RegisterController_WithConstructerWithTwoParametersAndTheCorrectParamsArePassedIn_ThenNoExceptionIsThrown()
{
_restRouteHandler.RegisterController<ControllerWithOneStringParameterAndOneIntegerParameter>("param1", 1);
Assert.IsTrue(true);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
using Restup.HttpMessage.Models.Schemas;
using System;
using System.Threading.Tasks;
using Restup.HttpMessage.Models.Schemas;
using Restup.Webserver.Attributes;
using Restup.Webserver.Models.Contracts;
using Restup.Webserver.Models.Schemas;
using Restup.Webserver.Rest;
using Restup.Webserver.UnitTests.TestHelpers;
using System;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Restup.Webserver.UnitTests.Rest
{
[TestClass]
public class RestRouteHandlerTests_UriFormatValidation
public class RestRouteHandlerTests_UriFormatValidation : RestRouteHandlerTests
{
private RestRouteHandler restHandler;

[TestInitialize()]
public void Initialize()
{
restHandler = new RestRouteHandler();
}

private class TwoUriFormatWithSameNameAndMethodController
{
[UriFormat("/Get")]
Expand All @@ -46,7 +37,7 @@ private class OnePostMethodController
[TestMethod]
public async Task RegisterController_OneControllerRegisteredTwice_ThrowsException()
{
restHandler.RegisterController<OnePostMethodController>();
_restRouteHandler.RegisterController<OnePostMethodController>();
await AssertHandleRequest("/Post", HttpMethod.POST, HttpResponseStatus.Created);
AssertRegisterControllerThrows<OnePostMethodController>();
await AssertHandleRequest("/Post", HttpMethod.POST, HttpResponseStatus.Created);
Expand All @@ -65,7 +56,7 @@ public OnePostMethodWithParameterizedConstructorController(string param)
[TestMethod]
public async Task RegisterController_OneParameterizedControllerRegisteredTwice_ThrowsException()
{
restHandler.RegisterController<OnePostMethodWithParameterizedConstructorController>("param");
_restRouteHandler.RegisterController<OnePostMethodWithParameterizedConstructorController>("param");
await AssertHandleRequest("/Post", HttpMethod.POST, HttpResponseStatus.Created);
AssertRegisterControllerThrows<OnePostMethodWithParameterizedConstructorController>("param");
await AssertHandleRequest("/Post", HttpMethod.POST, HttpResponseStatus.Created);
Expand All @@ -74,7 +65,7 @@ public async Task RegisterController_OneParameterizedControllerRegisteredTwice_T
[TestMethod]
public async Task RegisterController_TwoDifferentControllersWithSimilarlyNamedMethodsAndVerbs_ThrowsException()
{
restHandler.RegisterController<OnePostMethodController>();
_restRouteHandler.RegisterController<OnePostMethodController>();
await AssertHandleRequest("/Post", HttpMethod.POST, HttpResponseStatus.Created);
AssertRegisterControllerThrows<OnePostMethodWithParameterizedConstructorController>("param");
await AssertHandleRequest("/Post", HttpMethod.POST, HttpResponseStatus.Created);
Expand Down Expand Up @@ -128,24 +119,10 @@ public void RegisterController_AControllerWithMoreInUrlParameter_ThrowsException
AssertRegisterControllerThrows<UriFormatWithMoreInUrlParameterController>();
}

private void AssertRegisterControllerThrows<T>(params string[] args) where T : class
{
Assert.ThrowsException<Exception>(() =>
restHandler.RegisterController<T>(args)
);
}

private void AssertRegisterControllerThrows<T>() where T : class
{
Assert.ThrowsException<Exception>(() =>
restHandler.RegisterController<T>()
);
}

private async Task AssertHandleRequest(string uri, HttpMethod method, HttpResponseStatus expectedStatus)
{
var request = Utils.CreateHttpRequest(uri: new Uri(uri, UriKind.Relative), method: method);
var result = await restHandler.HandleRequest(request);
var result = await _restRouteHandler.HandleRequest(request);

Assert.AreEqual(expectedStatus, result.ResponseStatus);
}
Expand Down
38 changes: 38 additions & 0 deletions src/WebServer.UnitTests/TestHelpers/RestRouteHandlerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Restup.Webserver.Rest;

namespace Restup.Webserver.UnitTests.TestHelpers
{
public abstract class RestRouteHandlerTests
{
protected RestRouteHandler _restRouteHandler;

[TestInitialize]
public void Initialize()
{
_restRouteHandler = new RestRouteHandler();
}

protected void AssertRegisterControllerThrows<T>(params object[] args) where T : class
{
Assert.ThrowsException<Exception>(() =>
_restRouteHandler.RegisterController<T>(args)
);
}

protected void AssertRegisterControllerThrows<T>(Func<object[]> args) where T : class
{
Assert.ThrowsException<Exception>(() =>
_restRouteHandler.RegisterController<T>(args)
);
}

protected void AssertRegisterControllerThrows<T>() where T : class
{
Assert.ThrowsException<Exception>(() =>
_restRouteHandler.RegisterController<T>()
);
}
}
}
2 changes: 2 additions & 0 deletions src/WebServer.UnitTests/WebServer.UnitTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@
<Compile Include="File\MimeTypeProviderTests.cs" />
<Compile Include="Http\HttpServerTests.CorsSimpleRequests.cs" />
<Compile Include="Http\HttpServerTests.CorsPreflightedRequests.cs" />
<Compile Include="Rest\RestRouteHandlerTests.ControllerConstructor.cs" />
<Compile Include="Rest\RestRouteHandlerTests.UriFormatValidation.cs" />
<Compile Include="TestHelpers\RestRouteHandlerTests.cs" />
<Compile Include="TestHelpers\MockFile.cs" />
<Compile Include="TestHelpers\MockFileSystem.cs" />
<Compile Include="TestHelpers\StaticFileRouteHandlerTests.Fluent.cs" />
Expand Down
5 changes: 3 additions & 2 deletions src/WebServer/InstanceCreators/PerCallInstanceCreator.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
using Restup.Webserver.Models.Contracts;
using System;
using System.Reflection;

namespace Restup.Webserver.InstanceCreators
{
internal class PerCallInstanceCreator : IInstanceCreator
{
public object Create(Type instanceType, params object[] args)
public object Create(ConstructorInfo instanceType, object[] args)
{
return Activator.CreateInstance(instanceType, args);
return instanceType.Invoke(args);
}
}
}
13 changes: 7 additions & 6 deletions src/WebServer/InstanceCreators/SingletonInstanceCreator.cs
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
using Restup.Webserver.Models.Contracts;
using System;
using System;
using System.Reflection;
using Restup.Webserver.Models.Contracts;

namespace Restup.Webserver.InstanceCreators
{
internal class SingletonInstanceCreator : IInstanceCreator
{
private object _instance;
private object _instanceLock = new object();
private readonly object _instanceLock = new object();

public object Create(Type instanceType, params object[] args)
public object Create(ConstructorInfo instanceType, object[] args)
{
CacheInstance(instanceType, args);

return _instance;
}

private void CacheInstance(Type instanceType, object[] args)
private void CacheInstance(ConstructorInfo instanceType, object[] args)
{
if (_instance == null)
{
lock (_instanceLock)
{
if (_instance == null)
{
_instance = Activator.CreateInstance(instanceType, args);
_instance = instanceType.Invoke(args);
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/WebServer/Models/Contracts/IInstanceCreator.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System;
using System.Reflection;

namespace Restup.Webserver.Models.Contracts
{
interface IInstanceCreator
{
object Create(Type instanceType, object[] args);
object Create(ConstructorInfo instanceType, object[] args);
}
}
29 changes: 29 additions & 0 deletions src/WebServer/Rest/ReflectionHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using System;
using System.Linq;
using System.Reflection;

namespace Restup.Webserver.Rest
{
internal static class ReflectionHelper
{
internal static bool TryFindMatchingConstructor<T>(object[] args, out ConstructorInfo foundConstructor)
{
foreach (var constructorInfo in typeof(T).GetConstructors())
{
var parameters = constructorInfo.GetParameters();
if (args.Length != parameters.Length)
continue;

var argsTypes = args.Select(x => x.GetType());
var parameterTypes = parameters.Select(x => x.ParameterType);
if ( !argsTypes.SequenceEqual(parameterTypes))
continue;

foundConstructor = constructorInfo;
return true;
}
foundConstructor = null;
return false;
}
}
}
2 changes: 1 addition & 1 deletion src/WebServer/Rest/RestControllerMethodExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ protected override object ExecuteAnonymousMethod(RestControllerMethodInfo info,
}

return info.MethodInfo.Invoke(
instantiator.Create(info.MethodInfo.DeclaringType, info.ControllerConstructorArgs()),
instantiator.Create(info.ControllerConstructor, info.ControllerConstructorArgs()),
parameters);
}
}
Expand Down
9 changes: 4 additions & 5 deletions src/WebServer/Rest/RestControllerMethodInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,13 @@ internal enum TypeWrapper
internal bool HasContentParameter { get; }
internal Type ContentParameterType { get; }
internal TypeWrapper ReturnTypeWrapper { get; }
internal ConstructorInfo ControllerConstructor { get; }
internal Func<object[]> ControllerConstructorArgs { get; }

internal RestControllerMethodInfo(
MethodInfo methodInfo,
Func<object[]> constructorArgs,
TypeWrapper typeWrapper)
internal RestControllerMethodInfo(MethodInfo methodInfo, ConstructorInfo constructor, Func<object[]> constructorArgs, TypeWrapper typeWrapper)
{
constructorArgs.GuardNull(nameof(constructorArgs));
ControllerConstructor = constructor;

_uriParser = new UriParser();
MatchUri = GetUriFromMethod(methodInfo);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ protected override object ExecuteAnonymousMethod(RestControllerMethodInfo info,
}

return info.MethodInfo.Invoke(
instantiator.Create(info.MethodInfo.DeclaringType, info.ControllerConstructorArgs()),
instantiator.Create(info.ControllerConstructor, info.ControllerConstructorArgs()),
parameters);
}
}
Expand Down
Loading

0 comments on commit 3d830b2

Please sign in to comment.