[Registry] Cleans up ProjectConfigs and adds some functionality.

CB-220

This is a follow-on to
https://fuchsia-review.googlesource.com/c/cobalt/+/251890

We clean up ProjectConfigs in a way that is similar to what we did
for ClientConfig. But what we needed to do here was simpler.

- Add comments
- Change terminology from "config" to "registry"
- Add the methods is_empty() and is_single_project().

Change-Id: I1f422ca930b3b78750ebcac7a119618a75fe61ef
diff --git a/config/client_config.h b/config/client_config.h
index 93bf1e2..0e31c93 100644
--- a/config/client_config.h
+++ b/config/client_config.h
@@ -47,7 +47,6 @@
   // such a message, and then extracting the Metrics and EncodingConfigs from
   // that.
   //
-  //
   // Returns nullptr to indicate failure.
   static std::unique_ptr<ClientConfig> CreateFromCobaltRegistryBase64(
       const std::string& cobalt_registry_base64);
@@ -57,7 +56,6 @@
   // contain the bytes of the binary serialization of such a message, and then
   // extracting the Metrics and EncodingConfigs from that.
   //
-  //
   // Returns nullptr to indicate failure.
   static std::unique_ptr<ClientConfig> CreateFromCobaltRegistryBytes(
       const std::string& cobalt_registry_bytes);
