feat(spot): le fournisseur aWATTar sort du build livré (brief 3, T0)
Le fournisseur spot aWATTar était le seul flux sortant du moteur (invariant 10). Il n'est plus compilé que sous `qmake CONFIG+=etm_spotmarket` ; la source reste dans l'arbre pour la synchronisation amont. Sans l'option (build livré) : - `spotmarketdataproviderawattar.*` hors de SOURCES/HEADERS, enregistrement sous `#ifdef ETM_SPOTMARKET` ; - aucun `QNetworkAccessManager` : le plugin et l'expérience de simulation passent par le même point, `SpotMarketBuild::creer()` ; - `SetSpotMarketConfiguration` refuse l'activation avec `EnergyErrorInvalidParameter` + `fault: SPOT_MARKET_NOT_IN_BUILD` (code, pas de phrase). Désactiver reste accepté ; - `planSpotMarketCharging()` est inerte : `enabled()` ne peut plus devenir vrai. Tests : - simulation `testSpotMarketIsOutOfTheBuild` ; contre-épreuve faite (un QNetworkAccessManager réintroduit fait échouer le test) ; - spotmarket `shippedBuildHasNoProvider`, sur le .so réel ; les 4 tests qui lisent la liste aWATTar sont sautés hors option et passent avec l'option ; - `testDecisionReasonRendering` : ligne de rendu du nouveau code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9574d4b266
commit
a23161b3b6
@ -7,6 +7,20 @@ INCLUDEPATH += $$PWD
|
||||
# Uncommenter pour activer. Commité DÉSACTIVÉ jusqu'à preuve iso (3b-iv).
|
||||
DEFINES += ETM_ARBITRATOR
|
||||
|
||||
# [ETM] Fournisseur spot aWATTar — HORS DU BUILD LIVRÉ (invariant 10 : aucun flux sortant).
|
||||
# C'était le seul appel réseau du moteur : une requête HTTPS vers api.awattar.{at,de}. La
|
||||
# source reste dans l'arbre pour la synchronisation amont ; elle n'est compilée qu'avec
|
||||
# `qmake CONFIG+=etm_spotmarket`. Sans l'option : aucun fournisseur enregistré, aucun
|
||||
# QNetworkAccessManager construit, et l'activation par RPC est refusée avec un code
|
||||
# (SPOT_MARKET_NOT_IN_BUILD). Le gestionnaire et ses fonctions de calcul restent compilés :
|
||||
# SmartChargingManager (amont) en dépend, et les suites de test y injectent un fournisseur mock.
|
||||
etm_spotmarket {
|
||||
message("Fournisseur spot aWATTar COMPILÉ (CONFIG+=etm_spotmarket) — flux sortant actif")
|
||||
DEFINES += ETM_SPOTMARKET
|
||||
HEADERS += $$PWD/spotmarket/spotmarketdataproviderawattar.h
|
||||
SOURCES += $$PWD/spotmarket/spotmarketdataproviderawattar.cpp
|
||||
}
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 5) {
|
||||
message("Building using Qt6 support")
|
||||
CONFIG *= c++17
|
||||
@ -37,7 +51,6 @@ HEADERS += \
|
||||
$$PWD/smartchargingmanager.h \
|
||||
$$PWD/etm/forecast/forecaststore.h \
|
||||
$$PWD/spotmarket/spotmarketdataprovider.h \
|
||||
$$PWD/spotmarket/spotmarketdataproviderawattar.h \
|
||||
$$PWD/spotmarket/spotmarketmanager.h \
|
||||
$$PWD/types/chargingaction.h \
|
||||
$$PWD/types/charginginfo.h \
|
||||
@ -58,7 +71,6 @@ SOURCES += \
|
||||
$$PWD/smartchargingmanager.cpp \
|
||||
$$PWD/etm/forecast/forecaststore.cpp \
|
||||
$$PWD/spotmarket/spotmarketdataprovider.cpp \
|
||||
$$PWD/spotmarket/spotmarketdataproviderawattar.cpp \
|
||||
$$PWD/spotmarket/spotmarketmanager.cpp \
|
||||
$$PWD/types/chargingaction.cpp \
|
||||
$$PWD/types/charginginfo.cpp \
|
||||
|
||||
@ -27,6 +27,7 @@
|
||||
#include "nymeaenergyjsonhandler.h"
|
||||
#include "energymanagerconfiguration.h"
|
||||
#include "spotmarket/spotmarketmanager.h"
|
||||
#include "etm/spotmarketbuild.h" // [ETM] fournisseur spot selon le build
|
||||
|
||||
// [ETM] BEGIN — EnergyArbitrator flip. Remove block to revert to upstream SmartChargingManager.
|
||||
#ifdef ETM_ARBITRATOR
|
||||
@ -47,8 +48,9 @@ void EnergyPluginNymea::init()
|
||||
qCDebug(dcNymeaEnergy()) << "Initializing nymea.energy plugin";
|
||||
|
||||
EnergyManagerConfiguration *configuration = new EnergyManagerConfiguration(this);
|
||||
QNetworkAccessManager *networkManager = new QNetworkAccessManager(this);
|
||||
SpotMarketManager *spotMarketManager = new SpotMarketManager(networkManager, this);
|
||||
// [ETM] Aucun QNetworkAccessManager dans le build livré : le seul flux sortant du moteur
|
||||
// était le fournisseur spot aWATTar, compilé seulement avec CONFIG+=etm_spotmarket.
|
||||
SpotMarketManager *spotMarketManager = SpotMarketBuild::creer(this);
|
||||
|
||||
LoadConfigStore *loadConfigStore = nullptr;
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ HEADERS += \
|
||||
$$PWD/scheduler/rulebasedscheduler.h \
|
||||
$$PWD/ratios/energyratioscalculator.h \
|
||||
$$PWD/energyarbitrator.h \
|
||||
$$PWD/spotmarketbuild.h \
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/types/decisionreason.cpp \
|
||||
@ -32,3 +33,4 @@ SOURCES += \
|
||||
$$PWD/adapters/relayrouter.cpp \
|
||||
$$PWD/scheduler/rulebasedscheduler.cpp \
|
||||
$$PWD/energyarbitrator.cpp \
|
||||
$$PWD/spotmarketbuild.cpp \
|
||||
|
||||
19
energyplugin/etm/spotmarketbuild.cpp
Normal file
19
energyplugin/etm/spotmarketbuild.cpp
Normal file
@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync
|
||||
#include "spotmarketbuild.h"
|
||||
|
||||
#include "../spotmarket/spotmarketmanager.h"
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
SpotMarketManager *SpotMarketBuild::creer(QObject *parent)
|
||||
{
|
||||
#ifdef ETM_SPOTMARKET
|
||||
QNetworkAccessManager *reseau = new QNetworkAccessManager(parent);
|
||||
#else
|
||||
// AUCUN objet réseau. Le pointeur nul n'est lu que par les fournisseurs aWATTar, qui ne
|
||||
// sont pas compilés : un fournisseur injecté par un test apporte le sien.
|
||||
QNetworkAccessManager *reseau = nullptr;
|
||||
#endif
|
||||
return new SpotMarketManager(reseau, parent);
|
||||
}
|
||||
45
energyplugin/etm/spotmarketbuild.h
Normal file
45
energyplugin/etm/spotmarketbuild.h
Normal file
@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Copyright (C) 2025 - 2026, Patrick Schurig / ETM PowerSync
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class SpotMarketManager;
|
||||
|
||||
/*!
|
||||
* \file spotmarketbuild.h
|
||||
* \brief Le fournisseur spot selon le BUILD — un seul point de construction.
|
||||
*
|
||||
* \par Pourquoi un point unique
|
||||
* Le plugin (\c EnergyPluginNymea::init()) et l'expérience de simulation construisent chacun
|
||||
* leur \c SpotMarketManager. Si chacun décidait de son côté d'instancier ou non un
|
||||
* \c QNetworkAccessManager, le test vérifierait la copie de la simulation, jamais celle qui
|
||||
* part dans le paquet. Les deux passent donc par ici.
|
||||
*
|
||||
* \par Ce que l'option change
|
||||
* Sans \c CONFIG+=etm_spotmarket (le build livré) : aucun \c QNetworkAccessManager, aucun
|
||||
* fournisseur enregistré — le gestionnaire existe, mais \c setEnabled(true) est refusé faute
|
||||
* de fournisseur, donc \c planSpotMarketCharging() rend la main à sa première ligne.
|
||||
* Invariant 10 : le moteur ne contacte personne.
|
||||
*/
|
||||
namespace SpotMarketBuild {
|
||||
|
||||
//! \return Vrai si le fournisseur aWATTar est compilé dans ce build (\c ETM_SPOTMARKET).
|
||||
constexpr bool inclus()
|
||||
{
|
||||
#ifdef ETM_SPOTMARKET
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Construit le gestionnaire spot de ce build.
|
||||
* \param parent Parent Qt du gestionnaire — et du \c QNetworkAccessManager quand il existe.
|
||||
* \return Le gestionnaire. Sans l'option, il est construit avec un gestionnaire réseau
|
||||
* **nul** : aucun objet réseau n'est créé, et aucun fournisseur ne peut s'en servir.
|
||||
*/
|
||||
SpotMarketManager *creer(QObject *parent);
|
||||
|
||||
}
|
||||
@ -230,6 +230,11 @@ QString renderFr(const DecisionReason &reason, const QString &label)
|
||||
"REFUSÉE ; regarder le plugin météo")
|
||||
.arg(p.value(QStringLiteral("cause")).toString());
|
||||
|
||||
if (c == QLatin1String(DecisionCode::SpotMarketNotInBuild))
|
||||
// LE GESTE : aucun réglage ne l'active, c'est le paquet qui ne le contient pas.
|
||||
return QStringLiteral("Marché spot non disponible dans ce build — aucun fournisseur "
|
||||
"compilé (invariant 10, aucun flux sortant)");
|
||||
|
||||
// --- Sécurité et cycle de vie ------------------------------------------
|
||||
if (c == QLatin1String(DecisionCode::DegradedL2))
|
||||
return QStringLiteral("Compteur muet depuis >90 s — consigne de repli (L2 watchdog)");
|
||||
|
||||
@ -308,6 +308,17 @@ constexpr const char *MissingForecastSite = "MISSING_FORECAST_SITE";
|
||||
*/
|
||||
constexpr const char *ForecastPayloadRefused = "FORECAST_PAYLOAD_REFUSED";
|
||||
|
||||
/*!
|
||||
* \brief Activation du marché spot demandée, mais AUCUN fournisseur n'est compilé dans ce build.
|
||||
* params : aucun.
|
||||
*
|
||||
* \note Le fournisseur aWATTar est hors du build livré (invariant 10, \c CONFIG+=etm_spotmarket
|
||||
* pour le réintégrer). Rendu par \c SetSpotMarketConfiguration à côté de
|
||||
* \c EnergyErrorInvalidParameter : l'énumération amont n'a pas de valeur pour « fonction
|
||||
* absente », et le moteur n'en invente pas — le code dit lequel des refus c'est.
|
||||
*/
|
||||
constexpr const char *SpotMarketNotInBuild = "SPOT_MARKET_NOT_IN_BUILD";
|
||||
|
||||
// --- SG-Ready (PAC) ---------------------------------------------------------
|
||||
//! params: \c state — le verrou minStateHold maintient l'état (court-cycling compresseur).
|
||||
constexpr const char *LockMinStateHold = "LOCK_MIN_STATE_HOLD";
|
||||
|
||||
@ -26,11 +26,12 @@
|
||||
#ifdef ETM_ARBITRATOR
|
||||
#include "etm/energyarbitrator.h"
|
||||
#include "etm/forecast/forecaststore.h"
|
||||
#include "etm/types/decisionreason.h"
|
||||
#endif
|
||||
#include "etm/types/decisionreason.h" // hors ETM_ARBITRATOR aussi : SPOT_MARKET_NOT_IN_BUILD
|
||||
#include "types/charginginfo.h"
|
||||
#include "smartchargingmanager.h"
|
||||
#include "spotmarket/spotmarketmanager.h"
|
||||
#include "etm/spotmarketbuild.h"
|
||||
#include "etm/types/loadconfig.h"
|
||||
#include "etm/config/loadconfigstore.h"
|
||||
|
||||
@ -134,10 +135,15 @@ NymeaEnergyJsonHandler::NymeaEnergyJsonHandler(SpotMarketManager *spotMarketMana
|
||||
registerMethod("GetAvailableSpotMarketProviders", description, params, returns, Types::PermissionScopeControlThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
description = "Set the current spotmarket configuration. If enabled, the provideId must be valid. You can get the available providers using GetAvailableSpotMarketProviders.";
|
||||
description = "Set the current spotmarket configuration. If enabled, the provideId must be valid. You can get the available providers using GetAvailableSpotMarketProviders. "
|
||||
"[ETM] The shipped build compiles NO spot market provider (no outbound "
|
||||
"network flow): enabling is then refused with EnergyErrorInvalidParameter and "
|
||||
"'fault' SPOT_MARKET_NOT_IN_BUILD - a code, never a sentence. Disabling is "
|
||||
"always accepted.";
|
||||
params.insert("enabled", enumValueName(Bool));
|
||||
params.insert("o:providerId", enumValueName(Uuid));
|
||||
returns.insert("energyError", enumRef<EnergyManager::EnergyError>());
|
||||
returns.insert("o:fault", enumValueName(String)); // [ETM] SPOT_MARKET_NOT_IN_BUILD
|
||||
registerMethod("SetSpotMarketConfiguration", description, params, returns, Types::PermissionScopeControlThings);
|
||||
|
||||
params.clear(); returns.clear();
|
||||
@ -747,6 +753,20 @@ JsonReply *NymeaEnergyJsonHandler::SetSpotMarketConfiguration(const QVariantMap
|
||||
|
||||
EnergyManager::EnergyError error = EnergyManager::EnergyErrorNoError;
|
||||
|
||||
// [ETM] BEGIN — FONCTION ABSENTE DU BUILD, et le refus le dit par un code.
|
||||
// Sans fournisseur compilé, les refus ci-dessous tomberaient quand même, mais sous un
|
||||
// InvalidParameter indistinct : le client accuserait son providerId. Seul le cas « rien
|
||||
// n'est enregistré » est visé — un fournisseur injecté (tests) garde le chemin amont.
|
||||
if (enable && !SpotMarketBuild::inclus() && m_spotMarketManager->availableProviders().isEmpty()) {
|
||||
qCWarning(dcNymeaEnergy()).noquote()
|
||||
<< QStringLiteral("SetSpotMarketConfiguration refusé — %1 (motif %2)")
|
||||
.arg(renderFr({ DecisionCode::SpotMarketNotInBuild, {} }))
|
||||
.arg(QLatin1String(DecisionCode::SpotMarketNotInBuild));
|
||||
return createReply({{"energyError", enumValueName(EnergyManager::EnergyErrorInvalidParameter)},
|
||||
{"fault", QLatin1String(DecisionCode::SpotMarketNotInBuild)}});
|
||||
}
|
||||
// [ETM] END
|
||||
|
||||
if (enable && providerId.isNull()) {
|
||||
qCWarning(dcNymeaEnergy()) << "Could not enable spot market because ther is no valid provider id given.";
|
||||
error = EnergyManager::EnergyErrorInvalidParameter;
|
||||
|
||||
@ -26,15 +26,22 @@
|
||||
#include "../plugininfo.h"
|
||||
#include "../energysettings.h"
|
||||
|
||||
// [ETM] Le fournisseur aWATTar n'est compilé qu'avec CONFIG+=etm_spotmarket (energyplugin.pri).
|
||||
#ifdef ETM_SPOTMARKET
|
||||
#include "spotmarketdataproviderawattar.h"
|
||||
#endif
|
||||
|
||||
SpotMarketManager::SpotMarketManager(QNetworkAccessManager *networkManager, QObject *parent) :
|
||||
QObject{parent},
|
||||
m_networkManager{networkManager}
|
||||
{
|
||||
// Init known spot market providers
|
||||
// [ETM] BEGIN — aucun fournisseur réseau hors CONFIG+=etm_spotmarket (invariant 10).
|
||||
#ifdef ETM_SPOTMARKET
|
||||
registerProvider(new SpotMarketDataProviderAwattar(m_networkManager, SpotMarketDataProviderAwattar::AwattarCountryAustria, this));
|
||||
registerProvider(new SpotMarketDataProviderAwattar(m_networkManager, SpotMarketDataProviderAwattar::AwattarCountryGermany, this));
|
||||
#endif
|
||||
// [ETM] END
|
||||
|
||||
// Load settings
|
||||
EnergySettings settings;
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
#include "../../../energyplugin/nymeaenergyjsonhandler.h"
|
||||
#include "../../../energyplugin/energymanagerconfiguration.h"
|
||||
#include "../../../energyplugin/spotmarket/spotmarketmanager.h"
|
||||
#include "../../../energyplugin/etm/spotmarketbuild.h"
|
||||
#ifdef ETM_ARBITRATOR
|
||||
#include "../../../energyplugin/etm/energyarbitrator.h"
|
||||
#include "../../../energyplugin/etm/config/loadconfigstore.h"
|
||||
@ -83,8 +84,9 @@ void ExperiencePluginEnergyMock::init()
|
||||
|
||||
// This is the init call of our nymea.energy plugin
|
||||
EnergyManagerConfiguration *configuration = new EnergyManagerConfiguration(this);
|
||||
QNetworkAccessManager *networkManager = new QNetworkAccessManager(this);
|
||||
m_spotMarketManager = new SpotMarketManager(networkManager, this);
|
||||
// Même point de construction que la production (energypluginnymea.cpp) : c'est lui que
|
||||
// testSpotMarketIsOutOfTheBuild vérifie, pas une copie propre à la simulation.
|
||||
m_spotMarketManager = SpotMarketBuild::creer(this);
|
||||
LoadConfigStore *loadConfigStore = nullptr;
|
||||
// [ETM] BEGIN — flip identique à energypluginnymea.cpp
|
||||
#ifdef ETM_ARBITRATOR
|
||||
|
||||
@ -49,6 +49,10 @@ using namespace nymeaserver;
|
||||
#include "../../../energyplugin/etm/scheduler/rulebasedscheduler.h"
|
||||
#include "../../../energyplugin/etm/types/decisionreason.h"
|
||||
#endif
|
||||
#include "../../../energyplugin/etm/types/decisionreason.h"
|
||||
#include "../../../energyplugin/etm/spotmarketbuild.h"
|
||||
#include "../../../energyplugin/spotmarket/spotmarketmanager.h"
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
#include <functional>
|
||||
#include <QDir>
|
||||
@ -4137,6 +4141,11 @@ void Simulation::testDecisionReasonRendering()
|
||||
QStringLiteral("Charge désactivée — mise en état sûr (0 W) avant retrait (ECS-413)") },
|
||||
{ { DecisionCode::SafeStateSgReady }, QString(),
|
||||
QStringLiteral("Charge désactivée — mise en état sûr (état 2) avant retrait (ECS-413)") },
|
||||
|
||||
// --- Marché spot hors du build livré (brief 3, T0) ---
|
||||
{ { DecisionCode::SpotMarketNotInBuild }, QString(),
|
||||
QStringLiteral("Marché spot non disponible dans ce build — aucun fournisseur compilé "
|
||||
"(invariant 10, aucun flux sortant)") },
|
||||
};
|
||||
|
||||
QSet<QString> codesCouverts;
|
||||
@ -4162,7 +4171,9 @@ void Simulation::testDecisionReasonRendering()
|
||||
// la garantie « tout code a une ligne de rendu éprouvée » ne tenait plus.
|
||||
DecisionCode::BatteryReserve, DecisionCode::BelowMinPower,
|
||||
// 3g-2
|
||||
DecisionCode::EvGridStart, DecisionCode::PhaseLimit };
|
||||
DecisionCode::EvGridStart, DecisionCode::PhaseLimit,
|
||||
// brief 3, T0
|
||||
DecisionCode::SpotMarketNotInBuild };
|
||||
for (const QString &code : catalogue)
|
||||
QVERIFY2(codesCouverts.contains(code),
|
||||
qUtf8Printable(QStringLiteral("code %1 sans ligne de rendu attendue").arg(code)));
|
||||
@ -7067,6 +7078,79 @@ void Simulation::testTheSnapshotServesTheChannelNeverTheTable()
|
||||
#endif
|
||||
}
|
||||
|
||||
void Simulation::testSpotMarketIsOutOfTheBuild()
|
||||
{
|
||||
// BRIEF 3, T0 — le fournisseur spot aWATTar est HORS du build livré (invariant 10).
|
||||
//
|
||||
// C'était le seul flux sortant du moteur. La source reste dans l'arbre (synchronisation
|
||||
// amont) mais n'est compilée qu'avec CONFIG+=etm_spotmarket. Ce test tient les trois
|
||||
// promesses du build SANS l'option : aucun objet réseau, aucune activation possible, et un
|
||||
// refus qui DIT pourquoi — par un code, pas par une phrase.
|
||||
if (SpotMarketBuild::inclus())
|
||||
QSKIP("build CONFIG+=etm_spotmarket : le fournisseur aWATTar est compilé, rien à vérifier ici.");
|
||||
|
||||
cleanupTestCase();
|
||||
m_energyLogDbFilePath = ":/databases/2022-06-22-energylogs.sqlite";
|
||||
initTestCase();
|
||||
|
||||
// 1. AUCUN QNetworkAccessManager. L'expérience de simulation construit son gestionnaire spot
|
||||
// par SpotMarketBuild::creer(), le MÊME point que EnergyPluginNymea::init() : c'est donc
|
||||
// le chemin de production qui est vérifié, pas une copie.
|
||||
QVERIFY2(m_experiencePlugin->findChildren<QNetworkAccessManager *>().isEmpty(),
|
||||
"un QNetworkAccessManager a été construit alors que le build n'a aucun fournisseur");
|
||||
|
||||
SpotMarketManager *spot = m_experiencePlugin->spotMarketManager();
|
||||
QVERIFY(spot);
|
||||
QVERIFY2(spot->availableProviders().isEmpty(), "aucun fournisseur ne doit être enregistré");
|
||||
|
||||
// 2. L'ACTIVATION EST REFUSÉE — et sans plantage : avant [upstream-fix], setEnabled(true) sur
|
||||
// une liste vide déréférençait un fournisseur nul. C'est le chemin d'une box dont
|
||||
// energy.conf porte encore SpotMarket/enabled=true d'un build antérieur.
|
||||
spot->setEnabled(true);
|
||||
QVERIFY2(!spot->enabled(), "le marché spot s'est activé sans fournisseur");
|
||||
QVERIFY(!spot->available());
|
||||
|
||||
// 3. planSpotMarketCharging() est INERTE : sa première ligne rend la main sur !enabled(), et
|
||||
// le point 2 établit que enabled() ne peut pas devenir vrai. On l'appelle quand même, pour
|
||||
// que le jour où cette garde bouge, ce test passe par le chemin.
|
||||
#ifdef ETM_ARBITRATOR
|
||||
EnergyArbitrator *arb = dynamic_cast<EnergyArbitrator *>(m_experiencePlugin->smartChargingManager());
|
||||
QVERIFY(arb);
|
||||
arb->runSpotMarketPlanning(QDateTime::currentDateTimeUtc());
|
||||
for (auto it = arb->scheduledActions().constBegin(); it != arb->scheduledActions().constEnd(); ++it)
|
||||
QVERIFY2(!it.value().contains(ChargingAction::ChargingActionIssuerSpotMarketCharging),
|
||||
"une action spot a été planifiée par un build sans fournisseur");
|
||||
#endif
|
||||
|
||||
// 4. LE RPC DIT POURQUOI. Un client ancien qui envoie l'identifiant aWATTar (Autriche)
|
||||
// recevait InvalidParameter indistinct et accusait son providerId. Le code nomme la cause.
|
||||
QVariantMap rep = injectAndWait("NymeaEnergy.SetSpotMarketConfiguration",
|
||||
{{"enabled", true},
|
||||
{"providerId", QUuid("5196b3cc-b2ee-46d6-b63a-7af2cf70ba67")}})
|
||||
.toMap().value("params").toMap();
|
||||
QCOMPARE(rep.value("energyError").toString(), QStringLiteral("EnergyErrorInvalidParameter"));
|
||||
QCOMPARE(rep.value("fault").toString(), QString::fromLatin1(DecisionCode::SpotMarketNotInBuild));
|
||||
// Sans providerId : même refus, même code.
|
||||
rep = injectAndWait("NymeaEnergy.SetSpotMarketConfiguration", {{"enabled", true}})
|
||||
.toMap().value("params").toMap();
|
||||
QCOMPARE(rep.value("fault").toString(), QString::fromLatin1(DecisionCode::SpotMarketNotInBuild));
|
||||
QVERIFY(!spot->enabled());
|
||||
|
||||
// 5. Désactiver reste accepté, sans motif : c'est l'état du build, le confirmer n'est pas
|
||||
// une erreur.
|
||||
rep = injectAndWait("NymeaEnergy.SetSpotMarketConfiguration", {{"enabled", false}})
|
||||
.toMap().value("params").toMap();
|
||||
QCOMPARE(rep.value("energyError").toString(), QStringLiteral("EnergyErrorNoError"));
|
||||
QVERIFY(!rep.contains("fault"));
|
||||
|
||||
const QVariantMap conf = injectAndWait("NymeaEnergy.GetSpotMarketConfiguration")
|
||||
.toMap().value("params").toMap();
|
||||
QCOMPARE(conf.value("enabled").toBool(), false);
|
||||
QCOMPARE(conf.value("available").toBool(), false);
|
||||
QVERIFY(injectAndWait("NymeaEnergy.GetAvailableSpotMarketProviders")
|
||||
.toMap().value("params").toMap().value("providers").toList().isEmpty());
|
||||
}
|
||||
|
||||
void Simulation::testEveryGetterReturnsWhatItDeclares()
|
||||
{
|
||||
// LE TROU QUE LE 2026-09-09 A OUVERT.
|
||||
|
||||
@ -209,6 +209,8 @@ private slots:
|
||||
void testARefusedStateIsDistinguishableFromAnApplied();
|
||||
void testL4ShedsAPacAndSaysItForcedTheLock();
|
||||
void testDerogationTakesTheHeadButNotTheSafety();
|
||||
//! Brief 3 T0 — aucun objet réseau, aucune activation, un refus codé.
|
||||
void testSpotMarketIsOutOfTheBuild();
|
||||
//! TOUTE méthode Get* du namespace rend ce que sa déclaration promet.
|
||||
void testEveryGetterReturnsWhatItDeclares();
|
||||
//! Le snapshot sert la DÉCISION : le canal, jamais la table — et l'absence n'est pas zéro.
|
||||
|
||||
@ -28,6 +28,15 @@
|
||||
|
||||
#include "../../../energyplugin/smartchargingmanager.h"
|
||||
#include "../../mocks/spotmarketprovider/spotmarketdataprovidermock.h"
|
||||
#include "../../../energyplugin/etm/spotmarketbuild.h" // [ETM]
|
||||
#include "../../../energyplugin/etm/types/decisionreason.h" // [ETM]
|
||||
|
||||
// [ETM] Les tests qui lisent la liste des fournisseurs supposent aWATTar compilé. Dans le build
|
||||
// livré il ne l'est pas (invariant 10) : ils sont SAUTÉS, et shippedBuildHasNoProvider() vérifie
|
||||
// à leur place ce que le build livré promet.
|
||||
#define ETM_REQUIERT_AWATTAR() \
|
||||
if (!SpotMarketBuild::inclus()) \
|
||||
QSKIP("fournisseur aWATTar hors du build (CONFIG+=etm_spotmarket pour le compiler)")
|
||||
|
||||
TestSpotmarket::TestSpotmarket(QObject *parent):
|
||||
EnergyTestBase(parent)
|
||||
@ -35,8 +44,32 @@ TestSpotmarket::TestSpotmarket(QObject *parent):
|
||||
qRegisterMetaType<TimeFrames>();
|
||||
}
|
||||
|
||||
void TestSpotmarket::shippedBuildHasNoProvider()
|
||||
{
|
||||
// [ETM] Brief 3 T0. Cette suite charge le plugin RÉEL (libnymea_energypluginnymea.so), pas
|
||||
// l'expérience de simulation : c'est donc le binaire du paquet qui répond ici.
|
||||
if (SpotMarketBuild::inclus())
|
||||
QSKIP("build CONFIG+=etm_spotmarket : le fournisseur aWATTar est compilé.");
|
||||
|
||||
QVariant response = injectAndWait("NymeaEnergy.GetAvailableSpotMarketProviders");
|
||||
QVERIFY2(response.toMap().value("params").toMap().value("providers").toList().isEmpty(),
|
||||
"le build livré ne doit enregistrer aucun fournisseur");
|
||||
|
||||
QVariantMap params;
|
||||
params.insert("enabled", true);
|
||||
params.insert("providerId", QUuid("5196b3cc-b2ee-46d6-b63a-7af2cf70ba67")); // aWATTar AT
|
||||
response = injectAndWait("NymeaEnergy.SetSpotMarketConfiguration", params);
|
||||
const QVariantMap rep = response.toMap().value("params").toMap();
|
||||
QCOMPARE(rep.value("energyError").toString(), QStringLiteral("EnergyErrorInvalidParameter"));
|
||||
QCOMPARE(rep.value("fault").toString(), QString::fromLatin1(DecisionCode::SpotMarketNotInBuild));
|
||||
|
||||
response = injectAndWait("NymeaEnergy.GetSpotMarketConfiguration");
|
||||
QCOMPARE(response.toMap().value("params").toMap().value("enabled").toBool(), false);
|
||||
}
|
||||
|
||||
void TestSpotmarket::getAvailableProviders()
|
||||
{
|
||||
ETM_REQUIERT_AWATTAR();
|
||||
QVariant response = injectAndWait("NymeaEnergy.GetAvailableSpotMarketProviders");
|
||||
QVariantList providerList = response.toMap().value("params").toMap().value("providers").toList();
|
||||
QVERIFY2(providerList.count() > 0, "Available providers list is empty.");
|
||||
@ -51,6 +84,7 @@ void TestSpotmarket::getAvailableProviders()
|
||||
|
||||
void TestSpotmarket::getSpotmarketEntriesAndChargingSchedules()
|
||||
{
|
||||
ETM_REQUIERT_AWATTAR();
|
||||
QVariant response, notification;
|
||||
QSignalSpy notificationSpy(m_mockTcpServer, &MockTcpServer::outgoingData);;
|
||||
|
||||
@ -214,6 +248,7 @@ void TestSpotmarket::addCharingInfo_data()
|
||||
|
||||
void TestSpotmarket::addCharingInfo()
|
||||
{
|
||||
ETM_REQUIERT_AWATTAR();
|
||||
QFETCH(bool, addingCharger);
|
||||
QFETCH(bool, addingCar);
|
||||
QFETCH(bool, carValid);
|
||||
@ -334,6 +369,7 @@ void TestSpotmarket::setConfiguration_data()
|
||||
|
||||
void TestSpotmarket::setConfiguration()
|
||||
{
|
||||
ETM_REQUIERT_AWATTAR();
|
||||
QFETCH(bool, enabled);
|
||||
QFETCH(bool, useValidProvider);
|
||||
QFETCH(bool, useNullProvider);
|
||||
|
||||
@ -34,6 +34,10 @@ public:
|
||||
explicit TestSpotmarket(QObject *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
// [ETM] Brief 3 T0 — le plugin RÉEL (.so) sans CONFIG+=etm_spotmarket : aucun fournisseur,
|
||||
// activation refusée avec SPOT_MARKET_NOT_IN_BUILD.
|
||||
void shippedBuildHasNoProvider();
|
||||
|
||||
void getAvailableProviders();
|
||||
|
||||
void getSpotmarketEntriesAndChargingSchedules();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user