// Copyright © WireMock.Net using System; using System.Collections.Concurrent; using System.Linq.Expressions; using System.Threading.Tasks; using Moq; using Xunit; using WireMock.Models; using WireMock.Owin; using WireMock.Owin.Mappers; using WireMock.Util; using WireMock.Logging; using WireMock.Matchers; using System.Collections.Generic; #if NET6_0_OR_GREATER using System.Diagnostics; #endif using WireMock.Admin.Mappings; using WireMock.Admin.Requests; using WireMock.Settings; using FluentAssertions; using WireMock.Handlers; using WireMock.Matchers.Request; using WireMock.ResponseBuilders; using WireMock.RequestBuilders; #if NET6_0_OR_GREATER using WireMock.Owin.ActivityTracing; #endif #if NET452 using Microsoft.Owin; using IContext = Microsoft.Owin.IOwinContext; using IRequest = Microsoft.Owin.IOwinRequest; using IResponse = Microsoft.Owin.IOwinResponse; #else using Microsoft.AspNetCore.Http; using IContext = Microsoft.AspNetCore.Http.HttpContext; using IRequest = Microsoft.AspNetCore.Http.HttpRequest; using IResponse = Microsoft.AspNetCore.Http.HttpResponse; #endif namespace WireMock.Net.Tests.Owin; public class WireMockMiddlewareTests { private static readonly Guid NewGuid = new("98fae52e-76df-47d9-876f-2ee32e931d9b"); private static readonly DateTime UpdatedAt = new(2022, 12, 4); private readonly ConcurrentDictionary _mappings = new(); private readonly Mock _optionsMock; private readonly Mock _requestMapperMock; private readonly Mock _responseMapperMock; private readonly Mock _matcherMock; private readonly Mock _mappingMock; private readonly Mock _requestMatchResultMock; private readonly Mock _contextMock; private readonly WireMockMiddleware _sut; public WireMockMiddlewareTests() { var guidUtilsMock = new Mock(); guidUtilsMock.Setup(g => g.NewGuid()).Returns(NewGuid); _optionsMock = new Mock(); _optionsMock.SetupAllProperties(); _optionsMock.Setup(o => o.Mappings).Returns(_mappings); _optionsMock.Setup(o => o.LogEntries).Returns(new ConcurrentObservableCollection()); _optionsMock.Setup(o => o.Scenarios).Returns(new ConcurrentDictionary()); _optionsMock.Setup(o => o.Logger.Warn(It.IsAny(), It.IsAny())); _optionsMock.Setup(o => o.Logger.Error(It.IsAny(), It.IsAny())); _optionsMock.Setup(o => o.Logger.DebugRequestResponse(It.IsAny(), It.IsAny())); _requestMapperMock = new Mock(); _requestMapperMock.SetupAllProperties(); var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1"); _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); _responseMapperMock = new Mock(); _responseMapperMock.SetupAllProperties(); _responseMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).Returns(Task.FromResult(true)); _matcherMock = new Mock(); _matcherMock.SetupAllProperties(); // _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns((new MappingMatcherResult(), new MappingMatcherResult())); _contextMock = new Mock(); _mappingMock = new Mock(); _requestMatchResultMock = new Mock(); _requestMatchResultMock.Setup(r => r.TotalNumber).Returns(1); _requestMatchResultMock.Setup(r => r.MatchDetails).Returns(new List()); _sut = new WireMockMiddleware( null, _optionsMock.Object, _requestMapperMock.Object, _responseMapperMock.Object, _matcherMock.Object, guidUtilsMock.Object ); } [Fact] public async Task WireMockMiddleware_Invoke_NoMatch() { // Act await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); // Assert and Verify _optionsMock.Verify(o => o.Logger.Warn(It.IsAny(), It.IsAny()), Times.Once); Expression> match = r => (int)r.StatusCode! == 404 && ((StatusModel)r.BodyData!.BodyAsJson!).Status == "No matching mapping found"; _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); } [Fact] public async Task WireMockMiddleware_Invoke_NoMatch_When_SaveUnmatchedRequestsIsTrue_Should_Call_LocalFileSystemHandler_WriteUnmatchedRequest() { // Arrange var fileSystemHandlerMock = new Mock(); _optionsMock.Setup(o => o.FileSystemHandler).Returns(fileSystemHandlerMock.Object); _optionsMock.Setup(o => o.SaveUnmatchedRequests).Returns(true); // Act await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); // Assert _optionsMock.Verify(o => o.Logger.Warn(It.IsAny(), It.IsAny()), Times.Once); Expression> match = r => (int)r.StatusCode! == 404 && ((StatusModel)r.BodyData!.BodyAsJson!).Status == "No matching mapping found"; _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); // Verify fileSystemHandlerMock.Verify(f => f.WriteUnmatchedRequest("98fae52e-76df-47d9-876f-2ee32e931d9b.LogEntry.json", It.IsAny())); fileSystemHandlerMock.VerifyNoOtherCalls(); } [Fact] public async Task WireMockMiddleware_Invoke_IsAdminInterface_EmptyHeaders_401() { // Assign var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1", null, new Dictionary()); _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); _optionsMock.SetupGet(o => o.AuthenticationMatcher).Returns(new ExactMatcher()); _mappingMock.SetupGet(m => m.IsAdminInterface).Returns(true); var result = new MappingMatcherResult(_mappingMock.Object, _requestMatchResultMock.Object); _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns((result, result)); // Act await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); // Assert and Verify _optionsMock.Verify(o => o.Logger.Error(It.IsAny(), It.IsAny()), Times.Once); Expression> match = r => (int?)r.StatusCode == 401; _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); } [Fact] public async Task WireMockMiddleware_Invoke_IsAdminInterface_MissingHeader_401() { // Assign var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1", null, new Dictionary { { "h", new[] { "x" } } }); _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); _optionsMock.SetupGet(o => o.AuthenticationMatcher).Returns(new ExactMatcher()); _mappingMock.SetupGet(m => m.IsAdminInterface).Returns(true); var result = new MappingMatcherResult(_mappingMock.Object, _requestMatchResultMock.Object); _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns((result, result)); // Act await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); // Assert and Verify _optionsMock.Verify(o => o.Logger.Error(It.IsAny(), It.IsAny()), Times.Once); Expression> match = r => (int?)r.StatusCode == 401; _responseMapperMock.Verify(m => m.MapAsync(It.Is(match), It.IsAny()), Times.Once); } [Fact] public async Task WireMockMiddleware_Invoke_RequestLogExpirationDurationIsDefined() { // Assign _optionsMock.SetupGet(o => o.RequestLogExpirationDuration).Returns(1); // Act await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); } [Fact] public async Task WireMockMiddleware_Invoke_Mapping_Has_ProxyAndRecordSettings_And_SaveMapping_Is_True() { // Assign var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1", null, new Dictionary()); _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); _optionsMock.SetupGet(o => o.AuthenticationMatcher).Returns(new ExactMatcher()); var fileSystemHandlerMock = new Mock(); fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); var logger = new Mock(); var proxyAndRecordSettings = new ProxyAndRecordSettings { SaveMapping = true, SaveMappingToFile = true }; var settings = new WireMockServerSettings { FileSystemHandler = fileSystemHandlerMock.Object, Logger = logger.Object }; var responseBuilder = Response.Create().WithProxy(proxyAndRecordSettings); _mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder); _mappingMock.SetupGet(m => m.Settings).Returns(settings); var newMappingFromProxy = new Mapping(NewGuid, UpdatedAt, string.Empty, string.Empty, null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, false, null, null); _mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy)); var requestBuilder = Request.Create().UsingAnyMethod(); _mappingMock.SetupGet(m => m.RequestMatcher).Returns(requestBuilder); var result = new MappingMatcherResult(_mappingMock.Object, _requestMatchResultMock.Object); _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns((result, result)); // Act await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); // Assert and Verify fileSystemHandlerMock.Verify(f => f.WriteMappingFile(It.IsAny(), It.IsAny()), Times.Once); _mappings.Count.Should().Be(1); } [Fact] public async Task WireMockMiddleware_Invoke_Mapping_Has_ProxyAndRecordSettings_And_SaveMapping_Is_False_But_WireMockServerSettings_SaveMapping_Is_True() { // Assign var request = new RequestMessage(new UrlDetails("http://localhost/foo"), "GET", "::1", null, new Dictionary()); _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); _optionsMock.SetupGet(o => o.AuthenticationMatcher).Returns(new ExactMatcher()); var fileSystemHandlerMock = new Mock(); fileSystemHandlerMock.Setup(f => f.GetMappingFolder()).Returns("m"); var logger = new Mock(); var proxyAndRecordSettings = new ProxyAndRecordSettings { SaveMapping = false, SaveMappingToFile = false }; var settings = new WireMockServerSettings { FileSystemHandler = fileSystemHandlerMock.Object, Logger = logger.Object, ProxyAndRecordSettings = new ProxyAndRecordSettings { SaveMapping = true, SaveMappingToFile = true } }; var responseBuilder = Response.Create().WithProxy(proxyAndRecordSettings); _mappingMock.SetupGet(m => m.Provider).Returns(responseBuilder); _mappingMock.SetupGet(m => m.Settings).Returns(settings); var newMappingFromProxy = new Mapping(NewGuid, UpdatedAt, "my-title", "my-description", null, settings, Request.Create(), Response.Create(), 0, null, null, null, null, null, false, null, data: null); _mappingMock.Setup(m => m.ProvideResponseAsync(It.IsAny())).ReturnsAsync((new ResponseMessage(), newMappingFromProxy)); var requestBuilder = Request.Create().UsingAnyMethod(); _mappingMock.SetupGet(m => m.RequestMatcher).Returns(requestBuilder); var result = new MappingMatcherResult (_mappingMock.Object, _requestMatchResultMock.Object); _matcherMock.Setup(m => m.FindBestMatch(It.IsAny())).Returns((result, result)); // Act await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); // Assert and Verify fileSystemHandlerMock.Verify(f => f.WriteMappingFile(It.IsAny(), It.IsAny()), Times.Once); _mappings.Should().HaveCount(1); } #if NET6_0_OR_GREATER [Fact] public async Task WireMockMiddleware_Invoke_AdminPath_WithExcludeAdminRequests_ShouldNotStartActivity() { // Arrange var request = new RequestMessage(new UrlDetails("http://localhost/__admin/health"), "GET", "::1"); _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); _optionsMock.SetupGet(o => o.ActivityTracingOptions).Returns(new WireMock.Owin.ActivityTracing.ActivityTracingOptions { ExcludeAdminRequests = true }); var activityStarted = false; using var listener = new ActivityListener { ShouldListenTo = source => source.Name == WireMockActivitySource.SourceName, Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllDataAndRecorded, ActivityStarted = _ => activityStarted = true }; ActivitySource.AddActivityListener(listener); // Act await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); // Assert activityStarted.Should().BeFalse(); } [Fact] public async Task WireMockMiddleware_Invoke_NonAdminPath_WithTracingEnabled_ShouldStartActivity() { // Arrange var request = new RequestMessage(new UrlDetails("http://localhost/api/orders"), "GET", "::1"); _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); _optionsMock.SetupGet(o => o.ActivityTracingOptions).Returns(new WireMock.Owin.ActivityTracing.ActivityTracingOptions { ExcludeAdminRequests = true }); var activityStarted = false; using var listener = new ActivityListener { ShouldListenTo = source => source.Name == WireMockActivitySource.SourceName, Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllDataAndRecorded, ActivityStarted = _ => activityStarted = true }; ActivitySource.AddActivityListener(listener); // Act await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); // Assert activityStarted.Should().BeTrue(); } [Fact] public async Task WireMockMiddleware_Invoke_NonAdminPath_WithoutTracingOptions_ShouldNotStartActivity() { // Arrange var request = new RequestMessage(new UrlDetails("http://localhost/api/orders"), "GET", "::1"); _requestMapperMock.Setup(m => m.MapAsync(It.IsAny(), It.IsAny())).ReturnsAsync(request); _optionsMock.SetupGet(o => o.ActivityTracingOptions).Returns((WireMock.Owin.ActivityTracing.ActivityTracingOptions?)null); var activityStarted = false; using var listener = new ActivityListener { ShouldListenTo = source => source.Name == WireMockActivitySource.SourceName, Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllDataAndRecorded, ActivityStarted = _ => activityStarted = true }; ActivitySource.AddActivityListener(listener); // Act await _sut.Invoke(_contextMock.Object).ConfigureAwait(false); // Assert activityStarted.Should().BeFalse(); } #endif }