blob: e590557d7c5b292e00895e1e4a7b0d414d79de77 [file] [log] [blame]
// Copyright 2021 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 "src/modular/bin/sessionmgr/story_runner/annotation_controller_impl.h"
#include <fuchsia/element/cpp/fidl.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/modular/bin/sessionmgr/annotations.h"
#include "src/modular/bin/sessionmgr/testing/annotations_matchers.h"
#include "src/modular/lib/testing/test_with_session_storage.h"
namespace modular {
namespace {
using ::modular::annotations::AnnotationEq;
using ::testing::ByRef;
using ::testing::ElementsAre;
using ::testing::UnorderedElementsAre;
class AnnotationControllerImplTest : public modular_testing::TestWithSessionStorage {
public:
void SetUp() override {
TestWithSessionStorage::SetUp();
session_storage_ = MakeSessionStorage();
}
fuchsia::element::AnnotationControllerPtr CreateStoryWithAnnotations(
const std::string story_name, std::vector<fuchsia::modular::Annotation> annotations) {
story_id_ = session_storage_->CreateStory(std::move(story_name), std::move(annotations));
auto annotation_controller_impl =
std::make_unique<AnnotationControllerImpl>(story_id_, session_storage_.get());
fuchsia::element::AnnotationControllerPtr annotation_controller;
annotation_controller_impl->Connect(annotation_controller.NewRequest());
annotation_controllers_.push_back(std::move(annotation_controller_impl));
return annotation_controller;
}
std::string story_id() const { return story_id_; }
SessionStorage* session_storage() { return session_storage_.get(); }
private:
std::string story_id_;
std::unique_ptr<SessionStorage> session_storage_;
std::vector<std::unique_ptr<AnnotationControllerImpl>> annotation_controllers_;
};
// Tests that GetAnnotations returns an empty list of custom_annotations for a story that
// has no annotations.
TEST_F(AnnotationControllerImplTest, GetAnnotationsEmpty) {
auto annotation_controller = CreateStoryWithAnnotations("annotation-test-story", {});
std::vector<fuchsia::element::Annotation> annotations;
bool got_annotations{false};
annotation_controller->GetAnnotations(
[&](fuchsia::element::AnnotationController_GetAnnotations_Result result) {
EXPECT_FALSE(result.is_err());
annotations = std::move(result.response().annotations);
got_annotations = true;
});
RunLoopUntil([&]() { return got_annotations; });
EXPECT_TRUE(annotations.empty());
}
// Tests that GetAnnotations returns the existing annotations on a story.
TEST_F(AnnotationControllerImplTest, GetAnnotationsExisting) {
static constexpr auto kTestAnnotationKey = "test_annotation_key";
static constexpr auto kTestAnnotationValue = "test_annotation_value";
auto annotation = fuchsia::modular::Annotation{
.key = kTestAnnotationKey,
.value = std::make_unique<fuchsia::modular::AnnotationValue>(
fuchsia::modular::AnnotationValue::WithText(kTestAnnotationValue))};
std::vector<fuchsia::modular::Annotation> modular_annotations;
modular_annotations.push_back(std::move(annotation));
auto annotation_controller =
CreateStoryWithAnnotations("annotation-test-story", std::move(modular_annotations));
std::vector<fuchsia::element::Annotation> element_annotations;
bool got_annotations{false};
annotation_controller->GetAnnotations(
[&](fuchsia::element::AnnotationController_GetAnnotations_Result result) {
EXPECT_FALSE(result.is_err());
element_annotations = std::move(result.response().annotations);
got_annotations = true;
});
RunLoopUntil([&]() { return got_annotations; });
auto element_annotation = fuchsia::element::Annotation{
.key = modular::annotations::ToElementAnnotationKey(kTestAnnotationKey),
.value = fuchsia::element::AnnotationValue::WithText(kTestAnnotationValue)};
EXPECT_THAT(element_annotations,
ElementsAre(element::annotations::AnnotationEq(ByRef(element_annotation))));
}
// Tests that UpateAnnotations sets annotations on the element story.
TEST_F(AnnotationControllerImplTest, SetAnnotationsExisting) {
static constexpr auto kTestAnnotationKey = "test_annotation_key";
static constexpr auto kTestAnnotationValue = "test_annotation_value";
auto annotation_controller = CreateStoryWithAnnotations("annotation-test-story", {});
// Set annotations.
auto element_annotation = fuchsia::element::Annotation{
.key = modular::annotations::ToElementAnnotationKey(kTestAnnotationKey),
.value = fuchsia::element::AnnotationValue::WithText(kTestAnnotationValue)};
std::vector<fuchsia::element::Annotation> annotations_to_add;
annotations_to_add.push_back(fidl::Clone(element_annotation));
bool did_update_annotations{false};
annotation_controller->UpdateAnnotations(
std::move(annotations_to_add),
/*annotations_to_delete=*/{},
[&](fuchsia::element::AnnotationController_UpdateAnnotations_Result result) {
EXPECT_FALSE(result.is_err());
did_update_annotations = true;
});
RunLoopUntil([&]() { return did_update_annotations; });
// Read the annotations back and ensure they're the same.
std::vector<fuchsia::element::Annotation> got_element_annotations;
bool got_annotations{false};
annotation_controller->GetAnnotations(
[&](fuchsia::element::AnnotationController_GetAnnotations_Result result) {
EXPECT_FALSE(result.is_err());
got_element_annotations = std::move(result.response().annotations);
got_annotations = true;
});
RunLoopUntil([&]() { return got_annotations; });
EXPECT_THAT(got_element_annotations,
ElementsAre(element::annotations::AnnotationEq(ByRef(element_annotation))));
}
// Verifies that WatchAnnotations returns existing annotations on first call.
TEST_F(AnnotationControllerImplTest, WatchAnnotationsExisting) {
// Create a story with some annotations.
static constexpr auto kTestAnnotationKey = "test_annotation_key";
static constexpr auto kTestAnnotationValue = "test_annotation_value";
auto annotation = fuchsia::modular::Annotation{
.key = kTestAnnotationKey,
.value = std::make_unique<fuchsia::modular::AnnotationValue>(
fuchsia::modular::AnnotationValue::WithText(kTestAnnotationValue))};
std::vector<fuchsia::modular::Annotation> modular_annotations;
modular_annotations.push_back(fidl::Clone(annotation));
auto annotation_controller =
CreateStoryWithAnnotations("annotation-test-story", std::move(modular_annotations));
// Get the annotations.
bool done{false};
std::vector<fuchsia::element::Annotation> element_annotations;
annotation_controller->WatchAnnotations(
[&](fuchsia::element::AnnotationController_WatchAnnotations_Result result) {
ASSERT_TRUE(result.is_response());
element_annotations = std::move(result.response().annotations);
done = true;
});
RunLoopUntil([&] { return done; });
auto element_annotation = annotations::ToElementAnnotation(annotation);
EXPECT_THAT(element_annotations,
ElementsAre(element::annotations::AnnotationEq(ByRef(element_annotation))));
}
// Verifies that WatchAnnotations called concurrently on two different AnnotationControllers both
// return existing annotations on first call.
TEST_F(AnnotationControllerImplTest, WatchAnnotationsExistingMultipleClients) {
// Create a story with some annotations.
static constexpr auto kTestAnnotationKey = "test_annotation_key";
static constexpr auto kTestAnnotationValue = "test_annotation_value";
auto annotation = fuchsia::modular::Annotation{
.key = kTestAnnotationKey,
.value = std::make_unique<fuchsia::modular::AnnotationValue>(
fuchsia::modular::AnnotationValue::WithText(kTestAnnotationValue))};
std::vector<fuchsia::modular::Annotation> modular_annotations;
modular_annotations.push_back(fidl::Clone(annotation));
// Create two annotation controllers for the same story.
auto annotation_controller_1 =
CreateStoryWithAnnotations("annotation-test-story", std::move(modular_annotations));
auto annotation_controller_2 = CreateStoryWithAnnotations("annotation-test-story", {});
bool controller_1_done{false};
bool controller_2_done{false};
// Have both controllers call `WatchAnnotations` at the same time.
std::vector<fuchsia::element::Annotation> controller_1_element_annotations;
annotation_controller_1->WatchAnnotations(
[&](fuchsia::element::AnnotationController_WatchAnnotations_Result result) {
ASSERT_TRUE(result.is_response());
controller_1_element_annotations = std::move(result.response().annotations);
controller_1_done = true;
});
std::vector<fuchsia::element::Annotation> controller_2_element_annotations;
annotation_controller_2->WatchAnnotations(
[&](fuchsia::element::AnnotationController_WatchAnnotations_Result result) {
ASSERT_TRUE(result.is_response());
controller_2_element_annotations = std::move(result.response().annotations);
controller_2_done = true;
});
// This should also return the current set of annotations for both controller_1 and
// controller_2 right away, and not hang for updates.
RunLoopUntil([&] { return controller_1_done && controller_2_done; });
// Test that both controller annotations match the initial annotation set.
auto element_annotation = annotations::ToElementAnnotation(annotation);
EXPECT_THAT(controller_1_element_annotations,
ElementsAre(element::annotations::AnnotationEq(ByRef(element_annotation))));
EXPECT_THAT(controller_2_element_annotations,
ElementsAre(element::annotations::AnnotationEq(ByRef(element_annotation))));
}
// Verifies that WatchAnnotations returns updated annotations on subsequent calls.
TEST_F(AnnotationControllerImplTest, WatchAnnotationsUpdates) {
static constexpr auto kTestAnnotationKey_1 = "test_annotation_key_1";
static constexpr auto kTestAnnotationValue_1 = "test_annotation_value_1";
static constexpr auto kTestAnnotationKey_2 = "test_annotation_key_2";
static constexpr auto kTestAnnotationValue_2 = "test_annotation_value_2";
std::vector<fuchsia::modular::Annotation> modular_annotations;
auto initial_annotation = fuchsia::modular::Annotation{
.key = kTestAnnotationKey_1,
.value = std::make_unique<fuchsia::modular::AnnotationValue>(
fuchsia::modular::AnnotationValue::WithText(kTestAnnotationValue_1))};
modular_annotations.push_back(fidl::Clone(initial_annotation));
auto annotation_controller =
CreateStoryWithAnnotations("annotation-test-story", std::move(modular_annotations));
// Get the annotations.
bool first_watch_called{false};
std::vector<fuchsia::element::Annotation> first_watch_annotations;
annotation_controller->WatchAnnotations(
[&](fuchsia::element::AnnotationController_WatchAnnotations_Result result) {
// Ensure this callback is only called once.
ASSERT_FALSE(first_watch_called);
first_watch_called = true;
ASSERT_TRUE(result.is_response());
first_watch_annotations = std::move(result.response().annotations);
});
RunLoopUntil([&] { return first_watch_called; });
const fuchsia::element::Annotation first_annotation =
modular::annotations::ToElementAnnotation(initial_annotation);
EXPECT_THAT(first_watch_annotations,
ElementsAre(element::annotations::AnnotationEq(ByRef(first_annotation))));
// Start watching for annotations.
bool second_watch_called{false};
std::vector<fuchsia::element::Annotation> second_watch_annotations;
annotation_controller->WatchAnnotations(
[&](fuchsia::element::AnnotationController_WatchAnnotations_Result result) {
// Ensure this callback is only called once.
ASSERT_FALSE(second_watch_called);
second_watch_called = true;
ASSERT_TRUE(result.is_response());
second_watch_annotations = std::move(result.response().annotations);
});
// Add another annotation.
std::vector<fuchsia::modular::Annotation> new_annotations;
auto new_annotation = fuchsia::modular::Annotation{
.key = kTestAnnotationKey_2,
.value = std::make_unique<fuchsia::modular::AnnotationValue>(
fuchsia::modular::AnnotationValue::WithText(kTestAnnotationValue_2))};
new_annotations.push_back(fidl::Clone(new_annotation));
// Annotate the story.
bool done{false};
auto annotations_to_add = modular::annotations::ToElementAnnotations(new_annotations);
annotation_controller->UpdateAnnotations(
std::move(annotations_to_add),
/*annotations_to_delete=*/{},
[&](fuchsia::element::AnnotationController_UpdateAnnotations_Result result) {
EXPECT_FALSE(result.is_err());
done = true;
});
RunLoopUntil([&] { return done; });
// WatchAnnotations should have received the new annotations.
RunLoopUntil([&] { return second_watch_called; });
const fuchsia::element::Annotation second_annotation =
modular::annotations::ToElementAnnotation(new_annotation);
EXPECT_THAT(second_watch_annotations,
UnorderedElementsAre(element::annotations::AnnotationEq(ByRef(first_annotation)),
element::annotations::AnnotationEq(ByRef(second_annotation))));
}
} // namespace
} // namespace modular