1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. 2 3 using System.Collections.ObjectModel; 4 using System.Net.Http; 5 using System.Threading; 6 using System.Threading.Tasks; 7 using System.Web.Http.Controllers; 8 using Moq; 9 using Xunit; 10 using Assert = Microsoft.TestCommon.AssertEx; 11 using System.Web.Http.Hosting; 12 using System.Collections.Generic; 13 14 namespace System.Web.Http.Tracing.Tracers 15 { 16 public class HttpControllerTracerTest 17 { 18 private Mock<HttpActionDescriptor> _mockActionDescriptor; 19 private HttpControllerDescriptor _controllerDescriptor; 20 HttpControllerTracerTest()21 public HttpControllerTracerTest() 22 { 23 _mockActionDescriptor = new Mock<HttpActionDescriptor>() { CallBase = true }; 24 _mockActionDescriptor.Setup(a => a.ActionName).Returns("test"); 25 _mockActionDescriptor.Setup(a => a.GetParameters()).Returns(new Collection<HttpParameterDescriptor>(new HttpParameterDescriptor[0])); 26 27 _controllerDescriptor = new HttpControllerDescriptor(new HttpConfiguration(), "controller", typeof(ApiController)); 28 } 29 30 [Fact] Dispose_TracesAndInvokesInnerDisposeWhenControllerIsDisposable()31 public void Dispose_TracesAndInvokesInnerDisposeWhenControllerIsDisposable() 32 { 33 // Arrange 34 var mockController = new Mock<IHttpController>(); 35 var mockDisposable = mockController.As<IDisposable>(); 36 var request = new HttpRequestMessage(); 37 var traceWriter = new TestTraceWriter(); 38 var tracer = new HttpControllerTracer(request, mockController.Object, traceWriter); 39 var expectedTraces = new[] 40 { 41 new TraceRecord(request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.Begin, Operation = "Dispose" }, 42 new TraceRecord(request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.End, Operation = "Dispose" } 43 }; 44 45 // Act 46 ((IDisposable)tracer).Dispose(); 47 48 // Assert 49 Assert.Equal(expectedTraces, traceWriter.Traces, new TraceRecordComparer()); 50 mockDisposable.Verify(d => d.Dispose(), Times.Once()); 51 } 52 53 [Fact] Dispose_DoesNotTraceWhenControllerIsNotDisposable()54 public void Dispose_DoesNotTraceWhenControllerIsNotDisposable() 55 { 56 // Arrange 57 var mockController = new Mock<IHttpController>(); 58 var request = new HttpRequestMessage(); 59 var traceWriter = new TestTraceWriter(); 60 var tracer = new HttpControllerTracer(request, mockController.Object, traceWriter); 61 62 // Act 63 ((IDisposable)tracer).Dispose(); 64 65 // Assert 66 Assert.Empty(traceWriter.Traces); 67 } 68 69 [Fact] ExecuteAsync_RemovesInnerControllerFromReleaseListAndAddsItselfInstead()70 public void ExecuteAsync_RemovesInnerControllerFromReleaseListAndAddsItselfInstead() 71 { 72 // Arrange 73 var request = new HttpRequestMessage(); 74 var context = ContextUtil.CreateControllerContext(request: request); 75 var mockController = new Mock<IHttpController>(); 76 var mockDisposable = mockController.As<IDisposable>(); 77 mockController.Setup(c => c.ExecuteAsync(context, CancellationToken.None)) 78 .Callback<HttpControllerContext, CancellationToken>((cc, ct) => cc.Request.RegisterForDispose(mockDisposable.Object)) 79 .Returns(() => TaskHelpers.FromResult(new HttpResponseMessage())) 80 .Verifiable(); 81 context.ControllerDescriptor = _controllerDescriptor; 82 context.Controller = mockController.Object; 83 var traceWriter = new TestTraceWriter(); 84 var tracer = new HttpControllerTracer(request, mockController.Object, traceWriter); 85 86 // Act 87 ((IHttpController)tracer).ExecuteAsync(context, CancellationToken.None).WaitUntilCompleted(); 88 89 // Assert 90 IEnumerable<IDisposable> disposables = (IEnumerable<IDisposable>)request.Properties[HttpPropertyKeys.DisposableRequestResourcesKey]; 91 Assert.Contains(tracer, disposables); 92 Assert.DoesNotContain(mockDisposable.Object, disposables); 93 } 94 95 [Fact] ExecuteAsync_Invokes_Inner_And_Traces()96 public void ExecuteAsync_Invokes_Inner_And_Traces() 97 { 98 // Arrange 99 HttpResponseMessage response = new HttpResponseMessage(); 100 Mock<ApiController> mockController = new Mock<ApiController>() { CallBase = true }; 101 mockController.Setup(b => b.ExecuteAsync(It.IsAny<HttpControllerContext>(), It.IsAny<CancellationToken>())).Returns(TaskHelpers.FromResult<HttpResponseMessage>(response)); 102 103 HttpRequestMessage request = new HttpRequestMessage(); 104 HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(request: request); 105 controllerContext.ControllerDescriptor = _controllerDescriptor; 106 controllerContext.Controller = mockController.Object; 107 108 HttpActionContext actionContext = ContextUtil.CreateActionContext(controllerContext, actionDescriptor: _mockActionDescriptor.Object); 109 110 TestTraceWriter traceWriter = new TestTraceWriter(); 111 HttpControllerTracer tracer = new HttpControllerTracer(request, mockController.Object, traceWriter); 112 113 TraceRecord[] expectedTraces = new TraceRecord[] 114 { 115 new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.Begin }, 116 new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.End } 117 }; 118 119 // Act 120 HttpResponseMessage actualResponse = ((IHttpController)tracer).ExecuteAsync(controllerContext, CancellationToken.None).Result; 121 122 // Assert 123 Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer()); 124 Assert.Same(response, actualResponse); 125 } 126 127 [Fact] ExecuteAsync_Faults_And_Traces_When_Inner_Faults()128 public void ExecuteAsync_Faults_And_Traces_When_Inner_Faults() 129 { 130 // Arrange 131 InvalidOperationException exception = new InvalidOperationException(); 132 TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>(); 133 tcs.TrySetException(exception); 134 Mock<ApiController> mockController = new Mock<ApiController>() { CallBase = true }; 135 mockController.Setup(b => b.ExecuteAsync(It.IsAny<HttpControllerContext>(), It.IsAny<CancellationToken>())).Returns(tcs.Task); 136 137 HttpRequestMessage request = new HttpRequestMessage(); 138 HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(request: request); 139 controllerContext.ControllerDescriptor = _controllerDescriptor; 140 controllerContext.Controller = mockController.Object; 141 142 HttpActionContext actionContext = ContextUtil.CreateActionContext(controllerContext, actionDescriptor: _mockActionDescriptor.Object); 143 144 TestTraceWriter traceWriter = new TestTraceWriter(); 145 HttpControllerTracer tracer = new HttpControllerTracer(request, mockController.Object, traceWriter); 146 147 TraceRecord[] expectedTraces = new TraceRecord[] 148 { 149 new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.Begin }, 150 new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Error) { Kind = TraceKind.End } 151 }; 152 153 // Act 154 Exception thrown = Assert.Throws<InvalidOperationException>(() => ((IHttpController)tracer).ExecuteAsync(controllerContext, CancellationToken.None).Wait()); 155 156 // Assert 157 Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer()); 158 Assert.Same(exception, thrown); 159 Assert.Same(exception, traceWriter.Traces[1].Exception); 160 } 161 162 [Fact] ExecuteAsync_IsCancelled_And_Traces_When_Inner_IsCancelled()163 public void ExecuteAsync_IsCancelled_And_Traces_When_Inner_IsCancelled() 164 { 165 // Arrange 166 Mock<ApiController> mockController = new Mock<ApiController>() { CallBase = true }; 167 mockController.Setup(b => b.ExecuteAsync(It.IsAny<HttpControllerContext>(), It.IsAny<CancellationToken>())).Returns(TaskHelpers.Canceled<HttpResponseMessage>()); 168 169 HttpRequestMessage request = new HttpRequestMessage(); 170 HttpControllerContext controllerContext = ContextUtil.CreateControllerContext(request: request); 171 controllerContext.ControllerDescriptor = _controllerDescriptor; 172 controllerContext.Controller = mockController.Object; 173 174 HttpActionContext actionContext = ContextUtil.CreateActionContext(controllerContext, actionDescriptor: _mockActionDescriptor.Object); 175 176 TestTraceWriter traceWriter = new TestTraceWriter(); 177 HttpControllerTracer tracer = new HttpControllerTracer(request, mockController.Object, traceWriter); 178 179 TraceRecord[] expectedTraces = new TraceRecord[] 180 { 181 new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Info) { Kind = TraceKind.Begin }, 182 new TraceRecord(actionContext.Request, TraceCategories.ControllersCategory, TraceLevel.Warn) { Kind = TraceKind.End } 183 }; 184 185 // Act 186 Task task = ((IHttpController)tracer).ExecuteAsync(controllerContext, CancellationToken.None); 187 Exception thrown = Assert.Throws<TaskCanceledException>(() => task.Wait()); 188 189 // Assert 190 Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer()); 191 } 192 } 193 } 194