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
|