blob: d1323cb4820e15edaadf872c9ff907f9b68b3b7a [file]
/*
* Copyright (c) 2025 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 <app/clusters/boolean-state-server/boolean-state-cluster.h>
#include <pw_unit_test/framework.h>
#include <app/clusters/testing/AttributeTesting.h>
#include <app/server-cluster/AttributeListBuilder.h>
#include <app/server-cluster/DefaultServerCluster.h>
#include <app/server-cluster/testing/TestEventGenerator.h>
#include <app/server-cluster/testing/TestServerClusterContext.h>
#include <clusters/BooleanState/Attributes.h>
#include <clusters/BooleanState/Metadata.h>
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::BooleanState;
using namespace chip::app::Clusters::BooleanState::Attributes;
namespace {
struct TestBooleanStateCluster : public ::testing::Test
{
static void SetUpTestSuite() { ASSERT_EQ(Platform::MemoryInit(), CHIP_NO_ERROR); }
static void TearDownTestSuite() { Platform::MemoryShutdown(); }
};
constexpr size_t kBooleanStateFixedClusterCount = 2;
class BooleanStateClusterTest
{
BooleanStateCluster booleanState;
public:
template <typename... Args>
BooleanStateClusterTest(Args &&... args) : booleanState(std::forward<Args>(args)...)
{}
template <typename F>
void Check(F check)
{
check(booleanState);
}
};
class ReadAttribute
{
BooleanStateCluster & mCluster;
DataModel::ActionReturnStatus mStatus;
public:
ReadAttribute(BooleanStateCluster & cluster) : mCluster(cluster), mStatus(CHIP_NO_ERROR) {}
DataModel::ActionReturnStatus GetStatus() const { return mStatus; }
void operator()(DataModel::ReadAttributeRequest & request)
{
Platform::ScopedMemoryBufferWithSize<uint8_t> buffer;
ASSERT_NE(buffer.Alloc(1024).Get(), nullptr);
AttributeReportIBs::Builder attributeReportIBsBuilder;
TLV::TLVWriter reportWriter;
reportWriter.Init(buffer.Get(), buffer.AllocatedSize());
ASSERT_EQ(attributeReportIBsBuilder.Init(&reportWriter), CHIP_NO_ERROR);
AttributeValueEncoder encoder(attributeReportIBsBuilder, Access::SubjectDescriptor{}, request.path, 0 /* dataVersion */);
mStatus = mCluster.ReadAttribute(request, encoder);
}
};
} // namespace
TEST_F(TestBooleanStateCluster, AttributeTest)
{
for (EndpointId endpoint = 0; endpoint < kBooleanStateFixedClusterCount; ++endpoint)
{
BooleanStateClusterTest(endpoint).Check([&](BooleanStateCluster & booleanState) {
ReadOnlyBufferBuilder<DataModel::AttributeEntry> attributes;
ASSERT_EQ(booleanState.Attributes(ConcreteClusterPath(endpoint, BooleanState::Id), attributes), CHIP_NO_ERROR);
ReadOnlyBufferBuilder<DataModel::AttributeEntry> expected;
AttributeListBuilder listBuilder(expected);
ASSERT_EQ(listBuilder.Append(Span(BooleanState::Attributes::kMandatoryMetadata), {}), CHIP_NO_ERROR);
ASSERT_TRUE(Testing::EqualAttributeSets(attributes.TakeBuffer(), expected.TakeBuffer()));
});
}
}
TEST_F(TestBooleanStateCluster, ReadAttributeTest)
{
for (EndpointId endpoint = 0; endpoint < kBooleanStateFixedClusterCount; ++endpoint)
{
BooleanStateClusterTest(endpoint).Check([&](BooleanStateCluster & booleanState) {
chip::Test::TestServerClusterContext context;
EXPECT_EQ(booleanState.Startup(context.Get()), CHIP_NO_ERROR);
DataModel::ReadAttributeRequest request;
request.path.mEndpointId = endpoint;
request.path.mClusterId = BooleanState::Id;
request.path.mAttributeId = Globals::Attributes::ClusterRevision::Id;
ReadAttribute readAttribute(booleanState);
readAttribute(request);
EXPECT_TRUE(readAttribute.GetStatus().IsSuccess());
request.path.mAttributeId = FeatureMap::Id;
readAttribute(request);
EXPECT_TRUE(readAttribute.GetStatus().IsSuccess());
request.path.mAttributeId = StateValue::Id;
readAttribute(request);
EXPECT_TRUE(readAttribute.GetStatus().IsSuccess());
request.path.mAttributeId = 0xFFFF;
readAttribute(request);
EXPECT_TRUE(readAttribute.GetStatus().IsError());
});
}
}
TEST_F(TestBooleanStateCluster, StateValue)
{
for (EndpointId endpoint = 0; endpoint < kBooleanStateFixedClusterCount; ++endpoint)
{
BooleanStateClusterTest(endpoint).Check([](BooleanStateCluster & booleanState) {
chip::Test::TestServerClusterContext context;
EXPECT_EQ(booleanState.Startup(context.Get()), CHIP_NO_ERROR);
bool stateValue = false;
auto eventNumber = booleanState.SetStateValue(stateValue);
auto stateVal = booleanState.GetStateValue();
EXPECT_EQ(stateVal, stateValue);
EXPECT_FALSE(eventNumber.has_value());
stateValue = true;
eventNumber = booleanState.SetStateValue(stateValue);
stateVal = booleanState.GetStateValue();
EXPECT_EQ(stateVal, stateValue);
EXPECT_TRUE(eventNumber.has_value());
stateValue = true;
eventNumber = booleanState.SetStateValue(stateValue);
stateVal = booleanState.GetStateValue();
EXPECT_EQ(stateVal, stateValue);
EXPECT_FALSE(eventNumber.has_value());
stateValue = false;
eventNumber = booleanState.SetStateValue(stateValue);
stateVal = booleanState.GetStateValue();
EXPECT_EQ(stateVal, stateValue);
EXPECT_TRUE(eventNumber.has_value());
});
}
}
TEST_F(TestBooleanStateCluster, EventGeneratedOnStateChange)
{
for (EndpointId endpoint = 0; endpoint < kBooleanStateFixedClusterCount; ++endpoint)
{
BooleanStateClusterTest(endpoint).Check([&](BooleanStateCluster & booleanState) {
chip::Test::TestServerClusterContext context;
EXPECT_EQ(booleanState.Startup(context.Get()), CHIP_NO_ERROR);
// Ensure initial value is false, then change to true and expect an event
EXPECT_EQ(booleanState.GetStateValue(), false);
auto eventNumber = booleanState.SetStateValue(true);
auto & logOnlyEvents = context.EventsGenerator();
using EventType = chip::app::Clusters::BooleanState::Events::StateChange::Type;
// Lambda to verify the last emitted event metadata and payload
auto verifyLastEvent = [&](bool expectedStateValue) {
ASSERT_TRUE(eventNumber.has_value());
EXPECT_EQ(eventNumber.value(), logOnlyEvents.CurrentEventNumber());
EXPECT_EQ(logOnlyEvents.LastOptions().mPath,
ConcreteEventPath(endpoint, EventType::GetClusterId(), EventType::GetEventId()));
chip::app::Clusters::BooleanState::Events::StateChange::DecodableType decodedEvent;
ASSERT_EQ(logOnlyEvents.DecodeLastEvent(decodedEvent), CHIP_NO_ERROR);
EXPECT_EQ(decodedEvent.stateValue, expectedStateValue);
};
// Verify event with expected true value
verifyLastEvent(true);
// Now, change from true to false and expect an event
eventNumber = booleanState.SetStateValue(false);
// Verify event with expected false value
verifyLastEvent(false);
});
}
}
TEST_F(TestBooleanStateCluster, NoEventWhenValueUnchanged)
{
for (EndpointId endpoint = 0; endpoint < kBooleanStateFixedClusterCount; ++endpoint)
{
BooleanStateClusterTest(endpoint).Check([&](BooleanStateCluster & booleanState) {
chip::Test::TestServerClusterContext context;
EXPECT_EQ(booleanState.Startup(context.Get()), CHIP_NO_ERROR);
// Ensure initial value is false, then try to set false again and confirm no event occurs
EXPECT_EQ(booleanState.GetStateValue(), false);
// Get initial event count before attempting to set the same value
auto & logOnlyEvents = context.EventsGenerator();
EventNumber initialCount = logOnlyEvents.CurrentEventNumber();
// Re-set to the same value (false) and confirm no new event is generated
auto firstEvent = booleanState.SetStateValue(false);
EXPECT_FALSE(firstEvent.has_value());
EXPECT_EQ(logOnlyEvents.CurrentEventNumber(), initialCount);
// Change from false -> true and confirm an event occurs
auto secondEvent = booleanState.SetStateValue(true);
EXPECT_TRUE(secondEvent.has_value());
EXPECT_EQ(logOnlyEvents.CurrentEventNumber(), initialCount + 1);
// Re-set to the same value (true) and confirm no new event is generated
EventNumber eventCountAfterChange = logOnlyEvents.CurrentEventNumber();
auto thirdEvent = booleanState.SetStateValue(true);
EXPECT_FALSE(thirdEvent.has_value());
EXPECT_EQ(logOnlyEvents.CurrentEventNumber(), eventCountAfterChange);
});
}
}