| /** |
| * |
| * Copyright (c) 2026 Project CHIP Authors |
| * |
| * 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 <lib/core/StringBuilderAdapters.h> |
| #include <pw_unit_test/framework.h> |
| |
| #include <app/clusters/closure-control-server/ClosureControlCluster.h> |
| #include <app/clusters/closure-control-server/ClosureControlClusterDelegate.h> |
| #include <app/clusters/closure-control-server/ClosureControlClusterObjects.h> |
| #include <app/server-cluster/testing/AttributeTesting.h> |
| #include <app/server-cluster/testing/ClusterTester.h> |
| #include <app/server-cluster/testing/TestServerClusterContext.h> |
| #include <app/server-cluster/testing/ValidateGlobalAttributes.h> |
| #include <lib/support/CHIPMem.h> |
| #include <lib/support/TimerDelegateMock.h> |
| #include <platform/CHIPDeviceLayer.h> |
| #include <vector> |
| |
| using namespace chip; |
| using namespace chip::app; |
| using namespace chip::app::Clusters::ClosureControl; |
| using namespace chip::app::Clusters; |
| using namespace chip::Testing; |
| |
| using Status = chip::Protocols::InteractionModel::Status; |
| |
| // Mock callback functions |
| __attribute__((unused)) void |
| MatterClosureControlClusterServerAttributeChangedCallback(const chip::app::ConcreteAttributePath & attributePath) |
| { |
| // Mock implementation - no-op for tests |
| } |
| |
| namespace { |
| |
| TimerDelegateMock mockTimerDelegate; |
| |
| // Simple mock implementation of DelegateBase |
| class MockDelegate : public ClosureControlClusterDelegate |
| { |
| public: |
| virtual ~MockDelegate() = default; |
| |
| Status HandleStopCommand() override |
| { |
| ++stopCommandCalls; |
| return stopCommandStatus; |
| } |
| Status HandleMoveToCommand(const Optional<chip::app::Clusters::ClosureControl::TargetPositionEnum> & tag, |
| const Optional<bool> & latch, const Optional<Globals::ThreeLevelAutoEnum> & speed) override |
| { |
| ++moveToCommandCalls; |
| lastMoveToPosition = tag; |
| lastMoveToLatch = latch; |
| lastMoveToSpeed = speed; |
| return moveToCommandStatus; |
| } |
| Status HandleCalibrateCommand() override |
| { |
| ++calibrateCommandCalls; |
| return calibrateCommandStatus; |
| } |
| |
| bool IsReadyToMove() override { return isReadyToMove; } |
| ElapsedS GetCalibrationCountdownTime() override { return calibrationCountdownTime; } |
| ElapsedS GetMovingCountdownTime() override { return movingCountdownTime; } |
| ElapsedS GetWaitingForMotionCountdownTime() override { return waitingForMotionCountdownTime; } |
| |
| Status stopCommandStatus = Status::Success; |
| Status moveToCommandStatus = Status::Success; |
| Status calibrateCommandStatus = Status::Success; |
| bool isReadyToMove = true; |
| ElapsedS calibrationCountdownTime = 30; |
| ElapsedS movingCountdownTime = 20; |
| ElapsedS waitingForMotionCountdownTime = 10; |
| |
| int stopCommandCalls = 0; |
| int moveToCommandCalls = 0; |
| int calibrateCommandCalls = 0; |
| |
| bool OnCountdownTimeChanged(DataModel::Nullable<ElapsedS> newCountdownTime) override |
| { |
| mCountdownTimeValue = newCountdownTime; |
| mCountdownTimeChangedCalled = true; |
| return true; |
| } |
| bool OnMainStateChanged(MainStateEnum newState) override |
| { |
| mMainStateValue = newState; |
| mMainStateChangedCalled = true; |
| return true; |
| } |
| bool OnOverallCurrentStateChanged(DataModel::Nullable<GenericOverallCurrentState> newState) override |
| { |
| mOverallCurrentStateValue = newState; |
| mOverallCurrentStateChangedCalled = true; |
| return true; |
| } |
| bool OnOverallTargetStateChanged(DataModel::Nullable<GenericOverallTargetState> newState) override |
| { |
| mOverallTargetStateValue = newState; |
| mOverallTargetStateChangedCalled = true; |
| return true; |
| } |
| bool OnCurrentErrorListChanged(DataModel::List<const ClosureErrorEnum> newCurrentErrorList) override |
| { |
| mCurrentErrorListCopy.clear(); |
| for (auto it = newCurrentErrorList.begin(); it != newCurrentErrorList.end(); ++it) |
| { |
| mCurrentErrorListCopy.push_back(*it); |
| } |
| mCurrentErrorListCount = mCurrentErrorListCopy.size(); |
| mCurrentErrorListChangedCalled = true; |
| return true; |
| } |
| |
| bool GetCountdownTimeChangedCalled() const { return mCountdownTimeChangedCalled; } |
| bool GetMainStateChangedCalled() const { return mMainStateChangedCalled; } |
| bool GetOverallCurrentStateChangedCalled() const { return mOverallCurrentStateChangedCalled; } |
| bool GetOverallTargetStateChangedCalled() const { return mOverallTargetStateChangedCalled; } |
| bool GetCurrentErrorListChangedCalled() const { return mCurrentErrorListChangedCalled; } |
| DataModel::Nullable<ElapsedS> GetCountdownTimeValue() const { return mCountdownTimeValue; } |
| MainStateEnum GetMainStateValue() const { return mMainStateValue; } |
| DataModel::Nullable<GenericOverallCurrentState> GetOverallCurrentStateValue() const { return mOverallCurrentStateValue; } |
| DataModel::Nullable<GenericOverallTargetState> GetOverallTargetStateValue() const { return mOverallTargetStateValue; } |
| size_t GetCurrentErrorListCount() const { return mCurrentErrorListCount; } |
| const std::vector<ClosureErrorEnum> & GetCurrentErrorListCopy() const { return mCurrentErrorListCopy; } |
| |
| void Reset() |
| { |
| mCountdownTimeChangedCalled = false; |
| mCountdownTimeValue = DataModel::NullNullable; |
| mMainStateChangedCalled = false; |
| mMainStateValue = MainStateEnum::kUnknownEnumValue; |
| mOverallCurrentStateChangedCalled = false; |
| mOverallCurrentStateValue = DataModel::NullNullable; |
| mOverallTargetStateChangedCalled = false; |
| mOverallTargetStateValue = DataModel::NullNullable; |
| mCurrentErrorListChangedCalled = false; |
| mCurrentErrorListCopy.clear(); |
| mCurrentErrorListCount = 0; |
| } |
| |
| private: |
| bool mCountdownTimeChangedCalled = false; |
| DataModel::Nullable<ElapsedS> mCountdownTimeValue; |
| bool mMainStateChangedCalled = false; |
| MainStateEnum mMainStateValue; |
| bool mOverallCurrentStateChangedCalled = false; |
| DataModel::Nullable<GenericOverallCurrentState> mOverallCurrentStateValue; |
| bool mOverallTargetStateChangedCalled = false; |
| DataModel::Nullable<GenericOverallTargetState> mOverallTargetStateValue; |
| bool mCurrentErrorListChangedCalled = false; |
| std::vector<ClosureErrorEnum> mCurrentErrorListCopy; |
| size_t mCurrentErrorListCount = 0; |
| |
| // Move to command parameters |
| Optional<TargetPositionEnum> lastMoveToPosition = NullOptional; |
| Optional<bool> lastMoveToLatch = NullOptional; |
| Optional<Globals::ThreeLevelAutoEnum> lastMoveToSpeed = NullOptional; |
| }; |
| |
| class MockClusterConformance : public ClusterConformance |
| { |
| public: |
| MockClusterConformance() |
| { |
| // We need at least one feature to be supported from Positioning or MotionLatching (O.a+) |
| // So, we set the Positioning feature by default. |
| FeatureMap().Set(Feature::kPositioning); |
| } |
| }; |
| |
| class TestClosureControlCluster : public ::testing::Test |
| { |
| public: |
| TestClosureControlCluster() : |
| mCluster(kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, mockConformance, initParams }), |
| mClusterTester(mCluster) |
| {} |
| |
| static void SetUpTestSuite() { ASSERT_EQ(Platform::MemoryInit(), CHIP_NO_ERROR); } |
| |
| static void TearDownTestSuite() { Platform::MemoryShutdown(); } |
| |
| void SetUp() override |
| { |
| // Initialize cluster with test context to enable event generation |
| ASSERT_EQ(mCluster.Startup(mClusterTester.GetServerClusterContext()), CHIP_NO_ERROR); |
| initParams.mMainState = MainStateEnum::kStopped; |
| initParams.mOverallCurrentState = DataModel::NullNullable; |
| } |
| |
| void TearDown() override |
| { |
| mCluster.Shutdown(ClusterShutdownType::kClusterShutdown); |
| mockDelegate.Reset(); |
| } |
| |
| static DataModel::Nullable<GenericOverallCurrentState> PositioningState(CurrentPositionEnum position) |
| { |
| return DataModel::Nullable<GenericOverallCurrentState>( |
| GenericOverallCurrentState(Optional(DataModel::MakeNullable(position)), NullOptional, NullOptional)); |
| } |
| |
| static DataModel::Nullable<GenericOverallCurrentState> LatchedState(CurrentPositionEnum position, bool latched, |
| Optional<Globals::ThreeLevelAutoEnum> speed = NullOptional) |
| { |
| return DataModel::Nullable<GenericOverallCurrentState>(GenericOverallCurrentState( |
| Optional(DataModel::MakeNullable(position)), Optional(DataModel::MakeNullable(latched)), speed)); |
| } |
| |
| MockDelegate mockDelegate; |
| MockClusterConformance mockConformance; |
| ClusterInitParameters initParams; |
| const EndpointId kTestEndpointId = 1; |
| ClosureControlCluster mCluster; |
| ClusterTester mClusterTester; |
| }; |
| } // namespace |
| |
| TEST_F(TestClosureControlCluster, TestAttributesList) |
| { |
| std::vector<DataModel::AttributeEntry> expectedAttributes(ClosureControl::Attributes::kMandatoryMetadata.begin(), |
| ClosureControl::Attributes::kMandatoryMetadata.end()); |
| EXPECT_TRUE(IsAttributesListEqualTo(mCluster, expectedAttributes)); |
| |
| MockClusterConformance testConformance; |
| testConformance.OptionalAttributes().Set<Attributes::CountdownTime::Id>(); |
| ClosureControlCluster testCluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, testConformance, initParams }); |
| expectedAttributes.push_back(ClosureControl::Attributes::CountdownTime::kMetadataEntry); |
| EXPECT_TRUE(IsAttributesListEqualTo(testCluster, expectedAttributes)); |
| |
| testConformance.FeatureMap().Set(Feature::kMotionLatching); |
| ClosureControlCluster motionLatchingCluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, testConformance, initParams }); |
| expectedAttributes.push_back(ClosureControl::Attributes::LatchControlModes::kMetadataEntry); |
| EXPECT_TRUE(IsAttributesListEqualTo(motionLatchingCluster, expectedAttributes)); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestMandatoryAcceptedCommands) |
| { |
| EXPECT_TRUE(IsAcceptedCommandsListEqualTo(mCluster, |
| { |
| ClosureControl::Commands::Stop::kMetadataEntry, |
| ClosureControl::Commands::MoveTo::kMetadataEntry, |
| })); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestReadClusterRevision) |
| { |
| uint16_t clusterRevision = 0; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::ClusterRevision::Id, clusterRevision), CHIP_NO_ERROR); |
| EXPECT_EQ(clusterRevision, kRevision); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestReadFeatureMap) |
| { |
| BitFlags<Feature> featureMap; |
| EXPECT_EQ(mClusterTester.ReadAttribute(Attributes::FeatureMap::Id, featureMap), CHIP_NO_ERROR); |
| EXPECT_EQ(featureMap, mockConformance.FeatureMap()); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestCalibrationFeatureMapAndAcceptedCommands) |
| { |
| MockClusterConformance calibrateConformance; |
| calibrateConformance.FeatureMap().Set(Feature::kCalibration); |
| ClosureControlCluster calibrateCluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, calibrateConformance, initParams }); |
| ClusterTester tester(calibrateCluster); |
| BitFlags<Feature> featureMap; |
| EXPECT_EQ(tester.ReadAttribute(Attributes::FeatureMap::Id, featureMap), CHIP_NO_ERROR); |
| EXPECT_EQ(featureMap, calibrateConformance.FeatureMap()); |
| |
| EXPECT_TRUE(IsAcceptedCommandsListEqualTo(calibrateCluster, |
| { |
| ClosureControl::Commands::Stop::kMetadataEntry, |
| ClosureControl::Commands::MoveTo::kMetadataEntry, |
| ClosureControl::Commands::Calibrate::kMetadataEntry, |
| })); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestInstantaneousFeatureMapAndAcceptedCommands) |
| { |
| MockClusterConformance instantaneousConformance; |
| instantaneousConformance.FeatureMap().Set(Feature::kInstantaneous); |
| ClosureControlCluster instantaneousCluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, instantaneousConformance, initParams }); |
| ClusterTester tester(instantaneousCluster); |
| BitFlags<Feature> featureMap; |
| EXPECT_EQ(tester.ReadAttribute(Attributes::FeatureMap::Id, featureMap), CHIP_NO_ERROR); |
| EXPECT_EQ(featureMap, instantaneousConformance.FeatureMap()); |
| |
| EXPECT_TRUE(IsAcceptedCommandsListEqualTo(instantaneousCluster, |
| { |
| ClosureControl::Commands::MoveTo::kMetadataEntry, |
| })); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestMainState) |
| { |
| TestServerClusterContext testContext; |
| MockClusterConformance stateConformance; |
| ClosureControlCluster cluster(kTestEndpointId, |
| ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, stateConformance, initParams }); |
| ASSERT_EQ(cluster.Startup(testContext.Get()), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(cluster.SetMainState(MainStateEnum::kCalibrating), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| EXPECT_EQ(cluster.SetMainState(MainStateEnum::kProtected), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| EXPECT_EQ(cluster.SetMainState(MainStateEnum::kDisengaged), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| |
| stateConformance.FeatureMap().Set(Feature::kCalibration).Set(Feature::kProtection).Set(Feature::kManuallyOperable); |
| ClosureControlCluster featureCluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, stateConformance, initParams }); |
| ASSERT_EQ(featureCluster.Startup(testContext.Get()), CHIP_NO_ERROR); |
| EXPECT_EQ(featureCluster.SetMainState(MainStateEnum::kCalibrating), CHIP_NO_ERROR); |
| EXPECT_EQ(featureCluster.GetMainState(), MainStateEnum::kCalibrating); |
| EXPECT_EQ(mockDelegate.GetMainStateChangedCalled(), true); |
| EXPECT_EQ(mockDelegate.GetMainStateValue(), featureCluster.GetMainState()); |
| EXPECT_EQ(featureCluster.SetMainState(MainStateEnum::kProtected), CHIP_NO_ERROR); |
| EXPECT_EQ(featureCluster.GetMainState(), MainStateEnum::kProtected); |
| EXPECT_EQ(mockDelegate.GetMainStateChangedCalled(), true); |
| EXPECT_EQ(mockDelegate.GetMainStateValue(), featureCluster.GetMainState()); |
| EXPECT_EQ(featureCluster.SetMainState(MainStateEnum::kDisengaged), CHIP_NO_ERROR); |
| EXPECT_EQ(featureCluster.GetMainState(), MainStateEnum::kDisengaged); |
| EXPECT_EQ(mockDelegate.GetMainStateChangedCalled(), true); |
| EXPECT_EQ(mockDelegate.GetMainStateValue(), featureCluster.GetMainState()); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestSetMainStateUpdatesCountdownTime) |
| { |
| MockClusterConformance testConformance; |
| testConformance.FeatureMap().Set(Feature::kCalibration); |
| mockDelegate.calibrationCountdownTime = 33; |
| mockDelegate.movingCountdownTime = 22; |
| mockDelegate.waitingForMotionCountdownTime = 11; |
| ClosureControlCluster cluster(kTestEndpointId, |
| ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, testConformance, initParams }); |
| |
| EXPECT_EQ(cluster.SetMainState(MainStateEnum::kMoving), CHIP_NO_ERROR); |
| DataModel::Nullable<ElapsedS> countdownTime = cluster.GetCountdownTime(); |
| EXPECT_FALSE(countdownTime.IsNull()); |
| EXPECT_EQ(countdownTime.Value(), 22u); |
| EXPECT_EQ(mockDelegate.GetCountdownTimeValue(), countdownTime); |
| EXPECT_EQ(mockDelegate.GetCountdownTimeChangedCalled(), true); |
| |
| EXPECT_EQ(cluster.SetMainState(MainStateEnum::kWaitingForMotion), CHIP_NO_ERROR); |
| countdownTime = cluster.GetCountdownTime(); |
| EXPECT_FALSE(countdownTime.IsNull()); |
| EXPECT_EQ(countdownTime.Value(), 11u); |
| EXPECT_EQ(mockDelegate.GetCountdownTimeValue(), countdownTime); |
| EXPECT_EQ(mockDelegate.GetCountdownTimeChangedCalled(), true); |
| |
| EXPECT_EQ(cluster.SetMainState(MainStateEnum::kCalibrating), CHIP_NO_ERROR); |
| countdownTime = cluster.GetCountdownTime(); |
| EXPECT_FALSE(countdownTime.IsNull()); |
| EXPECT_EQ(countdownTime.Value(), 33u); |
| EXPECT_EQ(mockDelegate.GetCountdownTimeValue(), countdownTime); |
| EXPECT_EQ(mockDelegate.GetCountdownTimeChangedCalled(), true); |
| |
| EXPECT_EQ(cluster.SetMainState(MainStateEnum::kStopped), CHIP_NO_ERROR); |
| countdownTime = cluster.GetCountdownTime(); |
| EXPECT_FALSE(countdownTime.IsNull()); |
| EXPECT_EQ(countdownTime.Value(), 0u); |
| EXPECT_EQ(mockDelegate.GetCountdownTimeValue(), countdownTime); |
| EXPECT_EQ(mockDelegate.GetCountdownTimeChangedCalled(), true); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestSetCountdownTimeFromDelegateUnsupportedFeatures) |
| { |
| MockClusterConformance motionOnlyConformance; |
| motionOnlyConformance.FeatureMap().ClearAll().Set(Feature::kMotionLatching).Set(Feature::kInstantaneous); |
| ClosureControlCluster motionOnlyCluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, motionOnlyConformance, initParams }); |
| EXPECT_EQ(motionOnlyCluster.SetCountdownTimeFromDelegate(DataModel::MakeNullable<ElapsedS>(5)), |
| CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| |
| MockClusterConformance instantaneousConformance; |
| instantaneousConformance.FeatureMap().Set(Feature::kInstantaneous); |
| ClosureControlCluster instantaneousCluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, instantaneousConformance, initParams }); |
| EXPECT_EQ(instantaneousCluster.SetCountdownTimeFromDelegate(DataModel::MakeNullable<ElapsedS>(5)), |
| CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestSetCountdownTimeFromDelegateAndRead) |
| { |
| MockClusterConformance positioningConformance; |
| ClosureControlCluster cluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, positioningConformance, initParams }); |
| |
| EXPECT_EQ(cluster.SetCountdownTimeFromDelegate(DataModel::MakeNullable<ElapsedS>(1)), CHIP_NO_ERROR); |
| EXPECT_EQ(mockDelegate.GetCountdownTimeChangedCalled(), true); |
| DataModel::Nullable<ElapsedS> countdownTime = cluster.GetCountdownTime(); |
| EXPECT_FALSE(countdownTime.IsNull()); |
| EXPECT_EQ(countdownTime.Value(), 1u); |
| EXPECT_EQ(mockDelegate.GetCountdownTimeValue(), countdownTime); |
| |
| EXPECT_EQ(cluster.SetCountdownTimeFromDelegate(DataModel::MakeNullable<ElapsedS>(2)), CHIP_NO_ERROR); |
| EXPECT_EQ(mockDelegate.GetCountdownTimeChangedCalled(), true); |
| countdownTime = cluster.GetCountdownTime(); |
| EXPECT_FALSE(countdownTime.IsNull()); |
| EXPECT_EQ(countdownTime.Value(), 2u); |
| EXPECT_EQ(mockDelegate.GetCountdownTimeValue(), countdownTime); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestSetOverallCurrentStateFeatureValidation) |
| { |
| MockClusterConformance positioningConformance; |
| ClosureControlCluster cluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, positioningConformance, initParams }); |
| |
| DataModel::Nullable<GenericOverallCurrentState> validState(GenericOverallCurrentState( |
| Optional(DataModel::MakeNullable(CurrentPositionEnum::kFullyOpened)), NullOptional, NullOptional)); |
| EXPECT_EQ(cluster.SetOverallCurrentState(validState), CHIP_NO_ERROR); |
| EXPECT_EQ(mockDelegate.GetOverallCurrentStateChangedCalled(), true); |
| EXPECT_EQ(mockDelegate.GetOverallCurrentStateValue(), cluster.GetOverallCurrentState()); |
| |
| mockDelegate.Reset(); |
| DataModel::Nullable<GenericOverallCurrentState> invalidSpeed( |
| GenericOverallCurrentState(Optional(DataModel::MakeNullable(CurrentPositionEnum::kFullyOpened)), NullOptional, |
| Optional(Globals::ThreeLevelAutoEnum::kLow))); |
| EXPECT_EQ(cluster.SetOverallCurrentState(invalidSpeed), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| EXPECT_EQ(mockDelegate.GetOverallCurrentStateChangedCalled(), false); |
| |
| mockDelegate.Reset(); |
| DataModel::Nullable<GenericOverallCurrentState> invalidLatch( |
| GenericOverallCurrentState(Optional(DataModel::MakeNullable(CurrentPositionEnum::kFullyOpened)), |
| Optional(DataModel::MakeNullable(true)), NullOptional)); |
| EXPECT_EQ(cluster.SetOverallCurrentState(invalidLatch), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| EXPECT_EQ(mockDelegate.GetOverallCurrentStateChangedCalled(), false); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestSetOverallCurrentStateSecureStateValidation) |
| { |
| TestServerClusterContext testContext; |
| MockClusterConformance positioningConformance; |
| ClosureControlCluster cluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, positioningConformance, initParams }); |
| ASSERT_EQ(cluster.Startup(testContext.Get()), CHIP_NO_ERROR); |
| |
| DataModel::Nullable<GenericOverallCurrentState> invalidSecureState( |
| GenericOverallCurrentState(Optional(DataModel::MakeNullable(CurrentPositionEnum::kPartiallyOpened)), NullOptional, |
| NullOptional, DataModel::MakeNullable(true))); |
| EXPECT_EQ(cluster.SetOverallCurrentState(invalidSecureState), CHIP_ERROR_INVALID_ARGUMENT); |
| |
| DataModel::Nullable<GenericOverallCurrentState> validSecureState( |
| GenericOverallCurrentState(Optional(DataModel::MakeNullable(CurrentPositionEnum::kFullyClosed)), NullOptional, NullOptional, |
| DataModel::MakeNullable(true))); |
| EXPECT_EQ(cluster.SetOverallCurrentState(validSecureState), CHIP_NO_ERROR); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestHandleCalibrate) |
| { |
| MockClusterConformance positioningConformance; |
| ClosureControlCluster positioningCluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, positioningConformance, initParams }); |
| EXPECT_EQ(positioningCluster.HandleCalibrate(), Status::UnsupportedCommand); |
| |
| MockClusterConformance calibratingConformance; |
| calibratingConformance.FeatureMap().Set(Feature::kCalibration); |
| ClosureControlCluster cluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, calibratingConformance, initParams }); |
| EXPECT_EQ(cluster.SetMainState(MainStateEnum::kMoving), CHIP_NO_ERROR); |
| EXPECT_EQ(mockDelegate.GetMainStateChangedCalled(), true); |
| EXPECT_EQ(mockDelegate.GetMainStateValue(), cluster.GetMainState()); |
| |
| mockDelegate.Reset(); |
| EXPECT_EQ(cluster.HandleCalibrate(), Status::InvalidInState); |
| EXPECT_EQ(cluster.GetMainState(), MainStateEnum::kMoving); |
| EXPECT_EQ(mockDelegate.GetMainStateChangedCalled(), false); |
| |
| mockDelegate.Reset(); |
| EXPECT_EQ(cluster.SetMainState(MainStateEnum::kStopped), CHIP_NO_ERROR); |
| EXPECT_EQ(mockDelegate.GetMainStateChangedCalled(), true); |
| EXPECT_EQ(mockDelegate.GetMainStateValue(), cluster.GetMainState()); |
| EXPECT_EQ(cluster.HandleCalibrate(), Status::Success); |
| EXPECT_EQ(mockDelegate.calibrateCommandCalls, 1); |
| |
| MainStateEnum state = cluster.GetMainState(); |
| EXPECT_EQ(state, MainStateEnum::kCalibrating); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestHandleCalibrateDelegateFailure) |
| { |
| MockClusterConformance testConformance; |
| testConformance.FeatureMap().Set(Feature::kCalibration); |
| mockDelegate.calibrateCommandStatus = Status::Busy; |
| ClosureControlCluster cluster(kTestEndpointId, |
| ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, testConformance, initParams }); |
| |
| EXPECT_EQ(cluster.SetMainState(MainStateEnum::kStopped), CHIP_NO_ERROR); |
| EXPECT_EQ(cluster.HandleCalibrate(), Status::Busy); |
| EXPECT_EQ(mockDelegate.calibrateCommandCalls, 1); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestHandleStop) |
| { |
| MockClusterConformance testConformance; |
| testConformance.FeatureMap().Set(Feature::kCalibration); |
| ClosureControlCluster cluster(kTestEndpointId, |
| ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, testConformance, initParams }); |
| |
| EXPECT_EQ(cluster.SetMainState(MainStateEnum::kMoving), CHIP_NO_ERROR); |
| EXPECT_EQ(mockDelegate.GetMainStateChangedCalled(), true); |
| EXPECT_EQ(mockDelegate.GetMainStateValue(), cluster.GetMainState()); |
| EXPECT_EQ(cluster.HandleStop(), Status::Success); |
| EXPECT_EQ(mockDelegate.stopCommandCalls, 1); |
| |
| MainStateEnum state = cluster.GetMainState(); |
| EXPECT_EQ(state, MainStateEnum::kStopped); |
| |
| EXPECT_EQ(cluster.SetMainState(MainStateEnum::kError), CHIP_NO_ERROR); |
| EXPECT_EQ(cluster.HandleStop(), Status::Success); |
| EXPECT_EQ(mockDelegate.GetMainStateChangedCalled(), true); |
| EXPECT_EQ(mockDelegate.GetMainStateValue(), cluster.GetMainState()); |
| EXPECT_EQ(mockDelegate.stopCommandCalls, 1); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestHandleStopFeatureAndDelegateFailure) |
| { |
| MockClusterConformance instantaneousConformance; |
| instantaneousConformance.FeatureMap().Set(Feature::kInstantaneous); |
| ClosureControlCluster instantaneousCluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, instantaneousConformance, initParams }); |
| EXPECT_EQ(instantaneousCluster.HandleStop(), Status::UnsupportedCommand); |
| |
| MockClusterConformance positioningConformance; |
| mockDelegate.stopCommandStatus = Status::Busy; |
| ClosureControlCluster busyCluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, positioningConformance, initParams }); |
| EXPECT_EQ(busyCluster.SetMainState(MainStateEnum::kMoving), CHIP_NO_ERROR); |
| EXPECT_EQ(busyCluster.HandleStop(), Status::Busy); |
| EXPECT_EQ(mockDelegate.stopCommandCalls, 1); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestHandleMoveToNoArgumentsAndInvalidState) |
| { |
| MockClusterConformance positioningConformance; |
| ClosureControlCluster cluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, positioningConformance, initParams }); |
| EXPECT_EQ(cluster.SetOverallCurrentState(PositioningState(CurrentPositionEnum::kPartiallyOpened)), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(cluster.HandleMoveTo(NullOptional, NullOptional, NullOptional), Status::InvalidCommand); |
| |
| EXPECT_EQ(cluster.SetMainState(MainStateEnum::kError), CHIP_NO_ERROR); |
| EXPECT_EQ(cluster.HandleMoveTo(Optional(TargetPositionEnum::kMoveToFullyOpen), NullOptional, NullOptional), |
| Status::InvalidInState); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestHandleMoveToAllFeatures) |
| { |
| MockClusterConformance testConformance; |
| testConformance.FeatureMap().Set(Feature::kMotionLatching).Set(Feature::kSpeed); |
| ClosureControlCluster cluster(kTestEndpointId, |
| ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, testConformance, initParams }); |
| EXPECT_EQ(cluster.SetLatchControlModes(BitFlags<LatchControlModesBitmap>() |
| .Set(LatchControlModesBitmap::kRemoteLatching) |
| .Set(LatchControlModesBitmap::kRemoteUnlatching)), |
| CHIP_NO_ERROR); |
| EXPECT_EQ(cluster.SetOverallCurrentState( |
| LatchedState(CurrentPositionEnum::kPartiallyOpened, true, Optional(Globals::ThreeLevelAutoEnum::kLow))), |
| CHIP_NO_ERROR); |
| EXPECT_EQ(cluster.HandleMoveTo(Optional(TargetPositionEnum::kMoveToFullyOpen), Optional(false), |
| Optional(Globals::ThreeLevelAutoEnum::kHigh)), |
| Status::Success); |
| EXPECT_EQ(mockDelegate.moveToCommandCalls, 1); |
| |
| DataModel::Nullable<GenericOverallTargetState> targetState = cluster.GetOverallTargetState(); |
| EXPECT_FALSE(targetState.IsNull()); |
| EXPECT_EQ(targetState.Value().position.Value().Value(), TargetPositionEnum::kMoveToFullyOpen); |
| EXPECT_EQ(targetState.Value().latch.Value().Value(), false); |
| EXPECT_EQ(targetState.Value().speed.Value(), Globals::ThreeLevelAutoEnum::kHigh); |
| EXPECT_EQ(mockDelegate.GetOverallTargetStateValue(), targetState); |
| |
| MainStateEnum state = cluster.GetMainState(); |
| EXPECT_EQ(state, MainStateEnum::kMoving); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestHandleMoveToTransitionsToWaitingWhenNotReady) |
| { |
| MockClusterConformance positioningConformance; |
| mockDelegate.isReadyToMove = false; |
| ClosureControlCluster cluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, positioningConformance, initParams }); |
| EXPECT_EQ(cluster.SetOverallCurrentState(PositioningState(CurrentPositionEnum::kPartiallyOpened)), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(cluster.HandleMoveTo(Optional(TargetPositionEnum::kMoveToFullyOpen), NullOptional, NullOptional), Status::Success); |
| MainStateEnum state = cluster.GetMainState(); |
| EXPECT_EQ(state, MainStateEnum::kWaitingForMotion); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestHandleMoveToLatchedPositionChangeRequiresUnlatch) |
| { |
| MockClusterConformance testConformance; |
| testConformance.FeatureMap().Set(Feature::kMotionLatching); |
| ClosureControlCluster cluster(kTestEndpointId, |
| ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, testConformance, initParams }); |
| EXPECT_EQ(cluster.SetLatchControlModes(BitFlags<LatchControlModesBitmap>() |
| .Set(LatchControlModesBitmap::kRemoteLatching) |
| .Set(LatchControlModesBitmap::kRemoteUnlatching)), |
| CHIP_NO_ERROR); |
| EXPECT_EQ(cluster.SetOverallCurrentState(LatchedState(CurrentPositionEnum::kPartiallyOpened, true)), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(cluster.HandleMoveTo(Optional(TargetPositionEnum::kMoveToFullyOpen), NullOptional, NullOptional), |
| Status::InvalidInState); |
| EXPECT_EQ(cluster.HandleMoveTo(Optional(TargetPositionEnum::kMoveToFullyOpen), Optional(true), NullOptional), |
| Status::InvalidInState); |
| EXPECT_EQ(cluster.HandleMoveTo(Optional(TargetPositionEnum::kMoveToFullyOpen), Optional(false), NullOptional), Status::Success); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestHandleMoveToRemoteLatchingConformanceChecks) |
| { |
| MockClusterConformance testConformance; |
| testConformance.FeatureMap().Set(Feature::kMotionLatching); |
| ClosureControlCluster cluster(kTestEndpointId, |
| ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, testConformance, initParams }); |
| EXPECT_EQ(cluster.SetOverallCurrentState(DataModel::Nullable<GenericOverallCurrentState>( |
| GenericOverallCurrentState(NullOptional, Optional(DataModel::MakeNullable(true)), NullOptional))), |
| CHIP_NO_ERROR); |
| |
| EXPECT_EQ(cluster.SetLatchControlModes(BitFlags<LatchControlModesBitmap>().Set(LatchControlModesBitmap::kRemoteLatching)), |
| CHIP_NO_ERROR); |
| EXPECT_EQ(cluster.HandleMoveTo(NullOptional, Optional(false), NullOptional), Status::InvalidInState); |
| |
| EXPECT_EQ(cluster.SetLatchControlModes(BitFlags<LatchControlModesBitmap>().Set(LatchControlModesBitmap::kRemoteUnlatching)), |
| CHIP_NO_ERROR); |
| EXPECT_EQ(cluster.HandleMoveTo(NullOptional, Optional(true), NullOptional), Status::InvalidInState); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestHandleMoveToDelegateFailure) |
| { |
| MockClusterConformance positioningConformance; |
| positioningConformance.FeatureMap().Set(Feature::kPositioning); |
| mockDelegate.moveToCommandStatus = Status::Busy; |
| ClosureControlCluster cluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, positioningConformance, initParams }); |
| EXPECT_EQ(cluster.SetOverallCurrentState(PositioningState(CurrentPositionEnum::kPartiallyOpened)), CHIP_NO_ERROR); |
| EXPECT_EQ(cluster.HandleMoveTo(Optional(TargetPositionEnum::kMoveToFullyOpen), NullOptional, NullOptional), Status::Busy); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestHandleMoveToConstraintValidation) |
| { |
| MockClusterConformance testConformance; |
| testConformance.FeatureMap().Set(Feature::kMotionLatching).Set(Feature::kSpeed); |
| ClosureControlCluster cluster(kTestEndpointId, |
| ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, testConformance, initParams }); |
| EXPECT_EQ(cluster.SetOverallCurrentState( |
| LatchedState(CurrentPositionEnum::kPartiallyOpened, false, Optional(Globals::ThreeLevelAutoEnum::kLow))), |
| CHIP_NO_ERROR); |
| |
| EXPECT_EQ(cluster.HandleMoveTo(Optional(TargetPositionEnum::kUnknownEnumValue), NullOptional, NullOptional), |
| Status::ConstraintError); |
| EXPECT_EQ(cluster.HandleMoveTo(NullOptional, NullOptional, Optional(Globals::ThreeLevelAutoEnum::kUnknownEnumValue)), |
| Status::ConstraintError); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestErrorListLifecycle) |
| { |
| MockClusterConformance positioningConformance; |
| positioningConformance.FeatureMap().Set(Feature::kPositioning); |
| ClosureControlCluster cluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, positioningConformance, initParams }); |
| |
| ClosureErrorEnum list[kCurrentErrorListMaxSize] = {}; |
| Span<ClosureErrorEnum> errorSpan(list); |
| EXPECT_EQ(cluster.GetCurrentErrorList(errorSpan), CHIP_NO_ERROR); |
| EXPECT_TRUE(errorSpan.empty()); |
| |
| EXPECT_NE(cluster.AddErrorToCurrentErrorList(ClosureErrorEnum::kBlockedBySensor), CHIP_ERROR_DUPLICATE_MESSAGE_RECEIVED); |
| EXPECT_EQ(cluster.AddErrorToCurrentErrorList(ClosureErrorEnum::kBlockedBySensor), CHIP_ERROR_DUPLICATE_MESSAGE_RECEIVED); |
| |
| errorSpan = Span<ClosureErrorEnum>(list); |
| EXPECT_EQ(cluster.GetCurrentErrorList(errorSpan), CHIP_NO_ERROR); |
| EXPECT_EQ(errorSpan.size(), 1u); |
| EXPECT_EQ(errorSpan[0], ClosureErrorEnum::kBlockedBySensor); |
| |
| MainStateEnum state = cluster.GetMainState(); |
| EXPECT_EQ(state, MainStateEnum::kError); |
| |
| cluster.ClearCurrentErrorList(); |
| errorSpan = Span<ClosureErrorEnum>(list); |
| EXPECT_EQ(cluster.GetCurrentErrorList(errorSpan), CHIP_NO_ERROR); |
| EXPECT_TRUE(errorSpan.empty()); |
| |
| // Event generation errors are expected hence only logging the errors |
| LogErrorOnFailure(cluster.AddErrorToCurrentErrorList(ClosureErrorEnum::kPhysicallyBlocked)); |
| LogErrorOnFailure(cluster.AddErrorToCurrentErrorList(ClosureErrorEnum::kTemperatureLimited)); |
| LogErrorOnFailure(cluster.AddErrorToCurrentErrorList(ClosureErrorEnum::kBlockedBySensor)); |
| errorSpan = Span<ClosureErrorEnum>(list); |
| EXPECT_EQ(cluster.GetCurrentErrorList(errorSpan), CHIP_NO_ERROR); |
| EXPECT_EQ(errorSpan.size(), 3u); |
| EXPECT_EQ(errorSpan[0], ClosureErrorEnum::kPhysicallyBlocked); |
| EXPECT_EQ(errorSpan[1], ClosureErrorEnum::kTemperatureLimited); |
| EXPECT_EQ(errorSpan[2], ClosureErrorEnum::kBlockedBySensor); |
| EXPECT_EQ(mockDelegate.GetCurrentErrorListChangedCalled(), true); |
| // Cluster calls OnCurrentErrorListChanged with the list before each add; the last call (before 3rd add) had 2 elements. |
| const std::vector<ClosureErrorEnum> & delegateList = mockDelegate.GetCurrentErrorListCopy(); |
| EXPECT_EQ(delegateList.size(), 2u); |
| EXPECT_EQ(delegateList[0], ClosureErrorEnum::kPhysicallyBlocked); |
| EXPECT_EQ(delegateList[1], ClosureErrorEnum::kTemperatureLimited); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestErrorListBufferSizeValidation) |
| { |
| MockClusterConformance positioningConformance; |
| ClosureControlCluster cluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, positioningConformance, initParams }); |
| EXPECT_NE(cluster.AddErrorToCurrentErrorList(ClosureErrorEnum::kBlockedBySensor), CHIP_ERROR_DUPLICATE_MESSAGE_RECEIVED); |
| |
| ClosureErrorEnum shortList[1] = {}; |
| Span<ClosureErrorEnum> shortSpan(shortList); |
| EXPECT_EQ(cluster.GetCurrentErrorList(shortSpan), CHIP_ERROR_BUFFER_TOO_SMALL); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestErrorListInvalidUnknownValue) |
| { |
| MockClusterConformance positioningConformance; |
| ClosureControlCluster cluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, positioningConformance, initParams }); |
| |
| EXPECT_EQ(cluster.AddErrorToCurrentErrorList(ClosureErrorEnum::kUnknownEnumValue), CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestLatchControlModesFeatureValidation) |
| { |
| MockClusterConformance positioningConformance; |
| ClosureControlCluster positioningCluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, positioningConformance, initParams }); |
| |
| EXPECT_EQ( |
| positioningCluster.SetLatchControlModes(BitFlags<LatchControlModesBitmap>().Set(LatchControlModesBitmap::kRemoteLatching)), |
| CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| BitFlags<LatchControlModesBitmap> modes = positioningCluster.GetLatchControlModes(); |
| EXPECT_EQ(modes, BitFlags<LatchControlModesBitmap>()); |
| |
| MockClusterConformance latchConformance; |
| latchConformance.FeatureMap().Set(Feature::kMotionLatching); |
| ClosureControlCluster latchCluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, latchConformance, initParams }); |
| BitFlags<LatchControlModesBitmap> configuredModes = latchCluster.GetLatchControlModes(); |
| configuredModes.Set(LatchControlModesBitmap::kRemoteLatching).Set(LatchControlModesBitmap::kRemoteUnlatching); |
| EXPECT_EQ(latchCluster.SetLatchControlModes(configuredModes), CHIP_NO_ERROR); |
| EXPECT_EQ(latchCluster.GetLatchControlModes(), configuredModes); |
| } |
| |
| // Testing only feature validation. |
| // Event generation failure is expected. |
| TEST_F(TestClosureControlCluster, TestGenerateMovementCompletedEvent) |
| { |
| TestServerClusterContext testContext; |
| MockClusterConformance positioningConformance; |
| ClosureControlCluster positioningCluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, positioningConformance, initParams }); |
| ASSERT_EQ(positioningCluster.Startup(testContext.Get()), CHIP_NO_ERROR); |
| EXPECT_NE(positioningCluster.GenerateMovementCompletedEvent(), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| |
| MockClusterConformance instantaneousConformance; |
| instantaneousConformance.FeatureMap().Set(Feature::kInstantaneous); |
| ClosureControlCluster instantaneousCluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, instantaneousConformance, initParams }); |
| ASSERT_EQ(instantaneousCluster.Startup(testContext.Get()), CHIP_NO_ERROR); |
| EXPECT_EQ(instantaneousCluster.GenerateMovementCompletedEvent(), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| } |
| |
| TEST_F(TestClosureControlCluster, TestGenerateEngageStateChangedEvent) |
| { |
| TestServerClusterContext testContext; |
| MockClusterConformance positioningConformance; |
| ClosureControlCluster positioningCluster( |
| kTestEndpointId, ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, positioningConformance, initParams }); |
| ASSERT_EQ(positioningCluster.Startup(testContext.Get()), CHIP_NO_ERROR); |
| EXPECT_EQ(positioningCluster.GenerateEngageStateChangedEvent(true), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| |
| MockClusterConformance manuallyOperableConformance; |
| manuallyOperableConformance.FeatureMap().Set(Feature::kManuallyOperable); |
| ClosureControlCluster manuallyOperableCluster( |
| kTestEndpointId, |
| ClosureControlCluster::Context{ mockDelegate, mockTimerDelegate, manuallyOperableConformance, initParams }); |
| ASSERT_EQ(manuallyOperableCluster.Startup(testContext.Get()), CHIP_NO_ERROR); |
| EXPECT_NE(manuallyOperableCluster.GenerateEngageStateChangedEvent(true), CHIP_ERROR_UNSUPPORTED_CHIP_FEATURE); |
| } |