blob: b3ed09d11379da204528f47c4629f5a51a1effc3 [file]
/*
*
* Copyright (c) 2025 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "DeviceEnergyManagementMockDelegate.h"
#include <app/clusters/device-energy-management-server/DeviceEnergyManagementCluster.h>
#include <app/clusters/device-energy-management-server/tests/DeviceEnergyManagementMockDelegate.h>
#include <pw_unit_test/framework.h>
#include <app/server-cluster/testing/ClusterTester.h>
#include <app/server-cluster/testing/TestServerClusterContext.h>
#include <app/server-cluster/testing/ValidateGlobalAttributes.h>
#include <clusters/DeviceEnergyManagement/Attributes.h>
#include <clusters/DeviceEnergyManagement/Commands.h>
#include <clusters/DeviceEnergyManagement/Metadata.h>
#include <system/SystemClock.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::DeviceEnergyManagement;
using namespace chip::app::Clusters::DeviceEnergyManagement::Attributes;
using namespace chip::app::Clusters::DeviceEnergyManagement::Commands;
using namespace chip::Testing;
using chip::Testing::IsAcceptedCommandsListEqualTo;
using chip::Testing::IsAttributesListEqualTo;
namespace {
constexpr EndpointId kTestEndpointId = 1;
// Helper to check if real-time clock is available (required for time-based validations)
bool IsRealTimeClockAvailable()
{
uint32_t epoch = 0;
return chip::System::Clock::GetClock_MatterEpochS(epoch) == CHIP_NO_ERROR;
}
struct TestDeviceEnergyManagementCluster : public ::testing::Test
{
static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); }
static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); }
void SetUp() override {}
};
// =============================================================================
// Feature Tests
// =============================================================================
TEST_F(TestDeviceEnergyManagementCluster, TestFeatures)
{
TestServerClusterContext context;
DeviceEnergyManagementMockDelegate mockDelegate;
// Test 1: No features - only mandatory attributes
{
BitMask<Feature> noFeatures;
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, noFeatures, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
EXPECT_TRUE(IsAttributesListEqualTo(cluster,
{
ESAType::kMetadataEntry,
ESACanGenerate::kMetadataEntry,
ESAState::kMetadataEntry,
AbsMinPower::kMetadataEntry,
AbsMaxPower::kMetadataEntry,
}));
EXPECT_TRUE(IsAcceptedCommandsListEqualTo(cluster, {}));
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// Test 2: PowerAdjustment feature - PowerAdjustmentCapability and OptOutState attributes
{
BitMask<Feature> features(Feature::kPowerAdjustment);
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, features, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
EXPECT_TRUE(IsAttributesListEqualTo(cluster,
{
ESAType::kMetadataEntry,
ESACanGenerate::kMetadataEntry,
ESAState::kMetadataEntry,
AbsMinPower::kMetadataEntry,
AbsMaxPower::kMetadataEntry,
PowerAdjustmentCapability::kMetadataEntry,
OptOutState::kMetadataEntry,
}));
EXPECT_TRUE(IsAcceptedCommandsListEqualTo(cluster,
{
PowerAdjustRequest::kMetadataEntry,
CancelPowerAdjustRequest::kMetadataEntry,
}));
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// Test 3: PowerForecastReporting feature - Forecast attribute
{
BitMask<Feature> features(Feature::kPowerForecastReporting);
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, features, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
EXPECT_TRUE(IsAttributesListEqualTo(cluster,
{
ESAType::kMetadataEntry,
ESACanGenerate::kMetadataEntry,
ESAState::kMetadataEntry,
AbsMinPower::kMetadataEntry,
AbsMaxPower::kMetadataEntry,
Forecast::kMetadataEntry,
}));
EXPECT_TRUE(IsAcceptedCommandsListEqualTo(cluster, {}));
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// Test 4: StateForecastReporting feature - Forecast attribute
{
BitMask<Feature> features(Feature::kStateForecastReporting);
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, features, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
EXPECT_TRUE(IsAttributesListEqualTo(cluster,
{
ESAType::kMetadataEntry,
ESACanGenerate::kMetadataEntry,
ESAState::kMetadataEntry,
AbsMinPower::kMetadataEntry,
AbsMaxPower::kMetadataEntry,
Forecast::kMetadataEntry,
}));
EXPECT_TRUE(IsAcceptedCommandsListEqualTo(cluster, {}));
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// Test 5: Pausable feature - PauseRequest and ResumeRequest commands, OptOutState attribute
{
BitMask<Feature> features(Feature::kPausable);
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, features, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
EXPECT_TRUE(IsAttributesListEqualTo(cluster,
{
ESAType::kMetadataEntry,
ESACanGenerate::kMetadataEntry,
ESAState::kMetadataEntry,
AbsMinPower::kMetadataEntry,
AbsMaxPower::kMetadataEntry,
OptOutState::kMetadataEntry,
}));
EXPECT_TRUE(IsAcceptedCommandsListEqualTo(cluster,
{
PauseRequest::kMetadataEntry,
ResumeRequest::kMetadataEntry,
}));
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// Test 6: ForecastAdjustment feature - ModifyForecastRequest command, OptOutState attribute, CancelRequest command
{
BitMask<Feature> features(Feature::kForecastAdjustment);
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, features, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
EXPECT_TRUE(IsAttributesListEqualTo(cluster,
{
ESAType::kMetadataEntry,
ESACanGenerate::kMetadataEntry,
ESAState::kMetadataEntry,
AbsMinPower::kMetadataEntry,
AbsMaxPower::kMetadataEntry,
OptOutState::kMetadataEntry,
}));
EXPECT_TRUE(IsAcceptedCommandsListEqualTo(cluster,
{
ModifyForecastRequest::kMetadataEntry,
CancelRequest::kMetadataEntry,
}));
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// Test 7: StartTimeAdjustment feature - StartTimeAdjustRequest command, OptOutState attribute, CancelRequest command
{
BitMask<Feature> features(Feature::kStartTimeAdjustment);
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, features, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
EXPECT_TRUE(IsAttributesListEqualTo(cluster,
{
ESAType::kMetadataEntry,
ESACanGenerate::kMetadataEntry,
ESAState::kMetadataEntry,
AbsMinPower::kMetadataEntry,
AbsMaxPower::kMetadataEntry,
OptOutState::kMetadataEntry,
}));
EXPECT_TRUE(IsAcceptedCommandsListEqualTo(cluster,
{
StartTimeAdjustRequest::kMetadataEntry,
CancelRequest::kMetadataEntry,
}));
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// Test 8: ConstraintBasedAdjustment feature - RequestConstraintBasedForecast command, OptOutState attribute, CancelRequest
{
BitMask<Feature> features(Feature::kConstraintBasedAdjustment);
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, features, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
EXPECT_TRUE(IsAttributesListEqualTo(cluster,
{
ESAType::kMetadataEntry,
ESACanGenerate::kMetadataEntry,
ESAState::kMetadataEntry,
AbsMinPower::kMetadataEntry,
AbsMaxPower::kMetadataEntry,
OptOutState::kMetadataEntry,
}));
EXPECT_TRUE(IsAcceptedCommandsListEqualTo(cluster,
{
RequestConstraintBasedForecast::kMetadataEntry,
CancelRequest::kMetadataEntry,
}));
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// Test 9: All features - all optional attributes, all commands
{
BitMask<Feature> allFeatures(Feature::kPowerAdjustment, Feature::kPowerForecastReporting, Feature::kStateForecastReporting,
Feature::kStartTimeAdjustment, Feature::kPausable, Feature::kForecastAdjustment,
Feature::kConstraintBasedAdjustment);
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, allFeatures, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
EXPECT_TRUE(IsAttributesListEqualTo(cluster,
{
ESAType::kMetadataEntry,
ESACanGenerate::kMetadataEntry,
ESAState::kMetadataEntry,
AbsMinPower::kMetadataEntry,
AbsMaxPower::kMetadataEntry,
PowerAdjustmentCapability::kMetadataEntry,
Forecast::kMetadataEntry,
OptOutState::kMetadataEntry,
}));
EXPECT_TRUE(IsAcceptedCommandsListEqualTo(cluster,
{
PowerAdjustRequest::kMetadataEntry,
CancelPowerAdjustRequest::kMetadataEntry,
StartTimeAdjustRequest::kMetadataEntry,
PauseRequest::kMetadataEntry,
ResumeRequest::kMetadataEntry,
ModifyForecastRequest::kMetadataEntry,
RequestConstraintBasedForecast::kMetadataEntry,
CancelRequest::kMetadataEntry,
}));
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
}
// =============================================================================
// Startup Tests
// =============================================================================
TEST_F(TestDeviceEnergyManagementCluster, TestStartupFailsWithMismatchedEndpointId)
{
TestServerClusterContext context;
DeviceEnergyManagementMockDelegate mockDelegate;
BitMask<Feature> noFeatures;
constexpr EndpointId kClusterEndpointId = 1;
constexpr EndpointId kDelegateEndpointId = 2;
// Create cluster with one endpoint ID
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kClusterEndpointId, noFeatures, mockDelegate));
// Set delegate to a different endpoint ID
mockDelegate.SetEndpointId(kDelegateEndpointId);
// Startup should fail because endpoint IDs don't match
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_ERROR_INVALID_ARGUMENT);
}
TEST_F(TestDeviceEnergyManagementCluster, TestStartupSucceedsWithMatchingEndpointId)
{
TestServerClusterContext context;
DeviceEnergyManagementMockDelegate mockDelegate;
BitMask<Feature> noFeatures;
constexpr EndpointId kEndpointId = 1;
// Create cluster with endpoint ID
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kEndpointId, noFeatures, mockDelegate));
// Delegate endpoint ID is set in constructor, so they should match
EXPECT_EQ(mockDelegate.GetEndpointId(), kEndpointId);
// Startup should succeed because endpoint IDs match
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// =============================================================================
// Attribute Tests
// =============================================================================
TEST_F(TestDeviceEnergyManagementCluster, TestMandatoryAttributes)
{
TestServerClusterContext context;
DeviceEnergyManagementMockDelegate mockDelegate;
BitMask<Feature> noFeatures;
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, noFeatures, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
ClusterTester tester(cluster);
ESATypeEnum esaType = ESATypeEnum::kUnknownEnumValue;
ASSERT_EQ(tester.ReadAttribute(ESAType::Id, esaType), CHIP_NO_ERROR);
EXPECT_EQ(esaType, ESATypeEnum::kEvse);
bool esaCanGenerate = false;
ASSERT_EQ(tester.ReadAttribute(ESACanGenerate::Id, esaCanGenerate), CHIP_NO_ERROR);
EXPECT_EQ(esaCanGenerate, false);
ESAStateEnum esaState = ESAStateEnum::kUnknownEnumValue;
ASSERT_EQ(tester.ReadAttribute(ESAState::Id, esaState), CHIP_NO_ERROR);
EXPECT_EQ(esaState, ESAStateEnum::kOnline);
int64_t absMinPower = 0;
ASSERT_EQ(tester.ReadAttribute(AbsMinPower::Id, absMinPower), CHIP_NO_ERROR);
EXPECT_EQ(absMinPower, DeviceEnergyManagementMockDelegate::kAbsMinPower);
int64_t absMaxPower = 0;
ASSERT_EQ(tester.ReadAttribute(AbsMaxPower::Id, absMaxPower), CHIP_NO_ERROR);
EXPECT_EQ(absMaxPower, DeviceEnergyManagementMockDelegate::kAbsMaxPower);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// =============================================================================
// PowerAdjustRequest Command Tests
// =============================================================================
TEST_F(TestDeviceEnergyManagementCluster, TestPowerAdjustRequest)
{
TestServerClusterContext context;
DeviceEnergyManagementMockDelegate mockDelegate;
BitMask<Feature> features(Feature::kPowerAdjustment);
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, features, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
ClusterTester tester(cluster);
Commands::PowerAdjustRequest::Type command;
command.power = 1000;
command.duration = 60;
command.cause = AdjustmentCauseEnum::kLocalOptimization;
// Power out of range - rejected
command.power = DeviceEnergyManagementMockDelegate::kAbsMaxPower + 1000;
EXPECT_FALSE(tester.Invoke(Commands::PowerAdjustRequest::Id, command).IsSuccess());
EXPECT_EQ(mockDelegate.GetESAState(), ESAStateEnum::kOnline);
// Valid power adjustment - succeeds
command.power = 1000;
EXPECT_TRUE(tester.Invoke(Commands::PowerAdjustRequest::Id, command).IsSuccess());
EXPECT_EQ(mockDelegate.GetESAState(), ESAStateEnum::kPowerAdjustActive);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// =============================================================================
// CancelPowerAdjustRequest Command Tests
// =============================================================================
TEST_F(TestDeviceEnergyManagementCluster, TestCancelPowerAdjustRequest)
{
TestServerClusterContext context;
DeviceEnergyManagementMockDelegate mockDelegate;
BitMask<Feature> features(Feature::kPowerAdjustment);
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, features, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
ClusterTester tester(cluster);
Commands::CancelPowerAdjustRequest::Type cancelCommand;
// Cancel without active adjustment - fails (InvalidInState)
EXPECT_FALSE(tester.Invoke(Commands::CancelPowerAdjustRequest::Id, cancelCommand).IsSuccess());
// Start a power adjustment first
Commands::PowerAdjustRequest::Type powerAdjustCommand;
powerAdjustCommand.power = 1000;
powerAdjustCommand.duration = 60;
powerAdjustCommand.cause = AdjustmentCauseEnum::kLocalOptimization;
EXPECT_TRUE(tester.Invoke(Commands::PowerAdjustRequest::Id, powerAdjustCommand).IsSuccess());
EXPECT_EQ(mockDelegate.GetESAState(), ESAStateEnum::kPowerAdjustActive);
// Cancel - succeeds
EXPECT_TRUE(tester.Invoke(Commands::CancelPowerAdjustRequest::Id, cancelCommand).IsSuccess());
EXPECT_EQ(mockDelegate.GetESAState(), ESAStateEnum::kOnline);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// =============================================================================
// StartTimeAdjustRequest Command Tests
// =============================================================================
TEST_F(TestDeviceEnergyManagementCluster, TestStartTimeAdjustRequest)
{
TestServerClusterContext context;
DeviceEnergyManagementMockDelegate mockDelegate;
BitMask<Feature> features(Feature::kStartTimeAdjustment);
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, features, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
ClusterTester tester(cluster);
Commands::StartTimeAdjustRequest::Type command;
command.cause = AdjustmentCauseEnum::kLocalOptimization;
// Start time before earliestStartTime (900) - rejected
command.requestedStartTime = 800;
EXPECT_FALSE(tester.Invoke(Commands::StartTimeAdjustRequest::Id, command).IsSuccess());
// Start time that would push end beyond latestEndTime (2100) - rejected
// Forecast duration is 1000, so start at 1200 ends at 2200 > 2100
command.requestedStartTime = 1200;
EXPECT_FALSE(tester.Invoke(Commands::StartTimeAdjustRequest::Id, command).IsSuccess());
// Valid start time within range - succeeds
command.requestedStartTime = 1000;
EXPECT_TRUE(tester.Invoke(Commands::StartTimeAdjustRequest::Id, command).IsSuccess());
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// =============================================================================
// PauseRequest Command Tests
// =============================================================================
TEST_F(TestDeviceEnergyManagementCluster, TestPauseRequest)
{
TestServerClusterContext context;
DeviceEnergyManagementMockDelegate mockDelegate;
BitMask<Feature> features(Feature::kPausable);
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, features, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
ClusterTester tester(cluster);
Commands::PauseRequest::Type command;
command.duration = 60;
command.cause = AdjustmentCauseEnum::kLocalOptimization;
// Duration out of range - rejected
command.duration = 4000; // Exceeds maxPauseDuration (3600)
EXPECT_FALSE(tester.Invoke(Commands::PauseRequest::Id, command).IsSuccess());
EXPECT_EQ(mockDelegate.GetESAState(), ESAStateEnum::kOnline);
// OptOut rejects matching cause
command.duration = 60;
mockDelegate.SetOptOutState(OptOutStateEnum::kLocalOptOut);
EXPECT_FALSE(tester.Invoke(Commands::PauseRequest::Id, command).IsSuccess());
mockDelegate.SetOptOutState(OptOutStateEnum::kNoOptOut);
// Force set the ESA in an invalid state - rejected
EXPECT_EQ(mockDelegate.SetESAState(ESAStateEnum::kFault), CHIP_NO_ERROR);
EXPECT_FALSE(tester.Invoke(Commands::PauseRequest::Id, command).IsSuccess());
// Valid pause - succeeds
EXPECT_EQ(mockDelegate.SetESAState(ESAStateEnum::kOnline), CHIP_NO_ERROR);
EXPECT_TRUE(tester.Invoke(Commands::PauseRequest::Id, command).IsSuccess());
EXPECT_EQ(mockDelegate.GetESAState(), ESAStateEnum::kPaused);
EXPECT_EQ(mockDelegate.GetForecast().Value().forecastUpdateReason, ForecastUpdateReasonEnum::kLocalOptimization);
// Further pause while paused with valid duration - succeeds
command.cause = AdjustmentCauseEnum::kGridOptimization;
EXPECT_TRUE(tester.Invoke(Commands::PauseRequest::Id, command).IsSuccess());
EXPECT_EQ(mockDelegate.GetForecast().Value().forecastUpdateReason, ForecastUpdateReasonEnum::kGridOptimization);
// Further pause while paused with invalid duration - rejected but stays paused
command.duration = 5000;
EXPECT_FALSE(tester.Invoke(Commands::PauseRequest::Id, command).IsSuccess());
EXPECT_EQ(mockDelegate.GetESAState(), ESAStateEnum::kPaused);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// =============================================================================
// ResumeRequest Command Tests
// =============================================================================
TEST_F(TestDeviceEnergyManagementCluster, TestResumeRequest)
{
TestServerClusterContext context;
DeviceEnergyManagementMockDelegate mockDelegate;
BitMask<Feature> features(Feature::kPausable);
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, features, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
ClusterTester tester(cluster);
Commands::ResumeRequest::Type resumeCommand;
// Resume without being paused - fails (InvalidInState)
EXPECT_FALSE(tester.Invoke(Commands::ResumeRequest::Id, resumeCommand).IsSuccess());
// Pause first
Commands::PauseRequest::Type pauseCommand;
pauseCommand.duration = 60;
pauseCommand.cause = AdjustmentCauseEnum::kLocalOptimization;
EXPECT_TRUE(tester.Invoke(Commands::PauseRequest::Id, pauseCommand).IsSuccess());
EXPECT_EQ(mockDelegate.GetESAState(), ESAStateEnum::kPaused);
// Resume - succeeds
EXPECT_TRUE(tester.Invoke(Commands::ResumeRequest::Id, resumeCommand).IsSuccess());
EXPECT_EQ(mockDelegate.GetESAState(), ESAStateEnum::kOnline);
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// =============================================================================
// ModifyForecastRequest Command Tests
// =============================================================================
TEST_F(TestDeviceEnergyManagementCluster, TestModifyForecastRequest)
{
TestServerClusterContext context;
DeviceEnergyManagementMockDelegate mockDelegate;
BitMask<Feature> features(Feature::kForecastAdjustment);
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, features, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
ClusterTester tester(cluster);
Commands::ModifyForecastRequest::Type command;
command.cause = AdjustmentCauseEnum::kLocalOptimization;
// Wrong forecast ID - rejected
command.forecastID = 9999;
EXPECT_FALSE(tester.Invoke(Commands::ModifyForecastRequest::Id, command).IsSuccess());
// Correct forecast ID - succeeds
command.forecastID = mockDelegate.GetForecast().Value().forecastID;
EXPECT_TRUE(tester.Invoke(Commands::ModifyForecastRequest::Id, command).IsSuccess());
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// =============================================================================
// RequestConstraintBasedForecast Command Tests
// =============================================================================
TEST_F(TestDeviceEnergyManagementCluster, TestRequestConstraintBasedForecast)
{
if (!IsRealTimeClockAvailable())
{
GTEST_SKIP() << "Real-time clock not available";
}
TestServerClusterContext context;
DeviceEnergyManagementMockDelegate mockDelegate;
BitMask<Feature> features(Feature::kConstraintBasedAdjustment);
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, features, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
Commands::RequestConstraintBasedForecast::Type command;
command.cause = AdjustmentCauseEnum::kLocalOptimization;
ClusterTester tester(cluster);
auto result = tester.Invoke(Commands::RequestConstraintBasedForecast::Id, command);
EXPECT_TRUE(result.IsSuccess());
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
// =============================================================================
// CancelRequest Command Tests
// =============================================================================
TEST_F(TestDeviceEnergyManagementCluster, TestCancelRequest)
{
TestServerClusterContext context;
DeviceEnergyManagementMockDelegate mockDelegate;
BitMask<Feature> features(Feature::kStartTimeAdjustment);
DeviceEnergyManagementCluster cluster(DeviceEnergyManagementCluster::Config(kTestEndpointId, features, mockDelegate));
EXPECT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
ClusterTester tester(cluster);
Commands::CancelRequest::Type cancelCommand;
// Mock starts with kLocalOptimization - cancel succeeds
EXPECT_EQ(mockDelegate.GetForecast().Value().forecastUpdateReason, ForecastUpdateReasonEnum::kLocalOptimization);
EXPECT_TRUE(tester.Invoke(Commands::CancelRequest::Id, cancelCommand).IsSuccess());
// ESAState reset to Online, ForecastUpdateReason reset to InternalOptimization
EXPECT_EQ(mockDelegate.GetESAState(), ESAStateEnum::kOnline);
EXPECT_EQ(mockDelegate.GetForecast().Value().forecastUpdateReason, ForecastUpdateReasonEnum::kInternalOptimization);
// Cancel when already InternalOptimization - fails (InvalidInState)
EXPECT_FALSE(tester.Invoke(Commands::CancelRequest::Id, cancelCommand).IsSuccess());
cluster.Shutdown(ClusterShutdownType::kClusterShutdown);
}
} // namespace