| File: Latency\RequestLatencyTelemetryMiddlewareTests.cs | Web Access |
| Project: src\test\Libraries\Microsoft.AspNetCore.Diagnostics.Middleware.Tests\Microsoft.AspNetCore.Diagnostics.Middleware.Tests.csproj (Microsoft.AspNetCore.Diagnostics.Middleware.Tests) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.AmbientMetadata; using Microsoft.Extensions.Diagnostics.Latency; using Microsoft.Extensions.Http.Diagnostics; using Microsoft.Extensions.Options; using Moq; using Xunit; namespace Microsoft.AspNetCore.Diagnostics.Latency.Test; public class RequestLatencyTelemetryMiddlewareTests { [Fact] public async Task RequestLatency_GivenContext_InvokesOperations() { var ex1 = new TestExporter(); var ex2 = new TestExporter(); string serverName = "AppServer"; var nextInvoked = false; var m = new RequestLatencyTelemetryMiddleware((_) => { nextInvoked = true; return Task.CompletedTask; }, Options.Create(new RequestLatencyTelemetryOptions()), new List<ILatencyDataExporter> { ex1, ex2 }, Options.Create(new ApplicationMetadata { ApplicationName = serverName })); var lc = GetMockLatencyContext(); var httpContextMock = GetHttpContext(lc.Object); var fakeHttpResponseFeature = new FakeHttpResponseFeature(); httpContextMock.Features.Set<IHttpResponseFeature>(fakeHttpResponseFeature); await m.InvokeAsync(httpContextMock); await fakeHttpResponseFeature.StartAsync(); lc.Verify(c => c.Freeze()); var header = httpContextMock.Response.Headers[TelemetryConstants.ServerApplicationNameHeader]; Assert.True(header.Count > 0); Assert.Equal(serverName, header[0]); Assert.True(nextInvoked); Assert.True(ex1.Invoked == 1); Assert.True(ex2.Invoked == 1); } [Fact] public async Task RequestLatency_WithoutServiceMetadata_InvokesOperations() { var ex1 = new TestExporter(); var ex2 = new TestExporter(); var nextInvoked = false; var m = new RequestLatencyTelemetryMiddleware((_) => { nextInvoked = true; return Task.CompletedTask; }, Options.Create(new RequestLatencyTelemetryOptions()), new List<ILatencyDataExporter> { ex1, ex2 }); var lc = GetMockLatencyContext(); var httpContextMock = GetHttpContext(lc.Object); var fakeHttpResponseFeature = new FakeHttpResponseFeature(); httpContextMock.Features.Set<IHttpResponseFeature>(fakeHttpResponseFeature); await m.InvokeAsync(httpContextMock); await fakeHttpResponseFeature.StartAsync(); lc.Verify(c => c.Freeze()); Assert.False(httpContextMock.Response.Headers.TryGetValue(TelemetryConstants.ServerApplicationNameHeader, out var val)); Assert.True(nextInvoked); Assert.True(ex1.Invoked == 1); Assert.True(ex2.Invoked == 1); } [Fact] public async Task RequestLatency_WithServerNameHeadersSet_ReturnsLastServerName() { var ex1 = new TestExporter(); var ex2 = new TestExporter(); string serverName = "AppServer"; var nextInvoked = false; var m = new RequestLatencyTelemetryMiddleware((_) => { nextInvoked = true; return Task.CompletedTask; }, Options.Create(new RequestLatencyTelemetryOptions()), new List<ILatencyDataExporter> { ex1, ex2 }, Options.Create(new ApplicationMetadata { ApplicationName = serverName })); var lc = GetMockLatencyContext(); var httpContextMock = GetHttpContext(lc.Object); var fakeHttpResponseFeature = new FakeHttpResponseFeature(); httpContextMock.Features.Set<IHttpResponseFeature>(fakeHttpResponseFeature); httpContextMock.Response.Headers.Append(TelemetryConstants.ServerApplicationNameHeader, "testValue"); await m.InvokeAsync(httpContextMock); await fakeHttpResponseFeature.StartAsync(); lc.Verify(c => c.Freeze()); var header = httpContextMock.Response.Headers[TelemetryConstants.ServerApplicationNameHeader]; Assert.True(header.Count > 0); Assert.Equal(serverName, header[0]); Assert.True(nextInvoked); Assert.True(ex1.Invoked == 1); Assert.True(ex2.Invoked == 1); } [Fact] public async Task RequestLatency_NoServiceData_DoesNotAddHeader() { var ex1 = new TestExporter(); var ex2 = new TestExporter(); var nextInvoked = false; var m = new RequestLatencyTelemetryMiddleware((_) => { nextInvoked = true; return Task.CompletedTask; }, Options.Create(new RequestLatencyTelemetryOptions()), new List<ILatencyDataExporter> { ex1, ex2 }, Options.Create(new ApplicationMetadata())); var lc = GetMockLatencyContext(); var httpContextMock = GetHttpContext(lc.Object); var fakeHttpResponseFeature = new FakeHttpResponseFeature(); httpContextMock.Features.Set<IHttpResponseFeature>(fakeHttpResponseFeature); await m.InvokeAsync(httpContextMock); await fakeHttpResponseFeature.StartAsync(); lc.Verify(c => c.Freeze()); Assert.False(httpContextMock.Response.Headers.TryGetValue(TelemetryConstants.ServerApplicationNameHeader, out var val)); Assert.True(nextInvoked); Assert.True(ex1.Invoked == 1); Assert.True(ex2.Invoked == 1); } [Fact] public async Task RequestLatency_NoExporter() { var lc = GetMockLatencyContext(); var httpContextMock = GetHttpContext(lc.Object); var nextInvoked = false; var m = new RequestLatencyTelemetryMiddleware((_) => { nextInvoked = true; return Task.CompletedTask; }, Options.Create(new RequestLatencyTelemetryOptions()), Array.Empty<ILatencyDataExporter>(), Options.Create(new ApplicationMetadata())); await m.InvokeAsync(httpContextMock); lc.Verify(c => c.Freeze()); Assert.True(nextInvoked); } [Fact] public async Task RequestLatency_GivenTimeout_PassedToExport() { var exportTimeout = TimeSpan.FromSeconds(1); var ex1 = new TimeConsumingExporter(TimeSpan.FromSeconds(5)); var nextInvoked = false; var m = new RequestLatencyTelemetryMiddleware((_) => { nextInvoked = true; return Task.CompletedTask; }, Options.Create(new RequestLatencyTelemetryOptions { LatencyDataExportTimeout = exportTimeout }), new List<ILatencyDataExporter> { ex1 }, Options.Create(new ApplicationMetadata())); var lc = GetMockLatencyContext(); var httpContextMock = GetHttpContext(lc.Object); await m.InvokeAsync(httpContextMock); await httpContextMock.Response.CompleteAsync(); lc.Verify(c => c.Freeze()); Assert.True(nextInvoked); } private sealed class FakeHttpResponseFeature : HttpResponseFeature { private Func<Task> _responseStartingAsync = static () => Task.CompletedTask; public override void OnStarting(Func<object, Task> callback, object state) { ChainCallback(callback, state); } public override void OnCompleted(Func<object, Task> callback, object state) { ChainCallback(callback, state); } private void ChainCallback(Func<object, Task> callback, object state) { var prior = _responseStartingAsync; _responseStartingAsync = async () => { await prior(); await callback(state); }; } public async Task StartAsync() => await _responseStartingAsync(); } private static HttpContext GetHttpContext(ILatencyContext latencyContext) { var httpContextMock = new DefaultHttpContext(); var feature = new Mock<IHttpResponseFeature>(); feature.Setup(m => m.OnCompleted(It.IsAny<Func<object, Task>>(), It.IsAny<object>())) .Callback<Func<object, Task>, object>((c, o) => c(o)); httpContextMock.Features.Set(feature.Object); var serviceProviderMock = new Mock<IServiceProvider>(); serviceProviderMock .Setup(serviceProvider => serviceProvider.GetService(typeof(ILatencyContext))) .Returns(latencyContext); httpContextMock.RequestServices = serviceProviderMock.Object; return httpContextMock; } private static Mock<ILatencyContext> GetMockLatencyContext() { var cc = new Mock<ILatencyContext>(); return cc; } private class TestExporter : ILatencyDataExporter { public int Invoked { get; private set; } public async Task ExportAsync(LatencyData latencyData, CancellationToken cancellationToken) { Invoked++; await Task.CompletedTask; } } private class TimeConsumingExporter : ILatencyDataExporter { public int Invoked { get; private set; } private readonly TimeSpan _timeSpanToDelay; public TimeConsumingExporter(TimeSpan timeSpanToDelay) { _timeSpanToDelay = timeSpanToDelay; } public async Task ExportAsync(LatencyData latencyData, CancellationToken cancellationToken) { Invoked++; var e = await Record.ExceptionAsync(() => Task.Delay(_timeSpanToDelay, cancellationToken)); Assert.IsAssignableFrom<OperationCanceledException>(e); } } }