[ledger] Test LedgerRepositoryFactoryImpl

Test: The new test passes.

Change-Id: Ic3c00f69f764bfd02b26db35292414152270c54c
diff --git a/bin/ledger/app/BUILD.gn b/bin/ledger/app/BUILD.gn
index 3f6a790..104d01b 100644
--- a/bin/ledger/app/BUILD.gn
+++ b/bin/ledger/app/BUILD.gn
@@ -158,6 +158,7 @@
     "disk_cleanup_manager_unittest.cc",
     "fidl/serialization_size_unittest.cc",
     "ledger_manager_unittest.cc",
+    "ledger_repository_factory_impl_unittest.cc",
     "ledger_repository_impl_unittest.cc",
     "merging/common_ancestor_unittest.cc",
     "merging/conflict_resolver_client_unittest.cc",
diff --git a/bin/ledger/app/ledger_repository_factory_impl.h b/bin/ledger/app/ledger_repository_factory_impl.h
index 7271bd4..d43e888 100644
--- a/bin/ledger/app/ledger_repository_factory_impl.h
+++ b/bin/ledger/app/ledger_repository_factory_impl.h
@@ -40,10 +40,6 @@
       component::ObjectDir inspect_object_dir);
   ~LedgerRepositoryFactoryImpl() override;
 
- private:
-  class LedgerRepositoryContainer;
-  struct RepositoryInformation;
-
   // LedgerRepositoryFactoryErrorNotifierDelegate:
   void GetRepository(
       zx::channel repository_handle,
@@ -53,6 +49,10 @@
           repository_request,
       fit::function<void(Status)> callback) override;
 
