blob: 5c3c5491a27cffcc1444ead988a4c665b91fdbb3 [file] [log] [blame]
/*
* 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 <pw_unit_test/framework.h>
#include <access/Privilege.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app/ConcreteClusterPath.h>
#include <app/data-model-provider/MetadataTypes.h>
#include <app/data-model-provider/OperationTypes.h>
#include <app/data-model-provider/tests/ReadTesting.h>
#include <app/data-model-provider/tests/WriteTesting.h>
#include <app/server-cluster/DefaultServerCluster.h>
#include <app/server-cluster/ServerClusterContext.h>
#include <app/server-cluster/testing/TestServerClusterContext.h>
#include <lib/core/CHIPError.h>
#include <lib/core/DataModelTypes.h>
#include <lib/core/StringBuilderAdapters.h>
#include <lib/core/TLVReader.h>
#include <lib/support/ReadOnlyBuffer.h>
#include <cstdlib>
#include <optional>
using namespace chip;
using namespace chip::Test;
using namespace chip::app;
using namespace chip::app::Clusters;
using namespace chip::app::DataModel;
using namespace chip::app::Testing;
using namespace chip::Protocols::InteractionModel;
namespace {
class FakeDefaultServerCluster : public DefaultServerCluster
{
public:
FakeDefaultServerCluster(ConcreteClusterPath path) : DefaultServerCluster(path) {}
DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request,
AttributeValueEncoder & encoder) override
{
switch (request.path.mAttributeId)
{
case Globals::Attributes::FeatureMap::Id:
return encoder.Encode<uint32_t>(0);
case Globals::Attributes::ClusterRevision::Id:
return encoder.Encode<uint32_t>(123);
}
return CHIP_ERROR_INVALID_ARGUMENT;
}
void TestIncreaseDataVersion() { IncreaseDataVersion(); }
void TestNotifyAttributeChanged(AttributeId attributeId) { NotifyAttributeChanged(attributeId); }
};
} // namespace
TEST(TestDefaultServerCluster, TestDataVersion)
{
FakeDefaultServerCluster cluster({ 1, 2 });
DataVersion v1 = cluster.GetDataVersion({ 1, 2 });
cluster.TestIncreaseDataVersion();
ASSERT_EQ(cluster.GetDataVersion({ 1, 2 }), v1 + 1);
}
TEST(TestDefaultServerCluster, TestFlagsDefault)
{
FakeDefaultServerCluster cluster({ 1, 2 });
ASSERT_EQ(cluster.GetClusterFlags({ 1, 2 }).Raw(), 0u);
}
TEST(TestDefaultServerCluster, ListWriteNotification)
{
FakeDefaultServerCluster cluster({ 1, 2 });
// this does not test anything really, except we get 100% coverage and we see that we do not crash
cluster.ListAttributeWriteNotification({ 1, 2, 3 }, DataModel::ListWriteOperation::kListWriteBegin);
cluster.ListAttributeWriteNotification({ 1, 2, 3 }, DataModel::ListWriteOperation::kListWriteFailure);
}
TEST(TestDefaultServerCluster, AttributesDefault)
{
FakeDefaultServerCluster cluster({ 1, 2 });
ReadOnlyBufferBuilder<AttributeEntry> attributes;
ASSERT_EQ(cluster.Attributes({ 1, 1 }, attributes), CHIP_NO_ERROR);
ReadOnlyBuffer<AttributeEntry> data = attributes.TakeBuffer();
// 5 global attributes are currently supported. Ensure they are returned.
ASSERT_EQ(data.size(), 5u);
ASSERT_EQ(data[0].attributeId, Globals::Attributes::ClusterRevision::Id);
ASSERT_EQ(data[1].attributeId, Globals::Attributes::FeatureMap::Id);
ASSERT_EQ(data[2].attributeId, Globals::Attributes::AttributeList::Id);
ASSERT_EQ(data[3].attributeId, Globals::Attributes::AcceptedCommandList::Id);
ASSERT_EQ(data[4].attributeId, Globals::Attributes::GeneratedCommandList::Id);
// first 2 are normal, the rest are list
for (size_t i = 0; i < 5; i++)
{
ASSERT_EQ(data[i].HasFlags(AttributeQualityFlags::kListAttribute), (i >= 2));
ASSERT_EQ(data[i].GetReadPrivilege(), Access::Privilege::kView);
ASSERT_FALSE(data[i].GetWritePrivilege().has_value());
}
}
TEST(TestDefaultServerCluster, ListWriteIsANoop)
{
FakeDefaultServerCluster cluster({ 1, 2 });
// this is really for coverage, we are not calling anything useful
cluster.ListAttributeWriteNotification({ 1 /* endpoint */, 2 /* cluster */, 3 /* attribute */ },
DataModel::ListWriteOperation::kListWriteBegin);
cluster.ListAttributeWriteNotification({ 1 /* endpoint */, 2 /* cluster */, 3 /* attribute */ },
DataModel::ListWriteOperation::kListWriteSuccess);
}
TEST(TestDefaultServerCluster, CommandsDefault)
{
FakeDefaultServerCluster cluster({ 1, 2 });
ReadOnlyBufferBuilder<AcceptedCommandEntry> acceptedCommands;
ASSERT_EQ(cluster.AcceptedCommands({ 1, 1 }, acceptedCommands), CHIP_NO_ERROR);
ASSERT_TRUE(acceptedCommands.TakeBuffer().empty());
ReadOnlyBufferBuilder<CommandId> generatedCommands;
ASSERT_EQ(cluster.GeneratedCommands({ 1, 1 }, generatedCommands), CHIP_NO_ERROR);
ASSERT_TRUE(generatedCommands.TakeBuffer().empty());
}
TEST(TestDefaultServerCluster, WriteAttributeDefault)
{
FakeDefaultServerCluster cluster({ 1, 2 });
WriteOperation test(0 /* endpoint */, 1 /* cluster */, 1234 /* attribute */);
test.SetSubjectDescriptor(kAdminSubjectDescriptor);
AttributeValueDecoder decoder = test.DecoderFor<uint32_t>(12345);
ASSERT_EQ(cluster.WriteAttribute(test.GetRequest(), decoder), Status::UnsupportedWrite);
ASSERT_FALSE(decoder.TriedDecode());
}
TEST(TestDefaultServerCluster, InvokeDefault)
{
FakeDefaultServerCluster cluster({ 1, 2 });
TLV::TLVReader tlvReader;
InvokeRequest request;
request.path = { 0 /* endpoint */, 1 /* cluster */, 1234 /* command */ };
ASSERT_EQ(cluster.InvokeCommand(request, tlvReader, nullptr /* command handler, assumed unused here */),
Status::UnsupportedCommand);
}
TEST(TestDefaultServerCluster, NotifyAttributeChanged)
{
constexpr ClusterId kEndpointId = 321;
constexpr ClusterId kClusterId = 1122;
FakeDefaultServerCluster cluster({ kEndpointId, kClusterId });
// When no ServerClusterContext is set, only the data version should change.
DataVersion oldVersion = cluster.GetDataVersion({ kEndpointId, kClusterId });
cluster.TestNotifyAttributeChanged(123);
ASSERT_NE(cluster.GetDataVersion({ kEndpointId, kClusterId }), oldVersion);
// Create a ServerClusterContext and verify that attribute change notifications are processed.
TestServerClusterContext context;
ASSERT_EQ(cluster.Startup(context.Get()), CHIP_NO_ERROR);
oldVersion = cluster.GetDataVersion({ kEndpointId, kClusterId });
cluster.TestNotifyAttributeChanged(234);
ASSERT_NE(cluster.GetDataVersion({ kEndpointId, kClusterId }), oldVersion);
ASSERT_EQ(context.ChangeListener().DirtyList().size(), 1u);
ASSERT_EQ(context.ChangeListener().DirtyList()[0], AttributePathParams(kEndpointId, kClusterId, 234));
}