public void Should_support_list_of_operators(string left, string actualOp, string right, bool rightIsExpr, Operator expectedOp, Type exType = null, string errorMessage = null) { var boolStr = rightIsExpr ? "true" : "false"; var json = $@" {{ ""left"": ""{left}"", ""operator"": ""{actualOp}"", ""right"": ""{right}"", ""RightSideIsExpression"": {boolStr} }}"; var expected = new LeafExpression() { Left = left, Operator = expectedOp, Right = right, RightSideIsExpression = rightIsExpr }; if (exType != null) { Runner.RunScenario( given => A_leaf_expression(json), when => I_parse_json_expression_and_it_should_throw(exType, errorMessage)); } else { Runner.RunScenario( given => A_leaf_expression(json), when => I_parse_json_expression(), then => Parsed_expression_should_be(expected)); } }
public static Func <T, string> GetExpectation <T>(this LeafExpression leafExpression) { Func <T, string> getExpectation = leafExpression.RightSideIsExpression ? BuildGetStringFunc <T>(leafExpression.Right, true, leafExpression.Operator.ToString()) : t => $"{leafExpression.Operator.ToString()} {leafExpression.Right}"; return(getExpectation); }
public void Constructor_ValidParameters_ConstructedCorrectly(string resourceType, string path) { var mockOperator = new Mock <LeafExpressionOperator>().Object; var leafExpression = new LeafExpression(resourceType, path, mockOperator); Assert.AreEqual(resourceType, leafExpression.ResourceType); Assert.AreEqual(path, leafExpression.Path); Assert.AreEqual(mockOperator, leafExpression.Operator); }
public void Evaluate_ValidScope_ReturnsResultsOfOperatorEvaluation(string resourceType, string path, bool expectedEvaluationResult, string jtoken, string absolutePathForEvaluateExpression, int timesResolveIsCalledOnOriginalObject) { // Arrange var ruleDefinition = new RuleDefinition { Name = "testRule", Description = "test rule", Recommendation = "test recommendation", HelpUri = "https://helpUri" }; // JObject to evaluate, in this test, this is a subset of an ARM template var jsonToEvaluate = JObject.Parse(jtoken); // Setting up the Mock JsonPathResolver to return the expected values when JToken and Resolve are called var mockJsonPathResolver = new Mock <IJsonPathResolver>(); // The JToken property should return the JObject to evaluate mockJsonPathResolver .Setup(s => s.JToken) .Returns(jsonToEvaluate); // Resolve for the provided json path should return the JsonPathResolver mockJsonPathResolver .Setup(s => s.Resolve(It.Is <string>(p => string.Equals(p, path)))) .Returns(() => new[] { mockJsonPathResolver.Object }); // Call a helper function to get the appropriate scope of the JObject // absolutePathForEvaluateExpression is null when resourceType is not defined // When resourceType is defined, absolutePathForEvaluateExpression represents the absolute // json path of the path parameter specified var jTokenExpectedInEvaluateExpression = GetRelevantJTokenScope(jsonToEvaluate, absolutePathForEvaluateExpression); // EvaluateExpression for the provided scope should return the expected evaluationResult var mockLeafExpressionOperator = new Mock <LeafExpressionOperator>(); mockLeafExpressionOperator .Setup(o => o.EvaluateExpression(It.Is <JToken>(token => token == jTokenExpectedInEvaluateExpression))) .Returns(expectedEvaluationResult); var leafExpression = new LeafExpression(ruleDefinition, resourceType, path, mockLeafExpressionOperator.Object); // Act var results = leafExpression.Evaluate(jsonScope: mockJsonPathResolver.Object).ToList(); // Assert // Verify the number of time Resolve, JToken, and EvaluateExpression were called mockJsonPathResolver.Verify(s => s.Resolve(It.Is <string>(p => string.Equals(p, path))), Times.Exactly(timesResolveIsCalledOnOriginalObject)); mockJsonPathResolver.Verify(s => s.JToken, Times.Once); mockLeafExpressionOperator.Verify(o => o.EvaluateExpression(It.Is <JToken>(token => token == jTokenExpectedInEvaluateExpression)), Times.Once); Assert.AreEqual(1, results.Count); Assert.AreEqual(expectedEvaluationResult, results.First().Passed); Assert.AreEqual(ruleDefinition, results.First().RuleDefinition); }
public void Accept_single_filter() { var json = $@" {{ ""left"": ""DeviceType"", ""operator"": ""Equals"", ""right"": ""Breaker"" }}"; var expected = new LeafExpression() { Left = "DeviceType", Operator = Operator.Equals, Right = "Breaker", RightSideIsExpression = false }; Runner.RunScenario( given => A_leaf_expression(json), when => I_parse_json_expression(), then => Parsed_expression_should_be(expected)); }
public static Func <T, double> GetScore <T>(this LeafExpression leafExpression) { var ctxExpression = Expression.Parameter(typeof(T), "ctx"); var targetExpression = ctxExpression.EvaluateExpression( leafExpression.Left, leafExpression.Operator != Operator.IsNull && leafExpression.Operator != Operator.NotIsNull); if (targetExpression.Type == typeof(int)) { var lambda = Expression.Lambda <Func <T, int> >(targetExpression, ctxExpression); var getValue = lambda.Compile(); var expected = (int)Convert.ChangeType(leafExpression.Right, typeof(int)); double getScore(T t) { var actual = getValue(t); switch (leafExpression.Operator) { case Operator.GreaterThan: return(actual > expected ? 1.0 : (double)actual / expected); case Operator.GreaterOrEqual: return(actual >= expected ? 1.0 : (double)actual / expected); case Operator.LessThan: return(actual < expected ? 1.0 : (double)(actual - expected) / expected); case Operator.LessOrEqual: return(actual <= expected ? 1.0 : (double)(actual - expected) / expected); default: return(actual == expected ? 1.0 : 0.0); } } return(getScore); } if (targetExpression.Type == typeof(double)) { var lambda = Expression.Lambda <Func <T, double> >(targetExpression, ctxExpression); var getValue = lambda.Compile(); var expected = (double)Convert.ChangeType(leafExpression.Right, typeof(double)); double getScore(T t) { var actual = getValue(t); switch (leafExpression.Operator) { case Operator.GreaterThan: return(actual > expected ? 1.0 : actual / expected); case Operator.GreaterOrEqual: return(actual >= expected ? 1.0 : actual / expected); case Operator.LessThan: return(actual < expected ? 1.0 : (actual - expected) / expected); case Operator.LessOrEqual: return(actual <= expected ? 1.0 : (actual - expected) / expected); default: return(Math.Abs(actual - expected) < 0.001 ? 1.0 : 0.0); } } return(getScore); } if (targetExpression.Type == typeof(decimal)) { var lambda = Expression.Lambda <Func <T, decimal> >(targetExpression, ctxExpression); var getValue = lambda.Compile(); var expected = (decimal)Convert.ChangeType(leafExpression.Right, typeof(decimal)); double getScore(T t) { var actual = getValue(t); switch (leafExpression.Operator) { case Operator.GreaterThan: return(actual > expected ? 1.0 : (double)actual / (double)expected); case Operator.GreaterOrEqual: return(actual >= expected ? 1.0 : (double)actual / (double)expected); case Operator.LessThan: return(actual < expected ? 1.0 : (double)(actual - expected) / (double)expected); case Operator.LessOrEqual: return(actual <= expected ? 1.0 : (double)(actual - expected) / (double)expected); default: return(actual == expected ? 1.0 : 0.0); } } return(getScore); } if (targetExpression.Type == typeof(string)) { var lambda = Expression.Lambda <Func <T, string> >(targetExpression, ctxExpression); var getValue = lambda.Compile(); var expectedString = leafExpression.Right; var expectedArray = leafExpression.Right.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(s => s.Trim()).ToArray(); double getScore(T t) { var actual = getValue(t); switch (leafExpression.Operator) { case Operator.StartsWith: if (expectedString is string expected1) { return(actual.StartsWith(expected1) ? 1.0 : 0.0); } return(0.0); case Operator.NotStartsWith: if (expectedString is string expected2) { return(!actual.StartsWith(expected2) ? 1.0 : 0.0); } return(0.0); case Operator.Contains: if (expectedString is string expected3) { return(actual.Contains(expected3) ? 1.0 : 0.0); } return(0.0); case Operator.NotContains: if (expectedString is string expected4) { return(!actual.Contains(expected4) ? 1.0 : 0.0); } return(0.0); case Operator.In: return(expectedArray.Contains(actual) ? 1.0 : 0.0); case Operator.NotIn: return(expectedArray.Contains(actual) ? 0.0 : 1.0); } if (expectedString is string expected) { return(actual == expected ? 1.0 : 0.0); } return(0.0); } return(getScore); } if (targetExpression.Type == typeof(bool)) { var lambda = Expression.Lambda <Func <T, bool> >(targetExpression, ctxExpression); var getValue = lambda.Compile(); var expected = (bool)Convert.ChangeType(leafExpression.Right, typeof(bool)); double getScore(T t) { var actual = getValue(t); return(actual == expected ? 1.0 : 0.0); } return(getScore); } if (targetExpression.Type == typeof(IEnumerable <string>)) { var lambda = Expression.Lambda <Func <T, IEnumerable <string> > >(targetExpression, ctxExpression); var getValue = lambda.Compile(); var expectedString = leafExpression.Right; var expectedArray = leafExpression.Right.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(s => s.Trim()).ToArray(); double getScore(T t) { var actual = getValue(t)?.ToArray(); var commonCount = actual?.Distinct().Intersect(expectedArray).Count() ?? 0; var actualCount = actual?.Count() ?? 0; var expectedCount = expectedArray.Length; switch (leafExpression.Operator) { case Operator.In: return(actualCount == 0 ? 0 : (double)commonCount / actualCount); case Operator.ContainsAll: return(expectedCount == 0 ? 0 : (double)commonCount / expectedCount); case Operator.NotIn: return(actualCount == 0 ? 0 : (double)(actualCount - commonCount) / actualCount); case Operator.Contains: return(expectedString != null && actual?.Contains(expectedString) == true ? 1.0 : 0.0); case Operator.NotContains: return(expectedString == null || actual?.Contains(expectedString) == false ? 1.0 : 0.0); } return(actual == expectedArray ? 1.0 : 0.0); } return(getScore); } throw new NotSupportedException($"expression {leafExpression.Left} is not supported"); }
private static void ShouldBeEquivalent(LeafExpression actual, LeafExpression expected) { actual.Should().BeEquivalentTo(expected); }
public void Evaluate_ValidScope_ReturnsResultsOfOperatorEvaluation(string resourceType, string path, bool expectedEvaluationResult) { // Arrange // JObject to evaluate, in this test, this is a subset of an ARM template var jsonToEvaluate = JObject.Parse("{ \"property\": \"value\" }"); var expectedPathEvaluated = "expectedPath"; // Setting up the Mock JsonPathResolvers to return the expected values when JToken and Resolve are called var mockJsonPathResolver = new Mock <IJsonPathResolver>(); var mockResourcesResolved = new Mock <IJsonPathResolver>(); // The JToken property should return the JObject to evaluate mockJsonPathResolver .Setup(s => s.JToken) .Returns(jsonToEvaluate); // Resolve for the provided json path should return a JsonPathResolver. // Both mocks need to be prepared to return a value. // We can just reuse mockJsonPathResolver in both cases. mockJsonPathResolver .Setup(s => s.Resolve(It.Is <string>(p => string.Equals(p, path)))) .Returns(new[] { mockJsonPathResolver.Object }); mockResourcesResolved .Setup(s => s.Resolve(It.Is <string>(p => string.Equals(p, path)))) .Returns(new[] { mockJsonPathResolver.Object }); // Setup the mock resolver to return a JSON path mockJsonPathResolver .Setup(s => s.Path) .Returns(expectedPathEvaluated); // ResolveResourceType for the provided resource type should return a JsonPathResolver. // Return the mock specifically for testing the call to ResolveResourceType. mockJsonPathResolver .Setup(s => s.ResolveResourceType(It.Is <string>(r => string.Equals(r, resourceType)))) .Returns(new[] { mockResourcesResolved.Object }); // EvaluateExpression for the provided scope should return the expected evaluationResult var mockLeafExpressionOperator = new Mock <LeafExpressionOperator>(); mockLeafExpressionOperator .Setup(o => o.EvaluateExpression(It.Is <JToken>(token => token == jsonToEvaluate))) .Returns(expectedEvaluationResult); var leafExpression = new LeafExpression(resourceType, path, mockLeafExpressionOperator.Object); // Act var results = leafExpression.Evaluate(jsonScope: mockJsonPathResolver.Object).ToList(); // Assert // Verify actions on resolvers. // If a resource type is passed, it should resolve for the resource type, and the path should be resolved on the mock resource type. // If no resource type is passed, it should resolve the path directly and not use the mock resource type. // ResolveResourceType should never be called on the mock returned from resolving resource types already. mockJsonPathResolver.Verify(s => s.Resolve(It.Is <string>(p => string.Equals(p, path))), Times.Exactly(resourceType == null ? 1 : 0)); mockJsonPathResolver.Verify(s => s.ResolveResourceType(It.Is <string>(r => string.Equals(r, resourceType))), Times.Exactly(resourceType == null ? 0 : 1)); mockResourcesResolved.Verify(s => s.Resolve(It.Is <string>(p => string.Equals(p, path))), Times.Exactly(resourceType == null ? 0 : 1)); mockResourcesResolved.Verify(s => s.ResolveResourceType(It.IsAny <string>()), Times.Never); // The original mock is returned from both mocks when calling Resolve for a path, so the JToken should always come from it. mockJsonPathResolver.Verify(s => s.JToken, Times.Once); mockResourcesResolved.Verify(s => s.JToken, Times.Never); mockLeafExpressionOperator.Verify(o => o.EvaluateExpression(It.Is <JToken>(token => token == jsonToEvaluate)), Times.Once); Assert.AreEqual(1, results.Count); Assert.AreEqual(expectedEvaluationResult, results.First().Passed); Assert.AreEqual(expectedPathEvaluated, results.First().JsonPath); }
public void Evaluate_NullScope_ThrowsException() { var leafExpression = new LeafExpression(null, "path", new HasValueOperator(true, false)); leafExpression.Evaluate(jsonScope: null).ToList(); }
public static Func <T, string> GetEvidence <T>(this LeafExpression leafExpression) { return(BuildGetStringFunc <T>( leafExpression.Left, leafExpression.Operator != Operator.IsNull && leafExpression.Operator != Operator.NotIsNull)); }