blob: e23a838478c03d34f92705193d0c2938b72d8fa9 [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/sessionmgr/story_runner/link_impl.h"
#include <fuchsia/modular/cpp/fidl.h>
#include <lib/async/cpp/operation.h>
#include <lib/entity/cpp/json.h>
#include <lib/fidl/cpp/array.h>
#include <lib/fsl/vmo/strings.h>
#include "gtest/gtest.h"
#include "peridot/bin/sessionmgr/storage/constants_and_utils.h"
#include "peridot/lib/fidl/array_to_string.h"
#include "peridot/lib/fidl/clone.h"
#include "peridot/lib/fidl/json_xdr.h"
#include "peridot/lib/ledger_client/ledger_client.h"
#include "peridot/lib/ledger_client/page_client.h"
#include "peridot/lib/ledger_client/page_id.h"
#include "peridot/lib/rapidjson/rapidjson.h"
#include "peridot/lib/testing/test_with_ledger.h"
using fuchsia::modular::CreateLinkInfo;
using fuchsia::modular::CreateLinkInfoPtr;
using fuchsia::modular::Link;
using fuchsia::modular::LinkPath;
using fuchsia::modular::LinkPtr;
using fuchsia::modular::LinkWatcher;
using fuchsia::modular::LinkWatcherPtr;
namespace modular {
namespace {
class TestLinkWatcher : public LinkWatcher {
public:
TestLinkWatcher(std::function<void(fidl::StringPtr)> fn) : fn_(fn) {}
private:
void Notify(fuchsia::mem::Buffer json) override {
std::string json_string;
FXL_CHECK(fsl::StringFromVmo(json, &json_string));
fn_(json_string);
}
std::function<void(fidl::StringPtr)> fn_;
};
class LinkImplTest : public testing::TestWithLedger {
public:
std::unique_ptr<StoryStorage> MakeStorage(std::string ledger_page) {
auto page_id = MakePageId(ledger_page);
return std::make_unique<StoryStorage>(ledger_client(), page_id);
}
LinkPtr MakeLink(StoryStorage* const storage, std::string name) {
LinkPath link_path;
link_path.link_name = name;
LinkPtr ptr;
auto impl = std::make_unique<LinkImpl>(storage, std::move(link_path));
links_.AddBinding(std::move(impl), ptr.NewRequest());
return ptr;
}
void Watch(Link* link, std::function<void(fidl::StringPtr)> fn) {
auto watcher = std::make_unique<TestLinkWatcher>(fn);
LinkWatcherPtr ptr;
watchers_.AddBinding(std::move(watcher), ptr.NewRequest());
link->Watch(std::move(ptr));
}
void WatchAll(Link* link, std::function<void(fidl::StringPtr)> fn) {
auto watcher = std::make_unique<TestLinkWatcher>(fn);
LinkWatcherPtr ptr;
watchers_.AddBinding(std::move(watcher), ptr.NewRequest());
link->WatchAll(std::move(ptr));
}
void SetLink(Link* link, std::vector<std::string> path,
const std::string& value) {
fsl::SizedVmo vmo;
FXL_CHECK(fsl::VmoFromString(value, &vmo));
link->Set(fidl::VectorPtr(std::move(path)), std::move(vmo).ToTransport());
}
fidl::BindingSet<Link, std::unique_ptr<LinkImpl>> links_;
fidl::BindingSet<LinkWatcher, std::unique_ptr<TestLinkWatcher>> watchers_;
};
TEST_F(LinkImplTest, GetNull) {
auto storage = MakeStorage("page");
auto link = MakeLink(storage.get(), "foo");
bool get_done{};
link->Get(nullptr /* path */,
[&](std::unique_ptr<fuchsia::mem::Buffer> value) {
std::string content_string;
FXL_CHECK(fsl::StringFromVmo(*value, &content_string));
get_done = true;
EXPECT_EQ("null", content_string);
});
EXPECT_TRUE(RunLoopWithTimeoutOrUntil([&] { return get_done; }));
get_done = false;
fidl::VectorPtr<std::string> path;
path->push_back("one");
link->Get(std::move(path), [&](std::unique_ptr<fuchsia::mem::Buffer> value) {
std::string content_string;
FXL_CHECK(fsl::StringFromVmo(*value, &content_string));
get_done = true;
EXPECT_EQ("null", content_string);
});
EXPECT_TRUE(RunLoopWithTimeoutOrUntil([&] { return get_done; }));
}
TEST_F(LinkImplTest, WatchDefaultBehavior) {
// When we ask to watch a link, we should be notified immediately (on the
// next iteration of the event loop) of its current value.
auto storage = MakeStorage("page");
auto link = MakeLink(storage.get(), "mylink");
int notified_count{0};
Watch(link.get(), [&](const fidl::StringPtr& value) {
++notified_count;
EXPECT_EQ("null", value);
});
// We are only notified after the event loop runs.
EXPECT_EQ(0, notified_count);
EXPECT_TRUE(RunLoopWithTimeoutOrUntil([&] { return notified_count == 1; }));
// Same for WatchAll.
WatchAll(link.get(), [&](const fidl::StringPtr& value) {
++notified_count;
EXPECT_EQ("null", value);
});
EXPECT_TRUE(RunLoopWithTimeoutOrUntil([&] { return notified_count == 2; }));
}
TEST_F(LinkImplTest, SetAndWatch) {
auto storage = MakeStorage("page");
auto link = MakeLink(storage.get(), "mylink");
// Watch for our own changes, which we shouldn't see. The initial value
// ("null"), is sent immediately, though.
Watch(link.get(),
[&](const fidl::StringPtr& value) { EXPECT_EQ("null", value); });
// Also use WatchAll(), on which we should see our own changes.
fidl::StringPtr notified_value;
int notified_count{0};
WatchAll(link.get(), [&](const fidl::StringPtr& value) {
notified_value = value;
++notified_count;
});
SetLink(link.get(), {}, "42");
bool synced{};
link->Sync([&synced] { synced = true; });
EXPECT_TRUE(RunLoopWithTimeoutOrUntil([&synced] { return synced; }));
EXPECT_EQ(2, notified_count);
EXPECT_EQ("42", notified_value);
}
TEST_F(LinkImplTest, SetAndWatchAndGet) {
auto storage = MakeStorage("page");
auto link = MakeLink(storage.get(), "mylink");
fidl::StringPtr notified_value;
int notified_count{0};
WatchAll(link.get(), [&](const fidl::StringPtr& value) {
notified_value = value;
++notified_count;
});
SetLink(link.get(), {}, R"({
"one": 1,
"two": 2
})");
fidl::VectorPtr<std::string> path;
path->push_back("two");
SetLink(link.get(), std::move(path), R"("two")");
path->clear();
path->push_back("three");
SetLink(link.get(), std::move(path), R"(3)");
bool synced{};
link->Sync([&synced] { synced = true; });
EXPECT_TRUE(RunLoopWithTimeoutOrUntil([&] { return synced; }));
const std::string expected_value = R"({"one":1,"two":"two","three":3})";
EXPECT_EQ(4, notified_count); // initial, 3x Set
EXPECT_EQ(expected_value, notified_value);
bool get_done{};
link->Get(nullptr /* path */,
[&](std::unique_ptr<fuchsia::mem::Buffer> value) {
std::string content_string;
FXL_CHECK(fsl::StringFromVmo(*value, &content_string));
get_done = true;
EXPECT_EQ(expected_value, content_string);
});
EXPECT_TRUE(RunLoopWithTimeoutOrUntil([&] { return get_done; }));
}
TEST_F(LinkImplTest, SetNonJsonAndGetJsonPointer) {
auto storage = MakeStorage("page");
auto link = MakeLink(storage.get(), "mylink");
SetLink(link.get(), {}, R"({
"one": 1,
"two": 2invalidjson
})");
bool synced{};
link->Sync([&synced] { synced = true; });
EXPECT_TRUE(RunLoopWithTimeoutOrUntil([&] { return synced; }));
fidl::VectorPtr<std::string> path;
path->push_back("one");
bool get_done{};
link->Get(std::move(path), [&](std::unique_ptr<fuchsia::mem::Buffer> value) {
std::string content_string;
FXL_CHECK(fsl::StringFromVmo(*value, &content_string));
get_done = true;
EXPECT_EQ("null", content_string);
});
EXPECT_TRUE(RunLoopWithTimeoutOrUntil([&] { return get_done; }));
}
TEST_F(LinkImplTest, Erase) {
auto storage = MakeStorage("page");
auto link = MakeLink(storage.get(), "mylink");
SetLink(link.get(), {}, R"({
"one": 1,
"two": 2
})");
std::vector<std::string> path;
path.push_back("two");
link->Erase(std::move(path));
const std::string expected_value = R"({"one":1})";
bool get_done{};
link->Get(nullptr /* path */,
[&](std::unique_ptr<fuchsia::mem::Buffer> value) {
std::string content_string;
FXL_CHECK(fsl::StringFromVmo(*value, &content_string));
get_done = true;
EXPECT_EQ(expected_value, content_string);
});
EXPECT_TRUE(RunLoopWithTimeoutOrUntil([&] { return get_done; }));
}
TEST_F(LinkImplTest, SetAndGetEntity) {
auto storage = MakeStorage("page");
auto link = MakeLink(storage.get(), "mylink");
link->SetEntity("ref");
bool done{};
link->GetEntity([&](const fidl::StringPtr value) {
EXPECT_EQ("ref", value);
done = true;
});
EXPECT_TRUE(RunLoopWithTimeoutOrUntil([&] { return done; }));
}
TEST_F(LinkImplTest, MultipleConnections) {
// Create two storage instances, where each one gets its own connection to
// the Ledger. Then, create a LinkImpl on top of each, and test end-to-end.
auto storage1 = MakeStorage("page");
auto link1 = MakeLink(storage1.get(), "mylink");
auto storage2 = MakeStorage("page");
auto link2 = MakeLink(storage2.get(), "mylink");
int notified_count{0};
fidl::StringPtr last_value;
WatchAll(link1.get(), [&](const fidl::StringPtr& value) {
++notified_count;
last_value = value;
});
// Set two values on |link2|. On |link1|, we are guaranteed to get a
// notification about the second value, eventually.
SetLink(link2.get(), {}, "3");
SetLink(link2.get(), {}, "4");
EXPECT_TRUE(RunLoopWithTimeoutOrUntil([&] { return last_value == "4"; }));
// There is always an initial notification of the current state, so we
// will have been notified either 2 or 3 times: 2 if we only got a
// notification about the set to "4", and 3 if we got both the set to "3" and
// "4".
EXPECT_TRUE(notified_count == 2 || notified_count == 3) << notified_count;
}
} // namespace
} // namespace modular