DÉFAUT DE CLASSE, trouvé par l'audit demandé après la bascule de fuseau. Quatre instants étaient stockés depuis QDateTime::currentDateTime(), donc en spécification Qt::LocalTime : un tel objet ne garde que des composantes d'HORLOGE MURALE et résout son époque au moment de la CONVERSION, avec le fuseau courant. m_baselinePosee → GetEnergyRatios.measuredSince m_ecoMesureDepuis → progress.measuredSince m_divergenceDepuis → command.since m_lastTelemetryPublish → alimente le précédent Mesuré sur la box le 2026-09-06, bascule Paris→London sans redémarrage : measuredSince 1788645607 → 1788649207, +3600 s. Le champ posé pour rendre une couture lisible était lui-même déplacé par la couture. ET `.toUTC()` AU RENDU NE PROTÉGEAIT DE RIEN — c'est le piège, parce que les quatre rendus l'appelaient déjà et avaient l'air corrects. Démontré par un banc Qt avec bascule de /etc/localtime sous un objet déjà stocké : AVANT stocké-local → 1788755728 stocké-UTC → 1788755728 (Europe/Paris) APRÈS stocké-local → 1788759328 stocké-UTC → 1788755728 (Europe/London) Correctif : `.toUTC()` À LA CAPTURE. periodStartedAt reste construit dans le fuseau courant — c'est une frontière locale, elle DOIT bouger. Les deux champs n'ont pas la même nature et le stockage le reflète maintenant. TEST avec son oracle : l'ancrage tient sous re-rendu, la contre-épreuve reproduit la dérive de +3600 s, et une quatrième assertion dit ce qui compte — le même décalage est un DÉFAUT sur l'instant de pose et le COMPORTEMENT ATTENDU sur la frontière. Le nombre ne dit pas lequel on regarde ; seule la nature du champ le dit. SIXIÈME FORME D'HOMOGÉNÉITÉ (AGENTS.md) : ce n'est plus le mock, c'est L'ENVIRONNEMENT DE VÉRIFICATION. Box et téléphone en Europe/Paris pendant toute la campagne : à décalage nul, un instant rendu en UTC, en heure box ou en heure téléphone donne le MÊME TEXTE. Aucun défaut de conversion ne pouvait se voir, et rien dans le code ne pouvait le signaler — la prémisse fausse n'est pas dans le banc, elle est dans la coïncidence de deux réglages extérieurs. D'où le geste : basculer le fuseau de temps en temps est un RÉVÉLATEUR, pas seulement une manipulation de capture. Quinze minutes en Europe/London ont dénoncé deux défauts, dont un qui avait traversé toute la campagne. Suites : loadmodel 25/25 (+1), simulation 67/67, charging 48/48, spotmarket 32/32. NON DÉPLOYÉ.
90 lines
4.1 KiB
C++
90 lines
4.1 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();
|
|
|
|
// ── LM-1006-1 — le plafond de soutirage, étape 1 ───────────────────────
|
|
//! On compare des MARGES, jamais des plafonds : le plus contraignant mord, et on sait
|
|
//! lequel. Aucun plafond déclaré ≠ marge nulle. Départage stable à marge égale.
|
|
void testEcoSpreadHardensAndSaysSo();
|
|
void testEcoUnmeasurableBuysAtAConstantRate();
|
|
void testStoredInstantsSurviveATimeZoneChange();
|
|
void testReportingDayBoundaryIsPublishedNotDeducible();
|
|
void testDrawMarginComparesHeadroomsNotCaps();
|
|
};
|
|
|
|
#endif // TESTLOADMODEL_H
|