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