| // Copyright 2017 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/context_engine/context_repository.h" |
| |
| #include <fuchsia/modular/cpp/fidl.h> |
| #include <lib/context/cpp/context_helper.h> |
| #include <lib/context/cpp/context_metadata_builder.h> |
| #include <lib/fidl/cpp/clone.h> |
| #include <lib/fidl/cpp/optional.h> |
| |
| #include "gtest/gtest.h" |
| |
| using maxwell::ContextMetadataBuilder; |
| |
| namespace modular { |
| namespace { |
| |
| TEST(ContextGraph, GetChildrenRecursive_GetAncestors) { |
| ContextGraph graph; |
| |
| graph.AddEdge("a", "b"); |
| graph.AddEdge("b", "c"); |
| graph.AddEdge("b", "d"); |
| |
| auto children = graph.GetChildrenRecursive("b"); |
| EXPECT_EQ(2lu, children.size()); |
| EXPECT_TRUE(children.find("c") != children.end()); |
| EXPECT_TRUE(children.find("d") != children.end()); |
| |
| children = graph.GetChildrenRecursive("a"); |
| EXPECT_EQ(3lu, children.size()); |
| EXPECT_TRUE(children.find("b") != children.end()); |
| EXPECT_TRUE(children.find("c") != children.end()); |
| EXPECT_TRUE(children.find("d") != children.end()); |
| |
| auto ancestors = graph.GetAncestors("c"); |
| EXPECT_EQ(2lu, ancestors.size()); |
| EXPECT_EQ("a", ancestors[0]); |
| EXPECT_EQ("b", ancestors[1]); |
| } |
| |
| class ContextRepositoryTest : public ::testing::Test { |
| public: |
| ContextRepositoryTest() {} |
| |
| protected: |
| ContextRepository repository_; |
| }; |
| |
| class TestListener : public fuchsia::modular::ContextListener { |
| public: |
| fuchsia::modular::ContextUpdatePtr last_update; |
| |
| void OnContextUpdate(fuchsia::modular::ContextUpdate update) override { |
| last_update = fidl::MakeOptional(std::move(update)); |
| } |
| |
| void reset() { last_update.reset(); } |
| }; |
| |
| fuchsia::modular::ContextValue CreateValue( |
| fuchsia::modular::ContextValueType type, const std::string& content) { |
| fuchsia::modular::ContextValue value; |
| value.type = type; |
| value.content = content; |
| return value; |
| } |
| |
| fuchsia::modular::ContextValue CreateValue( |
| fuchsia::modular::ContextValueType type, const std::string& content, |
| fuchsia::modular::ContextMetadata meta) { |
| fuchsia::modular::ContextValue value; |
| value.type = type; |
| value.content = content; |
| value.meta = std::move(meta); |
| return value; |
| } |
| |
| } // namespace |
| |
| TEST_F(ContextRepositoryTest, GetAddUpdateRemove) { |
| // This test ensures that we can do basic, synchronous add/update/remove/get |
| // operations. |
| |
| // Show that when we set values, we can get them back. |
| auto id1 = repository_.Add( |
| CreateValue(fuchsia::modular::ContextValueType::ENTITY, "content")); |
| auto value1 = repository_.Get(id1); |
| ASSERT_TRUE(value1); |
| EXPECT_EQ(fuchsia::modular::ContextValueType::ENTITY, value1->type); |
| EXPECT_EQ("content", value1->content); |
| |
| // Setting another value doesn't affect the original value. |
| auto id2 = repository_.Add( |
| CreateValue(fuchsia::modular::ContextValueType::ENTITY, "content2")); |
| auto value2 = repository_.Get(id2); |
| ASSERT_TRUE(value2); |
| EXPECT_EQ("content2", value2->content); |
| value1 = repository_.Get(id1); |
| ASSERT_TRUE(value1); |
| EXPECT_EQ(fuchsia::modular::ContextValueType::ENTITY, value1->type); |
| EXPECT_EQ("content", value1->content); |
| |
| // Let's create metadata. |
| auto id3 = repository_.Add( |
| CreateValue(fuchsia::modular::ContextValueType::ENTITY, "content3", |
| ContextMetadataBuilder().SetStoryId("id3story").Build())); |
| auto value3 = repository_.Get(id3); |
| ASSERT_TRUE(value3); |
| EXPECT_EQ("content3", value3->content); |
| ASSERT_TRUE(value3->meta.story); |
| EXPECT_EQ("id3story", value3->meta.story->id); |
| |
| // Update one of the previous values. |
| repository_.Update( |
| id2, |
| CreateValue(fuchsia::modular::ContextValueType::ENTITY, "new content2", |
| ContextMetadataBuilder().SetStoryId("id2story").Build())); |
| value2 = repository_.Get(id2); |
| ASSERT_TRUE(value2); |
| ASSERT_TRUE(value2->meta.story); |
| EXPECT_EQ("id2story", value2->meta.story->id); |
| EXPECT_EQ("new content2", value2->content); |
| |
| // Now remove id3. |
| repository_.Remove(id3); |
| EXPECT_FALSE(repository_.Get(id3)); |
| EXPECT_TRUE(repository_.Get(id1)); |
| EXPECT_TRUE(repository_.Get(id2)); |
| |
| // And the others. |
| repository_.Remove(id1); |
| repository_.Remove(id2); |
| EXPECT_FALSE(repository_.Get(id1)); |
| EXPECT_FALSE(repository_.Get(id2)); |
| } |
| |
| TEST_F(ContextRepositoryTest, ValuesInheritMetadata) { |
| // When a value is added as a child of another value, the child inherits the |
| // metadata of its parent. |
| auto meta1 = ContextMetadataBuilder().SetStoryId("id").Build(); |
| auto id1 = repository_.Add(CreateValue( |
| fuchsia::modular::ContextValueType::STORY, "s", std::move(meta1))); |
| |
| auto meta2 = ContextMetadataBuilder().SetModuleUrl("url").Build(); |
| auto id2 = repository_.Add( |
| id1, CreateValue(fuchsia::modular::ContextValueType::MODULE, "m", |
| std::move(meta2))); |
| |
| auto value1 = repository_.GetMerged(id1); |
| ASSERT_TRUE(value1); |
| // value1's metadata shouldn't have changed. |
| ASSERT_TRUE(value1->meta.story); |
| EXPECT_EQ("id", value1->meta.story->id); |
| ASSERT_FALSE(value1->meta.mod); |
| |
| auto value2 = repository_.GetMerged(id2); |
| ASSERT_TRUE(value2); |
| // value2's metadata should combine both value1's and value2's. |
| ASSERT_TRUE(value2->meta.story); |
| EXPECT_EQ("id", value2->meta.story->id); |
| ASSERT_TRUE(value2->meta.mod); |
| ASSERT_TRUE(value2->meta.mod->url); |
| EXPECT_EQ("url", value2->meta.mod->url); |
| |
| // Changing the parent's metadata value should update the child's also. |
| meta1 = fuchsia::modular::ContextMetadata(); |
| meta1.story = fuchsia::modular::StoryMetadata::New(); |
| meta1.story->id = "newid"; |
| repository_.Update(id1, CreateValue(fuchsia::modular::ContextValueType::STORY, |
| "s", std::move(meta1))); |
| value2 = repository_.GetMerged(id2); |
| ASSERT_TRUE(value2); |
| ASSERT_TRUE(value2->meta.story); |
| EXPECT_EQ("newid", value2->meta.story->id); |
| ASSERT_TRUE(value2->meta.mod); |
| ASSERT_TRUE(value2->meta.mod->url); |
| EXPECT_EQ("url", value2->meta.mod->url); |
| |
| // If a parent contains metadata that the child also contains (they both have |
| // 'mod' metadata), the parent's takes precendence. |
| meta1 = |
| ContextMetadataBuilder(std::move(meta1)).SetModuleUrl("override").Build(); |
| repository_.Update(id1, CreateValue(fuchsia::modular::ContextValueType::STORY, |
| "s", std::move(meta1))); |
| value2 = repository_.GetMerged(id2); |
| ASSERT_TRUE(value2); |
| ASSERT_FALSE(value2->meta.story); |
| ASSERT_TRUE(value2->meta.mod); |
| ASSERT_EQ("override", value2->meta.mod->url); |
| } |
| |
| TEST_F(ContextRepositoryTest, ListenersGetUpdates) { |
| // We want to test these subscription behaviors. |
| // 1) A value is added but doesn't match our subscription. |
| // a) It's the wrong type (ie, STORY vs ENTITY) |
| // b) Its metadata doesn't match. |
| // 2) A value is added that matches our existing subscription. |
| // 3) A value is updated that newly matches our subscription. |
| // 4) When a value is removed, it is no longer returned. |
| |
| // (1) |
| fuchsia::modular::ContextQuery query; |
| fuchsia::modular::ContextSelector selector; |
| selector.type = fuchsia::modular::ContextValueType::ENTITY; |
| selector.meta = ContextMetadataBuilder().SetEntityTopic("topic").BuildPtr(); |
| AddToContextQuery(&query, "a", std::move(selector)); |
| |
| TestListener listener; |
| repository_.AddSubscription(std::move(query), &listener, |
| fuchsia::modular::SubscriptionDebugInfo()); |
| auto maybe_result = TakeContextValue(listener.last_update.get(), "a"); |
| ASSERT_TRUE(maybe_result.has_value()); |
| EXPECT_TRUE(maybe_result.value().empty()); |
| listener.reset(); |
| |
| // (a) |
| fuchsia::modular::ContextValue value; |
| value.type = fuchsia::modular::ContextValueType::STORY; |
| value.content = "no match"; |
| value.meta = ContextMetadataBuilder().SetEntityTopic("topic").Build(); |
| repository_.Add(std::move(value)); |
| // No new update because nothing changed for our subscription. |
| EXPECT_FALSE(listener.last_update); |
| listener.reset(); |
| |
| // (b) |
| value = fuchsia::modular::ContextValue(); |
| value.type = fuchsia::modular::ContextValueType::ENTITY; |
| value.content = "no match yet"; |
| value.meta = ContextMetadataBuilder().SetEntityTopic("not the topic").Build(); |
| auto id = repository_.Add(std::move(value)); // Save id for later. |
| // No new update because nothing changed for our subscription. |
| EXPECT_FALSE(listener.last_update); |
| listener.reset(); |
| |
| // (2) |
| value = fuchsia::modular::ContextValue(); |
| value.type = fuchsia::modular::ContextValueType::ENTITY; |
| value.content = "match"; |
| value.meta = ContextMetadataBuilder().SetEntityTopic("topic").Build(); |
| repository_.Add(std::move(value)); |
| maybe_result = TakeContextValue(listener.last_update.get(), "a"); |
| ASSERT_TRUE(maybe_result.has_value()); |
| { |
| auto& result = maybe_result.value(); |
| EXPECT_EQ(1lu, result.size()); |
| EXPECT_EQ("match", result.at(0).content); |
| } |
| listener.reset(); |
| |
| // (3) |
| value = fuchsia::modular::ContextValue(); |
| value.type = fuchsia::modular::ContextValueType::ENTITY; |
| value.content = "now it matches"; |
| // Add more metadata than the query is looking for. It shouldn't affect |
| // the query, because it doesn't express any constraint on 'type'. |
| value.meta = ContextMetadataBuilder() |
| .SetEntityTopic("topic") |
| .AddEntityType("type1") |
| .AddEntityType("type2") |
| .Build(); |
| repository_.Update(id, std::move(value)); |
| ASSERT_TRUE(listener.last_update); |
| maybe_result = TakeContextValue(listener.last_update.get(), "a"); |
| ASSERT_TRUE(maybe_result.has_value()); |
| { |
| auto& result = maybe_result.value(); |
| EXPECT_EQ(2lu, result.size()); |
| EXPECT_EQ("now it matches", result.at(0).content); |
| EXPECT_EQ("match", result.at(1).content); |
| } |
| listener.reset(); |
| |
| // (4) |
| repository_.Remove(id); |
| ASSERT_TRUE(listener.last_update); |
| maybe_result = TakeContextValue(listener.last_update.get(), "a"); |
| ASSERT_TRUE(maybe_result.has_value()); |
| { |
| auto& result = maybe_result.value(); |
| EXPECT_EQ(1lu, result.size()); |
| EXPECT_EQ("match", result.at(0).content); |
| } |
| listener.reset(); |
| } |
| |
| TEST_F(ContextRepositoryTest, ListenersGetUpdates_WhenParentsUpdated) { |
| // We should see updates to listeners when an update to a node's |
| // parent causes that node to be matched by a query. |
| fuchsia::modular::ContextQuery query; |
| fuchsia::modular::ContextSelector selector; |
| selector.type = fuchsia::modular::ContextValueType::ENTITY; |
| selector.meta = ContextMetadataBuilder().SetStoryId("match").BuildPtr(); |
| AddToContextQuery(&query, "a", std::move(selector)); |
| |
| TestListener listener; |
| repository_.AddSubscription(std::move(query), &listener, |
| fuchsia::modular::SubscriptionDebugInfo()); |
| ASSERT_TRUE(listener.last_update); |
| auto maybe_result = TakeContextValue(listener.last_update.get(), "a"); |
| ASSERT_TRUE(maybe_result.has_value()); |
| { |
| auto& result = maybe_result.value(); |
| EXPECT_EQ(0lu, result.size()); |
| listener.reset(); |
| } |
| |
| // Add a Story value. |
| fuchsia::modular::ContextValue story_value; |
| story_value.type = fuchsia::modular::ContextValueType::STORY; |
| story_value.meta = ContextMetadataBuilder().SetStoryId("no match").Build(); |
| fuchsia::modular::ContextValue first_story_value; |
| fidl::Clone(story_value, &first_story_value); // Save for later. |
| auto story_value_id = repository_.Add(std::move(story_value)); |
| |
| // Expect no update. |
| EXPECT_FALSE(listener.last_update); |
| |
| // Add an fuchsia::modular::Entity node, but it still shouldn't match. |
| fuchsia::modular::ContextValue entity_value; |
| entity_value.type = fuchsia::modular::ContextValueType::ENTITY; |
| entity_value.content = "content"; |
| repository_.Add(story_value_id, std::move(entity_value)); |
| |
| // Still expect no update. |
| EXPECT_FALSE(listener.last_update); |
| |
| // Update the story value so its metadata matches the query, and we should |
| // see the entity value returned in our update. |
| story_value = fuchsia::modular::ContextValue(); |
| story_value.type = fuchsia::modular::ContextValueType::STORY; |
| story_value.meta = ContextMetadataBuilder().SetStoryId("match").Build(); |
| fuchsia::modular::ContextValue matching_story_value; |
| fidl::Clone(story_value, &matching_story_value); // Save for later. |
| repository_.Update(story_value_id, std::move(story_value)); |
| |
| ASSERT_TRUE(listener.last_update); |
| maybe_result = TakeContextValue(listener.last_update.get(), "a"); |
| ASSERT_TRUE(maybe_result.has_value()); |
| { |
| auto& result = maybe_result.value(); |
| EXPECT_EQ(1lu, result.size()); |
| EXPECT_EQ("content", result.at(0).content); |
| // Make sure we adopted the parent metadata from the story node. |
| ASSERT_TRUE(result.at(0).meta.story); |
| EXPECT_EQ("match", result.at(0).meta.story->id); |
| } |
| listener.reset(); |
| |
| // Set the value back to something that doesn't match, and we should get an |
| // empty update. |
| repository_.Update(story_value_id, std::move(first_story_value)); |
| ASSERT_TRUE(listener.last_update); |
| maybe_result = TakeContextValue(listener.last_update.get(), "a"); |
| ASSERT_TRUE(maybe_result.has_value()); |
| EXPECT_EQ(0lu, maybe_result.value().size()); |
| listener.reset(); |
| |
| // Set it back to something that matched, and this time remove the value |
| // entirely. We should observe it go away. |
| repository_.Update(story_value_id, std::move(matching_story_value)); |
| ASSERT_TRUE(listener.last_update); |
| maybe_result = TakeContextValue(listener.last_update.get(), "a"); |
| ASSERT_TRUE(maybe_result.has_value()); |
| EXPECT_EQ(1lu, maybe_result.value().size()); |
| listener.reset(); |
| |
| repository_.Remove(story_value_id); |
| ASSERT_TRUE(listener.last_update); |
| maybe_result = TakeContextValue(listener.last_update.get(), "a"); |
| ASSERT_TRUE(maybe_result.has_value()); |
| EXPECT_TRUE(maybe_result.value().empty()); |
| listener.reset(); |
| } |
| |
| } // namespace modular |