| /* |
| * Copyright (c) 2025 Project CHIP Authors |
| * All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include <pw_unit_test/framework.h> |
| |
| #include <app/data-model-provider/MetadataTypes.h> |
| #include <app/data-model-provider/tests/ReadTesting.h> |
| #include <app/data-model-provider/tests/TestConstants.h> |
| #include <app/data-model-provider/tests/WriteTesting.h> |
| #include <app/server-cluster/DefaultServerCluster.h> |
| #include <app/server-cluster/ServerClusterContext.h> |
| #include <app/server-cluster/ServerClusterInterface.h> |
| #include <app/server-cluster/testing/TestEventGenerator.h> |
| #include <app/server-cluster/testing/TestServerClusterContext.h> |
| #include <clusters/Descriptor/ClusterId.h> |
| #include <data-model-providers/codedriven/CodeDrivenDataModelProvider.h> |
| #include <data-model-providers/codedriven/endpoint/SpanEndpoint.h> |
| #include <lib/core/TLV.h> |
| #include <lib/support/ReadOnlyBuffer.h> |
| #include <lib/support/Span.h> |
| #include <system/TLVPacketBufferBackingStore.h> |
| |
| #include <algorithm> |
| #include <vector> |
| |
| using namespace chip; |
| using namespace chip::app; |
| using namespace chip::app::Testing; |
| using SemanticTag = chip::app::Clusters::Globals::Structs::SemanticTagStruct::Type; |
| |
| class TestProviderChangeListener : public DataModel::ProviderChangeListener |
| { |
| public: |
| void MarkDirty(const AttributePathParams & path) override { mDirtyList.push_back(path); } |
| std::vector<AttributePathParams> mDirtyList; |
| }; |
| |
| class TestActionContext : public DataModel::ActionContext |
| { |
| public: |
| Messaging::ExchangeContext * CurrentExchange() override { return nullptr; } |
| }; |
| |
| class MockServerCluster : public DefaultServerCluster |
| { |
| public: |
| MockServerCluster(std::initializer_list<ConcreteClusterPath> paths, DataVersion dataVersion, |
| BitFlags<DataModel::ClusterQualityFlags> flags) : |
| DefaultServerCluster({ 0, 0 }), |
| mPaths(paths), mDataVersion(dataVersion), mFlags(flags), |
| mAttributeEntry(1, BitMask<DataModel::AttributeQualityFlags>(), std::nullopt, std::nullopt) |
| {} |
| |
| MockServerCluster(ConcreteClusterPath path, DataVersion dataVersion, BitFlags<DataModel::ClusterQualityFlags> flags) : |
| MockServerCluster({ path }, dataVersion, flags) |
| {} |
| |
| ~MockServerCluster() override = default; |
| |
| chip::Span<const ConcreteClusterPath> GetPaths() const override |
| { |
| return chip::Span<const ConcreteClusterPath>(mPaths.data(), mPaths.size()); |
| } |
| chip::DataVersion GetDataVersion(const ConcreteClusterPath &) const override { return mDataVersion; } |
| BitFlags<DataModel::ClusterQualityFlags> GetClusterFlags(const ConcreteClusterPath &) const override { return mFlags; } |
| |
| bool IsPathHandled(const ConcreteClusterPath & path) const |
| { |
| for (const auto & p : mPaths) |
| { |
| if (p.mEndpointId == path.mEndpointId && p.mClusterId == path.mClusterId) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request, |
| AttributeValueEncoder & encoder) override |
| { |
| if (!IsPathHandled(request.path)) |
| { |
| return DataModel::ActionReturnStatus(CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| if (request.path.mAttributeId != mAttributeEntry.attributeId) |
| { |
| return DataModel::ActionReturnStatus(Protocols::InteractionModel::Status::UnsupportedAttribute); |
| } |
| mLastReadRequest = request; |
| return encoder.Encode(mAttributeValue); |
| } |
| |
| DataModel::ActionReturnStatus WriteAttribute(const DataModel::WriteAttributeRequest & request, |
| AttributeValueDecoder & decoder) override |
| { |
| if (!IsPathHandled(request.path)) |
| { |
| return DataModel::ActionReturnStatus(CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| if (request.path.mAttributeId != mAttributeEntry.attributeId) |
| { |
| return DataModel::ActionReturnStatus(Protocols::InteractionModel::Status::UnsupportedAttribute); |
| } |
| mLastWriteRequest = request; |
| return decoder.Decode(mAttributeValue); |
| } |
| |
| std::optional<DataModel::ActionReturnStatus> InvokeCommand(const DataModel::InvokeRequest & request, |
| TLV::TLVReader & input_arguments, CommandHandler * handler) override |
| { |
| if (!IsPathHandled(request.path)) |
| { |
| return DataModel::ActionReturnStatus(CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| if (request.path.mCommandId != mAcceptedCommandEntry.commandId) |
| { |
| return DataModel::ActionReturnStatus(Protocols::InteractionModel::Status::UnsupportedCommand); |
| } |
| mLastInvokeRequest = request; |
| return DataModel::ActionReturnStatus(Protocols::InteractionModel::Status::Success); |
| } |
| |
| CHIP_ERROR Attributes(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder) override |
| { |
| if (!IsPathHandled(path)) |
| { |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| ReturnErrorOnFailure(builder.EnsureAppendCapacity(1)); |
| return builder.Append(mAttributeEntry); |
| } |
| |
| CHIP_ERROR AcceptedCommands(const ConcreteClusterPath & path, |
| ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder) override |
| { |
| if (!IsPathHandled(path)) |
| { |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| ReturnErrorOnFailure(builder.EnsureAppendCapacity(1)); |
| return builder.Append(mAcceptedCommandEntry); |
| } |
| |
| CHIP_ERROR GeneratedCommands(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<chip::CommandId> & builder) override |
| { |
| if (!IsPathHandled(path)) |
| { |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| ReturnErrorOnFailure(builder.EnsureAppendCapacity(1)); |
| return builder.Append(mGeneratedCommandId); |
| } |
| |
| CHIP_ERROR EventInfo(const ConcreteEventPath & path, DataModel::EventEntry & eventInfo) override |
| { |
| if (!IsPathHandled(path)) |
| { |
| return CHIP_ERROR_INVALID_ARGUMENT; |
| } |
| eventInfo = mEventEntry; |
| return CHIP_NO_ERROR; |
| } |
| |
| void ListAttributeWriteNotification(const ConcreteAttributePath & path, DataModel::ListWriteOperation opType) override |
| { |
| mLastListWriteOpPath = path; |
| mLastListWriteOpType = opType; |
| } |
| |
| CHIP_ERROR Startup(ServerClusterContext & context) override |
| { |
| startupCallCount++; |
| return DefaultServerCluster::Startup(context); |
| } |
| |
| void Shutdown() override |
| { |
| shutdownCallCount++; |
| DefaultServerCluster::Shutdown(); |
| } |
| |
| int startupCallCount = 0; |
| int shutdownCallCount = 0; |
| |
| DataModel::ReadAttributeRequest mLastReadRequest; |
| DataModel::WriteAttributeRequest mLastWriteRequest; |
| DataModel::InvokeRequest mLastInvokeRequest; |
| uint32_t mAttributeValue = 42; |
| std::vector<ConcreteClusterPath> mPaths; |
| DataVersion mDataVersion; |
| BitFlags<DataModel::ClusterQualityFlags> mFlags; |
| DataModel::AttributeEntry mAttributeEntry; |
| DataModel::AcceptedCommandEntry mAcceptedCommandEntry = { 1, true }; |
| CommandId mGeneratedCommandId = 2; |
| DataModel::EventEntry mEventEntry; |
| std::optional<ConcreteAttributePath> mLastListWriteOpPath; |
| std::optional<DataModel::ListWriteOperation> mLastListWriteOpType; |
| }; |
| |
| class TestCodeDrivenDataModelProvider : public ::testing::Test |
| { |
| public: |
| static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); } |
| static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); } |
| |
| protected: |
| TestProviderChangeListener mChangeListener; |
| chip::Test::LogOnlyEvents mEventGenerator; |
| TestActionContext mActionContext; |
| DataModel::InteractionModelContext mContext{ |
| .eventsGenerator = mEventGenerator, |
| .dataModelChangeListener = mChangeListener, |
| .actionContext = mActionContext, |
| }; |
| chip::Test::TestServerClusterContext mServerClusterTestContext; |
| CodeDrivenDataModelProvider mProvider; |
| std::vector<std::unique_ptr<SpanEndpoint>> mEndpointStorage; // To keep providers alive |
| std::vector<std::unique_ptr<EndpointInterfaceRegistration>> mOwnedRegistrations; // To keep registration objects alive |
| |
| TestCodeDrivenDataModelProvider() : |
| mProvider(mServerClusterTestContext.StorageDelegate(), mServerClusterTestContext.AttributePersistenceProvider()) |
| { |
| EXPECT_EQ(mProvider.Startup(mContext), CHIP_NO_ERROR); |
| } |
| |
| ~TestCodeDrivenDataModelProvider() override |
| { |
| mProvider.Shutdown(); |
| mEndpointStorage.clear(); |
| mOwnedRegistrations.clear(); |
| } |
| }; |
| |
| namespace { |
| |
| // Static Data Used for Tests |
| constexpr DataModel::EndpointEntry endpointEntry1 = { .id = 1, |
| .parentId = kInvalidEndpointId, |
| .compositionPattern = DataModel::EndpointCompositionPattern::kFullFamily }; |
| |
| constexpr DataModel::EndpointEntry endpointEntry2 = { .id = 2, |
| .parentId = kInvalidEndpointId, |
| .compositionPattern = DataModel::EndpointCompositionPattern::kTree }; |
| |
| constexpr DataModel::EndpointEntry endpointEntry3 = { .id = 3, |
| .parentId = kInvalidEndpointId, |
| .compositionPattern = DataModel::EndpointCompositionPattern::kFullFamily }; |
| |
| constexpr DataModel::EndpointEntry endpointEntry4 = { .id = 4, |
| .parentId = kInvalidEndpointId, |
| .compositionPattern = DataModel::EndpointCompositionPattern::kFullFamily }; |
| |
| SemanticTag semanticTag1 = { .mfgCode = VendorId::Google, |
| .namespaceID = 1, |
| .tag = 1, |
| .label = chip::Optional<chip::app::DataModel::Nullable<chip::CharSpan>>( |
| { chip::app::DataModel::MakeNullable(chip::CharSpan("label1", 6)) }) }; |
| SemanticTag semanticTag2 = { .mfgCode = VendorId::Google, |
| .namespaceID = 2, |
| .tag = 2, |
| .label = chip::Optional<chip::app::DataModel::Nullable<chip::CharSpan>>( |
| { chip::app::DataModel::MakeNullable(chip::CharSpan("label1", 6)) }) }; |
| |
| constexpr chip::EndpointId clientClusterId1 = 1; |
| constexpr chip::EndpointId clientClusterId2 = 2; |
| |
| // Define kMax constants for testing purposes |
| constexpr unsigned int kTestMaxDeviceTypes = 5; |
| |
| CHIP_ERROR ReadU32Attribute(DataModel::Provider & provider, const ConcreteAttributePath & path, uint32_t & value) |
| { |
| |
| ReadOperation testRequest(path); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| std::unique_ptr<AttributeValueEncoder> encoder = testRequest.StartEncoding(); |
| ReturnErrorOnFailure(provider.ReadAttribute(testRequest.GetRequest(), *encoder).GetUnderlyingError()); |
| ReturnErrorOnFailure(testRequest.FinishEncoding()); |
| |
| std::vector<DecodedAttributeData> attribute_data; |
| |
| ReturnErrorOnFailure(testRequest.GetEncodedIBs().Decode(attribute_data)); |
| VerifyOrReturnError(attribute_data.size() == 1u, CHIP_ERROR_INCORRECT_STATE); |
| |
| DecodedAttributeData & encodedData = attribute_data[0]; |
| |
| VerifyOrReturnError(encodedData.attributePath == testRequest.GetRequest().path, CHIP_ERROR_INCORRECT_STATE); |
| |
| return chip::app::DataModel::Decode<uint32_t>(encodedData.dataReader, value); |
| } |
| |
| CHIP_ERROR WriteU32Attribute(DataModel::Provider & provider, const ConcreteAttributePath & path, uint32_t value) |
| { |
| |
| WriteOperation testRequest(path); |
| testRequest.SetSubjectDescriptor(kAdminSubjectDescriptor); |
| |
| AttributeValueDecoder decoder = testRequest.DecoderFor(value); |
| return provider.WriteAttribute(testRequest.GetRequest(), decoder).GetUnderlyingError(); |
| } |
| |
| } // namespace |
| |
| TEST_F(TestCodeDrivenDataModelProvider, IterateOverEndpoints) |
| { |
| // Create 3 SpanEndpoints with different IDs |
| // EP1 |
| auto endpoint1 = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| |
| // EP2 |
| auto endpoint2 = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| |
| // EP3 |
| auto endpoint3 = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| |
| mEndpointStorage.push_back(std::move(endpoint1)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry1)); |
| ASSERT_EQ(mProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| mEndpointStorage.push_back(std::move(endpoint2)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry2)); |
| ASSERT_EQ(mProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| mEndpointStorage.push_back(std::move(endpoint3)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry3)); |
| ASSERT_EQ(mProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| |
| ReadOnlyBufferBuilder<DataModel::EndpointEntry> endpointsBuilder; |
| |
| ASSERT_EQ(mProvider.Endpoints(endpointsBuilder), CHIP_NO_ERROR); |
| |
| ReadOnlyBuffer<DataModel::EndpointEntry> endpoints_rb = endpointsBuilder.TakeBuffer(); |
| std::vector<DataModel::EndpointEntry> actual_endpoints(endpoints_rb.begin(), endpoints_rb.end()); |
| std::sort(actual_endpoints.begin(), actual_endpoints.end(), |
| [](const DataModel::EndpointEntry & a, const DataModel::EndpointEntry & b) { return a.id < b.id; }); |
| |
| ASSERT_EQ(actual_endpoints.size(), 3u); |
| EXPECT_EQ(actual_endpoints[0], endpointEntry1); |
| EXPECT_EQ(actual_endpoints[1], endpointEntry2); |
| EXPECT_EQ(actual_endpoints[2], endpointEntry3); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, IterateOverServerClusters) |
| { |
| static MockServerCluster mockServerCluster1(ConcreteClusterPath(1, 10), 1, BitFlags<DataModel::ClusterQualityFlags>()); |
| static MockServerCluster mockServerCluster2( |
| ConcreteClusterPath(1, 20), 2, |
| BitFlags<DataModel::ClusterQualityFlags>().Set(DataModel::ClusterQualityFlags::kDiagnosticsData)); |
| static MockServerCluster descriptorClusterEP1({ endpointEntry1.id, chip::app::Clusters::Descriptor::Id }, 1, |
| BitFlags<DataModel::ClusterQualityFlags>()); |
| |
| static ServerClusterRegistration registration1(mockServerCluster1); |
| static ServerClusterRegistration registration2(mockServerCluster2); |
| static ServerClusterRegistration registration3(descriptorClusterEP1); |
| |
| ASSERT_EQ(mProvider.AddCluster(registration1), CHIP_NO_ERROR); |
| ASSERT_EQ(mProvider.AddCluster(registration2), CHIP_NO_ERROR); |
| ASSERT_EQ(mProvider.AddCluster(registration3), CHIP_NO_ERROR); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry1)); |
| ASSERT_EQ(mProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| |
| ReadOnlyBufferBuilder<DataModel::ServerClusterEntry> builder; |
| ASSERT_EQ(mProvider.ServerClusters(endpointEntry1.id, builder), CHIP_NO_ERROR); |
| |
| auto serverClusters = builder.TakeBuffer(); |
| ASSERT_EQ(serverClusters.size(), 3u); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, IterateOverClientClusters) |
| { |
| static const ClusterId sClientClustersArray[] = { clientClusterId1, clientClusterId2 }; |
| |
| auto endpoint = std::make_unique<SpanEndpoint>( |
| SpanEndpoint::Builder().SetClientClusters(Span<const ClusterId>(sClientClustersArray)).Build()); |
| |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry1)); |
| ASSERT_EQ(mProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| |
| ReadOnlyBufferBuilder<chip::ClusterId> builder; |
| ASSERT_EQ(mProvider.ClientClusters(endpointEntry1.id, builder), CHIP_NO_ERROR); |
| |
| auto clientClusters = builder.TakeBuffer(); |
| ASSERT_EQ(clientClusters.size(), 2u); |
| EXPECT_EQ(clientClusters[0], clientClusterId1); |
| EXPECT_EQ(clientClusters[1], clientClusterId2); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, IterateOverTags) |
| { |
| static const SemanticTag sSemanticTagsArray[] = { semanticTag1, semanticTag2 }; |
| |
| auto endpoint = std::make_unique<SpanEndpoint>( |
| SpanEndpoint::Builder().SetSemanticTags(Span<const SemanticTag>(sSemanticTagsArray)).Build()); |
| |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry1)); |
| ASSERT_EQ(mProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| |
| ReadOnlyBufferBuilder<SemanticTag> builder; |
| ASSERT_EQ(mProvider.SemanticTags(endpointEntry1.id, builder), CHIP_NO_ERROR); |
| |
| auto tags = builder.TakeBuffer(); |
| ASSERT_EQ(tags.size(), 2u); |
| EXPECT_EQ(tags[0].mfgCode, semanticTag1.mfgCode); |
| EXPECT_EQ(tags[0].namespaceID, semanticTag1.namespaceID); |
| EXPECT_EQ(tags[0].tag, semanticTag1.tag); |
| EXPECT_EQ(tags[1].mfgCode, semanticTag2.mfgCode); |
| EXPECT_EQ(tags[1].namespaceID, semanticTag2.namespaceID); |
| EXPECT_EQ(tags[1].tag, semanticTag2.tag); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, IterateOverDeviceTypes) |
| { |
| static DataModel::DeviceTypeEntry sDeviceTypesData[kTestMaxDeviceTypes]; |
| for (unsigned int i = 0; i < kTestMaxDeviceTypes; i++) |
| { |
| sDeviceTypesData[i].deviceTypeId = static_cast<uint32_t>(i); |
| sDeviceTypesData[i].deviceTypeRevision = static_cast<uint8_t>((i % 255) + 1); |
| } |
| |
| auto endpoint = std::make_unique<SpanEndpoint>( |
| SpanEndpoint::Builder().SetDeviceTypes(Span<const DataModel::DeviceTypeEntry>(sDeviceTypesData)).Build()); |
| |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry1)); |
| ASSERT_EQ(mProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| |
| ReadOnlyBufferBuilder<DataModel::DeviceTypeEntry> builder; |
| ASSERT_EQ(mProvider.DeviceTypes(endpointEntry1.id, builder), CHIP_NO_ERROR); |
| |
| auto deviceTypesResult = builder.TakeBuffer(); |
| ASSERT_EQ(deviceTypesResult.size(), static_cast<size_t>(kTestMaxDeviceTypes)); |
| for (unsigned int i = 0; i < kTestMaxDeviceTypes; i++) |
| { |
| EXPECT_EQ(deviceTypesResult[i].deviceTypeId, static_cast<uint32_t>(i)); |
| EXPECT_EQ(deviceTypesResult[i].deviceTypeRevision, static_cast<uint8_t>((i % 255) + 1)); |
| } |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, AddAndRemoveEndpoints) |
| { |
| auto endpoint1 = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| |
| auto endpoint2 = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| |
| auto endpoint3 = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| |
| mEndpointStorage.push_back(std::move(endpoint1)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry1)); |
| ASSERT_EQ(mProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| mEndpointStorage.push_back(std::move(endpoint2)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry2)); |
| ASSERT_EQ(mProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| mEndpointStorage.push_back(std::move(endpoint3)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry3)); |
| ASSERT_EQ(mProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(mProvider.RemoveEndpoint(endpointEntry1.id), CHIP_NO_ERROR); |
| EXPECT_EQ(mProvider.RemoveEndpoint(endpointEntry3.id), CHIP_NO_ERROR); |
| |
| ReadOnlyBufferBuilder<DataModel::EndpointEntry> out; |
| EXPECT_EQ(mProvider.Endpoints(out), CHIP_NO_ERROR); |
| EXPECT_EQ(out.Size(), 1U); |
| auto endpoints = out.TakeBuffer(); |
| EXPECT_EQ(endpoints[0].id, endpointEntry2.id); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, EndpointWithStaticData) |
| { |
| CodeDrivenDataModelProvider localProvider(mServerClusterTestContext.StorageDelegate(), |
| mServerClusterTestContext.AttributePersistenceProvider()); |
| ASSERT_EQ(localProvider.Startup(mContext), CHIP_NO_ERROR); |
| |
| static const ClusterId clientClustersArray[] = { 0xD001, 0xD002 }; |
| static const SemanticTag semanticTagsArray[] = { { .mfgCode = VendorId::Google, .namespaceID = 10, .tag = 100 }, |
| { .mfgCode = VendorId::Google, .namespaceID = 11, .tag = 101 } }; |
| static const DataModel::DeviceTypeEntry deviceTypesArray[] = { { .deviceTypeId = 0x7001, .deviceTypeRevision = 1 }, |
| { .deviceTypeId = 0x7002, .deviceTypeRevision = 2 } }; |
| |
| SpanEndpoint ep = SpanEndpoint::Builder() |
| .SetClientClusters(chip::Span<const ClusterId>(clientClustersArray)) |
| .SetSemanticTags(chip::Span<const SemanticTag>(semanticTagsArray)) |
| .SetDeviceTypes(chip::Span<const DataModel::DeviceTypeEntry>(deviceTypesArray)) |
| .Build(); |
| |
| EndpointInterfaceRegistration registration(ep, endpointEntry4); |
| ASSERT_EQ(localProvider.AddEndpoint(registration), CHIP_NO_ERROR); |
| |
| ReadOnlyBufferBuilder<DataModel::EndpointEntry> epBuilder; |
| ASSERT_EQ(localProvider.Endpoints(epBuilder), CHIP_NO_ERROR); |
| auto eps = epBuilder.TakeBuffer(); |
| ASSERT_EQ(eps.size(), 1u); |
| EXPECT_EQ(eps[0].id, endpointEntry4.id); |
| |
| localProvider.Shutdown(); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, EndpointWithEmptyStaticData) |
| { |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder() |
| .SetClientClusters(Span<const ClusterId>()) |
| .SetSemanticTags(Span<const SemanticTag>()) |
| .SetDeviceTypes(Span<const DataModel::DeviceTypeEntry>()) |
| .Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry4)); |
| ASSERT_EQ(mProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, AddClusterFailsIfEndpointExists) |
| { |
| CodeDrivenDataModelProvider localProvider(mServerClusterTestContext.StorageDelegate(), |
| mServerClusterTestContext.AttributePersistenceProvider()); |
| ASSERT_EQ(localProvider.Startup(mContext), CHIP_NO_ERROR); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry1)); |
| ASSERT_EQ(localProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| |
| MockServerCluster testCluster(ConcreteClusterPath(endpointEntry1.id, 100), 1, {}); |
| ASSERT_EQ(testCluster.startupCallCount, 0); |
| |
| static ServerClusterRegistration registration(testCluster); |
| EXPECT_EQ(localProvider.AddCluster(registration), CHIP_ERROR_INCORRECT_STATE); |
| EXPECT_EQ(testCluster.startupCallCount, 0); |
| |
| localProvider.Shutdown(); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, AddClusterDoesntStartCluster) |
| { |
| CodeDrivenDataModelProvider localProvider(mServerClusterTestContext.StorageDelegate(), |
| mServerClusterTestContext.AttributePersistenceProvider()); |
| ASSERT_EQ(localProvider.Startup(mContext), CHIP_NO_ERROR); |
| |
| MockServerCluster testCluster(ConcreteClusterPath(endpointEntry1.id, 100), 1, {}); |
| ASSERT_EQ(testCluster.startupCallCount, 0); |
| |
| static ServerClusterRegistration registration(testCluster); |
| EXPECT_EQ(localProvider.AddCluster(registration), CHIP_NO_ERROR); |
| EXPECT_EQ(testCluster.startupCallCount, 0); |
| |
| localProvider.Shutdown(); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, AddClusterWithEmptyPathFails) |
| { |
| CodeDrivenDataModelProvider localProvider(mServerClusterTestContext.StorageDelegate(), |
| mServerClusterTestContext.AttributePersistenceProvider()); |
| ASSERT_EQ(localProvider.Startup(mContext), CHIP_NO_ERROR); |
| |
| MockServerCluster testCluster({}, 1, {}); |
| static ServerClusterRegistration registration(testCluster); |
| EXPECT_EQ(localProvider.AddCluster(registration), CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, ClusterStartupNotCalledWhenAddingToNonStartedProviderThenCalledOnProviderStartup) |
| { |
| CodeDrivenDataModelProvider localProvider(mServerClusterTestContext.StorageDelegate(), |
| mServerClusterTestContext.AttributePersistenceProvider()); |
| |
| MockServerCluster testCluster(ConcreteClusterPath(endpointEntry1.id, 101), 1, {}); |
| ASSERT_EQ(testCluster.startupCallCount, 0); |
| |
| static ServerClusterRegistration registration(testCluster); |
| ASSERT_EQ(localProvider.AddCluster(registration), CHIP_NO_ERROR); |
| EXPECT_EQ(testCluster.startupCallCount, 0); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry1)); |
| ASSERT_EQ(localProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| EXPECT_EQ(testCluster.startupCallCount, 0); |
| |
| ASSERT_EQ(localProvider.Startup(mContext), CHIP_NO_ERROR); |
| EXPECT_EQ(testCluster.startupCallCount, 1); |
| |
| localProvider.Shutdown(); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, RemoveClusterFailsIfEndpointExists) |
| { |
| CodeDrivenDataModelProvider localProvider(mServerClusterTestContext.StorageDelegate(), |
| mServerClusterTestContext.AttributePersistenceProvider()); |
| ASSERT_EQ(localProvider.Startup(mContext), CHIP_NO_ERROR); |
| |
| MockServerCluster testCluster(ConcreteClusterPath(endpointEntry1.id, 102), 1, {}); |
| static ServerClusterRegistration registration(testCluster); |
| ASSERT_EQ(localProvider.AddCluster(registration), CHIP_NO_ERROR); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry1)); |
| ASSERT_EQ(localProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(localProvider.RemoveCluster(&testCluster), CHIP_ERROR_INCORRECT_STATE); |
| EXPECT_EQ(testCluster.shutdownCallCount, 0); |
| |
| localProvider.Shutdown(); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, RemoveClusterSucceedsIfEndpointRemoved) |
| { |
| CodeDrivenDataModelProvider localProvider(mServerClusterTestContext.StorageDelegate(), |
| mServerClusterTestContext.AttributePersistenceProvider()); |
| ASSERT_EQ(localProvider.Startup(mContext), CHIP_NO_ERROR); |
| |
| MockServerCluster testCluster(ConcreteClusterPath(endpointEntry1.id, 102), 1, {}); |
| static ServerClusterRegistration registration(testCluster); |
| ASSERT_EQ(localProvider.AddCluster(registration), CHIP_NO_ERROR); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry1)); |
| ASSERT_EQ(localProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| |
| ASSERT_EQ(localProvider.RemoveEndpoint(endpointEntry1.id), CHIP_NO_ERROR); |
| EXPECT_EQ(localProvider.RemoveCluster(&testCluster), CHIP_NO_ERROR); |
| EXPECT_EQ(testCluster.shutdownCallCount, 1); |
| |
| localProvider.Shutdown(); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, ClusterShutdownNotCalledWhenRemovingFromNonStartedProvider) |
| { |
| CodeDrivenDataModelProvider localProvider(mServerClusterTestContext.StorageDelegate(), |
| mServerClusterTestContext.AttributePersistenceProvider()); |
| |
| MockServerCluster testCluster(ConcreteClusterPath(endpointEntry1.id, 103), 1, {}); |
| static ServerClusterRegistration registration(testCluster); |
| ASSERT_EQ(localProvider.AddCluster(registration), CHIP_NO_ERROR); |
| ASSERT_EQ(testCluster.startupCallCount, 0); |
| ASSERT_EQ(testCluster.shutdownCallCount, 0); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry1)); |
| ASSERT_EQ(localProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| |
| ASSERT_EQ(localProvider.RemoveEndpoint(endpointEntry1.id), CHIP_NO_ERROR); |
| ASSERT_EQ(localProvider.RemoveCluster(&testCluster), CHIP_NO_ERROR); |
| EXPECT_EQ(testCluster.shutdownCallCount, 0); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, ServerClustersMultiPath) |
| { |
| static MockServerCluster mockServerClusterMultiPath({ { 3, 40 }, { 3, 50 } }, 1, {}); |
| static ServerClusterRegistration registration(mockServerClusterMultiPath); |
| ASSERT_EQ(mProvider.AddCluster(registration), CHIP_NO_ERROR); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry3)); |
| ASSERT_EQ(mProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| |
| ReadOnlyBufferBuilder<DataModel::ServerClusterEntry> builder; |
| ASSERT_EQ(mProvider.ServerClusters(3, builder), CHIP_NO_ERROR); |
| |
| auto serverClusters = builder.TakeBuffer(); |
| ASSERT_EQ(serverClusters.size(), 2u); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, ReadAttribute) |
| { |
| static MockServerCluster testCluster({ 1, 10 }, 1, {}); |
| static ServerClusterRegistration registration(testCluster); |
| mProvider.AddCluster(registration); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ .id = 1 })); |
| mProvider.AddEndpoint(*mOwnedRegistrations.back()); |
| |
| uint32_t expectedValue = testCluster.mAttributeValue; |
| uint32_t readValue; |
| ReadU32Attribute(mProvider, ConcreteDataAttributePath(1, 10, 1), readValue); |
| EXPECT_EQ(expectedValue, readValue); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, WriteAttribute) |
| { |
| static MockServerCluster testCluster({ 1, 10 }, 1, {}); |
| static ServerClusterRegistration registration(testCluster); |
| mProvider.AddCluster(registration); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ .id = 1 })); |
| mProvider.AddEndpoint(*mOwnedRegistrations.back()); |
| |
| auto path = ConcreteDataAttributePath(1, 10, 1); |
| uint32_t valueToWrite = 123; |
| EXPECT_EQ(WriteU32Attribute(mProvider, path, valueToWrite), CHIP_NO_ERROR); |
| EXPECT_EQ(testCluster.mLastWriteRequest.path, path); |
| |
| uint32_t readValue; |
| ReadU32Attribute(mProvider, path, readValue); |
| EXPECT_EQ(readValue, valueToWrite); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, InvokeCommand) |
| { |
| static MockServerCluster testCluster({ 1, 10 }, 1, {}); |
| static ServerClusterRegistration registration(testCluster); |
| mProvider.AddCluster(registration); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ .id = 1 })); |
| mProvider.AddEndpoint(*mOwnedRegistrations.back()); |
| |
| System::PacketBufferHandle buffer = System::PacketBufferHandle::New(128); |
| TLV::TLVReader reader; |
| reader.Init(buffer->Start(), buffer->DataLength()); |
| DataModel::InvokeRequest request = { .path = ConcreteCommandPath(1, 10, 1) }; |
| auto result = mProvider.InvokeCommand(request, reader, nullptr); |
| EXPECT_TRUE(result.has_value()); |
| if (result.has_value()) |
| { |
| EXPECT_EQ(result.value().GetUnderlyingError(), CHIP_NO_ERROR); |
| } |
| EXPECT_EQ(testCluster.mLastInvokeRequest.path, request.path); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, IterateOverAttributes) |
| { |
| static MockServerCluster testCluster({ 1, 10 }, 1, {}); |
| static ServerClusterRegistration registration(testCluster); |
| mProvider.AddCluster(registration); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ .id = 1 })); |
| mProvider.AddEndpoint(*mOwnedRegistrations.back()); |
| |
| ReadOnlyBufferBuilder<DataModel::AttributeEntry> builder; |
| EXPECT_EQ(mProvider.Attributes(ConcreteClusterPath(1, 10), builder), CHIP_NO_ERROR); |
| auto attributes = builder.TakeBuffer(); |
| EXPECT_EQ(attributes.size(), 1u); |
| EXPECT_EQ(attributes[0].attributeId, testCluster.mAttributeEntry.attributeId); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, IterateOverAcceptedCommands) |
| { |
| static MockServerCluster testCluster({ 1, 10 }, 1, {}); |
| static ServerClusterRegistration registration(testCluster); |
| mProvider.AddCluster(registration); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ .id = 1 })); |
| mProvider.AddEndpoint(*mOwnedRegistrations.back()); |
| |
| ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> builder; |
| EXPECT_EQ(mProvider.AcceptedCommands(ConcreteClusterPath(1, 10), builder), CHIP_NO_ERROR); |
| auto commands = builder.TakeBuffer(); |
| EXPECT_EQ(commands.size(), 1u); |
| EXPECT_EQ(commands[0].commandId, testCluster.mAcceptedCommandEntry.commandId); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, IterateOverGeneratedCommands) |
| { |
| static MockServerCluster testCluster({ 1, 10 }, 1, {}); |
| static ServerClusterRegistration registration(testCluster); |
| mProvider.AddCluster(registration); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ .id = 1 })); |
| mProvider.AddEndpoint(*mOwnedRegistrations.back()); |
| |
| ReadOnlyBufferBuilder<CommandId> builder; |
| EXPECT_EQ(mProvider.GeneratedCommands(ConcreteClusterPath(1, 10), builder), CHIP_NO_ERROR); |
| auto commands = builder.TakeBuffer(); |
| EXPECT_EQ(commands.size(), 1u); |
| EXPECT_EQ(commands[0], testCluster.mGeneratedCommandId); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, GetEventInfo) |
| { |
| static MockServerCluster testCluster({ 1, 10 }, 1, {}); |
| static ServerClusterRegistration registration(testCluster); |
| mProvider.AddCluster(registration); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ .id = 1 })); |
| mProvider.AddEndpoint(*mOwnedRegistrations.back()); |
| |
| DataModel::EventEntry eventInfo; |
| testCluster.mEventEntry = { .readPrivilege = Access::Privilege::kView }; |
| EXPECT_EQ(mProvider.EventInfo(ConcreteEventPath(1, 10, 1), eventInfo), CHIP_NO_ERROR); |
| EXPECT_EQ(eventInfo.readPrivilege, testCluster.mEventEntry.readPrivilege); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, ListAttributeWriteNotification) |
| { |
| static MockServerCluster testCluster({ 1, 10 }, 1, {}); |
| static ServerClusterRegistration registration(testCluster); |
| mProvider.AddCluster(registration); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ .id = 1 })); |
| mProvider.AddEndpoint(*mOwnedRegistrations.back()); |
| |
| ConcreteAttributePath path(1, 10, 1); |
| mProvider.ListAttributeWriteNotification(path, DataModel::ListWriteOperation::kListWriteSuccess); |
| ASSERT_TRUE(testCluster.mLastListWriteOpPath.has_value()); |
| if (testCluster.mLastListWriteOpPath) |
| { |
| EXPECT_EQ(testCluster.mLastListWriteOpPath.value(), path); |
| } |
| ASSERT_TRUE(testCluster.mLastListWriteOpType.has_value()); |
| if (testCluster.mLastListWriteOpType) |
| { |
| EXPECT_EQ(testCluster.mLastListWriteOpType.value(), DataModel::ListWriteOperation::kListWriteSuccess); |
| } |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, Temporary_ReportAttributeChanged) |
| { |
| static MockServerCluster testCluster({ 1, 10 }, 1, {}); |
| static ServerClusterRegistration registration(testCluster); |
| mProvider.AddCluster(registration); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ .id = 1 })); |
| mProvider.AddEndpoint(*mOwnedRegistrations.back()); |
| |
| AttributePathParams path(1, 10, 1); |
| mProvider.Temporary_ReportAttributeChanged(path); |
| ASSERT_EQ(mChangeListener.mDirtyList.size(), 1u); |
| EXPECT_EQ(mChangeListener.mDirtyList[0].mEndpointId, path.mEndpointId); |
| EXPECT_EQ(mChangeListener.mDirtyList[0].mClusterId, path.mClusterId); |
| EXPECT_EQ(mChangeListener.mDirtyList[0].mAttributeId, path.mAttributeId); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, Shutdown) |
| { |
| static MockServerCluster testCluster(ConcreteClusterPath(endpointEntry1.id, 102), 1, {}); |
| static ServerClusterRegistration cluster_registration(testCluster); |
| ASSERT_EQ(mProvider.AddCluster(cluster_registration), CHIP_NO_ERROR); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| auto registration = |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ .id = 1 }); |
| ASSERT_EQ(mProvider.AddEndpoint(*registration), CHIP_NO_ERROR); |
| mOwnedRegistrations.push_back(std::move(registration)); |
| |
| ASSERT_EQ(mProvider.RemoveEndpoint(endpointEntry1.id), CHIP_NO_ERROR); |
| EXPECT_EQ(mProvider.Shutdown(), CHIP_NO_ERROR); |
| EXPECT_EQ(testCluster.shutdownCallCount, 1); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, ReadAttributeOnInvalidPath) |
| { |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ .id = 1 })); |
| mProvider.AddEndpoint(*mOwnedRegistrations.back()); |
| |
| uint32_t readValue; |
| EXPECT_EQ(ReadU32Attribute(mProvider, ConcreteDataAttributePath(1, 99, 1), readValue), CHIP_ERROR_KEY_NOT_FOUND); |
| EXPECT_EQ(ReadU32Attribute(mProvider, ConcreteDataAttributePath(99, 1, 1), readValue), CHIP_ERROR_KEY_NOT_FOUND); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, WriteAttributeOnInvalidPath) |
| { |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ .id = 1 })); |
| mProvider.AddEndpoint(*mOwnedRegistrations.back()); |
| |
| uint32_t valueToWrite = 123; |
| EXPECT_EQ(WriteU32Attribute(mProvider, ConcreteDataAttributePath(1, 99, 1), valueToWrite), CHIP_ERROR_KEY_NOT_FOUND); |
| EXPECT_EQ(WriteU32Attribute(mProvider, ConcreteDataAttributePath(99, 1, 1), valueToWrite), CHIP_ERROR_KEY_NOT_FOUND); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, RemoveNonExistentEndpoint) |
| { |
| EXPECT_EQ(mProvider.RemoveEndpoint(999), CHIP_ERROR_NOT_FOUND); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, InvokeCommandOnInvalidEndpoint) |
| { |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ .id = 1 })); |
| mProvider.AddEndpoint(*mOwnedRegistrations.back()); |
| |
| System::PacketBufferHandle buffer = System::PacketBufferHandle::New(128); |
| TLV::TLVReader reader; |
| reader.Init(buffer->Start(), buffer->DataLength()); |
| |
| DataModel::InvokeRequest requestUnsupportedEndpoint = { .path = ConcreteCommandPath(5, 10, 1) }; |
| auto result = mProvider.InvokeCommand(requestUnsupportedEndpoint, reader, nullptr); |
| ASSERT_TRUE(result.has_value()); |
| if (result) |
| { |
| EXPECT_EQ(result.value().GetUnderlyingError(), CHIP_ERROR_KEY_NOT_FOUND); |
| } |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, InvokeCommandOnInvalidCluster) |
| { |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ .id = 1 })); |
| mProvider.AddEndpoint(*mOwnedRegistrations.back()); |
| |
| System::PacketBufferHandle buffer = System::PacketBufferHandle::New(128); |
| TLV::TLVReader reader; |
| reader.Init(buffer->Start(), buffer->DataLength()); |
| |
| DataModel::InvokeRequest requestUnsupportedCluster = { .path = ConcreteCommandPath(1, 99, 1) }; |
| auto result = mProvider.InvokeCommand(requestUnsupportedCluster, reader, nullptr); |
| ASSERT_TRUE(result.has_value()); |
| if (result) |
| { |
| EXPECT_EQ(result.value().GetUnderlyingError(), CHIP_ERROR_KEY_NOT_FOUND); |
| } |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, SingleServerClusterInterfaceWithMultipleClustersAndMultipleEndpoints) |
| { |
| static MockServerCluster multiPathCluster({ { 1, 10 }, { 2, 30 } }, 1, {}); |
| static ServerClusterRegistration registration(multiPathCluster); |
| ASSERT_EQ(mProvider.AddCluster(registration), CHIP_NO_ERROR); |
| |
| auto endpoint1 = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint1)); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ .id = 1 })); |
| ASSERT_EQ(mProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| |
| auto endpoint2 = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint2)); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ .id = 2 })); |
| ASSERT_EQ(mProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| |
| { |
| ReadOnlyBufferBuilder<DataModel::ServerClusterEntry> builder; |
| ASSERT_EQ(mProvider.ServerClusters(1, builder), CHIP_NO_ERROR); |
| auto serverClusters = builder.TakeBuffer(); |
| ASSERT_EQ(serverClusters.size(), 1u); |
| EXPECT_EQ(serverClusters[0].clusterId, 10u); |
| } |
| |
| { |
| ReadOnlyBufferBuilder<DataModel::ServerClusterEntry> builder; |
| ASSERT_EQ(mProvider.ServerClusters(2, builder), CHIP_NO_ERROR); |
| auto serverClusters = builder.TakeBuffer(); |
| ASSERT_EQ(serverClusters.size(), 1u); |
| EXPECT_EQ(serverClusters[0].clusterId, 30u); |
| } |
| |
| { |
| ReadOnlyBufferBuilder<DataModel::ServerClusterEntry> builder; |
| EXPECT_EQ(mProvider.ServerClusters(99, builder), CHIP_IM_GLOBAL_STATUS(UnsupportedEndpoint)); |
| } |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, AddEndpointWithKInvalidId) |
| { |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*endpoint, DataModel::EndpointEntry{ kInvalidEndpointId })); |
| EndpointInterfaceRegistration * registration = mOwnedRegistrations.back().get(); |
| |
| EXPECT_EQ(mProvider.AddEndpoint(*registration), CHIP_ERROR_INVALID_ARGUMENT); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, AddDuplicateEndpointId) |
| { |
| mEndpointStorage.push_back(std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build())); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ 1 })); |
| EndpointInterfaceRegistration * registration1 = mOwnedRegistrations.back().get(); |
| |
| mEndpointStorage.push_back(std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build())); |
| mOwnedRegistrations.push_back( |
| std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), DataModel::EndpointEntry{ 1 })); |
| EndpointInterfaceRegistration * registration2 = mOwnedRegistrations.back().get(); |
| |
| EXPECT_EQ(mProvider.AddEndpoint(*registration1), CHIP_NO_ERROR); |
| EXPECT_EQ(mProvider.AddEndpoint(*registration2), CHIP_ERROR_DUPLICATE_KEY_ID); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, AddEndpointStartsClusterWithMultipleEndpoints) |
| { |
| CodeDrivenDataModelProvider localProvider(mServerClusterTestContext.StorageDelegate(), |
| mServerClusterTestContext.AttributePersistenceProvider()); |
| ASSERT_EQ(localProvider.Startup(mContext), CHIP_NO_ERROR); |
| |
| MockServerCluster testCluster({ { 1, 100 }, { 2, 100 } }, 1, {}); |
| static ServerClusterRegistration registration(testCluster); |
| ASSERT_EQ(localProvider.AddCluster(registration), CHIP_NO_ERROR); |
| ASSERT_EQ(testCluster.startupCallCount, 0); |
| |
| auto endpoint1 = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint1)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry1)); |
| ASSERT_EQ(localProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| ASSERT_EQ(testCluster.startupCallCount, 1); // Cluster should start on first endpoint |
| |
| auto endpoint2 = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint2)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry2)); |
| ASSERT_EQ(localProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| ASSERT_EQ(testCluster.startupCallCount, 1); // Should not start again |
| |
| localProvider.Shutdown(); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, RemoveEndpointShutsDownClusterWithMultipleEndpoints) |
| { |
| CodeDrivenDataModelProvider localProvider(mServerClusterTestContext.StorageDelegate(), |
| mServerClusterTestContext.AttributePersistenceProvider()); |
| ASSERT_EQ(localProvider.Startup(mContext), CHIP_NO_ERROR); |
| |
| MockServerCluster testCluster({ { 1, 100 }, { 2, 100 } }, 1, {}); |
| static ServerClusterRegistration registration(testCluster); |
| ASSERT_EQ(localProvider.AddCluster(registration), CHIP_NO_ERROR); |
| |
| auto endpoint1 = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint1)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry1)); |
| ASSERT_EQ(localProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| |
| auto endpoint2 = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint2)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry2)); |
| ASSERT_EQ(localProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| ASSERT_EQ(testCluster.startupCallCount, 1); |
| |
| ASSERT_EQ(localProvider.RemoveEndpoint(endpointEntry1.id), CHIP_NO_ERROR); |
| ASSERT_EQ(testCluster.shutdownCallCount, 0); |
| |
| ASSERT_EQ(localProvider.RemoveEndpoint(endpointEntry2.id), CHIP_NO_ERROR); |
| ASSERT_EQ(testCluster.shutdownCallCount, 1); |
| |
| localProvider.Shutdown(); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, RemoveEndpointThenRemoveClusterCallsShutdownOnce) |
| { |
| CodeDrivenDataModelProvider localProvider(mServerClusterTestContext.StorageDelegate(), |
| mServerClusterTestContext.AttributePersistenceProvider()); |
| ASSERT_EQ(localProvider.Startup(mContext), CHIP_NO_ERROR); |
| |
| MockServerCluster testCluster(ConcreteClusterPath(endpointEntry1.id, 102), 1, {}); |
| static ServerClusterRegistration registration(testCluster); |
| ASSERT_EQ(localProvider.AddCluster(registration), CHIP_NO_ERROR); |
| |
| auto endpoint = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry1)); |
| ASSERT_EQ(localProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| ASSERT_EQ(testCluster.startupCallCount, 1); |
| |
| ASSERT_EQ(localProvider.RemoveEndpoint(endpointEntry1.id), CHIP_NO_ERROR); |
| ASSERT_EQ(localProvider.RemoveCluster(&testCluster), CHIP_NO_ERROR); |
| EXPECT_EQ(testCluster.shutdownCallCount, 1); |
| |
| localProvider.Shutdown(); |
| } |
| |
| TEST_F(TestCodeDrivenDataModelProvider, StartupOnlyStartsClustersWithRegisteredEndpoints) |
| { |
| CodeDrivenDataModelProvider localProvider(mServerClusterTestContext.StorageDelegate(), |
| mServerClusterTestContext.AttributePersistenceProvider()); |
| |
| MockServerCluster clusterWithRegisteredEndpoint(ConcreteClusterPath(endpointEntry1.id, 100), 1, {}); |
| static ServerClusterRegistration registration1(clusterWithRegisteredEndpoint); |
| ASSERT_EQ(localProvider.AddCluster(registration1), CHIP_NO_ERROR); |
| |
| MockServerCluster clusterWithoutRegisteredEndpoint(ConcreteClusterPath(endpointEntry2.id, 200), 1, {}); |
| static ServerClusterRegistration registration2(clusterWithoutRegisteredEndpoint); |
| ASSERT_EQ(localProvider.AddCluster(registration2), CHIP_NO_ERROR); |
| |
| auto endpoint1 = std::make_unique<SpanEndpoint>(SpanEndpoint::Builder().Build()); |
| mEndpointStorage.push_back(std::move(endpoint1)); |
| mOwnedRegistrations.push_back(std::make_unique<EndpointInterfaceRegistration>(*mEndpointStorage.back(), endpointEntry1)); |
| ASSERT_EQ(localProvider.AddEndpoint(*mOwnedRegistrations.back()), CHIP_NO_ERROR); |
| |
| ASSERT_EQ(localProvider.Startup(mContext), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(clusterWithRegisteredEndpoint.startupCallCount, 1); |
| EXPECT_EQ(clusterWithoutRegisteredEndpoint.startupCallCount, 0); |
| |
| localProvider.Shutdown(); |
| } |