chromium/components/fuchsia_component_support/annotations_manager_unittest.cc

// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/fuchsia_component_support/annotations_manager.h"

#include "base/fuchsia/mem_buffer_util.h"
#include "base/test/gtest_util.h"
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace fuchsia_component_support {

namespace {

class AnnotationsManagerTest : public testing::Test {
 protected:
  AnnotationsManagerTest() = default;

  base::test::SingleThreadTaskEnvironment task_environment_{
      base::test::SingleThreadTaskEnvironment::MainThreadType::IO};
};

constexpr char kGlobalNamespace[] = "global";

constexpr char kAnnotationName1[] = "annotation1";
constexpr char kAnnotationName2[] = "annotation2";

constexpr char kAnnotationValue1[] = "annotation_value1";
constexpr char kAnnotationValue2[] = "annotation_value2";

TEST(MakeAnnotationTest, KeyNamespaceAndName) {
  auto annotation = MakeAnnotation(kAnnotationName1, "ignored");
  EXPECT_EQ(annotation.key.namespace_, kGlobalNamespace);
  EXPECT_EQ(annotation.key.value, kAnnotationName1);
}

TEST(MakeAnnotationTest, BooleanValue) {
  auto annotation = MakeBoolAnnotation(kAnnotationName1, true);
  ASSERT_TRUE(annotation.value.is_text());
  EXPECT_EQ(annotation.value.text(), "true");
}

TEST(MakeAnnotationTest, IntegerValue) {
  auto annotation = MakeIntAnnotation(kAnnotationName1, 12345678);
  ASSERT_TRUE(annotation.value.is_text());
  EXPECT_EQ(annotation.value.text(), "12345678");
}

TEST(MakeAnnotationTest, TextValue) {
  constexpr char kSmallText[] = "This is a short text annotation";
  auto annotation = MakeAnnotation(kAnnotationName1, kSmallText);
  ASSERT_TRUE(annotation.value.is_text());
  EXPECT_EQ(annotation.value.text(), kSmallText);
}

TEST(MakeAnnotationTest, LargeTextValue) {
  const std::string large_text(1024, 'a');
  auto annotation = MakeAnnotation(kAnnotationName1, large_text);
  ASSERT_TRUE(annotation.value.is_buffer());
  auto value = base::StringFromMemBuffer(annotation.value.buffer());
  EXPECT_EQ(value, large_text);
}

// Basic verification that a single client can connect.
TEST_F(AnnotationsManagerTest, SingleClient) {
  AnnotationsManager annotations;

  fuchsia::element::AnnotationControllerPtr ptr;
  annotations.Connect(ptr.NewRequest());
}

// Verifies that multiple clients can connect.
TEST_F(AnnotationsManagerTest, MultipleClients) {
  AnnotationsManager annotations;

  fuchsia::element::AnnotationControllerPtr ptr1;
  annotations.Connect(ptr1.NewRequest());
  fuchsia::element::AnnotationControllerPtr ptr2;
  annotations.Connect(ptr2.NewRequest());
}

// Verifies that WatchAnnotations() hanging-get behaviour:
// - first call returns all annotations.
// - watch when nothing has changed "hangs" until the next change.
// - watch after a change returns immediately.
TEST_F(AnnotationsManagerTest, WatchAnnotationsHangingGet) {
  AnnotationsManager annotations;

  fuchsia::element::AnnotationControllerPtr controller;
  annotations.Connect(controller.NewRequest());

  // Dispatch an initial watch, that will return immediately.
  bool received_annotations = false;
  controller->WatchAnnotations([&received_annotations](auto annotations) {
    EXPECT_EQ(annotations.response().annotations.size(), 0u);
    received_annotations = true;
  });
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(received_annotations);

  // Set a new watch, which should not return immediately, since nothing has
  // changed.
  received_annotations = false;
  controller->WatchAnnotations([&received_annotations](auto annotations) {
    EXPECT_EQ(annotations.response().annotations.size(), 1u);
    received_annotations = true;
  });
  base::RunLoop().RunUntilIdle();
  EXPECT_FALSE(received_annotations);

  // Set an annotation and spin the loop, to verify that the update is reported.
  std::vector<fuchsia::element::Annotation> to_set;
  to_set.push_back(MakeAnnotation(kAnnotationName1, kAnnotationValue1));
  annotations.UpdateAnnotations(std::move(to_set));
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(received_annotations);

  // Change the annotation and spin the loop, then call WatchAnnotations() to
  // verify that it returns immediately.
  to_set.clear();
  to_set.push_back(MakeAnnotation(kAnnotationName1, kAnnotationValue2));
  annotations.UpdateAnnotations(std::move(to_set));
  received_annotations = false;
  controller->WatchAnnotations([&received_annotations](auto annotations) {
    EXPECT_EQ(annotations.response().annotations.size(), 1u);
    received_annotations = true;
  });
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(received_annotations);
}

// Verifies that WatchAnnotations() returns all annotations when any annotation
// is updated, not just the changed one.
TEST_F(AnnotationsManagerTest, WatchAnnotationsReturnsAllAnnotations) {
  AnnotationsManager annotations;

  fuchsia::element::AnnotationControllerPtr controller;
  annotations.Connect(controller.NewRequest());

  // Set multiple annotations.
  std::vector<fuchsia::element::Annotation> annotations_to_set;
  annotations_to_set.push_back(
      MakeAnnotation(kAnnotationName1, kAnnotationValue1));
  annotations_to_set.push_back(
      MakeAnnotation(kAnnotationName2, kAnnotationValue2));
  annotations.UpdateAnnotations(std::move(annotations_to_set));

  // Dispatch an initial watch, that will return immediately.
  bool received_annotations = false;
  controller->WatchAnnotations([&received_annotations](auto annotations) {
    EXPECT_EQ(annotations.response().annotations.size(), 2u);
    received_annotations = true;
  });
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(received_annotations);

  // Update a single annotation, then call WatchAnnotations() again to verify
  // that all annotations including unchanged ones are returned.
  std::vector<fuchsia::element::Annotation> to_set;
  to_set.push_back(MakeAnnotation(kAnnotationName1, kAnnotationValue2));
  annotations.UpdateAnnotations(std::move(to_set));
  received_annotations = false;
  controller->WatchAnnotations([&received_annotations](auto annotations) {
    EXPECT_EQ(annotations.response().annotations.size(), 2u);
    received_annotations = true;
  });
  base::RunLoop().RunUntilIdle();
  EXPECT_TRUE(received_annotations);
}

// Verifies insertion of a new annotation via UpdateAnnotations().
TEST_F(AnnotationsManagerTest, UpdateAnnotationsSetsAnnotations) {
  AnnotationsManager annotations;

  auto all_annotations = annotations.GetAnnotations();
  ASSERT_EQ(all_annotations.size(), 0u);

  std::vector<fuchsia::element::Annotation> to_set;
  to_set.push_back(MakeAnnotation(kAnnotationName1, kAnnotationValue1));
  annotations.UpdateAnnotations(std::move(to_set));

  all_annotations = annotations.GetAnnotations();
  ASSERT_EQ(all_annotations.size(), 1u);
  EXPECT_EQ(all_annotations[0].key.value, kAnnotationName1);
  ASSERT_TRUE(all_annotations[0].value.is_text());
  EXPECT_EQ(all_annotations[0].value.text(), kAnnotationValue1);
}

// Verifies update of existing annotation via UpdateAnnotations().
TEST_F(AnnotationsManagerTest, UpdateAnnotationsUpdatesAnnotations) {
  AnnotationsManager annotations;

  std::vector<fuchsia::element::Annotation> to_set;
  to_set.push_back(MakeAnnotation(kAnnotationName1, kAnnotationValue1));
  annotations.UpdateAnnotations(std::move(to_set));
  to_set.push_back(MakeAnnotation(kAnnotationName1, kAnnotationValue2));
  annotations.UpdateAnnotations(std::move(to_set));

  auto all_annotations = annotations.GetAnnotations();
  ASSERT_EQ(all_annotations.size(), 1u);
  EXPECT_EQ(all_annotations[0].key.value, kAnnotationName1);
  ASSERT_TRUE(all_annotations[0].value.is_text());
  EXPECT_EQ(all_annotations[0].value.text(), kAnnotationValue2);
}

// Verifies that multiple annotations can be updated or insert in a single call.
TEST_F(AnnotationsManagerTest, UpdateAnnotationsMultipleAnnotations) {
  AnnotationsManager annotations;

  std::vector<fuchsia::element::Annotation> annotations_to_set;
  annotations_to_set.push_back(
      MakeAnnotation(kAnnotationName1, kAnnotationValue1));
  annotations_to_set.push_back(
      MakeAnnotation(kAnnotationName2, kAnnotationValue2));
  annotations.UpdateAnnotations(std::move(annotations_to_set));

  auto all_annotations = annotations.GetAnnotations();
  ASSERT_EQ(all_annotations.size(), 2u);
  EXPECT_EQ(all_annotations[0].key.value, kAnnotationName1);
  ASSERT_TRUE(all_annotations[0].value.is_text());
  EXPECT_EQ(all_annotations[0].value.text(), kAnnotationValue1);
  EXPECT_EQ(all_annotations[1].key.value, kAnnotationName2);
  ASSERT_TRUE(all_annotations[1].value.is_text());
  EXPECT_EQ(all_annotations[1].value.text(), kAnnotationValue2);
}

// Verifies that the controller fails the operation if duplicate annotations
// appear in the same bulk-UpdateAnnotations operation.
TEST_F(AnnotationsManagerTest,
       UpdateAnnotationsMultipleAnnotations_WithDuplicate) {
  AnnotationsManager annotations;

  std::vector<fuchsia::element::Annotation> annotations_to_set;
  annotations_to_set.push_back(
      MakeAnnotation(kAnnotationName1, kAnnotationValue1));
  annotations_to_set.push_back(
      MakeAnnotation(kAnnotationName1, kAnnotationValue2));

  EXPECT_FALSE(annotations.UpdateAnnotations(std::move(annotations_to_set)));
}

}  // namespace

}  // namespace fuchsia_component_support