/* * dbMango * * Copyright 2025 Deutsche Bank AG * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System.Text.Json.Serialization.Metadata; namespace Tests.Rms.Risk.Mango.Language; [TestFixture] public class AstToJsonTests { private static readonly JsonSerializerOptions _prettyPrint = new() { WriteIndented = true, TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; [Test] public void AddFields() { var ast = new AstAggregation("CollectionName"); ast.Add(new AstPipeline([ new AstStageAddFields( [ new AstLetExpression( new AstExpressionVariable("field1"), "FieldName"), new AstLetExpression( new AstExpressionVariable("field2")), ] ) ])); const string expected = """ [ { "$addFields": { "FieldName": "$field1", "field2": "$field2" } } ] """; var result = ast.AsJson()?.ToJsonString(_prettyPrint); Assert.That( result, Is.EqualTo(expected)); } [Test] public void AddFieldsFunctionCall() { var ast = new AstAggregation("CollectionName"); var expr = new AstExpressionFunctionCall( "concat", [ new AstFunctionArgument(null, new AstExpressionString("many")), new AstFunctionArgument(null, new AstExpressionString(" miles away")) ] ); ast.Add(new AstPipeline([ new AstStageAddFields( [ new AstLetExpression(expr, "Moon") ] ) ])); const string expected = """ [ { "$addFields": { "Moon": { "$concat": [ "many", " miles away" ] } } } ] """; var json = ast.AsJson(); var result = json?.ToJsonString(_prettyPrint); Assert.That( result, Is.EqualTo(expected)); } [Test] public void AddFieldsOperation() { var ast = new AstAggregation("CollectionName"); var expr = new AstExpressionOperation( AstExpressionOperation.OperationType.PLUS, new AstExpressionVariable("someField"), new AstExpressionNumber(123) ); ast.Add(new AstPipeline([ new AstStageAddFields( [ new AstLetExpression(expr, "Moon") ] ) ])); const string expected = """ [ { "$addFields": { "Moon": { "$add": [ "$someField", 123 ] } } } ] """; var json = ast.AsJson(); var result = json?.ToJsonString(_prettyPrint); Assert.That( result, Is.EqualTo(expected)); } [Test] public void AddFieldsComplexExpression() { var ast = new AstAggregation("CollectionName"); var expr = new AstExpressionFunctionCall( "concat", [ new( null, new AstExpressionFunctionCall("str", [ new( null, new AstExpressionOperation( AstExpressionOperation.OperationType.MINUS, new AstExpressionOperation( AstExpressionOperation.OperationType.PLUS, new AstExpressionOperation( AstExpressionOperation.OperationType.DIVIDE, new AstExpressionVariable("field1"), new AstExpressionNumber(1.234) ), new AstExpressionNumber(5) ), new AstExpressionFunctionCall("abs", [ new(null,new AstExpressionNumber(-4)) ] ) )) ])), new( null, new AstExpressionString(" miles away")) ] ); ast.Add(new AstPipeline([ new AstStageAddFields( [ new AstLetExpression(expr, "Moon") ] ) ])); const string expected = """ [ { "$addFields": { "Moon": { "$concat": [ { "$str": { "$subtract": [ { "$add": [ { "$divide": [ "$field1", 1.234 ] }, 5 ] }, { "$abs": -4 } ] } }, " miles away" ] } } } ] """; var json = ast.AsJson()?.ToJsonString(_prettyPrint); var formattedPipeline = JsonNode.Parse(expected)!.ToJsonString(_prettyPrint); Assert.That( json, Is.EqualTo(formattedPipeline) ); } [Test] public void AddFieldsFuncNamedArgs() { var ast = new AstAggregation("CollectionName"); var expr = new AstExpressionFunctionCall( "dateToString", [ new("format", new AstExpressionString("%Y-%m-%d")), new("date", new AstExpressionVariable("field1")) ] ); ast.Add(new AstPipeline([ new AstStageAddFields( [ new AstLetExpression(expr, "Moon") ] ) ])); const string expected = """ [ { "$addFields": { "Moon": { "$dateToString": { "format": "%Y-%m-%d", "date": "$field1" } } } } ] """; var json = ast.AsJson(); var result = json?.ToJsonString(_prettyPrint); Assert.That( result, Is.EqualTo(expected)); } [Test] public void Project() { var ast = new AstAggregation("CollectionName"); ast.Add(new AstPipeline([ new AstStageProject( [], [ new AstLetExpression( new AstExpressionVariable("_id")), new AstLetExpression( new AstExpressionVariable("field1"), "FieldName") ] ) ])); const string expected = """ [ { "$project": { "_id": 1, "FieldName": "$field1" } } ] """; var json = ast.AsJson(); var result = json?.ToJsonString(_prettyPrint); Assert.That( result, Is.EqualTo(expected)); } [Test] public void ProjectWithComplexVarNamed() { var ast = new AstAggregation("CollectionName"); ast.Add(new AstPipeline([ new AstStageProject( [], [ new AstLetExpression( new AstExpressionVariable("_id")), new AstLetExpression( new AstExpressionVariable("Very.Complex.Var"), "FieldName") ] ) ])); const string expected = """ [ { "$project": { "_id": 1, "FieldName": "$Very.Complex.Var" } } ] """; var json = ast.AsJson(); var result = json?.ToJsonString(_prettyPrint); Assert.That( result, Is.EqualTo(expected)); } [Test] public void ProjectWithComplexVarUnnamed() { var ast = new AstAggregation("CollectionName"); ast.Add(new AstPipeline([ new AstStageProject( [], [ new AstLetExpression( new AstExpressionVariable("_id")), new AstLetExpression( new AstExpressionVariable("Very.Complex.Var")) ] ) ])); const string expected = """ [ { "$project": { "_id": 1, "Very.Complex.Var": 1 } } ] """; var json = ast.AsJson(); var result = json?.ToJsonString(_prettyPrint); Assert.That( result, Is.EqualTo(expected)); } [Test] public void ProjectExclude() { var ast = new AstAggregation("CollectionName"); ast.Add(new AstPipeline([ new AstStageProject( [], [ new AstLetExpression( new AstExpressionVariable("field1")) ] , true ) ])); const string expected = """ [ { "$project": { "field1": 0 } } ] """; var json = ast.AsJson(); var result = json?.ToJsonString(_prettyPrint); Assert.That( result, Is.EqualTo(expected)); } [Test] public void WhereTest() { var ast = new AstAggregation("CollectionName"); var expr = new AstExpressionOperation( AstExpressionOperation.OperationType.AND, new AstExpressionOperation( AstExpressionOperation.OperationType.NEQ, new AstExpressionVariable("field1"), new AstExpressionString("Mars") ), new AstExpressionOperation( AstExpressionOperation.OperationType.EQ, new AstExpressionVariable("field1"), new AstExpressionString("Moon") ) ); var whereStage = new AstStageWhere(); whereStage.Add(expr); ast.Add(new AstPipeline([ whereStage ])); const string expected = """ [ { "$match": { "$and": [ { "field1": { "$ne": "Mars" } }, { "field1": "Moon" } ] } } ] """; var json = ast.AsJson(); var result = json?.ToJsonString(_prettyPrint); Assert.That( result, Is.EqualTo(expected)); } [Test] public void GroupByTest() { var ast = new AstAggregation("CollectionName"); var expr = new AstExpressionOperation( AstExpressionOperation.OperationType.AND, new AstExpressionOperation( AstExpressionOperation.OperationType.NEQ, new AstExpressionVariable("field1"), new AstExpressionString("Mars") ), new AstExpressionOperation( AstExpressionOperation.OperationType.EQ, new AstExpressionVariable("field1"), new AstExpressionString("Moon") ) ); var groupByStage = new AstStageGroupBy( [ new AstLetExpression(new AstExpressionVariable("idxField1")), new AstLetExpression(new AstExpressionVariable("idxField2")) ], [ new AstLetExpression(expr, "IsMoon") ] ); groupByStage.Add(expr); ast.Add(new AstPipeline([ groupByStage ])); const string expected = """ [ { "$group": { "_id": { "idxField1": "$idxField1", "idxField2": "$idxField2" }, "IsMoon": { "$and": [ { "field1": { "$ne": "Mars" } }, { "field1": "Moon" } ] } } } ] """; var json = ast.AsJson(); var result = json?.ToJsonString(_prettyPrint); Assert.That( result, Is.EqualTo(expected)); } [Test] public void GroupByWithAddToSet() { const string source = """ [{ "$group": { "_id": { "CurvePrefix": "$CurvePrefix", "CcyPair": "$CcyPair", "Tenor": "$Tenor" }, "Order": { "$max": "$Order" }, "Items": { "$addToSet": { "Name": { "$concat": [ "$Delta", " ", "$Type" ] }, "Value": "$Value" } } } }] """; var ast = LanguageParser.ParseAggregationJsonToAST("", source); var json = ast.AsJson(); var result = json?.ToJsonString(_prettyPrint); var expected = JsonNode.Parse(source)?.ToJsonString(_prettyPrint); Assert.That( result, Is.EqualTo(expected)); } [Test] public void GroupBySingleField() { var ast = new AstAggregation("CollectionName"); var groupByStage = new AstStageGroupBy( [ new AstLetExpression(new AstExpressionVariable("idxField1")), ], [ ] ); ast.Add(new AstPipeline([ groupByStage ])); const string expected = """ [ { "$group": { "_id": "$idxField1" } } ] """; var json = ast.AsJson(); var result = json?.ToJsonString(_prettyPrint); Assert.That( result, Is.EqualTo(expected)); } [Test] public void GroupByExpression() { var ast = new AstAggregation("CollectionName"); var groupByStage = new AstStageGroupBy( [ new AstLetExpression(new AstExpressionNull()), ], [ ] ); ast.Add(new AstPipeline([ groupByStage ])); const string expected = """ [ { "$group": { "_id": null } } ] """; var json = ast.AsJson(); var result = json?.ToJsonString(_prettyPrint); Assert.That( result, Is.EqualTo(expected)); } [Test] public void JoinTest() { var ast = new AstAggregation("CollectionName"); var expr1 = new AstExpressionOperation( AstExpressionOperation.OperationType.LTE, new AstExpressionVariable("distance"), new AstExpressionNumber(384400.123) ); var expr2 = new AstExpressionOperation( AstExpressionOperation.OperationType.NEQ, new AstExpressionVariable("planet"), new AstExpressionString("Mars") ); var joinStage = new AstStageJoin( "OtherCollection", "Data", [ new(new("myField1"), new("otherField1")), new(new("myField2"), new("otherField2")) ], [new AstLetExpression(expr1, "IsMoon")], new( [ new AstStageWhere(expr2) ] ) ); ast.Add(new AstPipeline([ joinStage ])); const string expected = """ [ { "$lookup": { "from": "OtherCollection", "localField": "myField1", "foreignField": "otherField1", "as": "Data", "let": { "IsMoon": { "distance": { "$lte": 384400.123 } } }, "pipeline": [ { "$match": { "planet": { "$ne": "Mars" } } } ] } } ] """; var json = ast.AsJson(); var result = json?.ToJsonString(_prettyPrint); Assert.That( result, Is.EqualTo(expected)); } [Test] public void DoTest() { var ast = new AstAggregation("CollectionName"); var doStage = new AstStageDo(); doStage.Json = JsonNode.Parse( """ { "$bucket": { "groupBy": "$year_born" } } """ ); ast.Add(new AstPipeline([ doStage ])); const string expected = """ [ { "$bucket": { "groupBy": "$year_born" } } ] """; var json = ast.AsJson(); var result = json?.ToJsonString(_prettyPrint); Assert.That( result, Is.EqualTo(expected)); } }