1 // Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
2 
3 using System.Net.Http;
4 using System.Reflection;
5 using System.Threading;
6 using System.Threading.Tasks;
7 using Xunit;
8 using Assert = Microsoft.TestCommon.AssertEx;
9 
10 namespace System.Web.Http.Tracing.Tracers
11 {
12     public class RequestMessageHandlerTracerTest
13     {
14         [Fact]
SendAsync_Traces_And_Invokes_Inner()15         public void SendAsync_Traces_And_Invokes_Inner()
16         {
17             // Arrange
18             HttpResponseMessage response = new HttpResponseMessage();
19             TestTraceWriter traceWriter = new TestTraceWriter();
20             RequestMessageHandlerTracer tracer = new RequestMessageHandlerTracer(traceWriter);
21             MockHttpMessageHandler mockInnerHandler = new MockHttpMessageHandler((rqst, cancellation) =>
22                                      TaskHelpers.FromResult<HttpResponseMessage>(response));
23             tracer.InnerHandler = mockInnerHandler;
24 
25             HttpRequestMessage request = new HttpRequestMessage();
26             TraceRecord[] expectedTraces = new TraceRecord[]
27             {
28                 new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
29                 new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Info) { Kind = TraceKind.End }
30             };
31 
32             MethodInfo method = typeof(DelegatingHandler).GetMethod("SendAsync",
33                                                                      BindingFlags.Public | BindingFlags.NonPublic |
34                                                                      BindingFlags.Instance);
35 
36             // Act
37             Task<HttpResponseMessage> task = method.Invoke(tracer, new object[] { request, CancellationToken.None }) as Task<HttpResponseMessage>;
38             HttpResponseMessage actualResponse = task.Result;
39 
40             // Assert
41             Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
42             Assert.Same(response, actualResponse);
43         }
44 
45         [Fact]
SendAsync_Traces_And_Throws_When_Inner_Throws()46         public void SendAsync_Traces_And_Throws_When_Inner_Throws()
47         {
48             // Arrange
49             InvalidOperationException exception = new InvalidOperationException("test");
50             TestTraceWriter traceWriter = new TestTraceWriter();
51             RequestMessageHandlerTracer tracer = new RequestMessageHandlerTracer(traceWriter);
52 
53             // DelegatingHandlers require an InnerHandler to run.  We create a mock one to simulate what
54             // would happen when a DelegatingHandler executing after the tracer throws.
55             MockHttpMessageHandler mockInnerHandler = new MockHttpMessageHandler((rqst, cancellation) => { throw exception; });
56             tracer.InnerHandler = mockInnerHandler;
57 
58             HttpRequestMessage request = new HttpRequestMessage();
59             TraceRecord[] expectedTraces = new TraceRecord[]
60             {
61                 new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
62                 new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Error) { Kind = TraceKind.End }
63             };
64 
65             MethodInfo method = typeof(DelegatingHandler).GetMethod("SendAsync",
66                                                                      BindingFlags.Public | BindingFlags.NonPublic |
67                                                                      BindingFlags.Instance);
68 
69             // Act
70             Exception thrown =
71                 Assert.Throws<TargetInvocationException>(
72                     () => method.Invoke(tracer, new object[] { request, CancellationToken.None }));
73 
74             // Assert
75             Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
76             Assert.Same(exception, thrown.InnerException);
77             Assert.Same(exception, traceWriter.Traces[1].Exception);
78         }
79 
80         [Fact]
SendAsync_Traces_And_Faults_When_Inner_Faults()81         public void SendAsync_Traces_And_Faults_When_Inner_Faults()
82         {
83             // Arrange
84             InvalidOperationException exception = new InvalidOperationException("test");
85             TaskCompletionSource<HttpResponseMessage> tcs = new TaskCompletionSource<HttpResponseMessage>();
86             tcs.TrySetException(exception);
87             TestTraceWriter traceWriter = new TestTraceWriter();
88             RequestMessageHandlerTracer tracer = new RequestMessageHandlerTracer(traceWriter);
89 
90             // DelegatingHandlers require an InnerHandler to run.  We create a mock one to simulate what
91             // would happen when a DelegatingHandler executing after the tracer returns a Task that throws.
92             MockHttpMessageHandler mockInnerHandler = new MockHttpMessageHandler((rqst, cancellation) => { return tcs.Task; });
93             tracer.InnerHandler = mockInnerHandler;
94 
95             HttpRequestMessage request = new HttpRequestMessage();
96             TraceRecord[] expectedTraces = new TraceRecord[]
97             {
98                 new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Info) { Kind = TraceKind.Begin },
99                 new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Error) { Kind = TraceKind.End }
100             };
101 
102             MethodInfo method = typeof(DelegatingHandler).GetMethod("SendAsync",
103                                                                      BindingFlags.Public | BindingFlags.NonPublic |
104                                                                      BindingFlags.Instance);
105 
106             // Act
107             Task<HttpResponseMessage> task =
108                 method.Invoke(tracer, new object[] { request, CancellationToken.None }) as Task<HttpResponseMessage>;
109 
110             // Assert
111             Exception thrown = Assert.Throws<InvalidOperationException>(() => task.Wait());
112             Assert.Equal<TraceRecord>(expectedTraces, traceWriter.Traces, new TraceRecordComparer());
113             Assert.Same(exception, thrown);
114             Assert.Same(exception, traceWriter.Traces[1].Exception);
115         }
116 
117 
118         // DelegatingHandler cannot be mocked with Moq
119         private class MockDelegatingHandler : DelegatingHandler
120         {
121             private Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _callback;
122 
MockDelegatingHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> callback)123             public MockDelegatingHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> callback)
124                 : base()
125             {
126                 _callback = callback;
127             }
128 
SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)129             protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
130             {
131                 return _callback(request, cancellationToken);
132             }
133         }
134 
135         // HttpMessageHandler cannot be mocked with Moq
136         private class MockHttpMessageHandler : HttpMessageHandler
137         {
138             private Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> _callback;
139 
MockHttpMessageHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> callback)140             public MockHttpMessageHandler(Func<HttpRequestMessage, CancellationToken, Task<HttpResponseMessage>> callback)
141                 : base()
142             {
143                 _callback = callback;
144             }
145 
SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)146             protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
147             {
148                 return _callback(request, cancellationToken);
149             }
150         }
151     }
152 }
153