diff --git a/energyplugin/etm/config/loadconfigstore.cpp b/energyplugin/etm/config/loadconfigstore.cpp index aa26f23..82fbbf8 100644 --- a/energyplugin/etm/config/loadconfigstore.cpp +++ b/energyplugin/etm/config/loadconfigstore.cpp @@ -33,6 +33,14 @@ QString valeurPourMotif(const QString &valeur) : 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 //! ligne) : une valeur persistée par un client ne fabrique pas de fausse ligne de journal. 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, " "\"%2\" et \"%3\" : deux commandes contradictoires et " "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")}, {QStringLiteral("indexes"), QVariantList{j, i}}, {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 " "%3 : leur ordre de service serait stable mais NON " "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) *fault = QVariantMap{{QStringLiteral("cause"), QStringLiteral("RANK_DUPLICATE")}, {QStringLiteral("indexes"), QVariantList{j, i}}, diff --git a/energyplugin/etm/config/loadconfigstore.h b/energyplugin/etm/config/loadconfigstore.h index 2091c65..d6b3c16 100644 --- a/energyplugin/etm/config/loadconfigstore.h +++ b/energyplugin/etm/config/loadconfigstore.h @@ -83,7 +83,8 @@ public: * \c setConfigs(), donc par cette vérification. * * \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 * (\c indexes, \c ids — la paire de charges). * \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. * - * \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). * \return Faux si deux charges portent le même rang. * diff --git a/tests/auto/loadmodel/testloadmodel.cpp b/tests/auto/loadmodel/testloadmodel.cpp index bb51fa3..9b97864 100644 --- a/tests/auto/loadmodel/testloadmodel.cpp +++ b/tests/auto/loadmodel/testloadmodel.cpp @@ -957,6 +957,44 @@ void TestLoadModel::testInheritedValuesAreEscapedInTheJournal() 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() { // [LM-1209-c] La marque dit « ce rang est un défaut, pas un choix ». Ce qui la lève est diff --git a/tests/auto/loadmodel/testloadmodel.h b/tests/auto/loadmodel/testloadmodel.h index 6c21e24..17b4c5d 100644 --- a/tests/auto/loadmodel/testloadmodel.h +++ b/tests/auto/loadmodel/testloadmodel.h @@ -96,6 +96,8 @@ private slots: void testAppendConfigBoundsOnlyTheAddedEntry(); //! Relecture q7-suite : une valeur héritée citée par load() est échappée — aucune fausse ligne. 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 ─────────────────────── //! On compare des MARGES, jamais des plafonds : le plus contraignant mord, et on sait