// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Testing; using Moq; using Xunit; namespace Microsoft.AspNetCore.Routing { public class ConstraintMatcherTest { private const string _name = "name"; [Fact] public void MatchUrlGeneration_DoesNotLogData() { // Arrange var sink = new TestSink(); var logger = new TestLogger(_name, sink, enabled: true); var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" }); var constraints = new Dictionary { {"a", new PassConstraint()}, {"b", new FailConstraint()} }; // Act RouteConstraintMatcher.Match( constraints: constraints, routeValues: routeValueDictionary, httpContext: new Mock().Object, route: new Mock().Object, routeDirection: RouteDirection.UrlGeneration, logger: logger); // Assert // There are no BeginScopes called. Assert.Empty(sink.Scopes); // There are no WriteCores called. Assert.Empty(sink.Writes); } [Fact] public void MatchFail_LogsCorrectData() { // Arrange & Act var constraints = new Dictionary { {"a", new PassConstraint()}, {"b", new FailConstraint()} }; var sink = SetUpMatch(constraints, loggerEnabled: true); var expectedMessage = "Route value 'value' with key 'b' did not match the constraint " + $"'{typeof(FailConstraint).FullName}'."; // Assert Assert.Empty(sink.Scopes); var write = Assert.Single(sink.Writes); Assert.Equal(expectedMessage, write.State?.ToString()); } [Fact] public void MatchSuccess_DoesNotLog() { // Arrange & Act var constraints = new Dictionary { {"a", new PassConstraint()}, {"b", new PassConstraint()} }; var sink = SetUpMatch(constraints, false); // Assert Assert.Empty(sink.Scopes); Assert.Empty(sink.Writes); } [Fact] public void ReturnsTrueOnValidConstraints() { var constraints = new Dictionary { {"a", new PassConstraint()}, {"b", new PassConstraint()} }; var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" }); Assert.True(RouteConstraintMatcher.Match( constraints: constraints, routeValues: routeValueDictionary, httpContext: new Mock().Object, route: new Mock().Object, routeDirection: RouteDirection.IncomingRequest, logger: NullLogger.Instance)); } [Fact] public void ConstraintsGetTheRightKey() { var constraints = new Dictionary { {"a", new PassConstraint("a")}, {"b", new PassConstraint("b")} }; var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" }); Assert.True(RouteConstraintMatcher.Match( constraints: constraints, routeValues: routeValueDictionary, httpContext: new Mock().Object, route: new Mock().Object, routeDirection: RouteDirection.IncomingRequest, logger: NullLogger.Instance)); } [Fact] public void ReturnsFalseOnInvalidConstraintsThatDontMatch() { var constraints = new Dictionary { {"a", new FailConstraint()}, {"b", new FailConstraint()} }; var routeValueDictionary = new RouteValueDictionary(new { c = "value", d = "value" }); Assert.False(RouteConstraintMatcher.Match( constraints: constraints, routeValues: routeValueDictionary, httpContext: new Mock().Object, route: new Mock().Object, routeDirection: RouteDirection.IncomingRequest, logger: NullLogger.Instance)); } [Fact] public void ReturnsFalseOnInvalidConstraintsThatMatch() { var constraints = new Dictionary { {"a", new FailConstraint()}, {"b", new FailConstraint()} }; var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" }); Assert.False(RouteConstraintMatcher.Match( constraints: constraints, routeValues: routeValueDictionary, httpContext: new Mock().Object, route: new Mock().Object, routeDirection: RouteDirection.IncomingRequest, logger: NullLogger.Instance)); } [Fact] public void ReturnsFalseOnValidAndInvalidConstraintsMixThatMatch() { var constraints = new Dictionary { {"a", new PassConstraint()}, {"b", new FailConstraint()} }; var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" }); Assert.False(RouteConstraintMatcher.Match( constraints: constraints, routeValues: routeValueDictionary, httpContext: new Mock().Object, route: new Mock().Object, routeDirection: RouteDirection.IncomingRequest, logger: NullLogger.Instance)); } [Fact] public void ReturnsTrueOnNullInput() { Assert.True(RouteConstraintMatcher.Match( constraints: null, routeValues: new RouteValueDictionary(), httpContext: new Mock().Object, route: new Mock().Object, routeDirection: RouteDirection.IncomingRequest, logger: NullLogger.Instance)); } private TestSink SetUpMatch(Dictionary constraints, bool loggerEnabled) { // Arrange var sink = new TestSink(); var logger = new TestLogger(_name, sink, loggerEnabled); var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" }); // Act RouteConstraintMatcher.Match( constraints: constraints, routeValues: routeValueDictionary, httpContext: new Mock().Object, route: new Mock().Object, routeDirection: RouteDirection.IncomingRequest, logger: logger); return sink; } private class PassConstraint : IRouteConstraint { private readonly string _expectedKey; public PassConstraint(string expectedKey = null) { _expectedKey = expectedKey; } public bool Match( HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) { if (_expectedKey != null) { Assert.Equal(_expectedKey, routeKey); } return true; } } private class FailConstraint : IRouteConstraint { public bool Match( HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) { return false; } } } }