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-
3134namespace {
3235
3336using namespace chip ;
3437using namespace chip ::app;
3538using namespace chip ::app::Clusters;
3639using namespace chip ::app::Clusters::GeneralDiagnostics::Attributes;
3740using namespace chip ::app::DataModel;
41+ using namespace chip ::Test;
42+ using namespace chip ::Testing;
3843
3944template <class T >
4045class 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+
6093struct 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
6699TEST_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
0 commit comments