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