//+------------------------------------------------------------------+ //| BuilderTest.mqh | //| Copyright 2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property strict #ifndef JSONPARSERBYLEO_WF_INC_BUILDERTEST_MQH #define JSONPARSERBYLEO_WF_INC_BUILDERTEST_MQH //+------------------------------------------------------------------+ //| Tests de CJsonBuilder (JSON valido) y CJsonBuilderStr (JSON | //| doblemente escapado, para meter un JSON como valor string). | //| No requieren parser ni fixture: cada test construye su propio | //| builder local y compara el string de salida via Build(). | //+------------------------------------------------------------------+ #include "Def.mqh" //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ namespace TSN { //====================================================================== // SECCION 1: CJsonBuilder (JSON valido, escape simple) //====================================================================== //+------------------------------------------------------------------+ //| 1. Objeto simple con un string | //+------------------------------------------------------------------+ class CTestBuilder_SimpleObjString : public ICiTest { public: inline string Name() const override final { return "Builder_SimpleObj_String"; } inline int Importance() const override final { return 95; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Obj(); b.Key("symbol").ValS("EURUSD").EndObj(); string out = b.Build(); if(out != "{\"symbol\":\"EURUSD\"}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 2. Objeto con multiples tipos (int, double, bool, null) | //+------------------------------------------------------------------+ class CTestBuilder_MultiTypeObj : public ICiTest { public: inline string Name() const override final { return "Builder_MultiTypeObj"; } inline int Importance() const override final { return 95; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Obj(); b.Key("id").Val(7); b.Key("price").Val(1.5, 2); b.Key("active").Val(true); b.Key("nothing").Null(); b.EndObj(); string out = b.Build(); if(out != "{\"id\":7,\"price\":1.50,\"active\":true,\"nothing\":null}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 3. Array simple de enteros | //+------------------------------------------------------------------+ class CTestBuilder_ArrayInts : public ICiTest { public: inline string Name() const override final { return "Builder_Array_Ints"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Arr().Val(1).Val(2).Val(3).EndArr(); string out = b.Build(); if(out != "[1,2,3]") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 4. Array vacio | //+------------------------------------------------------------------+ class CTestBuilder_EmptyArray : public ICiTest { public: inline string Name() const override final { return "Builder_EmptyArray"; } inline int Importance() const override final { return 75; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Arr().EndArr(); string out = b.Build(); if(out != "[]") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 5. Objeto vacio | //+------------------------------------------------------------------+ class CTestBuilder_EmptyObj : public ICiTest { public: inline string Name() const override final { return "Builder_EmptyObj"; } inline int Importance() const override final { return 75; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Obj().EndObj(); string out = b.Build(); if(out != "{}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 6. Objeto anidado dentro de objeto | //+------------------------------------------------------------------+ class CTestBuilder_NestedObjInObj : public ICiTest { public: inline string Name() const override final { return "Builder_NestedObjInObj"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Obj(); b.Key("meta").Obj(); b.Key("id").Val(1); b.EndObj(); b.EndObj(); string out = b.Build(); if(out != "{\"meta\":{\"id\":1}}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 7. Array de objetos | //+------------------------------------------------------------------+ class CTestBuilder_ArrayOfObjects : public ICiTest { public: inline string Name() const override final { return "Builder_ArrayOfObjects"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Arr(); b.Obj(); b.Key("id").Val(1).EndObj(); b.Obj(); b.Key("id").Val(2).EndObj(); b.EndArr(); string out = b.Build(); if(out != "[{\"id\":1},{\"id\":2}]") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 8. Array anidado (array de arrays) | //+------------------------------------------------------------------+ class CTestBuilder_NestedArrays : public ICiTest { public: inline string Name() const override final { return "Builder_NestedArrays"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Arr(); b.Arr().Val(1).Val(2).EndArr(); b.Arr().Val(3).Val(4).EndArr(); b.EndArr(); string out = b.Build(); if(out != "[[1,2],[3,4]]") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 9. ValS escapa comillas y backslash correctamente (simple) | //+------------------------------------------------------------------+ class CTestBuilder_EscapeQuotesBackslash : public ICiTest { public: inline string Name() const override final { return "Builder_ValS_EscapeQuotesBackslash"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Obj(); b.Key("k").ValS("say \"hi\"\\end").EndObj(); string out = b.Build(); if(out != "{\"k\":\"say \\\"hi\\\"\\\\end\"}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 10. ValS escapa newline y tab | //+------------------------------------------------------------------+ class CTestBuilder_EscapeNewlineTab : public ICiTest { public: inline string Name() const override final { return "Builder_ValS_EscapeNewlineTab"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Obj(); b.Key("k").ValS("line1\nline2\ttabbed").EndObj(); string out = b.Build(); if(out != "{\"k\":\"line1\\nline2\\ttabbed\"}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 11. ValS escapa unicode > 0xFF como \uXXXX | //+------------------------------------------------------------------+ class CTestBuilder_EscapeUnicode : public ICiTest { public: inline string Name() const override final { return "Builder_ValS_EscapeUnicode"; } inline int Importance() const override final { return 80; } bool Run(string &msg) override final { TSN::CJsonBuilder b; // U+2514 BOX DRAWINGS LIGHT UP AND RIGHT ("└") b.Obj(); b.Key("k").ValS("└").EndObj(); string out = b.Build(); if(out != "{\"k\":\"\\u2514\"}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 12. Val(double) respeta el numero de decimales (padding) | //+------------------------------------------------------------------+ class CTestBuilder_DoublePadding : public ICiTest { public: inline string Name() const override final { return "Builder_ValDouble_DigitsPadding"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Obj(); b.Key("v").Val(20.0, 8).EndObj(); string out = b.Build(); if(out != "{\"v\":20.00000000}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 13. Val(double) negativo | //+------------------------------------------------------------------+ class CTestBuilder_DoubleNegative : public ICiTest { public: inline string Name() const override final { return "Builder_ValDouble_Negative"; } inline int Importance() const override final { return 80; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Obj(); b.Key("v").Val(-1.5, 2).EndObj(); string out = b.Build(); if(out != "{\"v\":-1.50}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 14. Val(long) con valor grande | //+------------------------------------------------------------------+ class CTestBuilder_LongBigValue : public ICiTest { public: inline string Name() const override final { return "Builder_ValLong_BigValue"; } inline int Importance() const override final { return 75; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Obj(); b.Key("v").Val((long)9223372036854775).EndObj(); string out = b.Build(); if(out != "{\"v\":9223372036854775}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 15. Val(int) con cero y negativo | //+------------------------------------------------------------------+ class CTestBuilder_IntZeroAndNegative : public ICiTest { public: inline string Name() const override final { return "Builder_ValInt_ZeroAndNegative"; } inline int Importance() const override final { return 75; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Arr().Val(0).Val(-42).EndArr(); string out = b.Build(); if(out != "[0,-42]") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 16. KeyWV / ValSWV -> sin validacion/escape (raw passthrough) | //+------------------------------------------------------------------+ class CTestBuilder_RawNoValidation : public ICiTest { public: inline string Name() const override final { return "Builder_KeyWV_ValSWV_RawPassthrough"; } inline int Importance() const override final { return 70; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Obj(); b.KeyWV("raw_key").ValSWV("raw_value").EndObj(); string out = b.Build(); // Sin escape: se inserta literal, sin procesar caracteres especiales if(out != "{\"raw_key\":\"raw_value\"}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 17. ValidJson inserta fragmento crudo sin escapar (JSON valido) | //+------------------------------------------------------------------+ class CTestBuilder_ValidJsonFragment : public ICiTest { public: inline string Name() const override final { return "Builder_ValidJson_RawFragment"; } inline int Importance() const override final { return 80; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Obj(); b.Key("data").ValidJson("{\"nested\":true}").EndObj(); string out = b.Build(); if(out != "{\"data\":{\"nested\":true}}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 18. Clear() reinicia el builder correctamente | //+------------------------------------------------------------------+ class CTestBuilder_ClearResets : public ICiTest { public: inline string Name() const override final { return "Builder_Clear_Resets"; } inline int Importance() const override final { return 70; } bool Run(string &msg) override final { TSN::CJsonBuilder b; b.Obj(); b.Key("a").Val(1).EndObj(); b.Clear(); b.Obj(); b.Key("b").Val(2).EndObj(); string out = b.Build(); if(out != "{\"b\":2}") { msg = StringFormat("obtenido '%s' (deberia no contener residuo de 'a')", out); return false; } return true; } }; //====================================================================== // SECCION 2: CJsonBuilderStr (JSON doblemente escapado) //====================================================================== //+------------------------------------------------------------------+ //| 19. Objeto simple con doble escape de comillas | //+------------------------------------------------------------------+ class CTestBuilderStr_SimpleObjDoubleEscaped : public ICiTest { public: inline string Name() const override final { return "BuilderStr_SimpleObj_DoubleEscaped"; } inline int Importance() const override final { return 95; } bool Run(string &msg) override final { TSN::CJsonBuilderStr b; b.Obj(); b.Key("symbol").ValS("EURUSD").EndObj(); string out = b.Build(); // Cada '"' estructural se emite como \" (escapado una vez extra respecto al builder simple) if(out != "{\\\"symbol\\\":\\\"EURUSD\\\"}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 20. ValS escapa backslash+comilla con triple barra | //+------------------------------------------------------------------+ class CTestBuilderStr_EscapeQuoteTriple : public ICiTest { public: inline string Name() const override final { return "BuilderStr_ValS_EscapeQuoteTriple"; } inline int Importance() const override final { return 90; } bool Run(string &msg) override final { TSN::CJsonBuilderStr b; b.Obj(); b.Key("k").ValS("a\"b").EndObj(); string out = b.Build(); // La comilla dentro del valor -> \\\" (triple barra + comilla) if(out != "{\\\"k\\\":\\\"a\\\\\\\"b\\\"}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 21. ValS escapa newline con doble barra (\\n) | //+------------------------------------------------------------------+ class CTestBuilderStr_EscapeNewlineDouble : public ICiTest { public: inline string Name() const override final { return "BuilderStr_ValS_EscapeNewlineDouble"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonBuilderStr b; b.Obj(); b.Key("k").ValS("a\nb").EndObj(); string out = b.Build(); if(out != "{\\\"k\\\":\\\"a\\\\nb\\\"}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 22. ValS escapa unicode > 0xFF con doble barra (\\uXXXX) | //+------------------------------------------------------------------+ class CTestBuilderStr_EscapeUnicodeDouble : public ICiTest { public: inline string Name() const override final { return "BuilderStr_ValS_EscapeUnicodeDouble"; } inline int Importance() const override final { return 80; } bool Run(string &msg) override final { TSN::CJsonBuilderStr b; b.Obj(); b.Key("k").ValS("└").EndObj(); string out = b.Build(); if(out != "{\\\"k\\\":\\\"\\\\u2514\\\"}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 23. Objeto anidado con CJsonBuilderStr | //+------------------------------------------------------------------+ class CTestBuilderStr_NestedObj : public ICiTest { public: inline string Name() const override final { return "BuilderStr_NestedObj"; } inline int Importance() const override final { return 85; } bool Run(string &msg) override final { TSN::CJsonBuilderStr b; b.Obj(); b.Key("meta").Obj(); b.Key("id").Val(1); b.EndObj(); b.EndObj(); string out = b.Build(); if(out != "{\\\"meta\\\":{\\\"id\\\":1}}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 24. Array con CJsonBuilderStr y valores mixtos | //+------------------------------------------------------------------+ class CTestBuilderStr_ArrayMixedValues : public ICiTest { public: inline string Name() const override final { return "BuilderStr_Array_MixedValues"; } inline int Importance() const override final { return 80; } bool Run(string &msg) override final { TSN::CJsonBuilderStr b; b.Arr(); b.Val(1).Val(true).Null(); b.ValS("x").EndArr(); string out = b.Build(); if(out != "[1,true,null,\\\"x\\\"]") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 25. ValSWV en CJsonBuilderStr -> sigue envolviendo en \" sin | //| escapar el contenido interno | //+------------------------------------------------------------------+ class CTestBuilderStr_ValSWVRaw : public ICiTest { public: inline string Name() const override final { return "BuilderStr_ValSWV_RawPassthrough"; } inline int Importance() const override final { return 65; } bool Run(string &msg) override final { TSN::CJsonBuilderStr b; b.Obj(); b.Key("k").ValSWV("raw").EndObj(); string out = b.Build(); if(out != "{\\\"k\\\":\\\"raw\\\"}") { msg = StringFormat("obtenido '%s'", out); return false; } return true; } }; //+------------------------------------------------------------------+ //| 26. Comparativa cruzada: mismo contenido, distinto nivel de | //| escape entre CJsonBuilder y CJsonBuilderStr | //+------------------------------------------------------------------+ class CTestBuilder_CrossCompareEscapeLevels : public ICiTest { public: inline string Name() const override final { return "Builder_CrossCompare_EscapeLevels"; } inline int Importance() const override final { return 75; } bool Run(string &msg) override final { TSN::CJsonBuilder simple; TSN::CJsonBuilderStr doubled; simple.Obj(); simple.Key("k").ValS("v").EndObj(); doubled.Obj(); doubled.Key("k").ValS("v").EndObj(); string out_simple = simple.Build(); string out_doubled = doubled.Build(); if(out_simple != "{\"k\":\"v\"}") { msg = StringFormat("CJsonBuilder produjo '%s', esperado JSON valido simple", out_simple); return false; } if(out_doubled != "{\\\"k\\\":\\\"v\\\"}") { msg = StringFormat("CJsonBuilderStr produjo '%s', esperado doble escape", out_doubled); return false; } if(out_simple == out_doubled) { msg = "ambos builders produjeron el mismo string, se esperaba escape distinto"; return false; } return true; } }; } //+------------------------------------------------------------------+ #endif // JSONPARSERBYLEO_WF_INC_BUILDERTEST_MQH