JsonParserByLeo/Wf/Inc/NodeTest.mqh

952 lines
34 KiB
MQL5
Raw Permalink Normal View History

2026-07-21 09:42:30 -05:00
//+------------------------------------------------------------------+
//| NodeTest.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_NODETEST_MQH
#define JSONPARSERBYLEO_WF_INC_NODETEST_MQH
//+------------------------------------------------------------------+
//| Todos estos tests comparten UN SOLO parser ya parseado |
//| (fixture_test.json), inyectado por puntero. Ningun test |
//| vuelve a parsear -> cero costo repetido de Assing/Parse. |
//+------------------------------------------------------------------+
#include "Def.mqh"
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
namespace TSN
{
//+------------------------------------------------------------------+
//| 1. operator[] sobre clave existente top-level (string) |
//+------------------------------------------------------------------+
class CTestNode_BracketExistingKey : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_BracketExistingKey(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_Bracket_ExistingKey"; }
inline int Importance() const override final { return 95; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
string val = root["symbol"].ToString("__MISSING__");
if(val != "EURUSD")
{
msg = StringFormat("esperado 'EURUSD', obtenido '%s'", val);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 2. operator[] sobre clave inexistente -> nodo invalido |
//+------------------------------------------------------------------+
class CTestNode_BracketMissingKey : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_BracketMissingKey(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_Bracket_MissingKey"; }
inline int Importance() const override final { return 95; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
TSN::CJsonNode node = root["no_existe"];
string val = node.ToString("__DEFAULT__");
if(val != "__DEFAULT__")
{
msg = StringFormat("esperado default '__DEFAULT__', obtenido '%s'", val);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 3. Acceso encadenado profundo (5 niveles) |
//+------------------------------------------------------------------+
class CTestNode_BracketChainedDeep : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_BracketChainedDeep(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_Bracket_ChainedDeep5Levels"; }
inline int Importance() const override final { return 90; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
string val = root["deeply_nested"]["l1"]["l2"]["l3"]["l4"]["l5"].ToString("__MISSING__");
if(val != "reached_bottom")
{
msg = StringFormat("esperado 'reached_bottom', obtenido '%s'", val);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 4. At() sobre indice valido de array de strings |
//+------------------------------------------------------------------+
class CTestNode_AtValidIndex : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_AtValidIndex(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_At_ValidIndex"; }
inline int Importance() const override final { return 95; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
TSN::CJsonNode tags = root["tags"];
string first = tags.At(0).ToString("__MISSING__");
string second = tags.At(1).ToString("__MISSING__");
if(first != "fx" || second != "major")
{
msg = StringFormat("esperado 'fx'/'major', obtenido '%s'/'%s'", first, second);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 5. At() fuera de rango -> no debe crashear, InRangeSafe false |
//+------------------------------------------------------------------+
class CTestNode_AtOutOfRange : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_AtOutOfRange(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_At_OutOfRange"; }
inline int Importance() const override final { return 85; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
TSN::CJsonNode tags = root["tags"]; // 2 elementos, indices validos 0-1
const int index = 50;
if(tags.InRangeSafe(index))
{
string out = tags.At(index).ToString();
msg = StringFormat("acceso fuera de rango devolvio dato inesperado: %s", out);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 6. Array vacio -> At(0) invalido, sin crash |
//+------------------------------------------------------------------+
class CTestNode_AtOnEmptyArray : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_AtOnEmptyArray(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_At_EmptyArray"; }
inline int Importance() const override final { return 80; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
TSN::CJsonNode empty = root["empty_arr"];
if(empty.InRangeSafe(0))
{
msg = "InRangeSafe(0) en array vacio devolvio true";
return false;
}
string val = empty.At(0).ToString("__DEFAULT__");
if(val != "__DEFAULT__")
{
msg = StringFormat("At(0) en array vacio no devolvio default, obtenido '%s'", val);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 7. HasKey sobre clave existente |
//+------------------------------------------------------------------+
class CTestNode_HasKeyTrue : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_HasKeyTrue(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_HasKey_True"; }
inline int Importance() const override final { return 90; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
if(!root.HasKey("symbol"))
{
msg = "HasKey('symbol') devolvio false, esperado true";
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 8. HasKey sobre clave inexistente |
//+------------------------------------------------------------------+
class CTestNode_HasKeyFalse : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_HasKeyFalse(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_HasKey_False"; }
inline int Importance() const override final { return 90; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
if(root.HasKey("no_existe"))
{
msg = "HasKey('no_existe') devolvio true, esperado false";
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 9. HasKey sobre objeto vacio {} |
//+------------------------------------------------------------------+
class CTestNode_HasKeyEmptyObj : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_HasKeyEmptyObj(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_HasKey_EmptyObj"; }
inline int Importance() const override final { return 75; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
TSN::CJsonNode empty = root["empty_obj"];
if(empty.HasKey("cualquiera"))
{
msg = "HasKey sobre objeto vacio devolvio true";
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 10. HasKey en objeto grande -> fuerza el path de JIT perfect-hash|
//+------------------------------------------------------------------+
class CTestNode_HasKeyForcesHashing : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_HasKeyForcesHashing(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_HasKey_BigObj_ForcesHashing"; }
inline int Importance() const override final { return 85; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
TSN::CJsonNode big = root["big_obj_for_hash"];
// Accedemos varias veces para superar TSN_JSON_JIT_MIN_REF_TO_HASING
// y forzar la promocion a perfect-hash; debe seguir siendo correcto.
bool ok = true;
for(int i = 0; i < 10 && ok; i++)
ok = big.HasKey("k12");
if(!ok)
{
msg = "HasKey('k12') fallo tras forzar hashing";
return false;
}
if(big.HasKey("k99"))
{
msg = "HasKey('k99') (inexistente) devolvio true tras hashing";
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 11. operator[] en objeto grande tras hashing -> valor correcto |
//+------------------------------------------------------------------+
class CTestNode_BracketAfterHashing : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_BracketAfterHashing(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_Bracket_BigObj_AfterHashing"; }
inline int Importance() const override final { return 85; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
TSN::CJsonNode big = root["big_obj_for_hash"];
long val = 0;
for(int i = 0; i < 10; i++)
val = big["k20"].ToInt(-1);
if(val != 20)
{
msg = StringFormat("esperado 20 para 'k20', obtenido %d", val);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 12. GetKeys sobre objeto top-level |
//+------------------------------------------------------------------+
class CTestNode_GetKeysTopLevel : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_GetKeysTopLevel(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_GetKeys_TopLevel"; }
inline int Importance() const override final { return 90; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
string keys[];
int count = root.GetKeys(keys);
bool found = false;
for(int i = 0; i < count; i++)
if(keys[i] == "symbol") { found = true; break; }
if(!found)
{
msg = StringFormat("clave 'symbol' no aparece en GetKeys() (count=%d)", count);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 13. GetKeys sobre objeto vacio -> 0 |
//+------------------------------------------------------------------+
class CTestNode_GetKeysEmptyObj : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_GetKeysEmptyObj(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_GetKeys_EmptyObj"; }
inline int Importance() const override final { return 80; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
TSN::CJsonNode empty = root["empty_obj"];
string keys[];
int count = empty.GetKeys(keys);
if(count != 0)
{
msg = StringFormat("esperado 0 claves en objeto vacio, obtenido %d", count);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 14. GetKeys sobre objeto de 1 sola clave |
//+------------------------------------------------------------------+
class CTestNode_GetKeysSingleKeyObj : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_GetKeysSingleKeyObj(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_GetKeys_SingleKeyObj"; }
inline int Importance() const override final { return 70; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
TSN::CJsonNode single = root["single_key_obj"];
string keys[];
int count = single.GetKeys(keys);
if(count != 1 || keys[0] != "only")
{
msg = StringFormat("esperado 1 clave 'only', obtenido count=%d keys[0]='%s'", count, count > 0 ? keys[0] : "");
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 15. KeyToPosition sobre clave existente |
//+------------------------------------------------------------------+
class CTestNode_KeyToPositionValid : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_KeyToPositionValid(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_KeyToPosition_Valid"; }
inline int Importance() const override final { return 85; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
// "symbol" es la primera clave del fixture -> posicion 0
int pos = root.KeyToPosition("symbol");
if(pos != 0)
{
msg = StringFormat("esperado posicion 0 para 'symbol', obtenido %d", pos);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 16. KeyToPosition sobre clave inexistente -> -1 |
//+------------------------------------------------------------------+
class CTestNode_KeyToPositionInvalid : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_KeyToPositionInvalid(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_KeyToPosition_Invalid"; }
inline int Importance() const override final { return 85; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
int pos = root.KeyToPosition("no_existe");
if(pos != -1)
{
msg = StringFormat("esperado -1 para clave inexistente, obtenido %d", pos);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 17. ToInt sobre entero negativo |
//+------------------------------------------------------------------+
class CTestNode_ToIntNegative : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_ToIntNegative(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_ToInt_Negative"; }
inline int Importance() const override final { return 85; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
long val = root["neg_int"].ToInt(0);
if(val != -42)
{
msg = StringFormat("esperado -42, obtenido %d", val);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 18. ToIntSafe sobre string que parece entero -> conversion valida|
//+------------------------------------------------------------------+
class CTestNode_ToIntSafeFromString : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_ToIntSafeFromString(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_ToIntSafe_FromString"; }
inline int Importance() const override final { return 70; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
long val = 0;
bool ok = root["number_as_string"].ToIntSafe(val);
if(!ok || val != 123)
{
msg = StringFormat("esperado ok=true val=123, obtenido ok=%s val=%d", ok ? "true" : "false", val);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 19. ToDouble sobre float pequeno |
//+------------------------------------------------------------------+
class CTestNode_ToDoubleSmallFloat : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_ToDoubleSmallFloat(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_ToDouble_SmallFloat"; }
inline int Importance() const override final { return 80; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
double val = root["tiny_float"].ToDouble(-1.0);
if(MathAbs(val - 0.0001) > 0.0000001)
{
msg = StringFormat("esperado 0.0001, obtenido %.8f", val);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 20. ToDoubleSafe sobre nodo invalido -> false |
//+------------------------------------------------------------------+
class CTestNode_ToDoubleSafeOnMissing : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_ToDoubleSafeOnMissing(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_ToDoubleSafe_OnMissingNode"; }
inline int Importance() const override final { return 75; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
double val = 0.0;
bool ok = root["no_existe"].ToDoubleSafe(val);
if(ok)
{
msg = "ToDoubleSafe sobre nodo inexistente devolvio true";
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 21. ToBool sobre booleano true/false directos |
//+------------------------------------------------------------------+
class CTestNode_ToBoolDirect : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_ToBoolDirect(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_ToBool_Direct"; }
inline int Importance() const override final { return 90; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
bool active = root["active"].ToBool(false);
bool disabled = root["disabled"].ToBool(true);
if(!active || disabled)
{
msg = StringFormat("esperado active=true disabled=false, obtenido active=%s disabled=%s",
active ? "true" : "false", disabled ? "true" : "false");
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 22. IsNull sobre valor null explicito y sobre valor no-null |
//+------------------------------------------------------------------+
class CTestNode_IsNull : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_IsNull(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_IsNull"; }
inline int Importance() const override final { return 90; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
if(!root["nothing"].IsNull())
{
msg = "IsNull() sobre 'nothing' devolvio false, esperado true";
return false;
}
if(root["symbol"].IsNull())
{
msg = "IsNull() sobre 'symbol' (string) devolvio true, esperado false";
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 23. IsValid / IsInvalid sobre nodo existente vs faltante |
//+------------------------------------------------------------------+
class CTestNode_IsValidIsInvalid : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_IsValidIsInvalid(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_IsValid_IsInvalid"; }
inline int Importance() const override final { return 85; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
if(!root["symbol"].IsValid())
{
msg = "IsValid() sobre 'symbol' devolvio false";
return false;
}
if(!root["no_existe"].IsInvalid())
{
msg = "IsInvalid() sobre clave inexistente devolvio false";
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 24. ToArray<double> sobre array homogeneo de floats |
//+------------------------------------------------------------------+
class CTestNode_ToArrayFloats : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_ToArrayFloats(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_ToArray_Floats"; }
inline int Importance() const override final { return 85; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
double out[];
int count = root["float_arr"].ToArray(out);
if(count != 3 || MathAbs(out[0] - 1.1) > 0.0001 || MathAbs(out[2] - 3.3) > 0.0001)
{
msg = StringFormat("esperado count=3 [1.1,..,3.3], obtenido count=%d out[0]=%.4f out[2]=%.4f",
count, count > 0 ? out[0] : 0.0, count > 2 ? out[2] : 0.0);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 25. ToArray<int> sobre array de bools (0/1) |
//+------------------------------------------------------------------+
class CTestNode_ToArrayBools : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_ToArrayBools(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_ToArray_Bools"; }
inline int Importance() const override final { return 75; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
int out[];
int count = root["bool_arr"].ToArray(out); // [true,false,true]
if(count != 3 || out[0] != 1 || out[1] != 0 || out[2] != 1)
{
msg = StringFormat("esperado [1,0,1], obtenido count=%d", count);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 26. ExistInArrayStr sobre array de strings |
//+------------------------------------------------------------------+
class CTestNode_ExistInArrayStr : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_ExistInArrayStr(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_ExistInArrayStr"; }
inline int Importance() const override final { return 80; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
TSN::CJsonNode tags = root["tags"];
if(!tags.ExistInArrayStr("major"))
{
msg = "ExistInArrayStr('major') devolvio false, esperado true";
return false;
}
if(tags.ExistInArrayStr("no_existe"))
{
msg = "ExistInArrayStr('no_existe') devolvio true, esperado false";
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 27. ExistInArrayHomogeneo<long> sobre array de ints |
//+------------------------------------------------------------------+
class CTestNode_ExistInArrayHomogeneoInt : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_ExistInArrayHomogeneoInt(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_ExistInArrayHomogeneo_Int"; }
inline int Importance() const override final { return 75; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
TSN::CJsonNode arr = root["int_arr"]; // [1,2,3,4,5]
if(!arr.ExistInArrayHomogeneo((long)3))
{
msg = "ExistInArrayHomogeneo(3) devolvio false, esperado true";
return false;
}
if(arr.ExistInArrayHomogeneo((long)99))
{
msg = "ExistInArrayHomogeneo(99) devolvio true, esperado false";
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 28. BeginArr / iterador de array recorre todos los elementos |
//+------------------------------------------------------------------+
class CTestNode_IteratorArray : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_IteratorArray(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_Iterator_Array"; }
inline int Importance() const override final { return 85; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
TSN::CJsonNode arr = root["int_arr"]; // [1,2,3,4,5]
TSN::CJsonIteratorArray it = arr.BeginArr();
long sum = 0;
int n = 0;
while(it.IsValid())
{
sum += it.Val().ToInt(0);
n++;
it.Next();
}
if(n != 5 || sum != 15)
{
msg = StringFormat("esperado n=5 sum=15, obtenido n=%d sum=%d", n, sum);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 29. BeginObj / iterador de objeto recorre todas las claves |
//+------------------------------------------------------------------+
class CTestNode_IteratorObject : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_IteratorObject(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_Iterator_Object"; }
inline int Importance() const override final { return 85; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
TSN::CJsonNode meta = root["meta"]; // {id, nested}
TSN::CJsonIteratorObj it = meta.BeginObj();
int n = 0;
bool found_id = false;
while(it.IsValid())
{
if(it.Key() == "id")
found_id = true;
n++;
it.Next();
}
if(n != 2 || !found_id)
{
msg = StringFormat("esperado n=2 con clave 'id', obtenido n=%d found_id=%s", n, found_id ? "true" : "false");
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 30. Array de objetos: At(i)["campo"] combinado |
//+------------------------------------------------------------------+
class CTestNode_ArrayOfObjects : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_ArrayOfObjects(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_ArrayOfObjects_AtThenBracket"; }
inline int Importance() const override final { return 85; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
TSN::CJsonNode arr = root["arr_of_objs"];
string name1 = arr.At(1)["name"].ToString("__MISSING__");
long id2 = arr.At(2)["id"].ToInt(-1);
if(name1 != "b" || id2 != 3)
{
msg = StringFormat("esperado name1='b' id2=3, obtenido name1='%s' id2=%d", name1, id2);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 31. Array anidado: At(i).At(j) |
//+------------------------------------------------------------------+
class CTestNode_NestedArrayAccess : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_NestedArrayAccess(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_NestedArray_AtAt"; }
inline int Importance() const override final { return 85; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
// nested_arr = [[1,2],[3,4],[5,6]]
long val = root["nested_arr"].At(1).At(1).ToInt(-1);
if(val != 4)
{
msg = StringFormat("esperado 4, obtenido %d", val);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 32. Strings con escapes -> ToString desescapado correctamente |
//+------------------------------------------------------------------+
class CTestNode_EscapedString : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_EscapedString(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_ToString_EscapedChars"; }
inline int Importance() const override final { return 80; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
string val = root["escaped_str"].ToString("__MISSING__");
// Debe contener saltos de linea y tab reales, no las secuencias literales
if(StringFind(val, "\n") < 0 || StringFind(val, "\t") < 0)
{
msg = StringFormat("escape no resuelto correctamente, obtenido '%s'", val);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 33. String vacio "" |
//+------------------------------------------------------------------+
class CTestNode_EmptyString : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_EmptyString(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_ToString_EmptyString"; }
inline int Importance() const override final { return 65; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
string val = root["empty_str"].ToString("__DEFAULT__");
if(val != "")
{
msg = StringFormat("esperado string vacio, obtenido '%s'", val);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 34. StrLenRaw sobre string conocido |
//+------------------------------------------------------------------+
class CTestNode_StrLenRaw : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_StrLenRaw(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_StrLenRaw"; }
inline int Importance() const override final { return 60; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
// "EURUSD" = 6 caracteres, sin escapes -> raw len == 6
int len = root["symbol"].StrLenRaw();
if(len != 6)
{
msg = StringFormat("esperado 6, obtenido %d", len);
return false;
}
return true;
}
};
//+------------------------------------------------------------------+
//| 35. CompareStrWith sobre coincidencia y no-coincidencia |
//+------------------------------------------------------------------+
class CTestNode_CompareStrWith : public ICiTest
{
private:
TSN::CJsonParser* m_parser;
public:
CTestNode_CompareStrWith(TSN::CJsonParser* parser) : m_parser(parser) {}
inline string Name() const override final { return "Node_CompareStrWith"; }
inline int Importance() const override final { return 70; }
bool Run(string &msg) override final
{
TSN::CJsonNode root = m_parser.GetRoot();
TSN::CJsonNode sym = root["symbol"];
if(!sym.CompareStrWith("EURUSD"))
{
msg = "CompareStrWith('EURUSD') devolvio false, esperado true";
return false;
}
if(sym.CompareStrWith("GBPUSD"))
{
msg = "CompareStrWith('GBPUSD') devolvio true, esperado false";
return false;
}
return true;
}
};
}
//+------------------------------------------------------------------+
#endif // JSONPARSERBYLEO_WF_INC_NODETEST_MQH