Add unit test for EcosystemInformation cluster server implementation (#35953)

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 4880c10..5793ebf 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 @@
 std::unique_ptr<EcosystemLocationStruct> 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<uint16_t>::max());
+    VerifyOrReturnValue(
+        mLocationDescriptor.mLocationName.size() <= kLocationDescriptorNameMaxSize, nullptr,
+        ChipLogError(Zcl, "Location Name must be less than %u bytes", static_cast<uint16_t>(kLocationDescriptorNameMaxSize)));
 
     // std::make_unique does not have access to private constructor we workaround with using new
     std::unique_ptr<EcosystemLocationStruct> ret{ new EcosystemLocationStruct(std::move(mLocationDescriptor),
@@ -271,6 +273,7 @@
     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 3fe1621..13d2bc8 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 <vector>
 
 #include <app-common/zap-generated/cluster-objects.h>
+#include <app-common/zap-generated/ids/Clusters.h>
 
 #include <app/AttributeAccessInterface.h>
 
diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn
index 104a57a..e3ad975 100644
--- a/src/app/tests/BUILD.gn
+++ b/src/app/tests/BUILD.gn
@@ -154,6 +154,18 @@
   ]
 }
 
+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 @@
     "TestDataModelSerialization.cpp",
     "TestDefaultOTARequestorStorage.cpp",
     "TestDefaultThreadNetworkDirectoryStorage.cpp",
+    "TestEcosystemInformationCluster.cpp",
     "TestEventLoggingNoUTCTime.cpp",
     "TestEventOverflow.cpp",
     "TestEventPathParams.cpp",
@@ -228,6 +241,7 @@
   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_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 0000000..f8d9f34
--- /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 <app/clusters/ecosystem-information-server/ecosystem-information-server.h>
+#include <app/data-model-provider/tests/ReadTesting.h>
+#include <pw_unit_test/framework.h>
+
+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<EcosystemDeviceStruct>
+    CreateSimplestValidDeviceStruct(const RequiredEcosystemDeviceParams & requiredParams = kDefaultRequiredDeviceParams)
+    {
+        std::unique_ptr<EcosystemDeviceStruct> deviceInfo = EcosystemDeviceStruct::Builder()
+                                                                .SetOriginalEndpoint(requiredParams.originalEndpointId)
+                                                                .AddDeviceType(requiredParams.deviceType)
+                                                                .SetFabricIndex(requiredParams.fabicIndex)
+                                                                .Build();
+        VerifyOrDie(deviceInfo);
+        return deviceInfo;
+    }
+
+    std::unique_ptr<EcosystemLocationStruct> CreateValidLocationStruct(const char * requiredLocationName = kValidLocationName)
+    {
+        std::string locationName(requiredLocationName);
+        std::unique_ptr<EcosystemLocationStruct> 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<AttributeValueEncoder> 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<AttributeValueEncoder> 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<AttributeValueEncoder> deviceDirectoryEncoder = testDeviceDirectoryRequest.StartEncoding();
+    ASSERT_EQ(EcoInfoCluster().ReadAttribute(deviceDirectoryPath, *deviceDirectoryEncoder), CHIP_NO_ERROR);
+    ASSERT_EQ(testDeviceDirectoryRequest.FinishEncoding(), CHIP_NO_ERROR);
+    std::vector<Testing::DecodedAttributeData> 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<AttributeValueEncoder> locationDirectoryEncoder = testLocationDirectoryRequest.StartEncoding();
+    ASSERT_EQ(EcoInfoCluster().ReadAttribute(locationDirectoryPath, *locationDirectoryEncoder), CHIP_NO_ERROR);
+    ASSERT_EQ(testLocationDirectoryRequest.FinishEncoding(), CHIP_NO_ERROR);
+    std::vector<Testing::DecodedAttributeData> 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<EcosystemDeviceStruct> 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<EcosystemDeviceStruct> 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<EcosystemDeviceStruct> 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<EcosystemDeviceStruct> 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<EcosystemDeviceStruct> 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<AttributeValueEncoder> deviceDirectoryEncoder = testDeviceDirectoryRequest.StartEncoding();
+
+    ASSERT_EQ(EcoInfoCluster().ReadAttribute(deviceDirectoryPath, *deviceDirectoryEncoder), CHIP_NO_ERROR);
+    ASSERT_EQ(testDeviceDirectoryRequest.FinishEncoding(), CHIP_NO_ERROR);
+
+    std::vector<Testing::DecodedAttributeData> 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<EcosystemLocationStruct> 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<EcosystemLocationStruct> 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<EcosystemLocationStruct> 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<EcosystemLocationStruct> 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<AttributeValueEncoder> locationDirectoryEncoder = testLocationDirectoryRequest.StartEncoding();
+    ASSERT_EQ(EcoInfoCluster().ReadAttribute(locationDirectoryPath, *locationDirectoryEncoder), CHIP_NO_ERROR);
+    ASSERT_EQ(testLocationDirectoryRequest.FinishEncoding(), CHIP_NO_ERROR);
+
+    std::vector<Testing::DecodedAttributeData> 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