+ private:
+  class LedgerRepositoryContainer;
+  struct RepositoryInformation;
+
   // Binds |repository_request| to the repository stored in the directory opened
   // in |root_fd|.
   void GetRepositoryByFD(
diff --git a/bin/ledger/app/ledger_repository_factory_impl_unittest.cc b/bin/ledger/app/ledger_repository_factory_impl_unittest.cc
new file mode 100644
index 0000000..5391029
--- /dev/null
+++ b/bin/ledger/app/ledger_repository_factory_impl_unittest.cc
@@ -0,0 +1,326 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "peridot/bin/ledger/app/ledger_repository_factory_impl.h"
+
+#include <fuchsia/inspect/cpp/fidl.h>
+#include <lib/callback/capture.h>
+#include <lib/callback/set_when_called.h>
+#include <lib/fsl/io/fd.h>
+#include <lib/fxl/files/directory.h>
+#include <lib/fxl/files/unique_fd.h>
+#include <lib/fxl/strings/string_view.h>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "peridot/bin/ledger/testing/inspect.h"
+#include "peridot/bin/ledger/testing/test_with_environment.h"
+#include "peridot/lib/scoped_tmpfs/scoped_tmpfs.h"
+
+namespace ledger {
+namespace {
+
+using ::testing::ElementsAre;
+using ::testing::IsEmpty;
+using ::testing::Not;
+using ::testing::SizeIs;
+using ::testing::UnorderedElementsAre;
+
+constexpr fxl::StringView kObjectsName = "test objects";
+constexpr fxl::StringView kRepositoriesName = "repositories";
+constexpr fxl::StringView kUserID = "test user ID";
+
+class LedgerRepositoryFactoryImplTest : public TestWithEnvironment {
+ public:
+  LedgerRepositoryFactoryImplTest() {
+    object_dir_ = component::ObjectDir(
+        fbl::MakeRefCounted<component::Object>(kObjectsName));
+    repository_factory_ = std::make_unique<LedgerRepositoryFactoryImpl>(
+        &environment_, nullptr, object_dir_);
+  }
+
+  ~LedgerRepositoryFactoryImplTest() override {}
+
+ protected:
+  ::testing::AssertionResult CreateDirectory(std::string name);
+  ::testing::AssertionResult CallGetRepository(
+      std::string name,
+      ledger_internal::LedgerRepositoryPtr* ledger_repository_ptr);
+  ::testing::AssertionResult ReadTopLevelData(fuchsia::inspect::Object* object);
+  ::testing::AssertionResult ListTopLevelChildren(
+      fidl::VectorPtr<fidl::StringPtr>* children);
+  ::testing::AssertionResult OpenTopLevelRepositoriesChild(
+      fuchsia::inspect::InspectPtr* repositories_inspect_ptr);
+  ::testing::AssertionResult ReadData(fuchsia::inspect::InspectPtr* inspect_ptr,
+                                      fuchsia::inspect::Object* object);
+  ::testing::AssertionResult ListChildren(
+      fuchsia::inspect::InspectPtr* inspect_ptr,
+      fidl::VectorPtr<fidl::StringPtr>* children_names);
+  ::testing::AssertionResult OpenChild(
+      fuchsia::inspect::InspectPtr* parent_inspect_ptr,
+      fidl::StringPtr child_name,
+      fuchsia::inspect::InspectPtr* child_inspect_ptr);
+
+  scoped_tmpfs::ScopedTmpFS tmpfs_;
+  component::ObjectDir object_dir_;
+  std::unique_ptr<LedgerRepositoryFactoryImpl> repository_factory_;
+
+ private:
+  FXL_DISALLOW_COPY_AND_ASSIGN(LedgerRepositoryFactoryImplTest);
+};
+
+::testing::AssertionResult LedgerRepositoryFactoryImplTest::CreateDirectory(
+    std::string name) {
+  if (!files::CreateDirectoryAt(tmpfs_.root_fd(), name)) {
+    return ::testing::AssertionFailure()
+           << "Failed to create directory \"" << name << "\"!";
+  }
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult LedgerRepositoryFactoryImplTest::CallGetRepository(
+    std::string name,
+    ledger_internal::LedgerRepositoryPtr* ledger_repository_ptr) {
+  fxl::UniqueFD fd(openat(tmpfs_.root_fd(), name.c_str(), O_PATH));
+  if (!fd.is_valid()) {
+    return ::testing::AssertionFailure()
+           << "Failed to validate directory \"" << name << "\"!";
+  }
+
+  bool callback_called;
+  Status status = Status::UNKNOWN_ERROR;
+
+  repository_factory_->GetRepository(
+      fsl::CloneChannelFromFileDescriptor(fd.get()), nullptr,
+      kUserID.ToString(), ledger_repository_ptr->NewRequest(),
+      callback::Capture(callback::SetWhenCalled(&callback_called), &status));
+
+  if (!callback_called) {
+    return ::testing::AssertionFailure()
+           << "Callback passed to GetRepository not called!";
+  }
+  if (status != Status::OK) {
+    return ::testing::AssertionFailure() << "Status of GetRepository call was "
+                                         << static_cast<int32_t>(status) << "!";
+  }
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult LedgerRepositoryFactoryImplTest::ReadTopLevelData(
+    fuchsia::inspect::Object* object) {
+  bool callback_called;
+
+  object_dir_.object()->ReadData(
+      callback::Capture(callback::SetWhenCalled(&callback_called), object));
+  RunLoopUntilIdle();
+
+  if (!callback_called) {
+    return ::testing::AssertionFailure()
+           << "Callback passed to object_dir_.object()->ReadData not called!";
+  }
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult
+LedgerRepositoryFactoryImplTest::ListTopLevelChildren(
+    fidl::VectorPtr<fidl::StringPtr>* children) {
+  bool callback_called;
+
+  object_dir_.object()->ListChildren(
+      callback::Capture(callback::SetWhenCalled(&callback_called), children));
+  RunLoopUntilIdle();
+
+  if (!callback_called) {
+    return ::testing::AssertionFailure()
+           << "Callback passed to object_dir_.object()->ListChildren not "
+              "called!";
+  }
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult
+LedgerRepositoryFactoryImplTest::OpenTopLevelRepositoriesChild(
+    fuchsia::inspect::InspectPtr* repositories_inspect_ptr) {
+  bool callback_called;
+  bool success = false;
+
+  object_dir_.object()->OpenChild(
+      kRepositoriesName.ToString(), repositories_inspect_ptr->NewRequest(),
+      callback::Capture(callback::SetWhenCalled(&callback_called), &success));
+  RunLoopUntilIdle();
+
+  if (!callback_called) {
+    return ::testing::AssertionFailure()
+           << "Callback passed to object_dir_.object()->OpenChild not called!";
+  }
+  if (!success) {
+    return ::testing::AssertionFailure()
+           << "object_dir_.object()->OpenChild call unsuccessful!";
+  }
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult LedgerRepositoryFactoryImplTest::ReadData(
+    fuchsia::inspect::InspectPtr* inspect_ptr,
+    fuchsia::inspect::Object* object) {
+  bool callback_called;
+
+  (*inspect_ptr)
+      ->ReadData(
+          callback::Capture(callback::SetWhenCalled(&callback_called), object));
+  RunLoopUntilIdle();
+
+  if (!callback_called) {
+    return ::testing::AssertionFailure() << "ReadData callback not called!";
+  }
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult LedgerRepositoryFactoryImplTest::ListChildren(
+    fuchsia::inspect::InspectPtr* inspect_ptr,
+    fidl::VectorPtr<fidl::StringPtr>* children_names) {
+  bool callback_called;
+
+  (*inspect_ptr)
+      ->ListChildren(callback::Capture(
+          callback::SetWhenCalled(&callback_called), children_names));
+  RunLoopUntilIdle();
+
+  if (!callback_called) {
+    return ::testing::AssertionFailure() << "ListChildren callback not called!";
+  }
+  return ::testing::AssertionSuccess();
+}
+
+::testing::AssertionResult LedgerRepositoryFactoryImplTest::OpenChild(
+    fuchsia::inspect::InspectPtr* parent_inspect_ptr,
+    fidl::StringPtr child_name,
+    fuchsia::inspect::InspectPtr* child_inspect_ptr) {
+  bool callback_called;
+  bool success = false;
+
+  (*parent_inspect_ptr)
+      ->OpenChild(child_name, child_inspect_ptr->NewRequest(),
+                  callback::Capture(callback::SetWhenCalled(&callback_called),
+                                    &success));
+  RunLoopUntilIdle();
+
+  if (!callback_called) {
+    return ::testing::AssertionFailure() << "OpenChild callback not called!";
+  }
+  if (!success) {
+    return ::testing::AssertionFailure() << "OpenChild call unsuccessful!";
+  }
+  return ::testing::AssertionSuccess();
+}
+
+TEST_F(LedgerRepositoryFactoryImplTest, InspectAPINoRepositories) {
+  fuchsia::inspect::Object object;
+  fidl::VectorPtr<fidl::StringPtr> children;
+
+  ASSERT_TRUE(ReadTopLevelData(&object));
+  ASSERT_TRUE(ListTopLevelChildren(&children));
+
+  EXPECT_EQ(kObjectsName, *object.name);
+  EXPECT_THAT(*object.properties, IsEmpty());
+  EXPECT_THAT(*object.metrics, IsEmpty());
+  EXPECT_THAT(*children, IsEmpty());
+}
+
+TEST_F(LedgerRepositoryFactoryImplTest,
+       InspectAPITwoRepositoriesOneAccessedTwice) {
+  // The directories in which the two repositories will be created.
+  std::string first_directory = "first directory";
+  std::string second_directory = "second directory";
+
+  // The names of the two repositories, determined by the
+  // LedgerRepositoryFactoryImpl under test.
+  fidl::StringPtr first_repository_name;
+  fidl::StringPtr second_repository_name;
+
+  // Bindings to the two repositories. If these are not maintained, the
+  // LedgerRepositoryFactoryImpl::LedgerRepositoryContainer objects associated
+  // with the repositories will be destroyed and the repositories will no
+  // longer appear represented in the Inspect API.
+  ledger_internal::LedgerRepositoryPtr first_ledger_repository_ptr;
+  ledger_internal::LedgerRepositoryPtr second_ledger_repository_ptr;
+  ledger_internal::LedgerRepositoryPtr first_again_ledger_repository_ptr;
+
+  // Bindings to Inspect API "Inspect" objects. Over the course of the test the
+  // top-level object_dir_ will gain a "repositories" child which itself will
+  // gain two children (one for each created repository, with names chosen by
+  // the LedgerRepositoryFactoryImpl under test).
+  fuchsia::inspect::InspectPtr repositories_inspect_ptr;
+  fuchsia::inspect::InspectPtr first_repository_inspect_ptr;
+  fuchsia::inspect::InspectPtr second_repository_inspect_ptr;
+
+  // Temporary objects populated and cleared throughout the test.
+  fuchsia::inspect::Object object;
+  fidl::VectorPtr<fidl::StringPtr> children_names;
+
+  // Create the directories for the repositories.
+  ASSERT_TRUE(CreateDirectory(first_directory));
+  ASSERT_TRUE(CreateDirectory(second_directory));
+
+  // Request one repository, then query the object_dir_ (and its children) to
+  // verify that that repository is listed (and to learn the name under which
+  // it is listed) and that it was requested once.
+  ASSERT_TRUE(CallGetRepository(first_directory, &first_ledger_repository_ptr));
+  ASSERT_TRUE(ListTopLevelChildren(&children_names));
+  EXPECT_THAT(*children_names, ElementsAre(kRepositoriesName.ToString()));
+  ASSERT_TRUE(OpenTopLevelRepositoriesChild(&repositories_inspect_ptr));
+  ASSERT_TRUE(ListChildren(&repositories_inspect_ptr, &children_names));
+  EXPECT_THAT(*children_names, SizeIs(1));
+  first_repository_name = children_names->at(0);
+  EXPECT_THAT(*first_repository_name, Not(IsEmpty()));
+  ASSERT_TRUE(OpenChild(&repositories_inspect_ptr, first_repository_name,
+                        &first_repository_inspect_ptr));
+  ASSERT_TRUE(ReadData(&first_repository_inspect_ptr, &object));
+  EXPECT_EQ(first_repository_name, *object.name);
+  ExpectRequestsMetric(&object, 1UL);
+
+  // Request a second repository, then query the "repositories" Inspect object
+  // to verify that that second repository is listed in addition to the first
+  // (and to learn the name under which it is listed) and that the two
+  // repositories were each requested once.
+  ASSERT_TRUE(
+      CallGetRepository(second_directory, &second_ledger_repository_ptr));
+  ASSERT_TRUE(ListChildren(&repositories_inspect_ptr, &children_names));
+  EXPECT_THAT(*children_names, SizeIs(2));
+  second_repository_name =
+      *find_if_not(children_names->begin(), children_names->end(),
+                   [&first_repository_name](const auto& name) {
+                     return name == first_repository_name;
+                   });
+  EXPECT_THAT(*children_names, UnorderedElementsAre(first_repository_name,
+                                                    second_repository_name));
+  EXPECT_THAT(*second_repository_name, Not(IsEmpty()));
+  ASSERT_TRUE(OpenChild(&repositories_inspect_ptr, second_repository_name,
+                        &second_repository_inspect_ptr));
+  ASSERT_TRUE(ReadData(&first_repository_inspect_ptr, &object));
+  EXPECT_EQ(first_repository_name, *object.name);
+  ExpectRequestsMetric(&object, 1UL);
+  ASSERT_TRUE(ReadData(&second_repository_inspect_ptr, &object));
+  EXPECT_EQ(second_repository_name, *object.name);
+  ExpectRequestsMetric(&object, 1UL);
+
+  // Request the first repository a second time, then query the "repositories"
+  // Inspect object to verify that both repositories remain listed (with their
+  // same names) and are described as having been requested twice and once,
+  // respectively.
+  ASSERT_TRUE(
+      CallGetRepository(first_directory, &first_again_ledger_repository_ptr));
+  ASSERT_TRUE(ListChildren(&repositories_inspect_ptr, &children_names));
+  EXPECT_THAT(*children_names, UnorderedElementsAre(first_repository_name,
+                                                    second_repository_name));
+  ASSERT_TRUE(ReadData(&first_repository_inspect_ptr, &object));
+  EXPECT_EQ(first_repository_name, *object.name);
+  ExpectRequestsMetric(&object, 2UL);
+  ASSERT_TRUE(ReadData(&second_repository_inspect_ptr, &object));
+  EXPECT_EQ(second_repository_name, *object.name);
+  ExpectRequestsMetric(&object, 1UL);
+}
+
+}  // namespace
+}  // namespace ledger
diff --git a/bin/ledger/app/ledger_repository_impl_unittest.cc b/bin/ledger/app/ledger_repository_impl_unittest.cc
index bef8461..c3d7a31 100644
--- a/bin/ledger/app/ledger_repository_impl_unittest.cc
+++ b/bin/ledger/app/ledger_repository_impl_unittest.cc
@@ -21,32 +21,13 @@
 #include "peridot/bin/ledger/storage/fake/fake_db_factory.h"
 #include "peridot/bin/ledger/storage/public/types.h"
 #include "peridot/bin/ledger/testing/fake_disk_cleanup_manager.h"
+#include "peridot/bin/ledger/testing/inspect.h"
 #include "peridot/bin/ledger/testing/test_with_environment.h"
 #include "peridot/lib/scoped_tmpfs/scoped_tmpfs.h"
 
 namespace ledger {
 namespace {
 
-void ExpectRequestsMetric(fuchsia::inspect::Object* object,
-                          unsigned long expected_value) {
-  bool requests_found = false;
-  unsigned long extra_requests_found = 0UL;
-  unsigned long requests = 777'777;
-  for (auto& index : *object->metrics) {
-    if (index.key == "requests") {
-      if (!requests_found) {
-        requests_found = true;
-        requests = index.value.uint_value();
-      } else {
-        extra_requests_found++;
-      }
-    }
-  }
-  EXPECT_TRUE(requests_found);
-  EXPECT_EQ(expected_value, requests);
-  EXPECT_EQ(0UL, extra_requests_found);
-}
-
 class LedgerRepositoryImplTest : public TestWithEnvironment {
  public:
   LedgerRepositoryImplTest() {
diff --git a/bin/ledger/testing/BUILD.gn b/bin/ledger/testing/BUILD.gn
index ad39e70..6a4508b 100644
--- a/bin/ledger/testing/BUILD.gn
+++ b/bin/ledger/testing/BUILD.gn
@@ -16,6 +16,8 @@
     "fake_disk_cleanup_manager.h",
     "get_page_ensure_initialized.cc",
     "get_page_ensure_initialized.h",
+    "inspect.cc",
+    "inspect.h",
     "ledger_matcher.cc",
     "ledger_matcher.h",
     "page_data_generator.cc",
diff --git a/bin/ledger/testing/inspect.cc b/bin/ledger/testing/inspect.cc
new file mode 100644
index 0000000..dc9be8b
--- /dev/null
+++ b/bin/ledger/testing/inspect.cc
@@ -0,0 +1,34 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "peridot/bin/ledger/testing/inspect.h"
+
+#include <fuchsia/inspect/cpp/fidl.h>
+
+#include "gtest/gtest.h"
+
+namespace ledger {
+
+// TODO(crjohns, nathaniel): Migrate this to a testing::Matcher.
+void ExpectRequestsMetric(fuchsia::inspect::Object* object,
+                          unsigned long expected_value) {
+  bool requests_found = false;
+  unsigned long extra_requests_found = 0UL;
+  unsigned long requests = 0UL;
+  for (auto& index : *object->metrics) {
+    if (index.key == "requests") {
+      if (!requests_found) {
+        requests_found = true;
+        requests = index.value.uint_value();
+      } else {
+        extra_requests_found++;
+      }
+    }
+  }
+  EXPECT_TRUE(requests_found);
+  EXPECT_EQ(expected_value, requests);
+  EXPECT_EQ(0UL, extra_requests_found);
+}
+
+}  // namespace ledger
diff --git a/bin/ledger/testing/inspect.h b/bin/ledger/testing/inspect.h
new file mode 100644
index 0000000..7df672c
--- /dev/null
+++ b/bin/ledger/testing/inspect.h
@@ -0,0 +1,21 @@
+// Copyright 2018 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef PERIDOT_BIN_LEDGER_TESTING_INSPECT_H_
+#define PERIDOT_BIN_LEDGER_TESTING_INSPECT_H_
+
+#include <fuchsia/inspect/cpp/fidl.h>
+
+namespace ledger {
+
+// TODO(crjohns, nathaniel): Move to an "Inspect API testing helpers" library,
+// parameterizing by the metric name rather than having "requests" hard-coded.
+// EXPECTs that |object| has a metric named "requests" and that the value of
+// the "requests" metric is |expected_value|.
+void ExpectRequestsMetric(fuchsia::inspect::Object* object,
+                          unsigned long expected_value);
+
+}  // namespace ledger
+
+#endif  // PERIDOT_BIN_LEDGER_TESTING_INSPECT_H_