blob: df3b463d3b606996f582f3cb2cffeb1c39ff0746 [file] [log] [blame]
// 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