/* * 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; using Tests.Rms.Risk.Mango.Language.Data; namespace Tests.Rms.Risk.Mango.Language; [TestFixture] public class ScriptToJsonTests { private static readonly JsonSerializerOptions _prettyPrint = new() { WriteIndented = true, TypeInfoResolver = new DefaultJsonTypeInfoResolver() }; [SetUp] public void Setup() { } const string Pipeline = """ FROM "CollectionName" PIPELINE { WHERE // special syntax ( cob == "2025-04-22" && Department == "FX Options" ) && Book NOT IN ("CHFX", "JPYPJPY") ADD // special syntax pv + premiumPV AS TotalPV, abs( pv + pvMove ) AS MyPnl, // function call dateToString( format: "%Y-%m-%d", date: field1 ) AS "TodayStr", // named args { "data" AS Key, "value" AS Value } AS Nested, // nested object [{ "data1" AS Key, "v1" AS Value }, { "data2" AS Key, "v2" AS Value }] AS NestedArray // nested array PROJECT _id, TotalPV / 2 AS TotalPV PROJECT EXCLUDE TotalPV, MyPnl JOIN "MarketCollection" AS "Data" ON srcCCY == CCY // source data => TO LET spot * DF AS todayRate GROUP BY ccy, tenor LET sum(pv) AS TotalPV, sum(delta) AS TotalDelta DO { "$bucket": // plain JSON for unknown stages { "groupBy": "$year_born", // Field to group by boundaries: [ 1840, 1850, 1860, 1870, 1880 ], // Boundaries for the buckets default: "Other", // Bucket ID for documents which do not fall into a bucket output: { // Output for each bucket "count": { "$sum": 1 }, "artists" : { "$push": { "name": { "$concat": [ "$first_name", " ", "$last_name"] }, "year_born": $year_born } } } } } SORT BY _id } """; [Test] public void ParseTest() { var ast = LanguageParser.ParseScriptToAST(Pipeline); Assert.Multiple( () => { Assert.That( ast, Is.Not.Null ); Assert.That( ast.Pipeline, Is.Not.Null ); Assert.That( ast.Pipeline!.Stages.Count, Is.EqualTo(8) ); var count = 0; Assert.That( ast.Pipeline!.Stages[count++], Is.InstanceOf() ); Assert.That( ast.Pipeline!.Stages[count++], Is.InstanceOf() ); Assert.That( ast.Pipeline!.Stages[count++], Is.InstanceOf() ); Assert.That( ast.Pipeline!.Stages[count++], Is.InstanceOf() ); Assert.That( ast.Pipeline!.Stages[count++], Is.InstanceOf() ); Assert.That( ast.Pipeline!.Stages[count++], Is.InstanceOf() ); Assert.That( ast.Pipeline!.Stages[count++], Is.InstanceOf() ); }); } [Test] public void ReParseTest() { var ast = LanguageParser.ParseScriptToAST(Pipeline); Assert.That( ast, Is.Not.Null ); var newPipeline = ast!.AsText(); var newAst = LanguageParser.ParseScriptToAST(newPipeline); Assert.That( newAst, Is.Not.Null ); var newPipeline2 = newAst.AsText(); Assert.That( newPipeline2, Is.EqualTo(newPipeline) ); } [Test] public void ProjectWithComplexVarNamed() { const string pipeline = """ FROM "CollectionName" PIPELINE { PROJECT _id, "$Very.Complex.Var" AS FieldName } """; const string expected = """ [ { "$project": { "_id": 1, "FieldName": "$Very.Complex.Var" } } ] """; var ast = LanguageParser.ParseScriptToAST(pipeline); Assert.That( ast, Is.Not.Null ); var json = ast!.AsJson()!.ToJsonString(_prettyPrint); var formattedPipeline = JsonNode.Parse(expected)!.ToJsonString(_prettyPrint); Assert.That( json, Is.EqualTo(formattedPipeline) ); } [Test] public void FloatNumbers() { const string pipeline = """ FROM "CollectionName" PIPELINE { WHERE (RhoImpact > 1 OR RhoImpact < -1 OR Rho2NdOrderImpact > 1.234e-8 OR Rho2NdOrderImpact < -1E-08) } """; const string expected = """ [ { "$match": { "$or": [ { "RhoImpact": { "$gt": 1 } }, { "RhoImpact": { "$lt": -1 } }, { "Rho2NdOrderImpact": { "$gt": 1.234E-08 } }, { "Rho2NdOrderImpact": { "$lt": -1E-08 } } ] } } ] """; var ast = LanguageParser.ParseScriptToAST(pipeline); Assert.That( ast, Is.Not.Null ); var json = ast!.AsJson()!.ToJsonString(_prettyPrint); var formattedPipeline = JsonNode.Parse(expected)!.ToJsonString(_prettyPrint); Assert.That( json, Is.EqualTo(formattedPipeline) ); } [Test] public void InvertedCondition() { const string pipeline = """ FROM "CollectionName" PIPELINE { WHERE 1 == Field } """; const string expected = """ [ { "$match": { "Field": 1 } } ] """; var ast = LanguageParser.ParseScriptToAST(pipeline); Assert.That( ast, Is.Not.Null ); var json = ast!.AsJson()!.ToJsonString(_prettyPrint); var formattedPipeline = JsonNode.Parse(expected)!.ToJsonString(_prettyPrint); Assert.That( json, Is.EqualTo(formattedPipeline) ); } [Test] public void ProjectWithComplexVarUnnamed() { const string pipeline = """ FROM "CollectionName" PIPELINE { PROJECT "$Very Complex Var" } """; const string expected = """ [ { "$project": { "Very Complex Var": 1 } } ] """; var ast = LanguageParser.ParseScriptToAST(pipeline); Assert.That( ast, Is.Not.Null ); var json = ast!.AsJson()!.ToJsonString(_prettyPrint); var formattedPipeline = JsonNode.Parse(expected)!.ToJsonString(_prettyPrint); Assert.That( json, Is.EqualTo(formattedPipeline) ); } [Test] public void ProjectWithSimpleVarUnnamed() { const string pipeline = """ FROM "CollectionName" PIPELINE { PROJECT SimpleVar } """; const string expected = """ [ { "$project": { "SimpleVar": 1 } } ] """; var ast = LanguageParser.ParseScriptToAST(pipeline); Assert.That( ast, Is.Not.Null ); var json = ast!.AsJson()!.ToJsonString(_prettyPrint); var formattedPipeline = JsonNode.Parse(expected)!.ToJsonString(_prettyPrint); Assert.That( json, Is.EqualTo(formattedPipeline) ); } [Test] public void ProjectWithDollarVarUnnamed() { const string pipeline = """ FROM "CollectionName" PIPELINE { PROJECT $Simple.Var } """; const string expected = """ [ { "$project": { "Simple.Var": 1 } } ] """; var ast = LanguageParser.ParseScriptToAST(pipeline); Assert.That( ast, Is.Not.Null ); var json = ast!.AsJson()!.ToJsonString(_prettyPrint); var formattedPipeline = JsonNode.Parse(expected)!.ToJsonString(_prettyPrint); Assert.That( json, Is.EqualTo(formattedPipeline) ); } [Test] public void WhereSimpleAnd() { const string pipeline = """ FROM "CollectionName" PIPELINE { WHERE Field1 == 123 AND Field2 == 456 } """; const string expected = """ [ { "$match": { "$and": [ { "Field1": 123 }, { "Field2": 456 } ] } } ] """; var ast = LanguageParser.ParseScriptToAST(pipeline); Assert.That( ast, Is.Not.Null ); var json = ast!.AsJson()!.ToJsonString(_prettyPrint); var formattedPipeline = JsonNode.Parse(expected)!.ToJsonString(_prettyPrint); Assert.That( json, Is.EqualTo(formattedPipeline) ); } [Test] public void SortWithOptions() { const string pipeline = """ FROM "CollectionName" PIPELINE { SORT BY Field1 OPTIONS { "Field1" : { "$meta" : "textScore" } } } """; const string expected = """ [ { "$sort": { "Field1": { "$meta": "textScore" } } } ] """; var ast = LanguageParser.ParseScriptToAST(pipeline); Assert.That( ast, Is.Not.Null ); var json = ast!.AsJson()!.ToJsonString(_prettyPrint); var formattedPipeline = JsonNode.Parse(expected)!.ToJsonString(_prettyPrint); Assert.That( json, Is.EqualTo(formattedPipeline) ); } [Test] public void Bucket() { const string pipeline = """ FROM "CollectionName" PIPELINE { BUCKET Field1 / 100.0 BOUNDARIES 1,10,100,1000 DEFAULT "Ignored" LET Field1 / 100.0 AS Gain } """; const string expected = """ [ { "$bucket": { "groupBy": { "$divide": [ "$Field1", 100 ] }, "boundaries": [ 1, 10, 100, 1000 ], "default": "Ignored", "output": { "Gain": { "$divide": [ "$Field1", 100 ] } } } } ] """; var ast = LanguageParser.ParseScriptToAST(pipeline); Assert.That( ast, Is.Not.Null ); var json = ast!.AsJson()!.ToJsonString(_prettyPrint); var formattedPipeline = JsonNode.Parse(expected)!.ToJsonString(_prettyPrint); Assert.That( json, Is.EqualTo(formattedPipeline) ); } [Test] public void BucketAuto() { const string pipeline = """ FROM "CollectionName" PIPELINE { BUCKET AUTO Field1 / 100.0 BUCKETS 5 GRANULARITY "POWERSOF2" LET Field1 / 100.0 AS Gain } """; const string expected = """ [ { "$bucketAuto": { "groupBy": { "$divide": [ "$Field1", 100 ] }, "buckets": 5, "granularity": "POWERSOF2", "output": { "Gain": { "$divide": [ "$Field1", 100 ] } } } } ] """; var ast = LanguageParser.ParseScriptToAST(pipeline); Assert.That( ast, Is.Not.Null ); var json = ast!.AsJson()!.ToJsonString(_prettyPrint); var formattedPipeline = JsonNode.Parse(expected)!.ToJsonString(_prettyPrint); Assert.That( json, Is.EqualTo(formattedPipeline) ); } [Test] public void Subtract() { const string pipeline = """ FROM "TestCollection" PIPELINE { PROJECT ClosingRho - OpeningRho AS RhoMove } """; const string expected = """ [{ "$project" : { "RhoMove" : { "$subtract" : ["$ClosingRho", "$OpeningRho"] } } }] """; var ast = LanguageParser.ParseScriptToAST(pipeline); Assert.That( ast, Is.Not.Null ); var json = ast!.AsJson()!.ToJsonString(_prettyPrint); var formattedPipeline = JsonNode.Parse(expected)!.ToJsonString(_prettyPrint); Assert.That( json, Is.EqualTo(formattedPipeline) ); } [Test] public void Facet() { const string pipeline = """ FROM "TestCollection" PIPELINE { FACET RhoBranch PIPELINE { PROJECT ClosingRho - OpeningRho AS RhoMove }, VegaBranch PIPELINE { PROJECT ClosingVega - OpeningVega AS VegaMove } } """; const string expected = """ [ { "$facet": { "RhoBranch": [ { "$project": { "RhoMove": { "$subtract": [ "$ClosingRho", "$OpeningRho" ] } } } ], "VegaBranch": [ { "$project": { "VegaMove": { "$subtract": [ "$ClosingVega", "$OpeningVega" ] } } } ] } } ] """; var ast = LanguageParser.ParseScriptToAST(pipeline); Assert.That( ast, Is.Not.Null ); var json = ast!.AsJson()!.ToJsonString(_prettyPrint); var formattedPipeline = JsonNode.Parse(expected)!.ToJsonString(_prettyPrint); Assert.That( json, Is.EqualTo(formattedPipeline) ); } [Test, Ignore("Equality expected to be in a different format")] public void FxVegaBreakdownByTenorTest() { var ast = LanguageParser.ParseScriptToAST(FxVegaBreakdownByTenor.Script); Assert.That( ast, Is.Not.Null ); var json = ast!.AsJson()!.ToJsonString(_prettyPrint); var formattedPipeline = JsonNode.Parse(FxVegaBreakdownByTenor.Pipeline)!.ToJsonString(_prettyPrint); Assert.That( json, Is.EqualTo(formattedPipeline) ); } [Test, Ignore("Equality expected to be in a different format")] public void RhoBreakdownByTenorTest() { var ast = LanguageParser.ParseScriptToAST(RhoBreakdownByTenor.Script); Assert.That( ast, Is.Not.Null ); var json = ast!.AsJson()!.ToJsonString(_prettyPrint); var formattedPipeline = JsonNode.Parse(RhoBreakdownByTenor.Pipeline)!.ToJsonString(_prettyPrint); Assert.That( json, Is.EqualTo(formattedPipeline) ); } }