Les trois conditions, d'un bloc. INSERTION EN QUEUE. provisionEvChargerConfig() posait rang min − 1, écrasé à 1 — donc à égalité avec la charge que l'installateur avait posée là exprès. C'est désormais 1 + max(rangs). Queue de LISTE et non de domaine : les domaines ne sont pas contigus, donc « juste après le dernier ev » exigerait de pousser une charge d'un autre domaine, ce que la condition 2 interdit. Et comme max(domaine) ≤ max(liste), la queue de liste est toujours au moins aussi reculée — elle coïncide quand le domaine occupe la fin, et sinon place la nouvelle charge derrière tout le monde plutôt que de l'intercaler devant une charge classée. Plus fidèle au principe que la formulation d'origine. UNICITÉ. validateRanks() refuse une écriture où deux charges portent le même rang. Avec la mise en garde écrite noir sur blanc : l'égalité ne coûte pas le déterminisme — le tri est total sur (rang, identifiant) depuis 3g-2 — elle coûte la gouvernabilité. S'y tromper ferait chercher le défaut dans le tri. ET LA RÈGLE GÉNÉRALE QUI EN SORT (LM-1209-a-bis) : une vérification qui ÉCARTE et une vérification qui REFUSE ne peuvent pas être la même fonction. validateSet() nomme ce qui rend l'ensemble dangereux et load() s'en sert pour écarter ; y ajouter l'unicité de rang aurait fait perdre des charges au démarrage sur une configuration héritée — disparues de la configuration ET de l'arbitrage, sans applySafeState, soit ECS-413 rouvert par une autre porte. Les deux sont séparées ; validateRanks() n'est appelée que par setConfigs(). Au chargement, un doublon hérité est signalé bruyamment et CONSERVÉ. o:rankOrigin. Énumération fermée "auto" | "user", absente par défaut. Une énumération et non un booléen parce que le troisième état est le point : `rankIsDefault: false` affirmerait que le rang a été choisi, alors que c'est ce qu'on ignore d'une configuration héritée. L'absence doit porter le « on ne sait pas » ; un booléen ne sait pas se taire. Même motif que measurement.source, et la spec le nomme désormais comme un motif de conception plutôt qu'une décision au cas par cas. Et la précision demandée en écrivant : l'effacement porte sur le RANG, jamais sur la réception d'une écriture. SetLoadConfig remplace en bloc, donc réordonner une AUTRE charge renvoie forcément l'entrée de celle-ci — l'effacer à cette occasion perdrait une marque encore vraie. L'omission ne lève rien ; pour lever sans déplacer, un client envoie "user". Un "auto" fabriqué est refusé, l'aller-retour verbatim reste licite. Trois tests. testAutoProvisionedRankGoesToTheQueue vérifié échouant avec l'insertion en tête — et il échoue en disant que la borne n'entre PAS en configuration, les deux mécanismes se répondant : le rang libre par construction et le refus d'écriture en filet. testRankOriginFollowsTheRankNotTheWrite couvre le cas qui se serait perdu, un client naïf qui omet la clé en déplaçant une autre charge. testRankUniquenessRefusesWriteButNeverDiscards couvre la moitié qui ne se voit pas : qu'un chargement conserve ce qu'il aurait pu écarter. amd64 0 erreur / 0 avertissement. simulation 31/31, charging 16/16, loadmodel 20/20, spotmarket 7/7. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015F7G5VeaPVSMeVNjiGj36p
81 lines
3.5 KiB
C++
81 lines
3.5 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
#ifndef TESTLOADMODEL_H
|
|
#define TESTLOADMODEL_H
|
|
|
|
#include <QObject>
|
|
|
|
/*!
|
|
* \brief Logique pure du modèle de charge : table de paliers et identité matérielle.
|
|
*
|
|
* Aucun serveur nymea ici. Ce qui est éprouvé ne dépend ni d'un ThingManager ni d'un
|
|
* cycle d'arbitrage : la dérivation des paliers est une combinatoire, et
|
|
* \c EnergyArbitrator::sameHardware() une comparaison.
|
|
*/
|
|
class TestLoadModel : public QObject
|
|
{
|
|
Q_OBJECT
|
|
|
|
private slots:
|
|
//! La table dérive de TOUTES les sommes de sous-ensembles, triées, 0 compris.
|
|
void testStageTableIsDerivedFromSubsetSums();
|
|
|
|
//! Puissances distinctes : l'ordre de déclaration n'a aucun effet observable.
|
|
void testRelayOrderIsInertWhenPowersAreDistinct();
|
|
|
|
//! Puissances égales : l'ordre décide QUEL contact sert le palier — et ça se voit.
|
|
void testRelayOrderDecidesWhichContactServesATiedStage();
|
|
|
|
//! Un réordonnancement sans effet ne doit PAS coûter une reconstruction.
|
|
void testReorderingDistinctRelaysKeepsTheAdapter();
|
|
|
|
//! Mais un échange de contacts à puissance égale, si.
|
|
void testSwappingTiedRelaysRebuilds();
|
|
|
|
//! Libellé, rang et besoins se mettent à jour en place : jamais de reconstruction.
|
|
void testSoftFieldsNeverRebuild();
|
|
|
|
// ── §11 — compteur et sonde par charge ─────────────────────────────────
|
|
//! Les deux champs traversent l'aller-retour, et sont OMIS quand ils sont vides.
|
|
void testMeasurementThingsRoundTrip();
|
|
|
|
//! Ils ne sont pas des canaux de commande : ils n'entrent pas dans claimedThingIds().
|
|
void testMeasurementThingsAreNotClaimed();
|
|
|
|
//! Donc deux charges peuvent partager le même compteur et la même sonde.
|
|
void testTwoLoadsMayShareAMeter();
|
|
|
|
//! Et les changer ne reconstruit rien : ils n'ont aucun effet sur le matériel.
|
|
void testMeasurementThingsAreSoftFields();
|
|
|
|
// ── §12 — le plancher de modulation ────────────────────────────────────
|
|
//! Déclaré en dynamic, et il traverse l'aller-retour.
|
|
void testFloorIsDeclaredForContinuousLoads();
|
|
|
|
//! Interdit là où il se DÉRIVE : deux sources d'une même vérité divergent.
|
|
void testFloorIsRefusedWhereItWouldBeDerived();
|
|
|
|
//! Un plancher au-dessus du plafond rendrait la charge inatteignable.
|
|
void testFloorAboveCeilingIsRefused();
|
|
|
|
// ── 3g-2 — le rang de la borne vit dans LoadConfig (LM-1206/LM-1207) ───
|
|
//! Une entrée evcharger est valide SANS charge utile, et traverse l'aller-retour.
|
|
void testEvChargerConfigIsRankOnly();
|
|
|
|
//! Toute charge utile d'un autre mécanisme y est refusée (LM-300, union discriminée).
|
|
void testEvChargerConfigRefusesPayloads();
|
|
|
|
//! Elle revendique son Thing : une borne ne peut pas être aussi une charge pilotée.
|
|
void testEvChargerClaimsItsThing();
|
|
|
|
// ── §12 / LM-1209 — l'auto-provisionnement remplit un trou, il ne décide pas ───
|
|
//! L'unicité de rang REFUSE une écriture, mais n'écarte JAMAIS au chargement : les deux
|
|
//! vérifications sont séparées parce que leurs gestes le sont (LM-1209-a).
|
|
void testRankUniquenessRefusesWriteButNeverDiscards();
|
|
|
|
//! La marque de rang s'efface sur un RANG changé, pas sur une écriture reçue — et un
|
|
//! client ne peut pas fabriquer un "auto" (LM-1209-c).
|
|
void testRankOriginFollowsTheRankNotTheWrite();
|
|
};
|
|
|
|
#endif // TESTLOADMODEL_H
|