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:
Patrick Schurig 2026-10-12 12:48:27 +02:00
parent 9574d4b266
commit a23161b3b6
14 changed files with 260 additions and 9 deletions

View File

@ -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 \

View File

@ -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;

View File

@ -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 \

View 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);
}

View 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);
}

View File

@ -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)");

View File

@ -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";

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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.

View File

@ -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.

View File

@ -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);

View File

@ -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();