/src/Nancy.Tests/Unit/NancyEngineFixture.cs

https://github.com/factormystic/Nancy · C# · 456 lines · 337 code · 95 blank · 24 comment · 0 complexity · eb859d72113d8aea553bc1ab15329c47 MD5 · raw file

  1. namespace Nancy.Tests.Unit
  2. {
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using FakeItEasy;
  7. using Nancy.ErrorHandling;
  8. using Nancy.Extensions;
  9. using Nancy.Routing;
  10. using Nancy.Tests.Fakes;
  11. using Xunit;
  12. using ResolveResult = System.Tuple<Nancy.Routing.Route, DynamicDictionary, System.Func<NancyContext, Response>, System.Action<NancyContext>>;
  13. public class NancyEngineFixture
  14. {
  15. private readonly INancyEngine engine;
  16. private readonly IRouteResolver resolver;
  17. private readonly FakeRoute route;
  18. private readonly NancyContext context;
  19. private readonly INancyContextFactory contextFactory;
  20. private readonly Response response;
  21. private readonly IErrorHandler errorHandler;
  22. public NancyEngineFixture()
  23. {
  24. this.resolver = A.Fake<IRouteResolver>();
  25. this.response = new Response();
  26. this.route = new FakeRoute(response);
  27. this.context = new NancyContext();
  28. this.errorHandler = A.Fake<IErrorHandler>();
  29. A.CallTo(() => errorHandler.HandlesStatusCode(A<HttpStatusCode>.Ignored)).Returns(false);
  30. contextFactory = A.Fake<INancyContextFactory>();
  31. A.CallTo(() => contextFactory.Create()).Returns(context);
  32. A.CallTo(() => resolver.Resolve(A<NancyContext>.Ignored, A<IRouteCache>.Ignored)).Returns(new ResolveResult(route, DynamicDictionary.Empty, null, null));
  33. this.engine = new NancyEngine(resolver, A.Fake<IRouteCache>(), contextFactory, this.errorHandler);
  34. }
  35. [Fact]
  36. public void Should_throw_argumentnullexception_when_created_with_null_resolver()
  37. {
  38. // Given, When
  39. var exception =
  40. Record.Exception(() => new NancyEngine(null, A.Fake<IRouteCache>(), A.Fake<INancyContextFactory>(), this.errorHandler));
  41. // Then
  42. exception.ShouldBeOfType<ArgumentNullException>();
  43. }
  44. [Fact]
  45. public void Should_throw_argumentnullexception_when_created_with_null_routecache()
  46. {
  47. // Given, When
  48. var exception =
  49. Record.Exception(() => new NancyEngine(A.Fake<IRouteResolver>(), null, A.Fake<INancyContextFactory>(), this.errorHandler));
  50. // Then
  51. exception.ShouldBeOfType<ArgumentNullException>();
  52. }
  53. [Fact]
  54. public void Should_throw_argumentnullexception_when_created_with_null_context_factory()
  55. {
  56. // Given, When
  57. var exception =
  58. Record.Exception(() => new NancyEngine(A.Fake<IRouteResolver>(), A.Fake<IRouteCache>(), null, this.errorHandler));
  59. // Then
  60. exception.ShouldBeOfType<ArgumentNullException>();
  61. }
  62. [Fact]
  63. public void Should_throw_argumentnullexception_when_created_with_null_error_handler()
  64. {
  65. // Given, When
  66. var exception =
  67. Record.Exception(() => new NancyEngine(A.Fake<IRouteResolver>(), A.Fake<IRouteCache>(), A.Fake<INancyContextFactory>(), null));
  68. // Then
  69. exception.ShouldBeOfType<ArgumentNullException>();
  70. }
  71. [Fact]
  72. public void Should_invoke_resolved_route()
  73. {
  74. // Given
  75. var request = new Request("GET", "/", "http");
  76. // When
  77. this.engine.HandleRequest(request);
  78. // Then
  79. this.route.ActionWasInvoked.ShouldBeTrue();
  80. }
  81. [Fact]
  82. public void HandleRequest_Should_Throw_ArgumentNullException_When_Given_A_Null_Request()
  83. {
  84. var exception = Record.Exception(() => engine.HandleRequest(null));
  85. // Then
  86. exception.ShouldBeOfType<ArgumentNullException>();
  87. }
  88. [Fact]
  89. public void HandleRequest_should_get_context_from_context_factory()
  90. {
  91. var request = new Request("GET", "/", "http");
  92. this.engine.HandleRequest(request);
  93. A.CallTo(() => this.contextFactory.Create()).MustHaveHappened(Repeated.Exactly.Once);
  94. }
  95. [Fact]
  96. public void HandleRequest_should_set_correct_response_on_returned_context()
  97. {
  98. var request = new Request("GET", "/", "http");
  99. var result = this.engine.HandleRequest(request);
  100. result.Response.ShouldBeSameAs(this.response);
  101. }
  102. [Fact]
  103. public void Should_add_nancy_version_number_header_on_returned_response()
  104. {
  105. // Given
  106. var request = new Request("GET", "/", "http");
  107. // When
  108. var result = this.engine.HandleRequest(request);
  109. // Then
  110. result.Response.Headers.ContainsKey("Nancy-Version").ShouldBeTrue();
  111. }
  112. [Fact]
  113. public void Should_not_throw_exception_when_setting_nancy_version_header_and_it_already_existed()
  114. {
  115. // Given
  116. var cachedResponse = new Response();
  117. cachedResponse.Headers.Add("Nancy-Version", "1.2.3.4");
  118. Func<NancyContext, Response> preRequestHook = (ctx) => cachedResponse;
  119. var prePostResolver = A.Fake<IRouteResolver>();
  120. A.CallTo(() => prePostResolver.Resolve(A<NancyContext>.Ignored, A<IRouteCache>.Ignored)).Returns(new ResolveResult(route, DynamicDictionary.Empty, preRequestHook, null));
  121. var localEngine = new NancyEngine(prePostResolver, A.Fake<IRouteCache>(), contextFactory, this.errorHandler);
  122. var request = new Request("GET", "/", "http");
  123. // When
  124. var exception = Record.Exception(() => localEngine.HandleRequest(request));
  125. // Then
  126. exception.ShouldBeNull();
  127. }
  128. [Fact]
  129. public void Should_set_nancy_version_number_on_returned_response()
  130. {
  131. // Given
  132. var request = new Request("GET", "/", "http");
  133. var nancyVersion = typeof(INancyEngine).Assembly.GetName().Version;
  134. // When
  135. var result = this.engine.HandleRequest(request);
  136. // Then
  137. result.Response.Headers["Nancy-Version"].ShouldEqual(nancyVersion.ToString());
  138. }
  139. [Fact]
  140. public void HandleRequest_Null_PreRequest_Should_Not_Throw()
  141. {
  142. engine.PreRequestHook = null;
  143. var request = new Request("GET", "/", "http");
  144. this.engine.HandleRequest(request);
  145. }
  146. [Fact]
  147. public void HandleRequest_Null_PostRequest_Should_Not_Throw()
  148. {
  149. engine.PostRequestHook = null;
  150. var request = new Request("GET", "/", "http");
  151. this.engine.HandleRequest(request);
  152. }
  153. [Fact]
  154. public void HandleRequest_NonNull_PreRequest_Should_Call_PreRequest_With_Request_In_Context()
  155. {
  156. Request passedReqest = null;
  157. engine.PreRequestHook = (ctx) =>
  158. {
  159. passedReqest = ctx.Request;
  160. return null;
  161. };
  162. var request = new Request("GET", "/", "http");
  163. this.engine.HandleRequest(request);
  164. passedReqest.ShouldBeSameAs(request);
  165. }
  166. [Fact]
  167. public void HandleRequest_PreRequest_Returns_NonNull_Response_Should_Return_That_Response()
  168. {
  169. var response = A.Fake<Response>();
  170. engine.PreRequestHook = req => response;
  171. var request = new Request("GET", "/", "http");
  172. var result = this.engine.HandleRequest(request);
  173. result.Response.ShouldBeSameAs(response);
  174. }
  175. [Fact]
  176. public void HandleRequest_should_allow_post_request_hook_to_modify_context_items()
  177. {
  178. engine.PostRequestHook = ctx => ctx.Items.Add("PostReqTest", new object());
  179. var request = new Request("GET", "/", "http");
  180. var result = this.engine.HandleRequest(request);
  181. result.Items.ContainsKey("PostReqTest").ShouldBeTrue();
  182. }
  183. [Fact]
  184. public void HandleRequest_should_allow_post_request_hook_to_replace_response()
  185. {
  186. var newResponse = new Response();
  187. engine.PreRequestHook = ctx => ctx.Response = newResponse;
  188. var request = new Request("GET", "/", "http");
  189. var result = this.engine.HandleRequest(request);
  190. result.Response.ShouldBeSameAs(newResponse);
  191. }
  192. [Fact]
  193. public void HandleRequest_should_call_route_prereq_then_invoke_route_then_call_route_postreq()
  194. {
  195. // Given
  196. var executionOrder = new List<String>();
  197. Func<NancyContext, Response> preHook = (ctx) => { executionOrder.Add("Prehook"); return null; };
  198. Action<NancyContext> postHook = (ctx) => { executionOrder.Add("Posthook"); };
  199. this.route.Action = (d) => { executionOrder.Add("RouteInvoke"); return null; };
  200. var prePostResolver = A.Fake<IRouteResolver>();
  201. A.CallTo(() => prePostResolver.Resolve(A<NancyContext>.Ignored, A<IRouteCache>.Ignored)).Returns(new ResolveResult(route, DynamicDictionary.Empty, preHook, postHook));
  202. var localEngine = new NancyEngine(prePostResolver, A.Fake<IRouteCache>(), contextFactory, this.errorHandler);
  203. var request = new Request("GET", "/", "http");
  204. // When
  205. localEngine.HandleRequest(request);
  206. // Then
  207. executionOrder.Count().ShouldEqual(3);
  208. executionOrder.SequenceEqual(new[] { "Prehook", "RouteInvoke", "Posthook" }).ShouldBeTrue();
  209. }
  210. [Fact]
  211. public void HandleRequest_should_not_invoke_route_if_route_prereq_returns_response()
  212. {
  213. var executionOrder = new List<String>();
  214. Func<NancyContext, Response> preHook = (ctx) => { executionOrder.Add("Prehook"); return new Response(); };
  215. Action<NancyContext> postHook = (ctx) => { executionOrder.Add("Posthook"); };
  216. this.route.Action = (d) => { executionOrder.Add("RouteInvoke"); return null; };
  217. var prePostResolver = A.Fake<IRouteResolver>();
  218. A.CallTo(() => prePostResolver.Resolve(A<NancyContext>.Ignored, A<IRouteCache>.Ignored)).Returns(new ResolveResult(route, DynamicDictionary.Empty, preHook, postHook));
  219. var localEngine = new NancyEngine(prePostResolver, A.Fake<IRouteCache>(), contextFactory, this.errorHandler);
  220. var request = new Request("GET", "/", "http");
  221. localEngine.HandleRequest(request);
  222. executionOrder.Contains("RouteInvoke").ShouldBeFalse();
  223. }
  224. [Fact]
  225. public void HandleRequest_should_return_response_from_route_prereq_if_one_returned()
  226. {
  227. var preResponse = new Response();
  228. Func<NancyContext, Response> preHook = (ctx) => preResponse;
  229. var prePostResolver = A.Fake<IRouteResolver>();
  230. A.CallTo(() => prePostResolver.Resolve(A<NancyContext>.Ignored, A<IRouteCache>.Ignored)).Returns(new ResolveResult(route, DynamicDictionary.Empty, preHook, null));
  231. var localEngine = new NancyEngine(prePostResolver, A.Fake<IRouteCache>(), contextFactory, this.errorHandler);
  232. var request = new Request("GET", "/", "http");
  233. var result = localEngine.HandleRequest(request);
  234. result.Response.ShouldBeSameAs(preResponse);
  235. }
  236. [Fact]
  237. public void HandleRequest_should_allow_route_postreq_to_change_response()
  238. {
  239. var postResponse = new Response();
  240. Action<NancyContext> postHook = (ctx) => ctx.Response = postResponse;
  241. var prePostResolver = A.Fake<IRouteResolver>();
  242. A.CallTo(() => prePostResolver.Resolve(A<NancyContext>.Ignored, A<IRouteCache>.Ignored)).Returns(new ResolveResult(route, DynamicDictionary.Empty, null, postHook));
  243. var localEngine = new NancyEngine(prePostResolver, A.Fake<IRouteCache>(), contextFactory, this.errorHandler);
  244. var request = new Request("GET", "/", "http");
  245. var result = localEngine.HandleRequest(request);
  246. result.Response.ShouldBeSameAs(postResponse);
  247. }
  248. [Fact]
  249. public void HandleRequest_should_allow_route_postreq_to_add_items_to_context()
  250. {
  251. Action<NancyContext> postHook = (ctx) => ctx.Items.Add("RoutePostReq", new object());
  252. var prePostResolver = A.Fake<IRouteResolver>();
  253. A.CallTo(() => prePostResolver.Resolve(A<NancyContext>.Ignored, A<IRouteCache>.Ignored)).Returns(new ResolveResult(route, DynamicDictionary.Empty, null, postHook));
  254. var localEngine = new NancyEngine(prePostResolver, A.Fake<IRouteCache>(), contextFactory, this.errorHandler);
  255. var request = new Request("GET", "/", "http");
  256. var result = localEngine.HandleRequest(request);
  257. result.Items.ContainsKey("RoutePostReq").ShouldBeTrue();
  258. }
  259. [Fact]
  260. public void HandleRequest_prereq_returns_response_should_still_run_postreq()
  261. {
  262. var response = A.Fake<Response>();
  263. var postReqCalled = false;
  264. engine.PreRequestHook = req => response;
  265. engine.PostRequestHook = req => postReqCalled = true;
  266. var request = new Request("GET", "/", "http");
  267. this.engine.HandleRequest(request);
  268. postReqCalled.ShouldBeTrue();
  269. }
  270. [Fact]
  271. public void HandleRequest_route_prereq_returns_response_should_still_run_route_postreq_and_postreq()
  272. {
  273. var executionOrder = new List<String>();
  274. Action<NancyContext> postHook = (ctx) => { executionOrder.Add("Posthook"); };
  275. Func<NancyContext, Response> routePreHook = (ctx) => { executionOrder.Add("Routeprehook"); return new Response(); };
  276. Action<NancyContext> routePostHook = (ctx) => { executionOrder.Add("Routeposthook"); };
  277. this.route.Action = (d) => { executionOrder.Add("RouteInvoke"); return null; };
  278. var prePostResolver = A.Fake<IRouteResolver>();
  279. A.CallTo(() => prePostResolver.Resolve(A<NancyContext>.Ignored, A<IRouteCache>.Ignored)).Returns(new ResolveResult(route, DynamicDictionary.Empty, routePreHook, routePostHook));
  280. var localEngine = new NancyEngine(prePostResolver, A.Fake<IRouteCache>(), contextFactory, this.errorHandler);
  281. localEngine.PostRequestHook = postHook;
  282. var request = new Request("GET", "/", "http");
  283. localEngine.HandleRequest(request);
  284. executionOrder.Count().ShouldEqual(3);
  285. executionOrder.SequenceEqual(new[] { "Routeprehook", "Routeposthook", "Posthook" }).ShouldBeTrue();
  286. }
  287. [Fact]
  288. public void Should_ask_error_handler_if_it_can_handle_status_code()
  289. {
  290. var request = new Request("GET", "/", "http");
  291. this.engine.HandleRequest(request);
  292. A.CallTo(() => this.errorHandler.HandlesStatusCode(A<HttpStatusCode>.Ignored)).MustHaveHappened(Repeated.Exactly.Once);
  293. }
  294. [Fact]
  295. public void Should_not_invoke_error_handler_if_not_supported_status_code()
  296. {
  297. var request = new Request("GET", "/", "http");
  298. this.engine.HandleRequest(request);
  299. A.CallTo(() => this.errorHandler.Handle(A<HttpStatusCode>.Ignored, A<NancyContext>.Ignored)).MustNotHaveHappened();
  300. }
  301. [Fact]
  302. public void Should_invoke_error_handler_if_supported_status_code()
  303. {
  304. var request = new Request("GET", "/", "http");
  305. A.CallTo(() => this.errorHandler.HandlesStatusCode(A<HttpStatusCode>.Ignored)).Returns(true);
  306. this.engine.HandleRequest(request);
  307. A.CallTo(() => this.errorHandler.Handle(A<HttpStatusCode>.Ignored, A<NancyContext>.Ignored)).MustHaveHappened(Repeated.Exactly.Once);
  308. }
  309. [Fact]
  310. public void Should_set_status_code_to_500_if_route_throws()
  311. {
  312. var errorRoute = new Route("GET", "/", null, x => { throw new NotImplementedException(); });
  313. A.CallTo(() => resolver.Resolve(A<NancyContext>.Ignored, A<IRouteCache>.Ignored)).Returns(new ResolveResult(errorRoute, DynamicDictionary.Empty, null, null));
  314. var request = new Request("GET", "/", "http");
  315. var result = this.engine.HandleRequest(request);
  316. result.Response.StatusCode.ShouldEqual(HttpStatusCode.InternalServerError);
  317. }
  318. [Fact]
  319. public void Should_store_exception_details_if_route_throws()
  320. {
  321. var errorRoute = new Route("GET", "/", null, x => { throw new NotImplementedException(); });
  322. A.CallTo(() => resolver.Resolve(A<NancyContext>.Ignored, A<IRouteCache>.Ignored)).Returns(new ResolveResult(errorRoute, DynamicDictionary.Empty, null, null));
  323. var request = new Request("GET", "/", "http");
  324. var result = this.engine.HandleRequest(request);
  325. result.GetExceptionDetails().ShouldContain("NotImplementedException");
  326. }
  327. [Fact]
  328. public void Should_set_the_route_parameters_to_the_nancy_context_before_calling_the_module_before()
  329. {
  330. dynamic parameters = new DynamicDictionary();
  331. parameters.Foo = "Bar";
  332. Func<NancyContext, Response> moduleBefore = (ctx) => { Assert.Equal(this.context.Parameters, parameters); return null; };
  333. A.CallTo(() => resolver.Resolve(A<NancyContext>.Ignored, A<IRouteCache>.Ignored)).Returns(new ResolveResult(route, parameters, moduleBefore, null));
  334. var request = new Request("GET", "/", "http");
  335. engine.HandleRequest(request);
  336. Assert.Equal(this.context.Parameters, parameters);
  337. }
  338. [Fact]
  339. public void Should_invoke_the_error_request_hook_if_one_exists_when_route_throws()
  340. {
  341. var testEx = new Exception();
  342. var errorRoute = new Route("GET", "/", null, x => { throw testEx; });
  343. A.CallTo(() => resolver.Resolve(A<NancyContext>.Ignored, A<IRouteCache>.Ignored)).Returns(new ResolveResult(errorRoute, DynamicDictionary.Empty, null, null));
  344. Exception handledException = null;
  345. NancyContext handledContext = null;
  346. var errorResponse = new Response();
  347. Func<NancyContext, Exception, Response> routeErrorHook = (ctx, ex) =>
  348. {
  349. handledContext = ctx;
  350. handledException = ex;
  351. return errorResponse;
  352. };
  353. this.engine.OnErrorHook += routeErrorHook;
  354. var request = new Request("GET", "/", "http");
  355. var result = this.engine.HandleRequest(request);
  356. Assert.Equal(testEx, handledException);
  357. Assert.Equal(result, handledContext);
  358. Assert.Equal(result.Response, errorResponse);
  359. }
  360. }
  361. }