diff --git a/config/project_configs.cc b/config/project_configs.cc
index a765518..15bac1f 100644
--- a/config/project_configs.cc
+++ b/config/project_configs.cc
@@ -12,32 +12,45 @@
 
 std::unique_ptr<ProjectConfigs> ProjectConfigs::CreateFromCobaltRegistryBase64(
     const std::string& cobalt_registry_base64) {
-  std::string cobalt_config_bytes;
-  if (!crypto::Base64Decode(cobalt_registry_base64, &cobalt_config_bytes)) {
+  std::string cobalt_registry_bytes;
+  if (!crypto::Base64Decode(cobalt_registry_base64, &cobalt_registry_bytes)) {
     LOG(ERROR) << "Unable to parse the provided string as base-64";
     return nullptr;
   }
-  return CreateFromCobaltRegistryBytes(cobalt_config_bytes);
+  return CreateFromCobaltRegistryBytes(cobalt_registry_bytes);
 }
 
 std::unique_ptr<ProjectConfigs> ProjectConfigs::CreateFromCobaltRegistryBytes(
-    const std::string& cobalt_config_bytes) {
-  auto cobalt_config = std::make_unique<CobaltRegistry>();
-  if (!cobalt_config->ParseFromString(cobalt_config_bytes)) {
+    const std::string& cobalt_registry_bytes) {
+  auto cobalt_registry = std::make_unique<CobaltRegistry>();
+  if (!cobalt_registry->ParseFromString(cobalt_registry_bytes)) {
     LOG(ERROR) << "Unable to parse a CobaltRegistry from the provided bytes.";
     return nullptr;
   }
-  return CreateFromCobaltRegistryProto(std::move(cobalt_config));
+  return CreateFromCobaltRegistryProto(std::move(cobalt_registry));
 }
 
 std::unique_ptr<ProjectConfigs> ProjectConfigs::CreateFromCobaltRegistryProto(
-    std::unique_ptr<CobaltRegistry> cobalt_config) {
-  return std::make_unique<ProjectConfigs>(std::move(cobalt_config));
+    std::unique_ptr<CobaltRegistry> cobalt_registry) {
+  return std::make_unique<ProjectConfigs>(std::move(cobalt_registry));
 }
 
-ProjectConfigs::ProjectConfigs(std::unique_ptr<CobaltRegistry> cobalt_config)
-    : cobalt_config_(std::move(cobalt_config)) {
-  for (const auto& customer : cobalt_config_->customers()) {
+ProjectConfigs::ProjectConfigs(std::unique_ptr<CobaltRegistry> cobalt_registry)
+    : cobalt_registry_(std::move(cobalt_registry)) {
+  is_empty_ = cobalt_registry_->customers_size() == 0;
+  is_single_project_ = false;
+  if (cobalt_registry_->customers_size() == 1) {
+    auto customer = cobalt_registry_->customers(0);
+    if (customer.projects_size() == 1) {
+      auto project = customer.projects(0);
+      is_single_project_ = true;
+      single_customer_id_ = customer.customer_id();
+      single_customer_name_ = customer.customer_name();
+      single_project_id_ = project.project_id();
+      single_project_name_ = project.project_name();
+    }
+  }
+  for (const auto& customer : cobalt_registry_->customers()) {
     customers_by_id_[customer.customer_id()] = &customer;
     customers_by_name_[customer.customer_name()] = &customer;
     for (const auto& project : customer.projects()) {
diff --git a/config/project_configs.h b/config/project_configs.h
index 752b549..7449abe 100644
--- a/config/project_configs.h
+++ b/config/project_configs.h
@@ -16,29 +16,53 @@
 namespace cobalt {
 namespace config {
 
-// ProjectConfigs wraps a CobaltRegistry and offers convenient and efficient
-// methods for looking up a project.
+// ProjectConfigs provides a convenient interface over a |CobaltRegistry|
+// that is intended to be used on Cobalt's client-side.
+//
+// A CobaltRegistry can be in one of three states:
+//
+// (1) It can contain data for a single Cobalt 0.1 project.
+// (2) It can contain data for a single Cobalt 1.0 project.
+// (3) It can contain data for multiple Cobalt projects. In this case it
+//     may contain data for some Cobalt 0.1 projects and some Cobalt 1.0
+//     projects.
+//
+// This class is part of Cobalt 1.0. and as such it ignores the Cobalt 0.1
+// data in a |CobaltRegistry| and gives access only to the Cobalt 1.0 data.
+// So an instance of this class can be in one of three states corresponding
+// to the three states above:
+//
+// (1) It can be empty
+// (2) It can contain data for a single Cobalt 1.0 project.
+// (3) It can contain data for multiple Cobalt 1.0 projects.
+//
+// See the class |ClientConfig| for the Cobalt 0.1 analogue of this class.
 class ProjectConfigs {
  public:
   // Constructs and returns an instance of ProjectConfigs by first parsing
   // a CobaltRegistry proto message from |cobalt_registry_base64|, which should
   // contain the Base64 encoding of the bytes of the binary serialization of
   // such a message.
+  //
+  // Returns nullptr to indicate failure.
   static std::unique_ptr<ProjectConfigs> CreateFromCobaltRegistryBase64(
       const std::string& cobalt_registry_base64);
 
   // Constructs and returns an instance of ProjectConfigs by first parsing
-  // a CobaltRegistry proto message from |cobalt_config_bytes|, which should
+  // a CobaltRegistry proto message from |cobalt_registry_bytes|, which should
   // contain the bytes of the binary serialization of such a message.
+  //
+  // Returns nullptr to indicate failure.
   static std::unique_ptr<ProjectConfigs> CreateFromCobaltRegistryBytes(
-      const std::string& cobalt_config_bytes);
+      const std::string& cobalt_registry_bytes);
 
-  // Constructs and returns and instance of ProjectConfigs from |cobalt_config|.
+  // Constructs and returns and instance of ProjectConfigs that contains the
+  // data from |cobalt_registry|.
   static std::unique_ptr<ProjectConfigs> CreateFromCobaltRegistryProto(
-      std::unique_ptr<CobaltRegistry> cobalt_config);
+      std::unique_ptr<CobaltRegistry> cobalt_registry);
 
-  // Constructs a ProjectConfigs that wraps the given |cobalt_config|.
-  explicit ProjectConfigs(std::unique_ptr<CobaltRegistry> cobalt_config);
+  // Constructs a ProjectConfigs that contains the data from |cobalt_registry|.
+  explicit ProjectConfigs(std::unique_ptr<CobaltRegistry> cobalt_registry);
 
   // Returns the CustomerConfig for the customer with the given name, or
   // nullptr if there is no such customer.
@@ -73,8 +97,32 @@
                                               uint32_t metric_id,
                                               uint32_t report_id) const;
 
+  // Returns whether or not this instance of ProjectConfigs contains data for
+  // exactly one project.
+  bool is_single_project() { return is_single_project_; }
+
+  // Returns whether or not this instance of ProjectConfigs contains no
+  // project data.
+  bool is_empty() { return is_empty_; }
+
+  // If is_single_project() is true then this returns the customer ID of
+  // the single project. Otherwise the return value is undefined.
+  uint32_t single_customer_id() { return single_customer_id_; }
+
+  // If is_single_project() is true then this returns the customer name of
+  // the single project. Otherwise the return value is undefined.
+  std::string single_customer_name() { return single_customer_name_; }
+
+  // If is_single_project() is true then this returns the project ID of
+  // the single project. Otherwise the return value is undefined.
+  uint32_t single_project_id() { return single_project_id_; }
+
+  // If is_single_project() is true then this returns the project name of
+  // the single project. Otherwise the return value is undefined.
+  std::string single_project_name() { return single_project_name_; }
+
  private:
-  std::unique_ptr<CobaltRegistry> cobalt_config_;
+  std::unique_ptr<CobaltRegistry> cobalt_registry_;
 
   std::map<std::string, const CustomerConfig*> customers_by_name_;
 
@@ -92,6 +140,13 @@
   std::map<std::tuple<uint32_t, uint32_t, uint32_t, uint32_t>,
            const ReportDefinition*>
       reports_by_id_;
+
+  bool is_single_project_;
+  bool is_empty_;
+  uint32_t single_customer_id_ = 0;
+  std::string single_customer_name_;
+  uint32_t single_project_id_ = 0;
+  std::string single_project_name_;
 };
 
 }  // namespace config
diff --git a/config/project_configs_test.cc b/config/project_configs_test.cc
index 31fe192..414355f 100644
--- a/config/project_configs_test.cc
+++ b/config/project_configs_test.cc
@@ -19,23 +19,30 @@
 const size_t kNumMetricsPerProject = 5;
 const size_t kNumCustomers = 2;
 
-std::string NameForId(uint32_t id) {
+std::string NameForId(std::string prefix, uint32_t id) {
   std::ostringstream stream;
-  stream << "Name" << id;
+  stream << prefix << "Name" << id;
   return stream.str();
 }
 
+std::string CustomerNameForId(uint32_t id) {
+  return NameForId("Customer-", id);
+}
+std::string ProjectNameForId(uint32_t id) { return NameForId("Project-", id); }
+std::string ReportNameForId(uint32_t id) { return NameForId("Report-", id); }
+std::string MetricNameForId(uint32_t id) { return NameForId("Metric-", id); }
+
 // We create 3n projects for customer n.
 size_t NumProjectsForCustomer(uint32_t customer_id) { return 3 * customer_id; }
 
 void SetupReport(uint32_t report_id, ReportDefinition* report) {
   report->set_id(report_id);
-  report->set_report_name(NameForId(report_id));
+  report->set_report_name(ReportNameForId(report_id));
 }
 
 void SetupMetric(uint32_t metric_id, MetricDefinition* metric) {
   metric->set_id(metric_id);
-  metric->set_metric_name(NameForId(metric_id));
+  metric->set_metric_name(MetricNameForId(metric_id));
   for (size_t i = 1u; i < kNumReportsPerMetric; i++) {
     SetupReport(i, metric->add_reports());
   }
@@ -43,7 +50,7 @@
 
 void SetupProject(uint32_t project_id, ProjectConfig* project) {
   project->set_project_id(project_id);
-  project->set_project_name(NameForId(project_id));
+  project->set_project_name(ProjectNameForId(project_id));
   for (size_t i = 1u; i <= kNumMetricsPerProject; i++) {
     SetupMetric(i, project->add_metrics());
   }
@@ -52,18 +59,35 @@
 void SetupCustomer(uint32_t customer_id, size_t num_projects,
                    CustomerConfig* customer) {
   customer->set_customer_id(customer_id);
-  customer->set_customer_name(NameForId(customer_id));
+  customer->set_customer_name(CustomerNameForId(customer_id));
   for (auto i = 1u; i <= num_projects; i++) {
     SetupProject(i, customer->add_projects());
   }
 }
 
-std::unique_ptr<CobaltRegistry> NewTestConfig() {
-  auto cobalt_config = std::make_unique<CobaltRegistry>();
-  for (size_t i = 1u; i <= kNumCustomers; i++) {
-    SetupCustomer(i, NumProjectsForCustomer(i), cobalt_config->add_customers());
+std::unique_ptr<CobaltRegistry> NewTestRegistry(
+    size_t num_customers,
+    std::function<size_t(uint32_t)> num_projects_for_customer_fn) {
+  auto cobalt_registry = std::make_unique<CobaltRegistry>();
+  for (size_t i = 1u; i <= num_customers; i++) {
+    SetupCustomer(i, num_projects_for_customer_fn(i),
+                  cobalt_registry->add_customers());
   }
-  return cobalt_config;
+  return cobalt_registry;
+}
+
+std::unique_ptr<CobaltRegistry> NewTestRegistry() {
+  return NewTestRegistry(kNumCustomers, NumProjectsForCustomer);
+}
+
+// Constructs a ProjectConfigs that wraps a CobaltRegistry with the
+// specified number of customers and projects-per-customer.
+std::unique_ptr<ProjectConfigs> NewProjectConfigs(
+    size_t num_customers, size_t num_projects_per_customer) {
+  return std::make_unique<ProjectConfigs>(
+      NewTestRegistry(num_customers, [num_projects_per_customer](uint32_t) {
+        return num_projects_per_customer;
+      }));
 }
 
 }  // namespace
@@ -83,9 +107,10 @@
     if (expected_customer_id != customer_id) {
       return false;
     }
-    EXPECT_EQ(NameForId(expected_customer_id),
+    EXPECT_EQ(CustomerNameForId(expected_customer_id),
               customer_config->customer_name());
-    if (NameForId(expected_customer_id) != customer_config->customer_name()) {
+    if (CustomerNameForId(expected_customer_id) !=
+        customer_config->customer_name()) {
       return false;
     }
     size_t expected_num_projects = NumProjectsForCustomer(customer_id);
@@ -106,8 +131,10 @@
     if (expected_project_id != project_config->project_id()) {
       return false;
     }
-    EXPECT_EQ(NameForId(expected_project_id), project_config->project_name());
-    if (NameForId(expected_project_id) != project_config->project_name()) {
+    EXPECT_EQ(ProjectNameForId(expected_project_id),
+              project_config->project_name());
+    if (ProjectNameForId(expected_project_id) !=
+        project_config->project_name()) {
       return false;
     }
     size_t num_metrics = project_config->metrics_size();
@@ -119,7 +146,7 @@
   bool CheckProjectConfigs(const ProjectConfigs& project_configs) {
     for (uint32_t customer_id = 1; customer_id <= kNumCustomers;
          customer_id++) {
-      std::string expected_customer_name = NameForId(customer_id);
+      std::string expected_customer_name = CustomerNameForId(customer_id);
       size_t expected_num_projects = NumProjectsForCustomer(customer_id);
 
       // Check getting the customer by name.
@@ -141,7 +168,7 @@
 
       for (uint32_t project_id = 1; project_id <= expected_num_projects;
            project_id++) {
-        std::string project_name = NameForId(project_id);
+        std::string project_name = ProjectNameForId(project_id);
 
         // Check getting the project by name.
         bool success =
@@ -183,7 +210,7 @@
 
 // Test GetCustomerConfig by id.
 TEST_F(ProjectConfigsTest, GetCustomerConfigById) {
-  ProjectConfigs project_configs(NewTestConfig());
+  ProjectConfigs project_configs(NewTestRegistry());
   const CustomerConfig* customer;
 
   customer = project_configs.GetCustomerConfig(1);
@@ -201,7 +228,7 @@
 
 // Test GetProjectConfig by id.
 TEST_F(ProjectConfigsTest, GetProjectConfigById) {
-  ProjectConfigs project_configs(NewTestConfig());
+  ProjectConfigs project_configs(NewTestRegistry());
   const ProjectConfig* project;
 
   project = project_configs.GetProjectConfig(1, 1);
@@ -223,7 +250,7 @@
 
 // Test GetMetricDefintion.
 TEST_F(ProjectConfigsTest, GetMetricDefinitionById) {
-  ProjectConfigs project_configs(NewTestConfig());
+  ProjectConfigs project_configs(NewTestRegistry());
   const MetricDefinition* metric;
 
   metric = project_configs.GetMetricDefinition(1, 1, 1);
@@ -249,7 +276,7 @@
 
 // Test GetReportDefinition.
 TEST_F(ProjectConfigsTest, GetReportDefinitionById) {
-  ProjectConfigs project_configs(NewTestConfig());
+  ProjectConfigs project_configs(NewTestRegistry());
   const ReportDefinition* report;
 
   report = project_configs.GetReportDefinition(1, 1, 1, 1);
@@ -280,24 +307,24 @@
 // Tests using a ProjectConfigs constructed directly from a
 // CobaltRegistry
 TEST_F(ProjectConfigsTest, ConstructForCobaltRegistry) {
-  ProjectConfigs project_configs(NewTestConfig());
+  ProjectConfigs project_configs(NewTestRegistry());
   EXPECT_TRUE(CheckProjectConfigs(project_configs));
 }
 
 // Tests using a ProjectConfigs obtained via CreateFromCobaltRegistryBytes().
 TEST_F(ProjectConfigsTest, CreateFromCobaltRegistryBytes) {
-  auto cobalt_config = NewTestConfig();
+  auto cobalt_registry = NewTestRegistry();
   std::string bytes;
-  cobalt_config->SerializeToString(&bytes);
+  cobalt_registry->SerializeToString(&bytes);
   auto project_configs = ProjectConfigs::CreateFromCobaltRegistryBytes(bytes);
   EXPECT_TRUE(CheckProjectConfigs(*project_configs));
 }
 
 // Tests using a ProjectConfigs obtained via CreateFromCobaltRegistryBase64().
 TEST_F(ProjectConfigsTest, CreateFromCobaltRegistryBase64) {
-  auto cobalt_config = NewTestConfig();
+  auto cobalt_registry = NewTestRegistry();
   std::string bytes;
-  cobalt_config->SerializeToString(&bytes);
+  cobalt_registry->SerializeToString(&bytes);
   std::string cobalt_registry_base64;
   crypto::Base64Encode(bytes, &cobalt_registry_base64);
   auto project_configs =
@@ -305,5 +332,43 @@
   EXPECT_TRUE(CheckProjectConfigs(*project_configs));
 }
 
+// Tests the logic that determines whether or not a ProjectConfigs is empty
+// or contains a single project.
+TEST_F(ProjectConfigsTest, IsSingleProject) {
+  // An ProjectConfigs constructed from an empty CobaltRegistry is empty but is
+  // not a single project.
+  auto project_configs = NewProjectConfigs(0, 0);
+  EXPECT_FALSE(project_configs->is_single_project());
+  EXPECT_TRUE(project_configs->is_empty());
+
+  // A ProjectConfigs constructed from a CobaltRegistry with 1 customer with no
+  // projects is not empty and is not a single project.
+  project_configs = NewProjectConfigs(1, 0);
+  EXPECT_FALSE(project_configs->is_single_project());
+  EXPECT_FALSE(project_configs->is_empty());
+
+  // A ProjectConfigs constructed from a CobaltRegistry with 1 customer with 1
+  // project is not empty and is a single project.
+  project_configs = NewProjectConfigs(1, 1);
+  EXPECT_TRUE(project_configs->is_single_project());
+  EXPECT_FALSE(project_configs->is_empty());
+  EXPECT_EQ(1u, project_configs->single_customer_id());
+  EXPECT_EQ("Customer-Name1", project_configs->single_customer_name());
+  EXPECT_EQ(1u, project_configs->single_project_id());
+  EXPECT_EQ("Project-Name1", project_configs->single_project_name());
+
+  // A ProjectConfigs constructed from a CobaltRegistry with 1 customer with 2
+  // projects is not empty and is not a single project.
+  project_configs = NewProjectConfigs(1, 2);
+  EXPECT_FALSE(project_configs->is_single_project());
+  EXPECT_FALSE(project_configs->is_empty());
+
+  // A ProjectConfigs constructed from a CobaltRegistry with 2 customers with 1
+  // project each is not empty and is not a single project.
+  project_configs = NewProjectConfigs(2, 1);
+  EXPECT_FALSE(project_configs->is_single_project());
+  EXPECT_FALSE(project_configs->is_empty());
+}
+
 }  // namespace config
 }  // namespace cobalt