diff --git a/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp b/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp index 4880c1058dc7f9..5793ebfd1a4726 100644 --- a/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp +++ b/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp @@ -215,9 +215,11 @@ EcosystemLocationStruct::Builder::SetLocationDescriptorLastEdit(uint64_t aLocati std::unique_ptr EcosystemLocationStruct::Builder::Build() { VerifyOrReturnValue(!mIsAlreadyBuilt, nullptr, ChipLogError(Zcl, "Build() already called")); - VerifyOrReturnValue(!mLocationDescriptor.mLocationName.empty(), nullptr, ChipLogError(Zcl, "Must Provided Location Name")); - VerifyOrReturnValue(mLocationDescriptor.mLocationName.size() <= kLocationDescriptorNameMaxSize, nullptr, - ChipLogError(Zcl, "Must Location Name must be less than 64 bytes")); + VerifyOrReturnValue(!mLocationDescriptor.mLocationName.empty(), nullptr, ChipLogError(Zcl, "Must Provide Location Name")); + static_assert(kLocationDescriptorNameMaxSize <= std::numeric_limits::max()); + VerifyOrReturnValue( + mLocationDescriptor.mLocationName.size() <= kLocationDescriptorNameMaxSize, nullptr, + ChipLogError(Zcl, "Location Name must be less than %u bytes", static_cast(kLocationDescriptorNameMaxSize))); // std::make_unique does not have access to private constructor we workaround with using new std::unique_ptr ret{ new EcosystemLocationStruct(std::move(mLocationDescriptor), @@ -271,6 +273,7 @@ CHIP_ERROR EcosystemInformationServer::AddLocationInfo(EndpointId aEndpoint, con VerifyOrReturnError(aLocation, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError((aEndpoint != kRootEndpointId && aEndpoint != kInvalidEndpointId), CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(!aLocationId.empty(), CHIP_ERROR_INVALID_ARGUMENT); + VerifyOrReturnError(aLocationId.size() <= kUniqueLocationIdMaxSize, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(aFabricIndex >= kMinValidFabricIndex, CHIP_ERROR_INVALID_ARGUMENT); VerifyOrReturnError(aFabricIndex <= kMaxValidFabricIndex, CHIP_ERROR_INVALID_ARGUMENT); diff --git a/src/app/clusters/ecosystem-information-server/ecosystem-information-server.h b/src/app/clusters/ecosystem-information-server/ecosystem-information-server.h index 3fe162181391d4..13d2bc8a6da850 100644 --- a/src/app/clusters/ecosystem-information-server/ecosystem-information-server.h +++ b/src/app/clusters/ecosystem-information-server/ecosystem-information-server.h @@ -26,6 +26,7 @@ #include #include +#include #include diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn index 104a57a2fc019a..e3ad975bce4fa6 100644 --- a/src/app/tests/BUILD.gn +++ b/src/app/tests/BUILD.gn @@ -154,6 +154,18 @@ source_set("thread-border-router-management-test-srcs") { ] } +source_set("ecosystem-information-test-srcs") { + sources = [ + "${chip_root}/src/app/clusters/ecosystem-information-server/ecosystem-information-server.cpp", + "${chip_root}/src/app/clusters/ecosystem-information-server/ecosystem-information-server.h", + ] + public_deps = [ + "${chip_root}/src/app:interaction-model", + "${chip_root}/src/app/common:cluster-objects", + "${chip_root}/src/lib/core", + ] +} + source_set("app-test-stubs") { sources = [ "test-ember-api.cpp", @@ -201,6 +213,7 @@ chip_test_suite("tests") { "TestDataModelSerialization.cpp", "TestDefaultOTARequestorStorage.cpp", "TestDefaultThreadNetworkDirectoryStorage.cpp", + "TestEcosystemInformationCluster.cpp", "TestEventLoggingNoUTCTime.cpp", "TestEventOverflow.cpp", "TestEventPathParams.cpp", @@ -228,6 +241,7 @@ chip_test_suite("tests") { public_deps = [ ":app-test-stubs", ":binding-test-srcs", + ":ecosystem-information-test-srcs", ":operational-state-test-srcs", ":ota-requestor-test-srcs", ":power-cluster-test-srcs", @@ -236,6 +250,7 @@ chip_test_suite("tests") { "${chip_root}/src/app", "${chip_root}/src/app/codegen-data-model-provider:instance-header", "${chip_root}/src/app/common:cluster-objects", + "${chip_root}/src/app/data-model-provider/tests:encode-decode", "${chip_root}/src/app/icd/client:manager", "${chip_root}/src/app/tests:helpers", "${chip_root}/src/app/util/mock:mock_codegen_data_model", diff --git a/src/app/tests/TestEcosystemInformationCluster.cpp b/src/app/tests/TestEcosystemInformationCluster.cpp new file mode 100644 index 00000000000000..f8d9f34642f1cc --- /dev/null +++ b/src/app/tests/TestEcosystemInformationCluster.cpp @@ -0,0 +1,392 @@ +/* + * Copyright (c) 2024 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 "lib/support/CHIPMem.h" + +#include +#include +#include + +namespace chip { +namespace app { +namespace { + +using namespace Clusters; +using namespace Clusters::EcosystemInformation; + +const EndpointId kValidEndpointId = 1; +const Structs::DeviceTypeStruct::Type kValidDeviceType = { .deviceType = 0, .revision = 1 }; +constexpr Access::SubjectDescriptor kSubjectDescriptor = Testing::kAdminSubjectDescriptor; +const FabricIndex kValidFabricIndex = kSubjectDescriptor.fabricIndex; + +struct RequiredEcosystemDeviceParams +{ + EndpointId originalEndpointId = kValidEndpointId; + Structs::DeviceTypeStruct::Type deviceType = kValidDeviceType; + FabricIndex fabicIndex = kValidFabricIndex; +}; + +const RequiredEcosystemDeviceParams kDefaultRequiredDeviceParams; + +const EndpointId kAnotherValidEndpointId = 2; +static_assert(kValidEndpointId != kAnotherValidEndpointId); +const char * kValidLocationName = "AValidLocationName"; +const ClusterId kEcosystemInfoClusterId = EcosystemInformation::Id; +const AttributeId kDeviceDirectoryAttributeId = EcosystemInformation::Attributes::DeviceDirectory::Id; +const AttributeId kLocationDirectoryAttributeId = EcosystemInformation::Attributes::LocationDirectory::Id; + +} // namespace + +class TestEcosystemInformationCluster : public ::testing::Test +{ +public: + static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); } + static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); } + + Clusters::EcosystemInformation::EcosystemInformationServer & EcoInfoCluster() { return mClusterServer; } + + std::unique_ptr + CreateSimplestValidDeviceStruct(const RequiredEcosystemDeviceParams & requiredParams = kDefaultRequiredDeviceParams) + { + std::unique_ptr deviceInfo = EcosystemDeviceStruct::Builder() + .SetOriginalEndpoint(requiredParams.originalEndpointId) + .AddDeviceType(requiredParams.deviceType) + .SetFabricIndex(requiredParams.fabicIndex) + .Build(); + VerifyOrDie(deviceInfo); + return deviceInfo; + } + + std::unique_ptr CreateValidLocationStruct(const char * requiredLocationName = kValidLocationName) + { + std::string locationName(requiredLocationName); + std::unique_ptr locationInfo = + EcosystemLocationStruct::Builder().SetLocationName(locationName).Build(); + VerifyOrDie(locationInfo); + return locationInfo; + } + +private: + Clusters::EcosystemInformation::EcosystemInformationServer mClusterServer; +}; + +TEST_F(TestEcosystemInformationCluster, UnsupportedClusterWhenReadingDeviceDirectoryOnNewClusterServer) +{ + ConcreteAttributePath path(kValidEndpointId, kEcosystemInfoClusterId, kDeviceDirectoryAttributeId); + + Testing::ReadOperation testRequest(path); + std::unique_ptr encoder = testRequest.StartEncoding(); + + ASSERT_EQ(EcoInfoCluster().ReadAttribute(path, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedCluster)); +} + +TEST_F(TestEcosystemInformationCluster, UnsupportedClusterWhenReadingLocationDirectoryOnNewClusterServer) +{ + ConcreteAttributePath path(kValidEndpointId, kEcosystemInfoClusterId, kLocationDirectoryAttributeId); + + Testing::ReadOperation testRequest(path); + std::unique_ptr encoder = testRequest.StartEncoding(); + + ASSERT_EQ(EcoInfoCluster().ReadAttribute(path, *encoder), CHIP_IM_GLOBAL_STATUS(UnsupportedCluster)); +} + +TEST_F(TestEcosystemInformationCluster, EmptyReadAfterAddEcosystemInformationClusterToEndpoint) +{ + ConcreteAttributePath deviceDirectoryPath(kValidEndpointId, kEcosystemInfoClusterId, kDeviceDirectoryAttributeId); + ConcreteAttributePath locationDirectoryPath(kValidEndpointId, kEcosystemInfoClusterId, kLocationDirectoryAttributeId); + + ASSERT_EQ(EcoInfoCluster().AddEcosystemInformationClusterToEndpoint(kValidEndpointId), CHIP_NO_ERROR); + + Testing::ReadOperation testDeviceDirectoryRequest(deviceDirectoryPath); + std::unique_ptr deviceDirectoryEncoder = testDeviceDirectoryRequest.StartEncoding(); + ASSERT_EQ(EcoInfoCluster().ReadAttribute(deviceDirectoryPath, *deviceDirectoryEncoder), CHIP_NO_ERROR); + ASSERT_EQ(testDeviceDirectoryRequest.FinishEncoding(), CHIP_NO_ERROR); + std::vector deviceDirectoryAttributeData; + ASSERT_EQ(testDeviceDirectoryRequest.GetEncodedIBs().Decode(deviceDirectoryAttributeData), CHIP_NO_ERROR); + ASSERT_EQ(deviceDirectoryAttributeData.size(), 1u); + Testing::DecodedAttributeData & deviceDirectoryEncodedData = deviceDirectoryAttributeData[0]; + ASSERT_EQ(deviceDirectoryEncodedData.attributePath, testDeviceDirectoryRequest.GetRequest().path); + EcosystemInformation::Attributes::DeviceDirectory::TypeInfo::DecodableType decodableDeviceDirectory; + ASSERT_EQ(decodableDeviceDirectory.Decode(deviceDirectoryEncodedData.dataReader), CHIP_NO_ERROR); + size_t deviceDirectorySize = 0; + ASSERT_EQ(decodableDeviceDirectory.ComputeSize(&deviceDirectorySize), CHIP_NO_ERROR); + ASSERT_EQ(deviceDirectorySize, 0u); + + Testing::ReadOperation testLocationDirectoryRequest(locationDirectoryPath); + std::unique_ptr locationDirectoryEncoder = testLocationDirectoryRequest.StartEncoding(); + ASSERT_EQ(EcoInfoCluster().ReadAttribute(locationDirectoryPath, *locationDirectoryEncoder), CHIP_NO_ERROR); + ASSERT_EQ(testLocationDirectoryRequest.FinishEncoding(), CHIP_NO_ERROR); + std::vector locationDirectoryAttributeData; + ASSERT_EQ(testLocationDirectoryRequest.GetEncodedIBs().Decode(locationDirectoryAttributeData), CHIP_NO_ERROR); + ASSERT_EQ(locationDirectoryAttributeData.size(), 1u); + Testing::DecodedAttributeData & locationDirectoryEncodedData = locationDirectoryAttributeData[0]; + ASSERT_EQ(locationDirectoryEncodedData.attributePath, testLocationDirectoryRequest.GetRequest().path); + EcosystemInformation::Attributes::LocationDirectory::TypeInfo::DecodableType decodableLocationDirectory; + ASSERT_EQ(decodableLocationDirectory.Decode(locationDirectoryEncodedData.dataReader), CHIP_NO_ERROR); + size_t locationDirectorySize = 0; + ASSERT_EQ(decodableLocationDirectory.ComputeSize(&locationDirectorySize), CHIP_NO_ERROR); + ASSERT_EQ(locationDirectorySize, 0u); +} + +TEST_F(TestEcosystemInformationCluster, BuildingEcosystemDeviceStruct) +{ + EcosystemDeviceStruct::Builder deviceInfoBuilder; + std::unique_ptr deviceInfo = deviceInfoBuilder.Build(); + ASSERT_FALSE(deviceInfo); + + deviceInfoBuilder.SetOriginalEndpoint(1); + deviceInfo = deviceInfoBuilder.Build(); + ASSERT_FALSE(deviceInfo); + + auto deviceType = Structs::DeviceTypeStruct::Type(); + deviceType.revision = 1; + deviceInfoBuilder.AddDeviceType(deviceType); + deviceInfo = deviceInfoBuilder.Build(); + ASSERT_FALSE(deviceInfo); + + deviceInfoBuilder.SetFabricIndex(1); + deviceInfo = deviceInfoBuilder.Build(); + ASSERT_TRUE(deviceInfo); + + // Building a second device info with previously successfully built deviceInfoBuilder + // is expected to fail. + std::unique_ptr secondDeviceInfo = deviceInfoBuilder.Build(); + ASSERT_FALSE(secondDeviceInfo); +} + +TEST_F(TestEcosystemInformationCluster, BuildingInvalidEcosystemDeviceStruct) +{ + auto deviceType = Structs::DeviceTypeStruct::Type(); + deviceType.revision = 1; + const FabricIndex kFabricIndexTooLow = 0; + const FabricIndex kFabricIndexTooHigh = kMaxValidFabricIndex + 1; + + EcosystemDeviceStruct::Builder deviceInfoBuilder; + deviceInfoBuilder.SetOriginalEndpoint(1); + deviceInfoBuilder.AddDeviceType(deviceType); + deviceInfoBuilder.SetFabricIndex(kFabricIndexTooLow); + std::unique_ptr deviceInfo = deviceInfoBuilder.Build(); + ASSERT_FALSE(deviceInfo); + + deviceInfoBuilder.SetFabricIndex(kFabricIndexTooHigh); + deviceInfo = deviceInfoBuilder.Build(); + ASSERT_FALSE(deviceInfo); + + deviceInfoBuilder.SetFabricIndex(1); + // At this point deviceInfoBuilder would be able to be built successfully. + + std::string nameThatsTooLong(65, 'x'); + uint64_t nameEpochValueUs = 0; // This values doesn't matter. + deviceInfoBuilder.SetDeviceName(std::move(nameThatsTooLong), nameEpochValueUs); + deviceInfo = deviceInfoBuilder.Build(); + ASSERT_FALSE(deviceInfo); + + // Ending unit test by building something that should work just to make sure + // Builder isn't silently failing on building for some other reason. + std::string nameThatsMaxLength(64, 'x'); + deviceInfoBuilder.SetDeviceName(std::move(nameThatsMaxLength), nameEpochValueUs); + deviceInfo = deviceInfoBuilder.Build(); + ASSERT_TRUE(deviceInfo); +} + +TEST_F(TestEcosystemInformationCluster, AddDeviceInfoInvalidArguments) +{ + ASSERT_EQ(EcoInfoCluster().AddDeviceInfo(kValidEndpointId, nullptr), CHIP_ERROR_INVALID_ARGUMENT); + + std::unique_ptr deviceInfo = CreateSimplestValidDeviceStruct(); + ASSERT_TRUE(deviceInfo); + ASSERT_EQ(EcoInfoCluster().AddDeviceInfo(kRootEndpointId, std::move(deviceInfo)), CHIP_ERROR_INVALID_ARGUMENT); + + deviceInfo = CreateSimplestValidDeviceStruct(); + ASSERT_TRUE(deviceInfo); + ASSERT_EQ(EcoInfoCluster().AddDeviceInfo(kInvalidEndpointId, std::move(deviceInfo)), CHIP_ERROR_INVALID_ARGUMENT); +} + +TEST_F(TestEcosystemInformationCluster, AddDeviceInfo) +{ + std::unique_ptr deviceInfo = CreateSimplestValidDeviceStruct(); + // originalEndpoint and path endpoint do not need to be the same, for that reason we use a different value for + // path endpoint + static_assert(kAnotherValidEndpointId != kValidEndpointId); + ASSERT_EQ(EcoInfoCluster().AddDeviceInfo(kAnotherValidEndpointId, std::move(deviceInfo)), CHIP_NO_ERROR); + ConcreteAttributePath deviceDirectoryPath(kAnotherValidEndpointId, kEcosystemInfoClusterId, kDeviceDirectoryAttributeId); + Testing::ReadOperation testDeviceDirectoryRequest(deviceDirectoryPath); + testDeviceDirectoryRequest.SetSubjectDescriptor(kSubjectDescriptor); + std::unique_ptr deviceDirectoryEncoder = testDeviceDirectoryRequest.StartEncoding(); + + ASSERT_EQ(EcoInfoCluster().ReadAttribute(deviceDirectoryPath, *deviceDirectoryEncoder), CHIP_NO_ERROR); + ASSERT_EQ(testDeviceDirectoryRequest.FinishEncoding(), CHIP_NO_ERROR); + + std::vector attributeData; + ASSERT_EQ(testDeviceDirectoryRequest.GetEncodedIBs().Decode(attributeData), CHIP_NO_ERROR); + ASSERT_EQ(attributeData.size(), 1u); + Testing::DecodedAttributeData & encodedData = attributeData[0]; + ASSERT_EQ(encodedData.attributePath, testDeviceDirectoryRequest.GetRequest().path); + EcosystemInformation::Attributes::DeviceDirectory::TypeInfo::DecodableType decodableDeviceDirectory; + ASSERT_EQ(decodableDeviceDirectory.Decode(encodedData.dataReader), CHIP_NO_ERROR); + size_t size = 0; + ASSERT_EQ(decodableDeviceDirectory.ComputeSize(&size), CHIP_NO_ERROR); + ASSERT_EQ(size, 1u); + auto iterator = decodableDeviceDirectory.begin(); + ASSERT_TRUE(iterator.Next()); + auto deviceDirectoryEntry = iterator.GetValue(); + ASSERT_FALSE(deviceDirectoryEntry.deviceName.HasValue()); + ASSERT_FALSE(deviceDirectoryEntry.deviceNameLastEdit.HasValue()); + ASSERT_EQ(deviceDirectoryEntry.bridgedEndpoint, kInvalidEndpointId); + ASSERT_EQ(deviceDirectoryEntry.originalEndpoint, kValidEndpointId); + size_t deviceTypeListSize = 0; + ASSERT_EQ(deviceDirectoryEntry.deviceTypes.ComputeSize(&deviceTypeListSize), CHIP_NO_ERROR); + ASSERT_EQ(deviceTypeListSize, 1u); + auto deviceTypeIterator = deviceDirectoryEntry.deviceTypes.begin(); + ASSERT_TRUE(deviceTypeIterator.Next()); + auto deviceTypeEntry = deviceTypeIterator.GetValue(); + ASSERT_EQ(deviceTypeEntry.deviceType, 0u); + ASSERT_EQ(deviceTypeEntry.revision, 1); + ASSERT_FALSE(deviceTypeIterator.Next()); + size_t uniqueLocationIdListSize = 0; + ASSERT_EQ(deviceDirectoryEntry.uniqueLocationIDs.ComputeSize(&uniqueLocationIdListSize), CHIP_NO_ERROR); + ASSERT_EQ(uniqueLocationIdListSize, 0u); + ASSERT_EQ(deviceDirectoryEntry.uniqueLocationIDsLastEdit, 0u); + ASSERT_EQ(deviceDirectoryEntry.fabricIndex, kSubjectDescriptor.fabricIndex); + ASSERT_FALSE(iterator.Next()); +} + +TEST_F(TestEcosystemInformationCluster, BuildingEcosystemLocationStruct) +{ + EcosystemLocationStruct::Builder locationInfoBuilder; + + std::string validLocationName = "validName"; + locationInfoBuilder.SetLocationName(validLocationName); + + std::unique_ptr locationInfo = locationInfoBuilder.Build(); + ASSERT_TRUE(locationInfo); + + // Building a second device info with previously successfully built deviceInfoBuilder + // is expected to fail. + locationInfo = locationInfoBuilder.Build(); + ASSERT_FALSE(locationInfo); +} + +TEST_F(TestEcosystemInformationCluster, BuildingInvalidEcosystemLocationStruct) +{ + EcosystemLocationStruct::Builder locationInfoBuilder; + + std::string nameThatsTooLong(129, 'x'); + locationInfoBuilder.SetLocationName(nameThatsTooLong); + + std::unique_ptr locationInfo = locationInfoBuilder.Build(); + ASSERT_FALSE(locationInfo); + + // Ending unit test by building something that should work just to make sure + // Builder isn't silently failing on building for some other reason. + std::string nameThatsMaxLength(128, 'x'); + locationInfoBuilder.SetLocationName(nameThatsMaxLength); + locationInfo = locationInfoBuilder.Build(); + ASSERT_TRUE(locationInfo); +} + +TEST_F(TestEcosystemInformationCluster, AddLocationInfoInvalidArguments) +{ + const FabricIndex kFabricIndexTooLow = 0; + const FabricIndex kFabricIndexTooHigh = kMaxValidFabricIndex + 1; + const std::string kEmptyLocationIdStr; + const std::string kValidLocationIdStr = "SomeLocationString"; + const std::string kInvalidLocationIdTooLongStr(65, 'x'); + + std::unique_ptr locationInfo = CreateValidLocationStruct(); + ASSERT_TRUE(locationInfo); + ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kInvalidEndpointId, kValidLocationIdStr, kValidFabricIndex, std::move(locationInfo)), + CHIP_ERROR_INVALID_ARGUMENT); + + locationInfo = CreateValidLocationStruct(); + ASSERT_TRUE(locationInfo); + ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kRootEndpointId, kValidLocationIdStr, kValidFabricIndex, std::move(locationInfo)), + CHIP_ERROR_INVALID_ARGUMENT); + + locationInfo = CreateValidLocationStruct(); + ASSERT_TRUE(locationInfo); + ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kValidEndpointId, kEmptyLocationIdStr, kValidFabricIndex, std::move(locationInfo)), + CHIP_ERROR_INVALID_ARGUMENT); + + locationInfo = CreateValidLocationStruct(); + ASSERT_TRUE(locationInfo); + ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kValidEndpointId, kInvalidLocationIdTooLongStr, kValidFabricIndex, + std::move(locationInfo)), + CHIP_ERROR_INVALID_ARGUMENT); + + locationInfo = CreateValidLocationStruct(); + ASSERT_TRUE(locationInfo); + ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kValidEndpointId, kValidLocationIdStr, kFabricIndexTooLow, std::move(locationInfo)), + CHIP_ERROR_INVALID_ARGUMENT); + + locationInfo = CreateValidLocationStruct(); + ASSERT_TRUE(locationInfo); + ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kValidEndpointId, kValidLocationIdStr, kFabricIndexTooHigh, std::move(locationInfo)), + CHIP_ERROR_INVALID_ARGUMENT); + + // Sanity check that we can successfully add something after all the previously failed attempts + locationInfo = CreateValidLocationStruct(); + ASSERT_TRUE(locationInfo); + ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kValidEndpointId, kValidLocationIdStr, kValidFabricIndex, std::move(locationInfo)), + CHIP_NO_ERROR); + + // Adding a second identical entry is expected to fail + locationInfo = CreateValidLocationStruct(); + ASSERT_TRUE(locationInfo); + ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kValidEndpointId, kValidLocationIdStr, kValidFabricIndex, std::move(locationInfo)), + CHIP_ERROR_INVALID_ARGUMENT); +} + +TEST_F(TestEcosystemInformationCluster, AddLocationInfo) +{ + std::unique_ptr locationInfo = CreateValidLocationStruct(); + const char * kValidLocationIdStr = "SomeLocationIdString"; + ASSERT_EQ(EcoInfoCluster().AddLocationInfo(kValidEndpointId, kValidLocationIdStr, Testing::kAdminSubjectDescriptor.fabricIndex, + std::move(locationInfo)), + CHIP_NO_ERROR); + + ConcreteAttributePath locationDirectoryPath(kValidEndpointId, kEcosystemInfoClusterId, kLocationDirectoryAttributeId); + Testing::ReadOperation testLocationDirectoryRequest(locationDirectoryPath); + testLocationDirectoryRequest.SetSubjectDescriptor(Testing::kAdminSubjectDescriptor); + std::unique_ptr locationDirectoryEncoder = testLocationDirectoryRequest.StartEncoding(); + ASSERT_EQ(EcoInfoCluster().ReadAttribute(locationDirectoryPath, *locationDirectoryEncoder), CHIP_NO_ERROR); + ASSERT_EQ(testLocationDirectoryRequest.FinishEncoding(), CHIP_NO_ERROR); + + std::vector locationDirectoryAttributeData; + ASSERT_EQ(testLocationDirectoryRequest.GetEncodedIBs().Decode(locationDirectoryAttributeData), CHIP_NO_ERROR); + ASSERT_EQ(locationDirectoryAttributeData.size(), 1u); + Testing::DecodedAttributeData & locationDirectoryEncodedData = locationDirectoryAttributeData[0]; + ASSERT_EQ(locationDirectoryEncodedData.attributePath, testLocationDirectoryRequest.GetRequest().path); + EcosystemInformation::Attributes::LocationDirectory::TypeInfo::DecodableType decodableLocationDirectory; + ASSERT_EQ(decodableLocationDirectory.Decode(locationDirectoryEncodedData.dataReader), CHIP_NO_ERROR); + size_t locationDirectorySize = 0; + ASSERT_EQ(decodableLocationDirectory.ComputeSize(&locationDirectorySize), CHIP_NO_ERROR); + ASSERT_EQ(locationDirectorySize, 1u); + auto iterator = decodableLocationDirectory.begin(); + ASSERT_TRUE(iterator.Next()); + auto locationDirectoryEntry = iterator.GetValue(); + ASSERT_TRUE(locationDirectoryEntry.uniqueLocationID.data_equal(CharSpan::fromCharString(kValidLocationIdStr))); + ASSERT_TRUE(locationDirectoryEntry.locationDescriptor.locationName.data_equal(CharSpan::fromCharString(kValidLocationName))); + ASSERT_TRUE(locationDirectoryEntry.locationDescriptor.floorNumber.IsNull()); + ASSERT_TRUE(locationDirectoryEntry.locationDescriptor.areaType.IsNull()); + ASSERT_EQ(locationDirectoryEntry.locationDescriptorLastEdit, 0u); + ASSERT_EQ(locationDirectoryEntry.fabricIndex, Testing::kAdminSubjectDescriptor.fabricIndex); + ASSERT_FALSE(iterator.Next()); +} + +} // namespace app +} // namespace chip