Skip to content

Commit cfb4cc3

Browse files
Add unit tests for TimeSnapshot command in GeneralDiagnosticsCluster (#41291)
* Add unit tests for `TimeSnapshot` command in `GeneralDiagnosticsCluster` - Introduced a mock CommandHandler and a NullProvider for testing. - Implemented tests for TimeSnapshot command, including scenarios with and without POSIX time support. - Verified response values and ensured correct handling of command invocations. - Added helper methods for setting up TLV readers and buffers for command testing. * Refactor `TestGeneralDiagnosticsCluster` to improve command invocation testing - Moved command invocation logic into a helper method for better code reuse and clarity. * Handle status responses in `ClusterTester` when `InvokeCommand` returns `nullopt` When InvokeCommand returns nullopt, check for both data responses and status responses in the mock handler. Previously only data responses were checked. Now if a status response exists, use the last status from the handler. * Handle status responses and unexpected states in ClusterTester When InvokeCommand returns nullopt, check for both data responses and status responses. If a status response exists, use the last status from the handler. Also handle the unexpected case where neither response nor status is provided by setting an error status and logging a warning. * Refactor `TestGeneralDiagnosticsCluster` to improve command invocation and response handling - Moved the `InvokeTimeSnapshotAndGetResponse` method outside the test struct for better organization and clarity. - Simplified the command invocation process by ensuring `ClusterTester` is instantiated within each test case. - Enhanced readability by removing redundant code and ensuring consistent error handling during command execution. * Refactor `InvokeTimeSnapshotAndGetResponse` to use `ClusterTester` directly in tests - Updated the `InvokeTimeSnapshotAndGetResponse` function to accept `ClusterTester` instead of `GeneralDiagnosticsCluster` for improved clarity and consistency in test cases. - Adjusted all relevant test cases to reflect this change, ensuring proper command invocation and response handling. * Enhance error handling in `InvokeTimeSnapshotAndGetResponse` for improved robustness - Updated the InvokeTimeSnapshotAndGetResponse function to return appropriate error codes for various failure scenarios, ensuring better error handling during command execution. - Adjusted test cases to assert the return value of the function, enhancing the reliability of the tests and ensuring that errors are properly captured and reported. * Fix test helper to properly propagate errors Changed InvokeTimeSnapshotAndGetResponse() to return CHIP_ERROR instead of void. ASSERT macros inside void helper functions only return from the helper, not from the test itself, which can lead to tests continuing with uninitialized data and undefined behavior.
1 parent 476b1d3 commit cfb4cc3

File tree

5 files changed

+175
-91
lines changed

5 files changed

+175
-91
lines changed

src/app/clusters/general-diagnostics-server/CodegenIntegration.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,12 @@ class IntegrationDelegate : public CodegenClusterIntegration::Delegate
6161
#if defined(ZCL_USING_TIME_SYNCHRONIZATION_CLUSTER_SERVER)
6262
.enablePosixTime = true,
6363
#else
64-
.enablePosixTime = false,
64+
.enablePosixTime = false,
6565
#endif
6666
#if defined(GENERAL_DIAGNOSTICS_ENABLE_PAYLOAD_TEST_REQUEST_CMD)
67-
.enablePayloadSnaphot = true,
67+
.enablePayloadSnapshot = true,
6868
#else
69-
.enablePayloadSnaphot = false,
69+
.enablePayloadSnapshot = false,
7070
#endif
7171
};
7272
gServer.Create(optionalAttributeSet, functionsConfig);

