// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Collections.ObjectModel; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using System.Web.Http.Controllers; using Moq; using Xunit; using Assert = Microsoft.TestCommon.AssertEx; using System.Web.Http.Hosting; using System.Collections.Generic; namespace System.Web.Http.Tracing.Tracers { public class HttpControllerTracerTest { private Mock _mockActionDescriptor; private HttpControllerDescriptor _controllerDescriptor; public HttpControllerTracerTest() { _mockActionDescriptor = new Mock() { CallBase = true }; _mockActionDescriptor.Setup(a => a.ActionName).Returns("test"); _mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection(new HttpParameterDescriptor[0])); _controllerDescriptor = new HttpControllerDescriptor(new HttpConfiguration(), "controller", typeof(ApiController)); } [Fact] public void Dispose_TracesAndInvokesInnerDisposeWhenControllerIsDisposable() { // Arrange var mockController = new Mock(); var mockDisposable = mockController.As(); var request = new HttpRequestMessage(); var traceWriter = new TestTraceWriter(); var tracer = new HttpControllerTracer(request, mockController.Object, traceWriter); var expectedTraces = new[] { new TraceRecord(request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "Dispose" }, new TraceRecord(request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "Dispose" } }; // Act ((IDisposable)tracer).Dispose(); // Assert Assert.Equal(expectedTraces, traceWriter.Traces, new TraceRecordComparer()); mockDisposable.Verify(d => d.Dispose(), Times.Once()); } [Fact] public void Dispose_DoesNotTraceWhenControllerIsNotDisposable() { // Arrange var mockController = new Mock(); var request = new HttpRequestMessage(); var traceWriter = new TestTraceWriter(); var tracer = new HttpControllerTracer(request, mockController.Object, traceWriter); // Act ((IDisposable)tracer).Dispose(); // Assert Assert.Empty(traceWriter.Traces); } [Fact] public void ExecuteAsync_RemovesInnerControllerFromReleaseListAndAddsItselfInstead() { // Arrange var request = new HttpRequestMessage(); var context = ContextUtil.CreateControllerContext(request: request); var mockController = new Mock(); var mockDisposable = mockController.As(); mockController.Setup(c => c.ExecuteAsync(context, CancellationToken.None)) .Callback((cc, ct) => cc.Request.RegisterForDispose(mockDisposable.Object)) .Returns(() => TaskHelpers.FromResult(new HttpResponseMessage())) .Verifiable(); context.ControllerDescriptor = _controllerDescriptor; context.Controller = mockController.Object; var traceWriter = new TestTraceWriter(); var tracer = new HttpControllerTracer(request, mockController.Object, traceWriter); // Act ((IHttpController)tracer).ExecuteAsync(context, CancellationToken.None).WaitUntilCompleted(); // Assert IEnumerable disposables = (IEnumerable)request.Properties[HttpPropertyKeys.DisposableRequestResourcesKey]; Assert.Contains(tracer, disposables); Assert.DoesNotContain(mockDisposable.Object, disposables); } [Fact] public void ExecuteAsync_Invokes_Inner_And_Traces() { // Arrange HttpResponseMessage response = new HttpResponseMessage(); Mock mockController = new Mock() { CallBase = true }; mockController.Setup(b => b.ExecuteAsync(It.IsAny(), It.IsAny())).Returns(TaskHelpers.FromResult(response)); HttpRequestMessage request = new HttpRequestMessage(); HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(request: request); controllerContext.ControllerDescriptor = _controllerDescriptor; controllerContext.Controller = mockController.Object; HttpActionContext actionContext = ContextUtil.CreateActionContext(controllerContext, actionDescriptor: _mockActionDescriptor.Object); TestTraceWriter traceWriter = new TestTraceWriter(); HttpControllerTracer tracer = new HttpControllerTracer(request, mockController.Object, traceWriter); TraceRecord[] expectedTraces = new TraceRecord[] { new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.Begin }, new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.End } }; // Act HttpResponseMessage actualResponse = ((IHttpController)tracer).ExecuteAsync(controllerContext, CancellationToken.None).Result; // Assert Assert.Equal(expectedTraces, traceWriter.Traces, new TraceRecordComparer()); Assert.Same(response, actualResponse); } [Fact] public void ExecuteAsync_Faults_And_Traces_When_Inner_Faults() { // Arrange InvalidOperationException exception = new InvalidOperationException(); TaskCompletionSource tcs = new TaskCompletionSource(); tcs.TrySetException(exception); Mock mockController = new Mock() { CallBase = true }; mockController.Setup(b => b.ExecuteAsync(It.IsAny(), It.IsAny())).Returns(tcs.Task); HttpRequestMessage request = new HttpRequestMessage(); HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(request: request); controllerContext.ControllerDescriptor = _controllerDescriptor; controllerContext.Controller = mockController.Object; HttpActionContext actionContext = ContextUtil.CreateActionContext(controllerContext, actionDescriptor: _mockActionDescriptor.Object); TestTraceWriter traceWriter = new TestTraceWriter(); HttpControllerTracer tracer = new HttpControllerTracer(request, mockController.Object, traceWriter); TraceRecord[] expectedTraces = new TraceRecord[] { new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.Begin }, new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Error) { Kind = TraceKind.End } }; // Act Exception thrown = Assert.Throws(() => ((IHttpController)tracer).ExecuteAsync(controllerContext, CancellationToken.None).Wait()); // Assert Assert.Equal(expectedTraces, traceWriter.Traces, new TraceRecordComparer()); Assert.Same(exception, thrown); Assert.Same(exception, traceWriter.Traces[1].Exception); } [Fact] public void ExecuteAsync_IsCancelled_And_Traces_When_Inner_IsCancelled() { // Arrange Mock mockController = new Mock() { CallBase = true }; mockController.Setup(b => b.ExecuteAsync(It.IsAny(), It.IsAny())).Returns(TaskHelpers.Canceled()); HttpRequestMessage request = new HttpRequestMessage(); HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(request: request); controllerContext.ControllerDescriptor = _controllerDescriptor; controllerContext.Controller = mockController.Object; HttpActionContext actionContext = ContextUtil.CreateActionContext(controllerContext, actionDescriptor: _mockActionDescriptor.Object); TestTraceWriter traceWriter = new TestTraceWriter(); HttpControllerTracer tracer = new HttpControllerTracer(request, mockController.Object, traceWriter); TraceRecord[] expectedTraces = new TraceRecord[] { new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.Begin }, new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Warn) { Kind = TraceKind.End } }; // Act Task task = ((IHttpController)tracer).ExecuteAsync(controllerContext, CancellationToken.None); Exception thrown = Assert.Throws(() => task.Wait()); // Assert Assert.Equal(expectedTraces, traceWriter.Traces, new TraceRecordComparer()); } } }