// SPDX-License-Identifier: GPL-3.0-or-later /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 2013 - 2024, nymea GmbH * Copyright (C) 2024 - 2025, chargebyte austria GmbH * * This file is part of nymea-energy-plugin-nymea. * * nymea-energy-plugin-nymea.s free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * nymea-energy-plugin-nymea.s distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nymea-energy-plugin-nymea. If not, see . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #ifndef TESTSCHEDULER_H #define TESTSCHEDULER_H #include #include #include #include #include "types/energytimeslot.h" #include "types/flexibleload.h" #include "types/loadrole.h" #include "types/schedulerconfig.h" #include "schedulingstrategies/rulebasedstrategy.h" #include "schedulingstrategies/aistrategy.h" #include "schedulingstrategies/manualstrategy.h" #include "types/manualslotconfig.h" #include "adapters/loadadapterregistry.h" #include "adapters/adaptersettings.h" #include "schedulermanager.h" // Unit tests for the scheduling algorithm. // These tests operate on pure algorithm logic (RuleBasedStrategy, AIStrategy) // without requiring a running nymea server instance. class TestScheduler : public QObject { Q_OBJECT public: explicit TestScheduler(QObject *parent = nullptr); private slots: // Test 1: RuleBasedStrategy, typical winter day // Input: 24h price series with cheap night / expensive peak, solar bell curve 0→4kW→0 // EV plugged in at 18:00, deadline 07:00, SOC 40%, target 80% // → The chosen charging slots must be within the 6 cheapest before 07:00 // → Every active slot must have non-empty decisionReason void testRuleBasedStrategy_winterDay(); // Test 2: Manual override is respected // → Override slot 23:00 with powerW=0, reason="Départ annulé" // → Scheduler must NOT modify that slot on recompute // → All other eligible slots are still computed void testManualOverrideRespected(); // Test 3: Fallback when no tariff data (all prices = 0) // → Plan must remain valid (critical/inflexible loads still active) // → planHealth must be reported correctly (no crash) // → All slots must still have non-empty decisionReason void testFallbackEmptyTariffData(); // Test 4: Hot strategy swap // → Start with RuleBasedStrategy // → Swap to AIStrategy stub // → AIStrategy returns non-empty plan with "model not loaded" reason // → No crash, plan is valid void testHotStrategySwap(); // Test 5: ManualStrategy — slot configured, allocations applied exactly void testManualStrategy_basicSlot(); // Test 6: ManualStrategy — no config → fallback (critical loads only) void testManualStrategy_noConfig_fallback(); // Test 7: ManualStrategy — expired slot ignored, reason set correctly void testManualStrategy_expiredSlot(); // Test 8: ManualStrategy — repeating weekly slot applied to next recurrence void testManualStrategy_repeatingSlot(); // Test 9: ManualStrategy — JSON round-trip (persistence simulation) void testManualStrategy_persistence(); // Test A: LoadAdapterRegistry::detectAdapterType — static method, no ThingManager needed void testDetectAdapterType(); // Test B: AdapterSettings — save/load round-trip void testAdapterSettings_roundTrip(); // Test C: SchedulerManager — null registry → applyCurrentSlot is a no-op (no crash) void testSchedulerManager_nullRegistry_noCrash(); // Test D: LoadRole — string serialization round-trip void testLoadRole_stringRoundTrip(); private: // Build a synthetic 24h forecast starting at a given base time QList buildWinterForecast(const QDateTime &start) const; // Price vector for a typical winter day (24 hourly prices in EUR/kWh) static const double winterPrices[24]; // Sanity-check that all active slots (non-zero allocations) have a reason bool allActiveSlotsHaveReason(const QList &timeline) const; // Count how many of the chosen EV charging slots fall within the N cheapest eligible slots int countSlotsInCheapestN(const QList &timeline, const QDateTime &deadline, int n) const; }; #endif // TESTSCHEDULER_H