src/app/clusters/general-diagnostics-server/general-diagnostics-cluster.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ GeneralDiagnosticsClusterFullConfigurable::InvokeCommand(const DataModel::Invoke
462462
return HandleTimeSnapshot(*handler, request.path, request_data);
463463
}
464464
case GeneralDiagnostics::Commands::PayloadTestRequest::Id: {
465-
if (mFunctionConfig.enablePayloadSnaphot)
465+
if (mFunctionConfig.enablePayloadSnapshot)
466466
{
467467
GeneralDiagnostics::Commands::PayloadTestRequest::DecodableType request_data;
468468
ReturnErrorOnFailure(request_data.Decode(input_arguments));

src/app/clusters/general-diagnostics-server/general-diagnostics-cluster.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ namespace Clusters {
3232
struct GeneralDiagnosticsFunctionsConfig
3333
{
3434
bool enablePosixTime : 1;
35-
bool enablePayloadSnaphot : 1;
35+
bool enablePayloadSnapshot : 1;
3636
};
3737

3838
class GeneralDiagnosticsCluster : public DefaultServerCluster

src/app/clusters/general-diagnostics-server/tests/TestGeneralDiagnosticsCluster.cpp

Lines changed: 145 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,31 @@
1515
*/
1616
#include <pw_unit_test/framework.h>
1717

18+
#include <app-common/zap-generated/cluster-objects.h>
1819
#include <app/clusters/general-diagnostics-server/general-diagnostics-cluster.h>
1920
#include <app/clusters/testing/AttributeTesting.h>
21+
#include <app/clusters/testing/ClusterTester.h>
22+
#include <app/clusters/testing/ValidateGlobalAttributes.h>
2023
#include <app/data-model-provider/MetadataTypes.h>
2124
#include <app/server-cluster/DefaultServerCluster.h>
25+
#include <app/server-cluster/testing/TestServerClusterContext.h>
2226
#include <clusters/GeneralDiagnostics/Enums.h>
2327
#include <clusters/GeneralDiagnostics/Metadata.h>
2428
#include <lib/core/CHIPError.h>
2529
#include <lib/core/StringBuilderAdapters.h>
2630
#include <lib/support/ReadOnlyBuffer.h>
31+
#include <messaging/ExchangeContext.h>
2732
#include <platform/DiagnosticDataProvider.h>
2833

29-
#include <cmath>
30-
3134
namespace {
3235

3336
using namespace chip;
3437
using namespace chip::app;
3538
using namespace chip::app::Clusters;
3639
using namespace chip::app::Clusters::GeneralDiagnostics::Attributes;
3740
using namespace chip::app::DataModel;
41+
using namespace chip::Test;
42+
using namespace chip::Testing;
3843

3944
template <class T>
4045
class ScopedDiagnosticsProvider
@@ -57,10 +62,38 @@ class ScopedDiagnosticsProvider
5762
T mProvider;
5863
};
5964

65+
// Mock DiagnosticDataProvider for testing
66+
class NullProvider : public DeviceLayer::DiagnosticDataProvider
67+
{
68+
};
69+
70+
// Helper method to invoke TimeSnapshot command and decode response
71+
CHIP_ERROR InvokeTimeSnapshotAndGetResponse(ClusterTester & tester,
72+
GeneralDiagnostics::Commands::TimeSnapshotResponse::DecodableType & response)
73+
{
74+
GeneralDiagnostics::Commands::TimeSnapshot::Type request{};
75+
auto result = tester.Invoke(GeneralDiagnostics::Commands::TimeSnapshot::Id, request);
76+
77+
if (!result.status.has_value())
78+
{
79+
return CHIP_ERROR_INTERNAL;
80+
}
81+
82+
ReturnErrorOnFailure(result.status->GetUnderlyingError());
83+
84+
if (!result.response.has_value())
85+
{
86+
return CHIP_ERROR_INCORRECT_STATE;
87+
}
88+
89+
response = result.response.value();
90+
return CHIP_NO_ERROR;
91+
}
92+
6093
struct TestGeneralDiagnosticsCluster : public ::testing::Test
6194
{
62-
static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); }
63-
static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); }
95+
static void SetUpTestSuite() { ASSERT_EQ(Platform::MemoryInit(), CHIP_NO_ERROR); }
96+
static void TearDownTestSuite() { Platform::MemoryShutdown(); }
6497
};
6598

6699
TEST_F(TestGeneralDiagnosticsCluster, CompileTest)
@@ -71,8 +104,8 @@ TEST_F(TestGeneralDiagnosticsCluster, CompileTest)
71104
ASSERT_EQ(cluster.GetClusterFlags({ kRootEndpointId, GeneralDiagnostics::Id }), BitFlags<ClusterQualityFlags>());
72105

73106
const GeneralDiagnosticsFunctionsConfig functionsConfig{
74-
.enablePosixTime = true,
75-
.enablePayloadSnaphot = true,
107+
.enablePosixTime = true,
108+
.enablePayloadSnapshot = true,
76109
};
77110

