blob: 55ec518f478392e45b6d1f63b7cdf7115bc3583e [file] [log] [blame]
// Copyright 2018 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/storage/session_storage.h"
#include <memory>
#include <lib/async/cpp/future.h>
#include <lib/fsl/vmo/strings.h>
#include "gtest/gtest.h"
#include "peridot/lib/fidl/array_to_string.h"
#include "peridot/lib/ledger_client/page_id.h"
#include "peridot/lib/testing/test_with_ledger.h"
namespace modular {
namespace {
class SessionStorageTest : public testing::TestWithLedger {
protected:
std::unique_ptr<SessionStorage> CreateStorage(std::string page_id) {
return std::make_unique<SessionStorage>(ledger_client(),
MakePageId(page_id));
}
// Convenience method to create a story for the test cases where
// we're not testing CreateStory().
fidl::StringPtr CreateStory(
SessionStorage* storage,
fuchsia::modular::StoryOptions story_options = {}) {
auto future_story = storage->CreateStory(
nullptr /* name */, nullptr /* extra */, std::move(story_options));
bool done{};
fidl::StringPtr story_name;
future_story->Then(
[&](fidl::StringPtr name, fuchsia::ledger::PageId page_id) {
done = true;
story_name = std::move(name);
});
RunLoopUntil([&] { return done; });
return story_name;
}
};
TEST_F(SessionStorageTest, Create_VerifyData) {
// Create a single story, and verify that the data we have stored about it is
// correct.
auto storage = CreateStorage("page");
fidl::VectorPtr<fuchsia::modular::StoryInfoExtraEntry> extra_entries;
fuchsia::modular::StoryInfoExtraEntry entry;
entry.key = "key1";
entry.value = "value1";
extra_entries->push_back(std::move(entry));
entry.key = "key2";
entry.value = "value2";
extra_entries->push_back(std::move(entry));
fuchsia::modular::StoryOptions story_options;
story_options.kind_of_proto_story = true;
auto future_story = storage->CreateStory(
"story_name", std::move(extra_entries), std::move(story_options));
bool done{};
fidl::StringPtr story_name;
fuchsia::ledger::PageId page_id;
future_story->Then([&](fidl::StringPtr name, fuchsia::ledger::PageId page) {
done = true;
story_name = std::move(name);
page_id = std::move(page);
});
RunLoopUntil([&] { return done; });
// Get the StoryData for this story.
auto future_data = storage->GetStoryData(story_name);
done = false;
fuchsia::modular::internal::StoryData cached_data;
future_data->Then([&](fuchsia::modular::internal::StoryDataPtr data) {
ASSERT_TRUE(data);
EXPECT_EQ("story_name", data->story_name);
EXPECT_TRUE(data->story_options.kind_of_proto_story);
EXPECT_EQ(story_name, data->story_info.id);
ASSERT_TRUE(data->story_page_id);
EXPECT_EQ(page_id, *data->story_page_id);
EXPECT_TRUE(data->story_info.extra);
EXPECT_EQ(2u, data->story_info.extra->size());
EXPECT_EQ("key1", data->story_info.extra->at(0).key);
EXPECT_EQ("value1", data->story_info.extra->at(0).value);
EXPECT_EQ("key2", data->story_info.extra->at(1).key);
EXPECT_EQ("value2", data->story_info.extra->at(1).value);
done = true;
cached_data = std::move(*data);
});
RunLoopUntil([&] { return done; });
// Get the StoryData again, but this time by its name.
future_data = storage->GetStoryData("story_name");
done = false;
future_data->Then([&](fuchsia::modular::internal::StoryDataPtr data) {
ASSERT_TRUE(data);
ASSERT_EQ(cached_data, *data);
done = true;
});
RunLoopUntil([&] { return done; });
// Verify that GetAllStoryData() also returns the same information.
fidl::VectorPtr<fuchsia::modular::internal::StoryData> all_data;
auto future_all_data = storage->GetAllStoryData();
future_all_data->Then(
[&](std::vector<fuchsia::modular::internal::StoryData> data) {
all_data.reset(std::move(data));
});
RunLoopUntil([&] { return !!all_data; });
EXPECT_EQ(1u, all_data->size());
EXPECT_EQ(cached_data, all_data->at(0));
}
TEST_F(SessionStorageTest, CreateGetAllDelete) {
// Create a single story, call GetAllStoryData() to show that it was created,
// and then delete it.
//
// Pipeline all the calls such to show that we data consistency based on call
// order.
auto storage = CreateStorage("page");
auto future_story = storage->CreateStory(
"story_name", nullptr /* extra_info */, {} /* options */);
// Immediately after creation is complete, delete it.
FuturePtr<> delete_done;
future_story->Then(
[&](fidl::StringPtr story_name, fuchsia::ledger::PageId page_id) {
delete_done = storage->DeleteStory(story_name);
});
auto future_all_data = storage->GetAllStoryData();
fidl::VectorPtr<fuchsia::modular::internal::StoryData> all_data;
future_all_data->Then(
[&](std::vector<fuchsia::modular::internal::StoryData> data) {
all_data.reset(std::move(data));
});
RunLoopUntil([&] { return !!all_data; });
// Given the ordering, we expect the story we created to show up.
EXPECT_EQ(1u, all_data->size());
// But if we get all data again, we should see no stories.
future_all_data = storage->GetAllStoryData();
all_data.reset();
future_all_data->Then(
[&](std::vector<fuchsia::modular::internal::StoryData> data) {
all_data.reset(std::move(data));
});
RunLoopUntil([&] { return !!all_data; });
EXPECT_EQ(0u, all_data->size());
}
TEST_F(SessionStorageTest, CreateMultipleAndDeleteOne) {
// Create two stories.
//
// * Their ids should be different.
// * They should get different Ledger page ids.
// * If we GetAllStoryData() we should see both of them.
auto storage = CreateStorage("page");
auto future_story1 = storage->CreateStory("story1", nullptr /* extra_info */,
{} /* options */);
auto future_story2 =
storage->CreateStory("story2", nullptr /* extra_info */, {} /* options*/);
fidl::StringPtr story1_name;
fuchsia::ledger::PageId story1_pageid;
fidl::StringPtr story2_name;
fuchsia::ledger::PageId story2_pageid;
bool done = false;
Wait("SessionStorageTest.CreateMultipleAndDeleteOne.wait",
{future_story1, future_story2})
->Then([&](auto results) {
story1_name = std::move(std::get<0>(results[0]));
story1_pageid = std::move(std::get<1>(results[0]));
story2_name = std::move(std::get<0>(results[1]));
story2_pageid = std::move(std::get<1>(results[1]));
done = true;
});
RunLoopUntil([&] { return done; });
EXPECT_NE(story1_name, story2_name);
EXPECT_NE(story1_pageid, story2_pageid);
auto future_all_data = storage->GetAllStoryData();
fidl::VectorPtr<fuchsia::modular::internal::StoryData> all_data;
future_all_data->Then(
[&](std::vector<fuchsia::modular::internal::StoryData> data) {
all_data.reset(std::move(data));
});
RunLoopUntil([&] { return !!all_data; });
EXPECT_EQ(2u, all_data->size());
// Now delete one of them, and we should see that GetAllStoryData() only
// returns one entry.
bool delete_done{};
storage->DeleteStory("story1")->Then([&] { delete_done = true; });
future_all_data = storage->GetAllStoryData();
all_data.reset();
future_all_data->Then(
[&](std::vector<fuchsia::modular::internal::StoryData> data) {
all_data.reset(std::move(data));
});
RunLoopUntil([&] { return !!all_data; });
EXPECT_TRUE(delete_done);
EXPECT_EQ(1u, all_data->size());
// If we try to get the story by id, or by name, we expect both to return
// null.
auto future_data = storage->GetStoryData(story1_name);
done = false;
future_data->Then([&](fuchsia::modular::internal::StoryDataPtr data) {
EXPECT_TRUE(data == nullptr);
done = true;
});
future_data = storage->GetStoryData("story1");
done = false;
future_data->Then([&](fuchsia::modular::internal::StoryDataPtr data) {
EXPECT_TRUE(data == nullptr);
done = true;
});
// TODO(thatguy): Verify that the story's page was also deleted.
// MI4-1002
}
TEST_F(SessionStorageTest, DeleteStoryDeletesStoryPage) {
// When we call DeleteStory, we expect the story's page to be completely
// emptied.
auto storage = CreateStorage("page");
auto future_story = storage->CreateStory(
"story_name", nullptr /* extra_info */, {} /* options */);
bool done{false};
auto story_page_id = fuchsia::ledger::PageId::New();
future_story->Then([&](fidl::StringPtr id, fuchsia::ledger::PageId page_id) {
*story_page_id = std::move(page_id);
done = true;
});
RunLoopUntil([&] { return done; });
// Add some fake content to the story's page, so that we dan show that
// it is deleted when we instruct SessionStorage to delete the story.
fuchsia::ledger::PagePtr story_page;
ledger_client()->ledger()->GetPage(
std::move(story_page_id), story_page.NewRequest(),
[&](fuchsia::ledger::Status status) {
ASSERT_EQ(fuchsia::ledger::Status::OK, status);
});
done = false;
story_page->Put(to_array("key"), to_array("value"),
[&](fuchsia::ledger::Status status) { done = true; });
RunLoopUntil([&] { return done; });
// Delete the story.
done = false;
storage->DeleteStory("story_name")->Then([&] { done = true; });
RunLoopUntil([&] { return done; });
// Show that the underlying page is now empty.
fuchsia::ledger::PageSnapshotPtr snapshot;
story_page->GetSnapshot(snapshot.NewRequest(), to_array("") /* prefix */,
nullptr /* watcher */,
[&](fuchsia::ledger::Status status) {
ASSERT_EQ(fuchsia::ledger::Status::OK, status);
});
done = false;
snapshot->GetEntries(to_array("") /* key_start */, nullptr /* token */,
[&](fuchsia::ledger::Status status,
std::vector<fuchsia::ledger::Entry> entries,
fuchsia::ledger::TokenPtr next_token) {
ASSERT_EQ(fuchsia::ledger::Status::OK, status);
EXPECT_EQ(nullptr, next_token);
EXPECT_TRUE(entries.empty());
done = true;
});
RunLoopUntil([&] { return done; });
}
TEST_F(SessionStorageTest, UpdateLastFocusedTimestamp) {
auto storage = CreateStorage("page");
auto story_name = CreateStory(storage.get());
storage->UpdateLastFocusedTimestamp(story_name, 10);
auto future_data = storage->GetStoryData(story_name);
bool done{};
future_data->Then([&](fuchsia::modular::internal::StoryDataPtr data) {
EXPECT_EQ(10, data->story_info.last_focus_time);
done = true;
});
RunLoopUntil([&] { return done; });
}
TEST_F(SessionStorageTest, ObserveCreateUpdateDelete_Local) {
auto storage = CreateStorage("page");
bool updated{};
fidl::StringPtr updated_story_name;
fuchsia::modular::internal::StoryData updated_story_data;
storage->set_on_story_updated(
[&](fidl::StringPtr story_name,
fuchsia::modular::internal::StoryData story_data) {
updated_story_name = std::move(story_name);
updated_story_data = std::move(story_data);
updated = true;
});
bool deleted{};
fidl::StringPtr deleted_story_name;
storage->set_on_story_deleted([&](fidl::StringPtr story_name) {
deleted_story_name = std::move(story_name);
deleted = true;
});
auto created_story_name = CreateStory(storage.get());
RunLoopUntil([&] { return updated; });
EXPECT_EQ(created_story_name, updated_story_name);
EXPECT_EQ(created_story_name, updated_story_data.story_info.id);
// Update something and see a new notification.
updated = false;
storage->UpdateLastFocusedTimestamp(created_story_name, 42);
RunLoopUntil([&] { return updated; });
EXPECT_EQ(created_story_name, updated_story_name);
EXPECT_EQ(42, updated_story_data.story_info.last_focus_time);
// Update options and see a new notification.
updated = false;
fuchsia::modular::StoryOptions story_options;
story_options.kind_of_proto_story = true;
storage->UpdateStoryOptions(created_story_name, std::move(story_options));
RunLoopUntil([&] { return updated; });
EXPECT_EQ(created_story_name, updated_story_name);
EXPECT_EQ(created_story_name, updated_story_data.story_info.id);
EXPECT_TRUE(updated_story_data.story_options.kind_of_proto_story);
// Delete the story and expect to see a notification.
storage->DeleteStory(created_story_name);
RunLoopUntil([&] { return deleted; });
EXPECT_EQ(created_story_name, deleted_story_name);
}
TEST_F(SessionStorageTest, ObserveCreateUpdateDelete_Remote) {
// Just like above, but we're going to trigger all of the operations that
// would cause a chagne notification on a different Ledger page connection to
// simulate them happening on another device.
auto storage = CreateStorage("page");
auto remote_storage = CreateStorage("page");
bool updated{};
fidl::StringPtr updated_story_name;
fuchsia::modular::internal::StoryData updated_story_data;
storage->set_on_story_updated(
[&](fidl::StringPtr story_name,
fuchsia::modular::internal::StoryData story_data) {
updated_story_name = std::move(story_name);
updated_story_data = std::move(story_data);
updated = true;
});
bool deleted{};
fidl::StringPtr deleted_story_name;
storage->set_on_story_deleted([&](fidl::StringPtr story_name) {
deleted_story_name = std::move(story_name);
deleted = true;
});
auto created_story_name = CreateStory(remote_storage.get());
RunLoopUntil([&] { return updated; });
EXPECT_EQ(created_story_name, updated_story_name);
EXPECT_EQ(created_story_name, updated_story_data.story_info.id);
// Update something and see a new notification.
updated = false;
remote_storage->UpdateLastFocusedTimestamp(created_story_name, 42);
RunLoopUntil([&] { return updated; });
EXPECT_EQ(created_story_name, updated_story_name);
EXPECT_EQ(42, updated_story_data.story_info.last_focus_time);
// Update options and see a new notification.
updated = false;
fuchsia::modular::StoryOptions story_options;
story_options.kind_of_proto_story = true;
remote_storage->UpdateStoryOptions(created_story_name,
std::move(story_options));
RunLoopUntil([&] { return updated; });
EXPECT_EQ(created_story_name, updated_story_name);
EXPECT_EQ(created_story_name, updated_story_data.story_info.id);
EXPECT_TRUE(updated_story_data.story_options.kind_of_proto_story);
// Delete the story and expect to see a notification.
remote_storage->DeleteStory(created_story_name);
RunLoopUntil([&] { return deleted; });
EXPECT_EQ(created_story_name, deleted_story_name);
}
TEST_F(SessionStorageTest, UpdateStoryOptions) {
auto storage = CreateStorage("page");
auto story_name = CreateStory(storage.get());
bool done{};
// Start by setting an option.
fuchsia::modular::StoryOptions story_options;
story_options.kind_of_proto_story = true;
storage->UpdateStoryOptions(story_name, std::move(story_options))->Then([&] {
done = true;
});
RunLoopUntil([&] { return done; });
// Read the options (we should only see 1 even when we added 2 since it's the
// same).
done = false;
storage->GetStoryData(story_name)
->Then([&](fuchsia::modular::internal::StoryDataPtr data) {
EXPECT_TRUE(data->story_options.kind_of_proto_story);
done = true;
});
RunLoopUntil([&] { return done; });
// Update the option again.
fuchsia::modular::StoryOptions story_options2;
story_options.kind_of_proto_story = false;
storage->UpdateStoryOptions(story_name, std::move(story_options2))->Then([&] {
done = true;
});
RunLoopUntil([&] { return done; });
// We should see the last value we set.
done = false;
storage->GetStoryData(story_name)
->Then([&](fuchsia::modular::internal::StoryDataPtr data) {
EXPECT_FALSE(data->story_options.kind_of_proto_story);
done = true;
});
RunLoopUntil([&] { return done; });
}
TEST_F(SessionStorageTest, GetStoryStorage) {
auto storage = CreateStorage("page");
auto story_name = CreateStory(storage.get());
bool done{};
auto get_story_future = storage->GetStoryStorage(story_name);
get_story_future->Then([&](std::unique_ptr<StoryStorage> result) {
EXPECT_NE(nullptr, result);
done = true;
});
RunLoopUntil([&] { return done; });
}
TEST_F(SessionStorageTest, GetStoryStorageNoStory) {
auto storage = CreateStorage("page");
CreateStory(storage.get());
bool done{};
auto get_story_future = storage->GetStoryStorage("fake");
get_story_future->Then([&](std::unique_ptr<StoryStorage> result) {
EXPECT_EQ(nullptr, result);
done = true;
});
RunLoopUntil([&] { return done; });
}
TEST_F(SessionStorageTest, ReadSnapshot) {
std::string kSnapshotData = "snapshot";
auto storage = CreateStorage("page");
auto story_name = CreateStory(storage.get());
bool done{};
fsl::SizedVmo snapshot;
fsl::VmoFromString(kSnapshotData, &snapshot);
storage->WriteSnapshot(story_name, std::move(snapshot).ToTransport())
->Then([&] { done = true; });
RunLoopUntil([&] { return done; });
done = false;
storage->ReadSnapshot(story_name)
->Then([&](fuchsia::mem::BufferPtr snapshot_ptr) {
std::string snapshot_string;
fsl::StringFromVmo(*snapshot_ptr.get(), &snapshot_string);
EXPECT_EQ(kSnapshotData, snapshot_string);
done = true;
});
RunLoopUntil([&] { return done; });
}
TEST_F(SessionStorageTest, DeleteStory_DeletesSnapshot) {
std::string kSnapshotData = "snapshot";
auto storage = CreateStorage("page");
auto story_name = CreateStory(storage.get());
bool done{};
fsl::SizedVmo snapshot;
fsl::VmoFromString(kSnapshotData, &snapshot);
storage->WriteSnapshot(story_name, std::move(snapshot).ToTransport())
->Then([&] { done = true; });
RunLoopUntil([&] { return done; });
// Deleting the story should delete the snapshot.
storage->DeleteStory(story_name)->Then([&] { done = true; });
RunLoopUntil([&] { return done; });
done = false;
storage->ReadSnapshot(story_name)
->Then([&](fuchsia::mem::BufferPtr snapshot_ptr) {
EXPECT_TRUE(snapshot_ptr == nullptr);
done = true;
});
RunLoopUntil([&] { return done; });
}
} // namespace
} // namespace modular