952 lines
34 KiB
MQL5
952 lines
34 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| 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
|