78111
GeneralDiagnosticsClusterFullConfigurable clusterWithTimeAndPayload(optionalAttributeSet, functionsConfig);
@@ -84,52 +117,31 @@ TEST_F(TestGeneralDiagnosticsCluster, AttributesTest)
84117
{
85118
{
86119
// everything returns empty here ..
87-
class NullProvider : public DeviceLayer::DiagnosticDataProvider
88-
{
89-
};
90120
const GeneralDiagnosticsCluster::OptionalAttributeSet optionalAttributeSet;
91121
ScopedDiagnosticsProvider<NullProvider> nullProvider;
92122
GeneralDiagnosticsCluster cluster(optionalAttributeSet);
93123

94124
// Check required accepted commands are present
95-
ConcreteClusterPath generalDiagnosticsPath = ConcreteClusterPath(kRootEndpointId, GeneralDiagnostics::Id);
96-
97-
ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> acceptedCommandsBuilder;
98-
ASSERT_EQ(cluster.AcceptedCommands(generalDiagnosticsPath, acceptedCommandsBuilder), CHIP_NO_ERROR);
99-
ReadOnlyBuffer<DataModel::AcceptedCommandEntry> acceptedCommands = acceptedCommandsBuilder.TakeBuffer();
100-
ASSERT_EQ(acceptedCommands.size(), 2u);
101-
102-
ASSERT_EQ(acceptedCommands[0].commandId, GeneralDiagnostics::Commands::TestEventTrigger::Id);
103-
ASSERT_EQ(acceptedCommands[0].GetInvokePrivilege(),
104-
GeneralDiagnostics::Commands::TestEventTrigger::kMetadataEntry.GetInvokePrivilege());
105-
106-
ASSERT_EQ(acceptedCommands[1].commandId, GeneralDiagnostics::Commands::TimeSnapshot::Id);
107-
ASSERT_EQ(acceptedCommands[1].GetInvokePrivilege(),
108-
GeneralDiagnostics::Commands::TimeSnapshot::kMetadataEntry.GetInvokePrivilege());
125+
ASSERT_TRUE(IsAcceptedCommandsListEqualTo(cluster,
126+
{
127+
GeneralDiagnostics::Commands::TestEventTrigger::kMetadataEntry,
128+
GeneralDiagnostics::Commands::TimeSnapshot::kMetadataEntry,
129+
}));
109130

110131
// Check required generated commands are present
111-
ReadOnlyBufferBuilder<chip::CommandId> generatedCommandsBuilder;
112-
ASSERT_EQ(cluster.GeneratedCommands(generalDiagnosticsPath, generatedCommandsBuilder), CHIP_NO_ERROR);
113-
ReadOnlyBuffer<chip::CommandId> generatedCommands = generatedCommandsBuilder.TakeBuffer();
114-
115-
ASSERT_EQ(generatedCommands.size(), 1u);
116-
ASSERT_EQ(generatedCommands[0], GeneralDiagnostics::Commands::TimeSnapshotResponse::Id);
132+
ASSERT_TRUE(IsGeneratedCommandsListEqualTo(cluster,
133+
{
134+
GeneralDiagnostics::Commands::TimeSnapshotResponse::Id,
135+
}));
117136

118137
// Everything is unimplemented, so attributes are just the global and mandatory ones.
119-
ReadOnlyBufferBuilder<DataModel::AttributeEntry> attributesBuilder;
120-
ASSERT_EQ(cluster.Attributes(generalDiagnosticsPath, attributesBuilder), CHIP_NO_ERROR);
121-
122-
ReadOnlyBufferBuilder<DataModel::AttributeEntry> expectedBuilder;
123-
ASSERT_EQ(expectedBuilder.ReferenceExisting(DefaultServerCluster::GlobalAttributes()), CHIP_NO_ERROR);
124-
ASSERT_EQ(expectedBuilder.AppendElements({
125-
GeneralDiagnostics::Attributes::NetworkInterfaces::kMetadataEntry,
126-
GeneralDiagnostics::Attributes::RebootCount::kMetadataEntry,
127-
GeneralDiagnostics::Attributes::UpTime::kMetadataEntry,
128-
GeneralDiagnostics::Attributes::TestEventTriggersEnabled::kMetadataEntry,
129-
}),
130-
CHIP_NO_ERROR);
131-
132-
ASSERT_TRUE(Testing::EqualAttributeSets(attributesBuilder.TakeBuffer(), expectedBuilder.TakeBuffer()));
138+
ASSERT_TRUE(IsAttributesListEqualTo(cluster,
139+
{
140+
GeneralDiagnostics::Attributes::NetworkInterfaces::kMetadataEntry,
141+
GeneralDiagnostics::Attributes::RebootCount::kMetadataEntry,
142+
GeneralDiagnostics::Attributes::UpTime::kMetadataEntry,
143+
GeneralDiagnostics::Attributes::TestEventTriggersEnabled::kMetadataEntry,
144+
}));
133145
}
134146

135147
{
@@ -146,22 +158,22 @@ TEST_F(TestGeneralDiagnosticsCluster, AttributesTest)
146158
totalOperationalHours = 456;
147159
return CHIP_NO_ERROR;
148160
}
149-
CHIP_ERROR GetBootReason(chip::app::Clusters::GeneralDiagnostics::BootReasonEnum & bootReason) override
161+
CHIP_ERROR GetBootReason(app::Clusters::GeneralDiagnostics::BootReasonEnum & bootReason) override
150162
{
151163
bootReason = GeneralDiagnostics::BootReasonEnum::kSoftwareReset;
152164
return CHIP_NO_ERROR;
153165
}
154166
CHIP_ERROR
155-
GetActiveHardwareFaults(chip::DeviceLayer::GeneralFaults<DeviceLayer::kMaxHardwareFaults> & hardwareFaults) override
167+
GetActiveHardwareFaults(DeviceLayer::GeneralFaults<DeviceLayer::kMaxHardwareFaults> & hardwareFaults) override
156168
{
157169
return CHIP_NO_ERROR;
158170
}
159-
CHIP_ERROR GetActiveRadioFaults(chip::DeviceLayer::GeneralFaults<DeviceLayer::kMaxRadioFaults> & radioFaults) override
171+
CHIP_ERROR GetActiveRadioFaults(DeviceLayer::GeneralFaults<DeviceLayer::kMaxRadioFaults> & radioFaults) override
160172
{
161173
return CHIP_NO_ERROR;
162174
}
163175
CHIP_ERROR
164-
GetActiveNetworkFaults(chip::DeviceLayer::GeneralFaults<DeviceLayer::kMaxNetworkFaults> & networkFaults) override
176+
GetActiveNetworkFaults(DeviceLayer::GeneralFaults<DeviceLayer::kMaxNetworkFaults> & networkFaults) override
165177
{
166178
return CHIP_NO_ERROR;
167179
}
@@ -180,49 +192,31 @@ TEST_F(TestGeneralDiagnosticsCluster, AttributesTest)
180192
GeneralDiagnosticsCluster cluster(optionalAttributeSet);
181193

182194
// Check mandatory commands are present
183-
ConcreteClusterPath generalDiagnosticsPath = ConcreteClusterPath(kRootEndpointId, GeneralDiagnostics::Id);
184-
185-
ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> commandsBuilder;
186-
ASSERT_EQ(cluster.AcceptedCommands(generalDiagnosticsPath, commandsBuilder), CHIP_NO_ERROR);
187-
ReadOnlyBuffer<DataModel::AcceptedCommandEntry> commands = commandsBuilder.TakeBuffer();
188-
ASSERT_EQ(commands.size(), 2u);
189-
190-
ASSERT_EQ(commands[0].commandId, GeneralDiagnostics::Commands::TestEventTrigger::Id);
191-
ASSERT_EQ(commands[0].GetInvokePrivilege(),
192-
GeneralDiagnostics::Commands::TestEventTrigger::kMetadataEntry.GetInvokePrivilege());
193-
194-
ASSERT_EQ(commands[1].commandId, GeneralDiagnostics::Commands::TimeSnapshot::Id);
195-
ASSERT_EQ(commands[1].GetInvokePrivilege(),
196-
GeneralDiagnostics::Commands::TimeSnapshot::kMetadataEntry.GetInvokePrivilege());
195+
ASSERT_TRUE(IsAcceptedCommandsListEqualTo(cluster,
196+
{
197+
GeneralDiagnostics::Commands::TestEventTrigger::kMetadataEntry,
198+
GeneralDiagnostics::Commands::TimeSnapshot::kMetadataEntry,
199+
}));
197200

198201
// Check required generated commands are present
199-
ReadOnlyBufferBuilder<chip::CommandId> generatedCommandsBuilder;
200-
ASSERT_EQ(cluster.GeneratedCommands(generalDiagnosticsPath, generatedCommandsBuilder), CHIP_NO_ERROR);
201-
ReadOnlyBuffer<chip::CommandId> generatedCommands = generatedCommandsBuilder.TakeBuffer();
202-
203-
ASSERT_EQ(generatedCommands.size(), 1u);
204-
ASSERT_EQ(generatedCommands[0], GeneralDiagnostics::Commands::TimeSnapshotResponse::Id);
202+
ASSERT_TRUE(IsGeneratedCommandsListEqualTo(cluster,
203+
{
204+
GeneralDiagnostics::Commands::TimeSnapshotResponse::Id,
205+
}));
205206

206207
// Everything is implemented, so attributes are the global ones and ALL optional ones as well.
207-
ReadOnlyBufferBuilder<DataModel::AttributeEntry> attributesBuilder;
208-
ASSERT_EQ(cluster.Attributes(generalDiagnosticsPath, attributesBuilder), CHIP_NO_ERROR);
209-
210-
ReadOnlyBufferBuilder<DataModel::AttributeEntry> expectedBuilder;
211-
ASSERT_EQ(expectedBuilder.ReferenceExisting(DefaultServerCluster::GlobalAttributes()), CHIP_NO_ERROR);
212-
ASSERT_EQ(expectedBuilder.AppendElements({
213-
GeneralDiagnostics::Attributes::NetworkInterfaces::kMetadataEntry,
214-
GeneralDiagnostics::Attributes::RebootCount::kMetadataEntry,
215-
GeneralDiagnostics::Attributes::UpTime::kMetadataEntry,
216-
GeneralDiagnostics::Attributes::TestEventTriggersEnabled::kMetadataEntry,
217-
GeneralDiagnostics::Attributes::TotalOperationalHours::kMetadataEntry,
218-
GeneralDiagnostics::Attributes::BootReason::kMetadataEntry,
219-
GeneralDiagnostics::Attributes::ActiveHardwareFaults::kMetadataEntry,
220-
GeneralDiagnostics::Attributes::ActiveRadioFaults::kMetadataEntry,
221-
GeneralDiagnostics::Attributes::ActiveNetworkFaults::kMetadataEntry,
222-
}),
223-
CHIP_NO_ERROR);
224-
225-
ASSERT_TRUE(Testing::EqualAttributeSets(attributesBuilder.TakeBuffer(), expectedBuilder.TakeBuffer()));
208+
ASSERT_TRUE(IsAttributesListEqualTo(cluster,
209+
{
210+
GeneralDiagnostics::Attributes::NetworkInterfaces::kMetadataEntry,
211+
GeneralDiagnostics::Attributes::RebootCount::kMetadataEntry,
212+
GeneralDiagnostics::Attributes::UpTime::kMetadataEntry,
213+
GeneralDiagnostics::Attributes::TestEventTriggersEnabled::kMetadataEntry,
214+
GeneralDiagnostics::Attributes::TotalOperationalHours::kMetadataEntry,
215+
GeneralDiagnostics::Attributes::BootReason::kMetadataEntry,
216+
GeneralDiagnostics::Attributes::ActiveHardwareFaults::kMetadataEntry,
217+
GeneralDiagnostics::Attributes::ActiveRadioFaults::kMetadataEntry,
218+
GeneralDiagnostics::Attributes::ActiveNetworkFaults::kMetadataEntry,
219+
}));
226220

227221
// Check proper read/write of values and returns
228222
uint16_t rebootCount = 0;
@@ -247,4 +241,69 @@ TEST_F(TestGeneralDiagnosticsCluster, AttributesTest)
247241
}
248242
}
249243

