Matter SDK Coverage Report
Current view: top level - app/server-cluster/testing - ClusterTester.h (source / functions) Coverage Total Hit
Test: SHA:3f9cd168e84cd831b7699126f5296f5c5498690f Lines: 91.4 % 151 138
Test Date: 2026-04-27 19:52:19 Functions: 100.0 % 302 302

            Line data    Source code
       1              : /*
       2              :  *    Copyright (c) 2025 Project CHIP Authors
       3              :  *
       4              :  *    Licensed under the Apache License, Version 2.0 (the "License");
       5              :  *    you may not use this file except in compliance with the License.
       6              :  *    You may obtain a copy of the License at
       7              :  *
       8              :  *        http://www.apache.org/licenses/LICENSE-2.0
       9              :  *
      10              :  *    Unless required by applicable law or agreed to in writing, software
      11              :  *    distributed under the License is distributed on an "AS IS" BASIS,
      12              :  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      13              :  *    See the License for the specific language governing permissions and
      14              :  *    limitations under the License.
      15              :  */
      16              : 
      17              : #pragma once
      18              : 
      19              : #include <app/AttributeValueDecoder.h>
      20              : #include <app/AttributeValueEncoder.h>
      21              : #include <app/CommandHandler.h>
      22              : #include <app/ConcreteAttributePath.h>
      23              : #include <app/ConcreteClusterPath.h>
      24              : #include <app/ConcreteCommandPath.h>
      25              : #include <app/ConcreteEventPath.h>
      26              : #include <app/data-model-provider/ActionReturnStatus.h>
      27              : #include <app/data-model-provider/MetadataTypes.h>
      28              : #include <app/data-model-provider/StringBuilderAdapters.h>
      29              : #include <app/data-model-provider/tests/ReadTesting.h>
      30              : #include <app/data-model-provider/tests/WriteTesting.h>
      31              : #include <app/data-model/List.h>
      32              : #include <app/data-model/NullObject.h>
      33              : #include <app/server-cluster/ServerClusterInterface.h>
      34              : #include <app/server-cluster/testing/FabricTestFixture.h>
      35              : #include <app/server-cluster/testing/MockCommandHandler.h>
      36              : #include <app/server-cluster/testing/TestServerClusterContext.h>
      37              : #include <clusters/shared/Attributes.h>
      38              : #include <credentials/FabricTable.h>
      39              : #include <credentials/PersistentStorageOpCertStore.h>
      40              : #include <crypto/CHIPCryptoPAL.h>
      41              : #include <lib/core/CHIPError.h>
      42              : #include <lib/core/CHIPPersistentStorageDelegate.h>
      43              : #include <lib/core/DataModelTypes.h>
      44              : #include <lib/core/StringBuilderAdapters.h>
      45              : #include <lib/core/TLVReader.h>
      46              : #include <lib/support/ReadOnlyBuffer.h>
      47              : #include <lib/support/Span.h>
      48              : #include <protocols/interaction_model/StatusCode.h>
      49              : 
      50              : #include <algorithm>
      51              : #include <memory>
      52              : #include <optional>
      53              : #include <type_traits>
      54              : #include <vector>
      55              : 
      56              : namespace chip {
      57              : namespace Testing {
      58              : 
      59              : // Helper class for testing clusters.
      60              : //
      61              : // This class ensures that data read by attribute is referencing valid memory for all
      62              : // read requests until the ClusterTester object goes out of scope. (for the case where the underlying read references a list or
      63              : // string that points to TLV data).
      64              : //
      65              : // Read/Write of all attribute types should work, but make sure to use ::Type for encoding
      66              : // and ::DecodableType for decoding structure types.
      67              : //
      68              : // Example of usage:
      69              : //
      70              : // ExampleCluster cluster(someEndpointId);
      71              : //
      72              : // // Possibly steps to setup the cluster
      73              : //
      74              : // ClusterTester tester(cluster);
      75              : // app::Clusters::ExampleCluster::Attributes::FeatureMap::TypeInfo::DecodableType features;
      76              : // ASSERT_EQ(tester.ReadAttribute(FeatureMap::Id, features), CHIP_NO_ERROR);
      77              : //
      78              : // app::Clusters::ExampleCluster::Attributes::ExampleListAttribute::TypeInfo::DecodableType list;
      79              : // ASSERT_EQ(tester.ReadAttribute(LabelList::Id, list), CHIP_NO_ERROR);
      80              : // auto it = list.begin();
      81              : // while (it.Next())
      82              : // {
      83              : //     ASSERT_GT(it.GetValue().label.size(), 0u);
      84              : // }
      85              : //
      86              : 
      87              : template <typename>
      88              : inline constexpr bool kIsList = false;
      89              : 
      90              : template <typename T>
      91              : inline constexpr bool kIsList<app::DataModel::List<T>> = true;
      92              : 
      93              : // All Cluster Servers are expected to support both of the below list writing patterns.
      94              : enum class ListWritingPattern
      95              : {
      96              :     ReplaceAll, // Write the list by encoding the entire list in a single TLV Array, which will replace the entire list on the
      97              :                 // cluster side.
      98              :     ClearAllThenAppendItems, // Write the list by first sending a write with an empty list (TLV Array) to clear the entire list on
      99              :                              // the cluster side, then writing list items individually one by one (Appending Items).
     100              : };
     101              : 
     102              : class ClusterTester
     103              : {
     104              : public:
     105          633 :     ClusterTester(app::ServerClusterInterface & cluster) : mCluster(cluster), mFabricTestFixture(nullptr) {}
     106              : 
     107              :     // Constructor with FabricHelper
     108              :     ClusterTester(app::ServerClusterInterface & cluster, FabricTestFixture * fabricHelper) :
     109              :         mCluster(cluster), mFabricTestFixture(fabricHelper)
     110              :     {}
     111              : 
     112           43 :     TestServerClusterContext & GetTestContext() { return mTestServerClusterContext; }
     113          522 :     app::ServerClusterContext & GetServerClusterContext() { return mTestServerClusterContext.Get(); }
     114              : 
     115              :     // Read attribute into `out` parameter.
     116              :     // The `out` parameter must be of the correct type for the attribute being read.
     117              :     // Use `app::Clusters::<ClusterName>::Attributes::<AttributeName>::TypeInfo::DecodableType` for the `out` parameter to be spec
     118              :     // compliant (see the comment of the class for usage example).
     119              :     // Will construct the attribute path using the first path returned by `GetPaths()` on the cluster.
     120              :     // @returns `CHIP_ERROR_INCORRECT_STATE` if `GetPaths()` doesn't return a list with one path.
     121              :     // @returns `CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)` if the attribute is not present in AttributeList.
     122              :     template <typename T>
     123          845 :     app::DataModel::ActionReturnStatus ReadAttribute(AttributeId attr_id, T & out)
     124              :     {
     125          845 :         VerifyOrReturnError(VerifyClusterPathsValid(), CHIP_ERROR_INCORRECT_STATE);
     126              : 
     127              :         // Verify that the attribute is present in AttributeList before attempting to read it.
     128              :         // This ensures tests match real-world behavior where the Interaction Model checks AttributeList first.
     129          845 :         VerifyOrReturnError(IsAttributeInAttributeList(attr_id), Protocols::InteractionModel::Status::UnsupportedAttribute);
     130              : 
     131          839 :         auto path = mCluster.GetPaths()[0];
     132              : 
     133              :         // Store the read operation in a vector<std::unique_ptr<...>> to ensure its lifetime
     134              :         // using std::unique_ptr because ReadOperation is non-copyable and non-movable
     135              :         // vector reallocation is not an issue since we store unique_ptrs
     136          839 :         std::unique_ptr<chip::Testing::ReadOperation> readOperation =
     137              :             std::make_unique<chip::Testing::ReadOperation>(path.mEndpointId, path.mClusterId, attr_id);
     138              : 
     139          839 :         mReadOperations.push_back(std::move(readOperation));
     140          839 :         chip::Testing::ReadOperation & readOperationRef = *mReadOperations.back().get();
     141              : 
     142          839 :         const Access::SubjectDescriptor subjectDescriptor = mHandler.GetSubjectDescriptor();
     143          839 :         readOperationRef.SetSubjectDescriptor(subjectDescriptor);
     144              : 
     145          839 :         std::unique_ptr<app::AttributeValueEncoder> encoder = readOperationRef.StartEncoding();
     146          839 :         app::DataModel::ActionReturnStatus status           = mCluster.ReadAttribute(readOperationRef.GetRequest(), *encoder);
     147          839 :         VerifyOrReturnError(status.IsSuccess(), status);
     148          836 :         ReturnErrorOnFailure(readOperationRef.FinishEncoding());
     149              : 
     150          836 :         std::vector<chip::Testing::DecodedAttributeData> attributeData;
     151          836 :         ReturnErrorOnFailure(readOperationRef.GetEncodedIBs().Decode(attributeData));
     152          836 :         VerifyOrReturnError(attributeData.size() == 1u, CHIP_ERROR_INCORRECT_STATE);
     153              : 
     154          836 :         return app::DataModel::Decode(attributeData[0].dataReader, out);
     155          839 :     }
     156              : 
     157              :     // Write attribute from `value` parameter.
     158              :     // The `value` parameter must be of the correct type for the attribute being written.
     159              :     // Use `app::Clusters::<ClusterName>::Attributes::<AttributeName>::TypeInfo::Type` for the `value` parameter to be spec
     160              :     // compliant (see the comment of the class for usage example).
     161              :     // Will construct the attribute path using the first path returned by `GetPaths()` on the cluster.
     162              :     // WARNING: This method should NOT be used for writing list attributes, use the overload below that takes a ListWritingPattern
     163              :     // parameter instead.
     164              : 
     165              :     // @returns `CHIP_ERROR_INCORRECT_STATE` if `GetPaths()` doesn't return a list with one path.
     166              :     // @returns `CHIP_IM_GLOBAL_STATUS(UnsupportedAttribute)` if the attribute is not present in AttributeList.
     167              :     template <typename T, std::enable_if_t<!kIsList<T>, int> = 0>
     168          164 :     app::DataModel::ActionReturnStatus WriteAttribute(AttributeId attr, const T & value)
     169              :     {
     170          164 :         const auto & paths = mCluster.GetPaths();
     171              : 
     172          164 :         VerifyOrReturnError(paths.size() == 1u, CHIP_ERROR_INCORRECT_STATE);
     173              : 
     174              :         // Verify that the attribute is present in AttributeList before attempting to write it.
     175              :         // This ensures tests match real-world behavior where the Interaction Model checks AttributeList first.
     176          164 :         VerifyOrReturnError(IsAttributeInAttributeList(attr), Protocols::InteractionModel::Status::UnsupportedAttribute);
     177              : 
     178          164 :         app::ConcreteAttributePath path(paths[0].mEndpointId, paths[0].mClusterId, attr);
     179          164 :         chip::Testing::WriteOperation writeOp(path);
     180              : 
     181              :         // Create a stable object on the stack
     182          164 :         const Access::SubjectDescriptor subjectDescriptor = mHandler.GetSubjectDescriptor();
     183          164 :         writeOp.SetSubjectDescriptor(subjectDescriptor);
     184              : 
     185              :         uint8_t buffer[1024];
     186          164 :         TLV::TLVWriter writer;
     187          164 :         writer.Init(buffer);
     188              : 
     189              :         // - DataModel::Encode(integral, enum, etc.) for simple types.
     190              :         // - DataModel::Encode(List<X>) for lists (which iterates and calls Encode on elements).
     191              :         // - DataModel::Encode(Struct) for non-fabric-scoped structs.
     192              :         // - Note: For attribute writes, DataModel::EncodeForWrite is usually preferred for fabric-scoped types,
     193              :         //         but the generic DataModel::Encode often works as a top-level function.
     194              :         //         If you use EncodeForWrite, you ensure fabric-scoped list items are handled correctly:
     195              : 
     196              :         if constexpr (app::DataModel::IsFabricScoped<T>::value)
     197              :         {
     198              :             ReturnErrorOnFailure(chip::app::DataModel::EncodeForWrite(writer, TLV::AnonymousTag(), value));
     199              :         }
     200              :         else
     201              :         {
     202          164 :             ReturnErrorOnFailure(chip::app::DataModel::Encode(writer, TLV::AnonymousTag(), value));
     203              :         }
     204              : 
     205          164 :         ReturnErrorOnFailure(writer.Finalize());
     206              : 
     207          164 :         TLV::TLVReader reader;
     208          164 :         reader.Init(buffer, writer.GetLengthWritten());
     209          164 :         ReturnErrorOnFailure(reader.Next());
     210              : 
     211          164 :         app::AttributeValueDecoder decoder(reader, writeOp.GetRequest().subjectDescriptor);
     212              : 
     213          164 :         return mCluster.WriteAttribute(writeOp.GetRequest(), decoder);
     214              :     }
     215              : 
     216              :     // This WriteAttribute overload is for writing list attributes, and allows specifying the pattern used for mutating/writing the
     217              :     // list on the cluster side (i.e. Replacing the list in its entirety vs Clearing the list in its entirety then appending/writing
     218              :     // list items individually).
     219              :     // Cluster servers usually support both patterns of writing lists, Therefore we should always test list attributes using both
     220              :     // patterns.
     221              :     template <typename T>
     222           18 :     app::DataModel::ActionReturnStatus WriteAttribute(AttributeId attr, const app::DataModel::List<T> & listValue,
     223              :                                                       ListWritingPattern listWritingPattern)
     224              :     {
     225           18 :         const auto & paths = mCluster.GetPaths();
     226              : 
     227           18 :         VerifyOrReturnError(paths.size() == 1u, CHIP_ERROR_INCORRECT_STATE);
     228              : 
     229              :         // Verify that the attribute is present in AttributeList before attempting to write it.
     230              :         // This ensures tests match real-world behavior where the Interaction Model checks AttributeList first.
     231           18 :         VerifyOrReturnError(IsAttributeInAttributeList(attr), Protocols::InteractionModel::Status::UnsupportedAttribute);
     232              : 
     233           18 :         app::ConcreteAttributePath path(paths[0].mEndpointId, paths[0].mClusterId, attr);
     234           18 :         chip::Testing::WriteOperation writeOp(path);
     235              : 
     236              :         // Create a stable object on the stack
     237           18 :         const Access::SubjectDescriptor subjectDescriptor = mHandler.GetSubjectDescriptor();
     238           18 :         writeOp.SetSubjectDescriptor(subjectDescriptor);
     239              : 
     240           18 :         switch (listWritingPattern)
     241              :         {
     242            8 :         case ListWritingPattern::ReplaceAll: {
     243              : 
     244              :             // For ReplaceAll, we need to write the entire list in one go
     245            8 :             writeOp.SetListOperation(app::ConcreteDataAttributePath::ListOperation::ReplaceAll);
     246            8 :             auto decoder = writeOp.DecoderFor(listValue);
     247            8 :             return mCluster.WriteAttribute(writeOp.GetRequest(), decoder);
     248              :         }
     249              : 
     250           10 :         case ListWritingPattern::ClearAllThenAppendItems: {
     251              : 
     252              :             // We first write an empty list to clear the existing list using ReplaceAll List Operation
     253           10 :             writeOp.SetListOperation(app::ConcreteDataAttributePath::ListOperation::ReplaceAll);
     254           10 :             auto decoder = writeOp.DecoderFor(app::DataModel::List<uint8_t>());
     255           10 :             auto status  = mCluster.WriteAttribute(writeOp.GetRequest(), decoder);
     256           10 :             VerifyOrReturnValue(status.IsSuccess(), status);
     257              : 
     258              :             // We now write each list item individually with AppendItem list operation.
     259           10 :             writeOp.SetListOperation(app::ConcreteDataAttributePath::ListOperation::AppendItem);
     260           23 :             for (const auto & listItem : listValue)
     261              :             {
     262           17 :                 auto decoderAppend = writeOp.DecoderFor(listItem);
     263           17 :                 auto statusAppend  = mCluster.WriteAttribute(writeOp.GetRequest(), decoderAppend);
     264           17 :                 VerifyOrReturnValue(statusAppend.IsSuccess(), statusAppend);
     265              :             }
     266            6 :             return Protocols::InteractionModel::Status::Success;
     267              :         }
     268            0 :         default:
     269            0 :             ChipLogError(Test, "Unhandled ListWritingPattern");
     270            0 :             return CHIP_ERROR_INVALID_ARGUMENT;
     271              :         }
     272              :     }
     273              : 
     274              :     // Result structure for Invoke operations, containing both status and decoded response.
     275              :     template <typename ResponseType>
     276              :     struct InvokeResult
     277              :     {
     278              :         std::optional<app::DataModel::ActionReturnStatus> status;
     279              :         std::optional<ResponseType> response;
     280              : 
     281              :         // Returns true if the command was successful and response is available
     282          413 :         bool IsSuccess() const
     283              :         {
     284              :             if constexpr (std::is_same_v<ResponseType, app::DataModel::NullObjectType>)
     285          230 :                 return status.has_value() && status->IsSuccess();
     286              :             else
     287          183 :                 return status.has_value() && status->IsSuccess() && response.has_value();
     288              :         }
     289              : 
     290              :         // Returns the ClusterStatusCode if available, otherwise returns std::nullopt.
     291              :         // This allows tests to check the status code with a single EXPECT_EQ()
     292              :         // (i.e. without having to ASSERT_TRUE(status.has_value()) first).
     293          192 :         std::optional<Protocols::InteractionModel::ClusterStatusCode> GetStatusCode() const
     294              :         {
     295          192 :             VerifyOrReturnValue(status.has_value(), std::nullopt);
     296          192 :             return status->GetStatusCode();
     297              :         }
     298              :     };
     299              : 
     300              :     // Invoke a command and return the decoded result.
     301              :     // The `request` parameter must be of the correct type for the command being invoked.
     302              :     // Use `app::Clusters::<ClusterName>::Commands::<CommandName>::Type` for the `request` parameter to be spec compliant
     303              :     // Will construct the command path using the first path returned by `GetPaths()` on the cluster.
     304              :     // @returns `CHIP_ERROR_INCORRECT_STATE` if `GetPaths()` doesn't return a list with one path.
     305              :     template <typename RequestType, typename ResponseType = typename RequestType::ResponseType>
     306          662 :     [[nodiscard]] InvokeResult<ResponseType> Invoke(chip::CommandId commandId, const RequestType & request)
     307              :     {
     308          662 :         InvokeResult<ResponseType> result;
     309              : 
     310          662 :         const auto & paths = mCluster.GetPaths();
     311          662 :         VerifyOrReturnValue(paths.size() == 1u, result);
     312              : 
     313          662 :         mHandler.ClearResponses();
     314          662 :         mHandler.ClearStatuses();
     315              : 
     316              :         // Verify that the command is present in AcceptedCommands before attempting to invoke it.
     317              :         // This ensures tests match real-world behavior where the Interaction Model checks AcceptedCommands first.
     318          662 :         if (!IsCommandAnAcceptedCommand(commandId))
     319              :         {
     320            4 :             result.status = Protocols::InteractionModel::Status::UnsupportedCommand;
     321            4 :             return result;
     322              :         }
     323              : 
     324          658 :         const Access::SubjectDescriptor subjectDescriptor = mHandler.GetSubjectDescriptor();
     325          658 :         const app::DataModel::InvokeRequest invokeRequest({ paths[0].mEndpointId, paths[0].mClusterId, commandId },
     326              :                                                           subjectDescriptor);
     327              : 
     328          658 :         TLV::TLVWriter writer;
     329          658 :         writer.Init(mTlvBuffer);
     330              : 
     331          658 :         TLV::TLVReader reader;
     332              : 
     333         1316 :         VerifyOrReturnValue(request.Encode(writer, TLV::AnonymousTag()) == CHIP_NO_ERROR, result);
     334         1316 :         VerifyOrReturnValue(writer.Finalize() == CHIP_NO_ERROR, result);
     335              : 
     336          658 :         reader.Init(mTlvBuffer, writer.GetLengthWritten());
     337         1316 :         VerifyOrReturnValue(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()) == CHIP_NO_ERROR, result);
     338              : 
     339          658 :         result.status = mCluster.InvokeCommand(invokeRequest, reader, &mHandler);
     340              : 
     341              :         // If InvokeCommand returned nullopt, it means the command implementation handled the response.
     342              :         // We need to check the mock handler for a data response or a status response.
     343          658 :         if (!result.status.has_value())
     344              :         {
     345          240 :             if (mHandler.HasResponse())
     346              :             {
     347              :                 // A data response was added, so the command is successful.
     348          239 :                 result.status = app::DataModel::ActionReturnStatus(CHIP_NO_ERROR);
     349              :             }
     350            1 :             else if (mHandler.HasStatus())
     351              :             {
     352              :                 // A status response was added. Use the last one.
     353            1 :                 result.status = app::DataModel::ActionReturnStatus(mHandler.GetLastStatus().status);
     354              :             }
     355              :             else
     356              :             {
     357              :                 // Neither response nor status was provided; this is unexpected.
     358              :                 // This would happen either in error (as mentioned here) or if the command is supposed
     359              :                 // to be handled asynchronously. ClusterTester does not support such asynchronous processing.
     360            0 :                 result.status = app::DataModel::ActionReturnStatus(CHIP_ERROR_INCORRECT_STATE);
     361            0 :                 ChipLogError(
     362              :                     Test, "InvokeCommand returned nullopt, but neither HasResponse nor HasStatus is true. Setting error status.");
     363              :             }
     364              :         }
     365              : 
     366              :         // If command was successful and there's a response, decode it (skip for NullObjectType)
     367              :         if constexpr (!std::is_same_v<ResponseType, app::DataModel::NullObjectType>)
     368              :         {
     369          271 :             if (result.status.has_value() && result.status->IsSuccess() && mHandler.HasResponse())
     370              :             {
     371          237 :                 ResponseType decodedResponse;
     372          237 :                 CHIP_ERROR decodeError = mHandler.DecodeResponse(decodedResponse);
     373          474 :                 if (decodeError == CHIP_NO_ERROR)
     374              :                 {
     375          237 :                     result.response = std::move(decodedResponse);
     376              :                 }
     377              :                 else
     378              :                 {
     379              :                     // Decode failed; reflect error in status and log
     380            0 :                     result.status = app::DataModel::ActionReturnStatus(decodeError);
     381            0 :                     ChipLogError(Test, "DecodeResponse failed: %s", decodeError.AsString());
     382              :                 }
     383              :             }
     384              :         }
     385              : 
     386          658 :         return result;
     387              :     }
     388              : 
     389              :     // convenience method: most requests have a `GetCommandId` (and GetClusterId() as well).
     390              :     template <typename RequestType, typename ResponseType = typename RequestType::ResponseType>
     391          278 :     [[nodiscard]] InvokeResult<ResponseType> Invoke(const RequestType & request)
     392              :     {
     393          278 :         return Invoke(RequestType::GetCommandId(), request);
     394              :     }
     395              : 
     396              :     // Returns the next generated event from the event generator in the test server cluster context
     397            3 :     std::optional<LogOnlyEvents::EventInformation> GetNextGeneratedEvent()
     398              :     {
     399            3 :         return mTestServerClusterContext.EventsGenerator().GetNextEvent();
     400              :     }
     401              : 
     402              :     // TODO: Add methods to test AttributeChangeListener notifications.
     403           17 :     std::vector<app::ConcreteAttributePath> & GetDirtyList() { return mTestServerClusterContext.ChangeListener().DirtyList(); }
     404              : 
     405              :     // Returns true if the given attribute appears in the dirty list.
     406              :     // Will construct the attribute path using the first path returned by `GetPaths()` on the cluster
     407              :     // Will VerifyOrDie that `GetPaths()` returns exactly one path.
     408            4 :     bool IsAttributeDirty(AttributeId attributeId)
     409              :     {
     410            4 :         const auto & paths = mCluster.GetPaths();
     411            4 :         VerifyOrDie(paths.size() == 1);
     412            4 :         app::ConcreteAttributePath target(paths[0].mEndpointId, paths[0].mClusterId, attributeId);
     413            4 :         const auto & list = GetDirtyList();
     414            4 :         return std::find(list.begin(), list.end(), target) != list.end();
     415              :     }
     416              : 
     417          131 :     void SetFabricIndex(FabricIndex fabricIndex)
     418              :     {
     419          131 :         auto subjectDescriptor        = mHandler.GetSubjectDescriptor();
     420          131 :         subjectDescriptor.fabricIndex = fabricIndex;
     421          131 :         mHandler.SetSubjectDescriptor(subjectDescriptor);
     422          131 :         mHandler.SetFabricIndex(fabricIndex);
     423          131 :     }
     424              : 
     425           28 :     void SetSubjectDescriptor(const Access::SubjectDescriptor & subjectDescriptor)
     426              :     {
     427           28 :         mHandler.SetSubjectDescriptor(subjectDescriptor);
     428           28 :     }
     429              : 
     430              :     FabricTestFixture * GetFabricHelper() { return mFabricTestFixture; }
     431              : 
     432              : private:
     433          845 :     bool VerifyClusterPathsValid()
     434              :     {
     435          845 :         auto paths = mCluster.GetPaths();
     436          845 :         if (paths.size() != 1)
     437              :         {
     438            0 :             ChipLogError(Test, "cluster.GetPaths() did not return exactly one path");
     439            0 :             return false;
     440              :         }
     441          845 :         return true;
     442              :     }
     443              : 
     444         1027 :     bool IsAttributeInAttributeList(AttributeId attr_id)
     445              :     {
     446              :         // Attributes are listed by path, so this is only correct for single-path clusters.
     447         1027 :         VerifyOrDie(mCluster.GetPaths().size() == 1);
     448              : 
     449         1027 :         ReadOnlyBufferBuilder<app::DataModel::AttributeEntry> builder;
     450         2054 :         if (CHIP_ERROR err = mCluster.Attributes(mCluster.GetPaths()[0], builder); err != CHIP_NO_ERROR)
     451              :         {
     452            0 :             ChipLogError(Test, "Failed to get attribute list: %" CHIP_ERROR_FORMAT, err.Format());
     453            0 :             return false;
     454              :         }
     455              : 
     456         1027 :         ReadOnlyBuffer<app::DataModel::AttributeEntry> attributeEntries = builder.TakeBuffer();
     457         1027 :         return std::any_of(attributeEntries.begin(), attributeEntries.end(),
     458        10056 :                            [&](const app::DataModel::AttributeEntry & entry) { return entry.attributeId == attr_id; });
     459         1027 :     }
     460              : 
     461          662 :     bool IsCommandAnAcceptedCommand(CommandId commandId)
     462              :     {
     463              :         // Commands are listed by path, so this is only correct for single-path clusters.
     464          662 :         VerifyOrDie(mCluster.GetPaths().size() == 1);
     465              : 
     466          662 :         ReadOnlyBufferBuilder<app::DataModel::AcceptedCommandEntry> builder;
     467         1324 :         if (CHIP_ERROR err = mCluster.AcceptedCommands(mCluster.GetPaths()[0], builder); err != CHIP_NO_ERROR)
     468              :         {
     469            0 :             ChipLogError(Test, "Failed to get accepted commands: %" CHIP_ERROR_FORMAT, err.Format());
     470            0 :             return false;
     471              :         }
     472              : 
     473          662 :         ReadOnlyBuffer<app::DataModel::AcceptedCommandEntry> commandEntries = builder.TakeBuffer();
     474          662 :         return std::any_of(commandEntries.begin(), commandEntries.end(),
     475         2242 :                            [&](const app::DataModel::AcceptedCommandEntry & entry) { return entry.commandId == commandId; });
     476          662 :     }
     477              : 
     478              :     TestServerClusterContext mTestServerClusterContext{};
     479              :     app::ServerClusterInterface & mCluster;
     480              : 
     481              :     // Buffer size for TLV encoding/decoding of command payloads.
     482              :     // 256 bytes was chosen as a conservative upper bound for typical command payloads in tests.
     483              :     // All command payloads used in tests must fit within this buffer; tests with larger payloads will fail.
     484              :     // If protocol or test requirements change, this value may need to be increased.
     485              :     // Increased to 1024 to support certificate management commands which include X.509 certificates (~400+ bytes)
     486              :     static constexpr size_t kTlvBufferSize = 1024;
     487              : 
     488              :     chip::Testing::MockCommandHandler mHandler;
     489              :     uint8_t mTlvBuffer[kTlvBufferSize];
     490              :     std::vector<std::unique_ptr<ReadOperation>> mReadOperations;
     491              : 
     492              :     FabricTestFixture * mFabricTestFixture;
     493              : };
     494              : 
     495              : } // namespace Testing
     496              : } // namespace chip
        

Generated by: LCOV version 2.0-1