LoadConfigStore : un libellé hérité n'est cité que sur 64 caractères dans les refus d'ensemble (m-1)
appendConfig() ne borne que l'entrée ajoutée, mais validateSet() (THING_SHARED) et validateRanks() (RANK_DUPLICATE) citaient en entier les libellés des deux entrées en conflit — dont une héritée, sans borne. provisionEvChargerConfig() journalise cette phrase. Les libellés y passent par libellePourMotif() : 64 caractères au plus (leftChars, jamais au milieu d'une paire), suivis de « … ». Le motif publié (faultParams) ne change pas. Test : testInheritedLabelIsCutInSetRefusals — héritée de 300 caractères ; l'ajout d'une borne refusé sur THING_SHARED puis sur RANK_DUPLICATE : motif exact inchangé, phrase avec les 64 premiers caractères et sans le libellé entier. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
58380dd7db
commit
76b348bf8b
@ -33,6 +33,14 @@ QString valeurPourMotif(const QString &valeur)
|
|||||||
: LoadConfigStore::leftChars(valeur, 16) + QStringLiteral("…");
|
: LoadConfigStore::leftChars(valeur, 16) + QStringLiteral("…");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Un libellé cité dans un motif : ses 64 premiers caractères. Celui d'une entrée héritée n'est
|
||||||
|
//! pas borné, et l'ajout automatique d'une borne journalise ce motif (relecture q7-suite, m-1).
|
||||||
|
QString libellePourMotif(const QString &libelle)
|
||||||
|
{
|
||||||
|
return libelle.toUcs4().size() <= 64 ? libelle
|
||||||
|
: LoadConfigStore::leftChars(libelle, 64) + QStringLiteral("…");
|
||||||
|
}
|
||||||
|
|
||||||
//! Une valeur citée au journal, entre guillemets et ÉCHAPPÉE par Qt (\c "\n" pour un saut de
|
//! Une valeur citée au journal, entre guillemets et ÉCHAPPÉE par Qt (\c "\n" pour un saut de
|
||||||
//! ligne) : une valeur persistée par un client ne fabrique pas de fausse ligne de journal.
|
//! ligne) : une valeur persistée par un client ne fabrique pas de fausse ligne de journal.
|
||||||
QString citee(const QString &valeur)
|
QString citee(const QString &valeur)
|
||||||
@ -203,7 +211,7 @@ bool LoadConfigStore::validateSet(const LoadConfigs &configs, QString *error, QV
|
|||||||
return fail(QStringLiteral("le Thing %1 est commandé par deux charges actives, "
|
return fail(QStringLiteral("le Thing %1 est commandé par deux charges actives, "
|
||||||
"\"%2\" et \"%3\" : deux commandes contradictoires et "
|
"\"%2\" et \"%3\" : deux commandes contradictoires et "
|
||||||
"une puissance comptée deux fois")
|
"une puissance comptée deux fois")
|
||||||
.arg(thing, configs.at(j).label(), c.label()),
|
.arg(thing, libellePourMotif(configs.at(j).label()), libellePourMotif(c.label())),
|
||||||
QVariantMap{{QStringLiteral("cause"), QStringLiteral("THING_SHARED")},
|
QVariantMap{{QStringLiteral("cause"), QStringLiteral("THING_SHARED")},
|
||||||
{QStringLiteral("indexes"), QVariantList{j, i}},
|
{QStringLiteral("indexes"), QVariantList{j, i}},
|
||||||
{QStringLiteral("ids"), QVariantList{configs.at(j).id(), c.id()}}});
|
{QStringLiteral("ids"), QVariantList{configs.at(j).id(), c.id()}}});
|
||||||
@ -225,7 +233,8 @@ bool LoadConfigStore::validateRanks(const LoadConfigs &configs, QString *error,
|
|||||||
*error = QStringLiteral("les charges \"%1\" et \"%2\" portent toutes deux le rang "
|
*error = QStringLiteral("les charges \"%1\" et \"%2\" portent toutes deux le rang "
|
||||||
"%3 : leur ordre de service serait stable mais NON "
|
"%3 : leur ordre de service serait stable mais NON "
|
||||||
"RÉGLABLE — il se déciderait sur leurs identifiants")
|
"RÉGLABLE — il se déciderait sur leurs identifiants")
|
||||||
.arg(configs.at(j).label(), c.label()).arg(c.priority());
|
.arg(libellePourMotif(configs.at(j).label()), libellePourMotif(c.label()))
|
||||||
|
.arg(c.priority());
|
||||||
if (fault)
|
if (fault)
|
||||||
*fault = QVariantMap{{QStringLiteral("cause"), QStringLiteral("RANK_DUPLICATE")},
|
*fault = QVariantMap{{QStringLiteral("cause"), QStringLiteral("RANK_DUPLICATE")},
|
||||||
{QStringLiteral("indexes"), QVariantList{j, i}},
|
{QStringLiteral("indexes"), QVariantList{j, i}},
|
||||||
|
|||||||
@ -83,7 +83,8 @@ public:
|
|||||||
* \c setConfigs(), donc par cette vérification.
|
* \c setConfigs(), donc par cette vérification.
|
||||||
*
|
*
|
||||||
* \param configs Ensemble complet à confronter (les charges désactivées sont ignorées).
|
* \param configs Ensemble complet à confronter (les charges désactivées sont ignorées).
|
||||||
* \param[out] error Motif de refus, en français, exploitable tel quel.
|
* \param[out] error Motif de refus, en français, exploitable tel quel ; un libellé n'y est
|
||||||
|
* cité que sur ses 64 premiers caractères (celui d'une entrée héritée n'est pas borné).
|
||||||
* \param[out] fault Si refus : \c ID_DUPLICATE (\c index, \c id) ou \c THING_SHARED
|
* \param[out] fault Si refus : \c ID_DUPLICATE (\c index, \c id) ou \c THING_SHARED
|
||||||
* (\c indexes, \c ids — la paire de charges).
|
* (\c indexes, \c ids — la paire de charges).
|
||||||
* \return true si l'ensemble est cohérent ; false au PREMIER conflit rencontré.
|
* \return true si l'ensemble est cohérent ; false au PREMIER conflit rencontré.
|
||||||
@ -94,7 +95,8 @@ public:
|
|||||||
/*!
|
/*!
|
||||||
* \brief Contrôle de BONNE FORME, appliqué aux ÉCRITURES et à elles seules. §12 / LM-1209-a.
|
* \brief Contrôle de BONNE FORME, appliqué aux ÉCRITURES et à elles seules. §12 / LM-1209-a.
|
||||||
*
|
*
|
||||||
* \param configs Ensemble candidat. \param error Motif du refus, si refus.
|
* \param configs Ensemble candidat. \param error Motif du refus, si refus ; un libellé n'y
|
||||||
|
* est cité que sur ses 64 premiers caractères.
|
||||||
* \param[out] fault Si refus : \c RANK_DUPLICATE (\c indexes, \c ids, \c rank).
|
* \param[out] fault Si refus : \c RANK_DUPLICATE (\c indexes, \c ids, \c rank).
|
||||||
* \return Faux si deux charges portent le même rang.
|
* \return Faux si deux charges portent le même rang.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -957,6 +957,44 @@ void TestLoadModel::testInheritedValuesAreEscapedInTheJournal()
|
|||||||
QVERIFY2(journal.faussesLignes().isEmpty(), qUtf8Printable(journal.faussesLignes().join(QStringLiteral(" | "))));
|
QVERIFY2(journal.faussesLignes().isEmpty(), qUtf8Printable(journal.faussesLignes().join(QStringLiteral(" | "))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TestLoadModel::testInheritedLabelIsCutInSetRefusals()
|
||||||
|
{
|
||||||
|
// [m-1] L'ajout d'une borne ne borne que l'entrée ajoutée ; ses refus d'ENSEMBLE citaient
|
||||||
|
// pourtant le libellé hérité en entier, et provisionEvChargerConfig() le journalise.
|
||||||
|
const QString cfgPath = QDir::tempPath() + "/etm-loadcfg-libelle-motif.json";
|
||||||
|
QFile::remove(cfgPath);
|
||||||
|
qputenv("NYMEA_ENERGY_LOAD_CONFIG", cfgPath.toUtf8());
|
||||||
|
const auto nettoyer = qScopeGuard([&] { QFile::remove(cfgPath); });
|
||||||
|
|
||||||
|
// Héritée : rang 2, libellé de 300 caractères, et elle commande le Thing de la borne.
|
||||||
|
const QString tropLong = QString(299, QLatin1Char('L')) + QLatin1Char('Z');
|
||||||
|
QVariantMap herite = ballonMap();
|
||||||
|
herite.insert("label", tropLong);
|
||||||
|
herite.insert("priority", 2);
|
||||||
|
herite.insert("relays", QVariantList{QVariantMap{{"thingId", BORNE}, {"powerW", 1000}}});
|
||||||
|
ecrireFichier(cfgPath, {herite});
|
||||||
|
QTest::ignoreMessage(QtWarningMsg, QRegularExpression(QStringLiteral("LIBELLÉ TROP LONG")));
|
||||||
|
LoadConfigStore store;
|
||||||
|
QCOMPARE(store.configs().count(), 1);
|
||||||
|
const QString cite = LoadConfigStore::leftChars(tropLong, 64) + QStringLiteral("…");
|
||||||
|
QString err;
|
||||||
|
QVariantMap motif;
|
||||||
|
|
||||||
|
// THING_SHARED : le motif publié est inchangé, la phrase ne cite que 64 caractères.
|
||||||
|
QVERIFY(!store.appendConfig(LoadConfig::fromMap(borneMap(BORNE, 3)), &err, &motif));
|
||||||
|
QCOMPARE(json(motif), json(QVariantMap{{"cause", "THING_SHARED"}, {"indexes", QVariantList{0, 1}},
|
||||||
|
{"ids", QVariantList{"ballon-a", BORNE}}}));
|
||||||
|
QVERIFY2(err.contains(cite) && !err.contains(tropLong), qUtf8Printable(err));
|
||||||
|
|
||||||
|
// RANK_DUPLICATE : idem.
|
||||||
|
const QString autreBorne = QStringLiteral("{dddddddd-0000-0000-0000-0000000000f2}");
|
||||||
|
QVERIFY(!store.appendConfig(LoadConfig::fromMap(borneMap(autreBorne, 2)), &err, &motif));
|
||||||
|
QCOMPARE(json(motif), json(QVariantMap{{"cause", "RANK_DUPLICATE"}, {"indexes", QVariantList{0, 1}},
|
||||||
|
{"ids", QVariantList{"ballon-a", autreBorne}}, {"rank", 2}}));
|
||||||
|
QVERIFY2(err.contains(cite) && !err.contains(tropLong), qUtf8Printable(err));
|
||||||
|
QCOMPARE(store.configs().count(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
void TestLoadModel::testRankOriginFollowsTheRankNotTheWrite()
|
void TestLoadModel::testRankOriginFollowsTheRankNotTheWrite()
|
||||||
{
|
{
|
||||||
// [LM-1209-c] La marque dit « ce rang est un défaut, pas un choix ». Ce qui la lève est
|
// [LM-1209-c] La marque dit « ce rang est un défaut, pas un choix ». Ce qui la lève est
|
||||||
|
|||||||
@ -96,6 +96,8 @@ private slots:
|
|||||||
void testAppendConfigBoundsOnlyTheAddedEntry();
|
void testAppendConfigBoundsOnlyTheAddedEntry();
|
||||||
//! Relecture q7-suite : une valeur héritée citée par load() est échappée — aucune fausse ligne.
|
//! Relecture q7-suite : une valeur héritée citée par load() est échappée — aucune fausse ligne.
|
||||||
void testInheritedValuesAreEscapedInTheJournal();
|
void testInheritedValuesAreEscapedInTheJournal();
|
||||||
|
//! Relecture q7-suite (m-1) : un refus d'ensemble ne cite que 64 caractères d'un libellé hérité.
|
||||||
|
void testInheritedLabelIsCutInSetRefusals();
|
||||||
|
|
||||||
// ── LM-1006-1 — le plafond de soutirage, étape 1 ───────────────────────
|
// ── LM-1006-1 — le plafond de soutirage, étape 1 ───────────────────────
|
||||||
//! On compare des MARGES, jamais des plafonds : le plus contraignant mord, et on sait
|
//! On compare des MARGES, jamais des plafonds : le plus contraignant mord, et on sait
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user