244+
TEST_F(TestGeneralDiagnosticsCluster, TimeSnapshotCommandTest)
245+
{
246+
// Create a cluster with no optional attributes enabled
247+
const GeneralDiagnosticsCluster::OptionalAttributeSet optionalAttributeSet;
248+
ScopedDiagnosticsProvider<NullProvider> nullProvider;
249+
GeneralDiagnosticsCluster cluster(optionalAttributeSet);
250+
251+
ClusterTester tester(cluster);
252+
ASSERT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
253+
254+
// Invoke TimeSnapshot command and get response
255+
GeneralDiagnostics::Commands::TimeSnapshotResponse::DecodableType response;
256+
ASSERT_EQ(InvokeTimeSnapshotAndGetResponse(tester, response), CHIP_NO_ERROR);
257+
258+
// Basic configuration excludes POSIX time
259+
EXPECT_TRUE(response.posixTimeMs.IsNull());
260+
}
261+
262+
TEST_F(TestGeneralDiagnosticsCluster, TimeSnapshotCommandWithPosixTimeTest)
263+
{
264+
// Configure cluster with POSIX time support enabled and no optional attributes enabled
265+
const GeneralDiagnosticsCluster::OptionalAttributeSet optionalAttributeSet;
266+
ScopedDiagnosticsProvider<NullProvider> nullProvider;
267+
const GeneralDiagnosticsFunctionsConfig functionsConfig{
268+
.enablePosixTime = true,
269+
.enablePayloadSnapshot = false,
270+
};
271+
GeneralDiagnosticsClusterFullConfigurable cluster(optionalAttributeSet, functionsConfig);
272+
273+
ClusterTester tester(cluster);
274+
ASSERT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
275+
276+
// Invoke TimeSnapshot command and get response
277+
GeneralDiagnostics::Commands::TimeSnapshotResponse::DecodableType response;
278+
ASSERT_EQ(InvokeTimeSnapshotAndGetResponse(tester, response), CHIP_NO_ERROR);
279+
280+
// POSIX time is included when available (system dependent)
281+
if (!response.posixTimeMs.IsNull())
282+
{
283+
EXPECT_GT(response.posixTimeMs.Value(), 0u);
284+
}
285+
}
286+
287+
TEST_F(TestGeneralDiagnosticsCluster, TimeSnapshotResponseValues)
288+
{
289+
// Create a cluster with no optional attributes enabled
290+
const GeneralDiagnosticsCluster::OptionalAttributeSet optionalAttributeSet;
291+
ScopedDiagnosticsProvider<NullProvider> nullProvider;
292+
GeneralDiagnosticsCluster cluster(optionalAttributeSet);
293+
294+
ClusterTester tester(cluster);
295+
ASSERT_EQ(cluster.Startup(tester.GetServerClusterContext()), CHIP_NO_ERROR);
296+
297+
// First invocation. Capture initial timestamp
298+
GeneralDiagnostics::Commands::TimeSnapshotResponse::DecodableType firstResponse;
299+
ASSERT_EQ(InvokeTimeSnapshotAndGetResponse(tester, firstResponse), CHIP_NO_ERROR);
300+
301+
// Second invocation. Capture subsequent timestamp
302+
GeneralDiagnostics::Commands::TimeSnapshotResponse::DecodableType secondResponse;
303+
ASSERT_EQ(InvokeTimeSnapshotAndGetResponse(tester, secondResponse), CHIP_NO_ERROR);
304+
305+
// Verify second response is also valid and greater than or equal to first
306+
EXPECT_GE(secondResponse.systemTimeMs, firstResponse.systemTimeMs);
307+
}
308+
250309
} // namespace

