// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. using System.Net.Http; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Xunit; using Assert = Microsoft.TestCommon.AssertEx; namespace System.Web.Http.Tracing.Tracers { public class RequestMessageHandlerTracerTest { [Fact] public void SendAsync_Traces_And_Invokes_Inner() { // Arrange HttpResponseMessage response = new HttpResponseMessage(); TestTraceWriter traceWriter = new TestTraceWriter(); RequestMessageHandlerTracer tracer = new RequestMessageHandlerTracer(traceWriter); MockHttpMessageHandler mockInnerHandler = new MockHttpMessageHandler((rqst, cancellation) => TaskHelpers.FromResult(response)); tracer.InnerHandler = mockInnerHandler; HttpRequestMessage request = new HttpRequestMessage(); TraceRecord[] expectedTraces = new TraceRecord[] { new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Info) { Kind = TraceKind.Begin }, new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Info) { Kind = TraceKind.End } }; MethodInfo method = typeof(DelegatingHandler).GetMethod("SendAsync", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); // Act Task task = method.Invoke(tracer, new object[] { request, CancellationToken.None }) as Task; HttpResponseMessage actualResponse = task.Result; // Assert Assert.Equal(expectedTraces, traceWriter.Traces, new TraceRecordComparer()); Assert.Same(response, actualResponse); } [Fact] public void SendAsync_Traces_And_Throws_When_Inner_Throws() { // Arrange InvalidOperationException exception = new InvalidOperationException("test"); TestTraceWriter traceWriter = new TestTraceWriter(); RequestMessageHandlerTracer tracer = new RequestMessageHandlerTracer(traceWriter); // DelegatingHandlers require an InnerHandler to run. We create a mock one to simulate what // would happen when a DelegatingHandler executing after the tracer throws. MockHttpMessageHandler mockInnerHandler = new MockHttpMessageHandler((rqst, cancellation) => { throw exception; }); tracer.InnerHandler = mockInnerHandler; HttpRequestMessage request = new HttpRequestMessage(); TraceRecord[] expectedTraces = new TraceRecord[] { new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Info) { Kind = TraceKind.Begin }, new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Error) { Kind = TraceKind.End } }; MethodInfo method = typeof(DelegatingHandler).GetMethod("SendAsync", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); // Act Exception thrown = Assert.Throws( () => method.Invoke(tracer, new object[] { request, CancellationToken.None })); // Assert Assert.Equal(expectedTraces, traceWriter.Traces, new TraceRecordComparer()); Assert.Same(exception, thrown.InnerException); Assert.Same(exception, traceWriter.Traces[1].Exception); } [Fact] public void SendAsync_Traces_And_Faults_When_Inner_Faults() { // Arrange InvalidOperationException exception = new InvalidOperationException("test"); TaskCompletionSource tcs = new TaskCompletionSource(); tcs.TrySetException(exception); TestTraceWriter traceWriter = new TestTraceWriter(); RequestMessageHandlerTracer tracer = new RequestMessageHandlerTracer(traceWriter); // DelegatingHandlers require an InnerHandler to run. We create a mock one to simulate what // would happen when a DelegatingHandler executing after the tracer returns a Task that throws. MockHttpMessageHandler mockInnerHandler = new MockHttpMessageHandler((rqst, cancellation) => { return tcs.Task; }); tracer.InnerHandler = mockInnerHandler; HttpRequestMessage request = new HttpRequestMessage(); TraceRecord[] expectedTraces = new TraceRecord[] { new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Info) { Kind = TraceKind.Begin }, new TraceRecord(request, TraceCategories.RequestCategory, TraceLevel.Error) { Kind = TraceKind.End } }; MethodInfo method = typeof(DelegatingHandler).GetMethod("SendAsync", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); // Act Task task = method.Invoke(tracer, new object[] { request, CancellationToken.None }) as Task; // Assert Exception thrown = Assert.Throws(() => task.Wait()); Assert.Equal(expectedTraces, traceWriter.Traces, new TraceRecordComparer()); Assert.Same(exception, thrown); Assert.Same(exception, traceWriter.Traces[1].Exception); } // DelegatingHandler cannot be mocked with Moq private class MockDelegatingHandler : DelegatingHandler { private Func> _callback; public MockDelegatingHandler(Func> callback) : base() { _callback = callback; } protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { return _callback(request, cancellationToken); } } // HttpMessageHandler cannot be mocked with Moq private class MockHttpMessageHandler : HttpMessageHandler { private Func> _callback; public MockHttpMessageHandler(Func> callback) : base() { _callback = callback; } protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { return _callback(request, cancellationToken); } } } }