src/app/clusters/testing/ClusterTester.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,31 @@ class ClusterTester
175175

176176
result.status = mCluster.InvokeCommand(invokeRequest, reader, &mHandler);
177177

178+
// If InvokeCommand returned nullopt, it means the command implementation handled the response.
179+
// We need to check the mock handler for a data response or a status response.
180+
if (!result.status.has_value())
181+
{
182+
if (mHandler.HasResponse())
183+
{
184+
// A data response was added, so the command is successful.
185+
result.status = app::DataModel::ActionReturnStatus(CHIP_NO_ERROR);
186+
}
187+
else if (mHandler.HasStatus())
188+
{
189+
// A status response was added. Use the last one.
190+
result.status = app::DataModel::ActionReturnStatus(mHandler.GetLastStatus().status);
191+
}
192+
else
193+
{
194+
// Neither response nor status was provided; this is unexpected.
195+
// This would happen either in error (as mentioned here) or if the command is supposed
196+
// to be handled asynchronously. ClusterTester does not support such asynchronous processing.
197+
result.status = app::DataModel::ActionReturnStatus(CHIP_ERROR_INCORRECT_STATE);
198+
ChipLogError(
199+
Test, "InvokeCommand returned nullopt, but neither HasResponse nor HasStatus is true. Setting error status.");
200+
}
201+
}
202+
178203
// If command was successful and there's a response, decode it (skip for NullObjectType)
179204
if constexpr (!std::is_same_v<ResponseType, app::DataModel::NullObjectType>)
180205
{

0 commit comments

Comments
 (0)