| // 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 <fuchsia/ledger/cloud/cpp/fidl.h> |
| #include <gtest/gtest.h> |
| #include <lib/fsl/socket/strings.h> |
| #include <lib/fsl/vmo/sized_vmo.h> |
| #include <lib/fsl/vmo/strings.h> |
| |
| #include "peridot/lib/commit_pack/commit_pack.h" |
| #include "src/ledger/bin/tests/cloud_provider/convert.h" |
| #include "src/ledger/bin/tests/cloud_provider/types.h" |
| #include "src/ledger/bin/tests/cloud_provider/validation_test.h" |
| #include "src/lib/uuid/uuid.h" |
| |
| namespace cloud_provider { |
| namespace { |
| |
| // Verifies that the given array of commits contains a commit of the given id |
| // and data. |
| ::testing::AssertionResult CheckThatCommitsContain( |
| const std::vector<CommitPackEntry>& entries, const std::string& id, |
| const std::string& data) { |
| for (auto& entry : entries) { |
| if (entry.id != id) { |
| continue; |
| } |
| |
| if (entry.data != data) { |
| return ::testing::AssertionFailure() |
| << "The commit of the expected id: 0x" << ToHex(id) << " (" << id |
| << ") " |
| << " was found but its data doesn't match - expected: 0x" |
| << ToHex(data) << " but found: " << ToHex(entry.data); |
| } |
| |
| return ::testing::AssertionSuccess(); |
| } |
| |
| return ::testing::AssertionFailure() |
| << "The commit of the id: " << ToHex(id) << " (" << id << ") " |
| << " is missing."; |
| } |
| |
| class PageCloudTest : public ValidationTest, public PageCloudWatcher { |
| public: |
| PageCloudTest() {} |
| ~PageCloudTest() override {} |
| |
| protected: |
| ::testing::AssertionResult GetPageCloud(fidl::VectorPtr<uint8_t> app_id, |
| fidl::VectorPtr<uint8_t> page_id, |
| PageCloudSyncPtr* page_cloud) { |
| *page_cloud = PageCloudSyncPtr(); |
| Status status = Status::INTERNAL_ERROR; |
| |
| if (cloud_provider_->GetPageCloud(std::move(app_id), std::move(page_id), |
| page_cloud->NewRequest(), |
| &status) != ZX_OK) { |
| return ::testing::AssertionFailure() |
| << "Failed to retrieve the page cloud due to channel error."; |
| } |
| |
| if (status != Status::OK) { |
| return ::testing::AssertionFailure() |
| << "Failed to retrieve the page cloud, received status: " |
| << fidl::ToUnderlying(status); |
| } |
| |
| return ::testing::AssertionSuccess(); |
| } |
| |
| ::testing::AssertionResult GetLatestPositionToken( |
| PageCloudSyncPtr* page_cloud, |
| std::unique_ptr<cloud_provider::Token>* token) { |
| Status status = Status::INTERNAL_ERROR; |
| std::unique_ptr<cloud_provider::CommitPack> commit_pack; |
| if ((*page_cloud)->GetCommits(nullptr, &status, &commit_pack, token) != |
| ZX_OK) { |
| return ::testing::AssertionFailure() |
| << "Failed to retrieve the position token due to channel error."; |
| } |
| |
| if (status != Status::OK) { |
| return ::testing::AssertionFailure() |
| << "Failed to retrieve the position token, received status: " |
| << fidl::ToUnderlying(status); |
| } |
| |
| return ::testing::AssertionSuccess(); |
| } |
| |
| int on_new_commits_calls_ = 0; |
| std::vector<CommitPackEntry> on_new_commits_commits_; |
| cloud_provider::Token on_new_commits_position_token_; |
| OnNewCommitsCallback on_new_commits_commits_callback_; |
| |
| cloud_provider::Status on_error_status_ = cloud_provider::Status::OK; |
| |
| private: |
| // PageCloudWatcher: |
| void OnNewCommits(CommitPack commits, cloud_provider::Token position_token, |
| OnNewCommitsCallback callback) override { |
| std::vector<CommitPackEntry> entries; |
| ASSERT_TRUE(DecodeCommitPack(commits, &entries)); |
| |
| on_new_commits_calls_++; |
| std::move(entries.begin(), entries.end(), |
| std::back_inserter(on_new_commits_commits_)); |
| on_new_commits_position_token_ = std::move(position_token); |
| on_new_commits_commits_callback_ = std::move(callback); |
| } |
| |
| void OnNewObject(std::vector<uint8_t> /*id*/, fuchsia::mem::Buffer /*data*/, |
| OnNewObjectCallback /*callback*/) override { |
| // We don't have any implementations yet that support this API. |
| // TODO(ppi): add tests for the OnNewObject notifications. |
| FXL_NOTIMPLEMENTED(); |
| } |
| |
| void OnError(cloud_provider::Status status) override { |
| on_error_status_ = status; |
| } |
| }; |
| |
| TEST_F(PageCloudTest, GetPageCloud) { |
| PageCloudSyncPtr page_cloud; |
| ASSERT_TRUE(GetPageCloud(ToArray("app_id"), ToArray("page_id"), &page_cloud)); |
| } |
| |
| TEST_F(PageCloudTest, GetNoCommits) { |
| PageCloudSyncPtr page_cloud; |
| ASSERT_TRUE(GetPageCloud(ToArray("app_id"), ToArray("page_id"), &page_cloud)); |
| |
| std::unique_ptr<cloud_provider::CommitPack> commit_pack; |
| std::unique_ptr<Token> token; |
| Status status = Status::INTERNAL_ERROR; |
| ASSERT_EQ(ZX_OK, |
| page_cloud->GetCommits(nullptr, &status, &commit_pack, &token)); |
| EXPECT_EQ(Status::OK, status); |
| ASSERT_TRUE(commit_pack); |
| std::vector<CommitPackEntry> entries; |
| ASSERT_TRUE(DecodeCommitPack(*commit_pack, &entries)); |
| EXPECT_TRUE(entries.empty()); |
| EXPECT_FALSE(token); |
| } |
| |
| TEST_F(PageCloudTest, AddAndGetCommits) { |
| PageCloudSyncPtr page_cloud; |
| ASSERT_TRUE(GetPageCloud(ToArray("app_id"), ToArray("page_id"), &page_cloud)); |
| |
| std::vector<CommitPackEntry> entries{{"id0", "data0"}, {"id1", "data1"}}; |
| CommitPack commit_pack; |
| ASSERT_TRUE(EncodeCommitPack(entries, &commit_pack)); |
| Status status = Status::INTERNAL_ERROR; |
| ASSERT_EQ(ZX_OK, page_cloud->AddCommits(std::move(commit_pack), &status)); |
| EXPECT_EQ(Status::OK, status); |
| |
| std::unique_ptr<CommitPack> result; |
| std::unique_ptr<Token> token; |
| ASSERT_EQ(ZX_OK, page_cloud->GetCommits(nullptr, &status, &result, &token)); |
| EXPECT_EQ(Status::OK, status); |
| ASSERT_TRUE(result); |
| ASSERT_TRUE(DecodeCommitPack(*result, &entries)); |
| EXPECT_EQ(2u, entries.size()); |
| EXPECT_TRUE(CheckThatCommitsContain(entries, "id0", "data0")); |
| EXPECT_TRUE(CheckThatCommitsContain(entries, "id1", "data1")); |
| EXPECT_TRUE(token); |
| } |
| |
| TEST_F(PageCloudTest, GetCommitsByPositionToken) { |
| PageCloudSyncPtr page_cloud; |
| ASSERT_TRUE(GetPageCloud(ToArray("app_id"), ToArray("page_id"), &page_cloud)); |
| |
| // Add two commits. |
| std::vector<CommitPackEntry> entries{{"id0", "data0"}, {"id1", "data1"}}; |
| CommitPack commit_pack; |
| ASSERT_TRUE(EncodeCommitPack(entries, &commit_pack)); |
| Status status = Status::INTERNAL_ERROR; |
| ASSERT_EQ(ZX_OK, page_cloud->AddCommits(std::move(commit_pack), &status)); |
| EXPECT_EQ(Status::OK, status); |
| |
| // Retrieve the position token of the newest of the two (`id1`). |
| std::unique_ptr<cloud_provider::Token> token; |
| ASSERT_TRUE(GetLatestPositionToken(&page_cloud, &token)); |
| EXPECT_TRUE(token); |
| |
| // Add one more commit. |
| std::vector<CommitPackEntry> more_entries{{"id2", "data2"}}; |
| ASSERT_TRUE(EncodeCommitPack(more_entries, &commit_pack)); |
| ASSERT_EQ(ZX_OK, page_cloud->AddCommits(std::move(commit_pack), &status)); |
| EXPECT_EQ(Status::OK, status); |
| |
| // Retrieve the commits again with the position token of `id1`. |
| std::unique_ptr<CommitPack> result; |
| ASSERT_EQ(ZX_OK, |
| page_cloud->GetCommits(std::move(token), &status, &result, &token)); |
| EXPECT_EQ(Status::OK, status); |
| ASSERT_TRUE(result); |
| ASSERT_TRUE(DecodeCommitPack(*result, &entries)); |
| |
| // As per the API contract, the response must include `id2` and everything |
| // newer than it. It may or may not include `id0` and `id1`. |
| EXPECT_TRUE(CheckThatCommitsContain(entries, "id2", "data2")); |
| } |
| |
| TEST_F(PageCloudTest, AddAndGetObjects) { |
| PageCloudSyncPtr page_cloud; |
| ASSERT_TRUE(GetPageCloud(ToArray("app_id"), ToArray("page_id"), &page_cloud)); |
| |
| fsl::SizedVmo data; |
| ASSERT_TRUE(fsl::VmoFromString("bazinga!", &data)); |
| Status status = Status::INTERNAL_ERROR; |
| // Generate a random ID - the current cloud provider implementations don't |
| // erase storage objects upon .Erase(), and we want to avoid interference from |
| // previous test runs. |
| // TODO(ppi): use a fixed ID here once the cloud provider implementations |
| // support erasing objects. |
| const std::string id = uuid::Generate(); |
| ASSERT_EQ(ZX_OK, page_cloud->AddObject( |
| ToArray(id), std::move(data).ToTransport(), &status)); |
| EXPECT_EQ(Status::OK, status); |
| |
| ::fuchsia::mem::BufferPtr buffer_ptr; |
| ASSERT_EQ(ZX_OK, page_cloud->GetObject(ToArray(id), &status, &buffer_ptr)); |
| EXPECT_EQ(Status::OK, status); |
| std::string read_data; |
| ASSERT_TRUE(fsl::StringFromVmo(*buffer_ptr, &read_data)); |
| EXPECT_EQ("bazinga!", read_data); |
| } |
| |
| TEST_F(PageCloudTest, AddSameObjectTwice) { |
| PageCloudSyncPtr page_cloud; |
| ASSERT_TRUE(GetPageCloud(ToArray("app_id"), ToArray("page_id"), &page_cloud)); |
| |
| fsl::SizedVmo data; |
| ASSERT_TRUE(fsl::VmoFromString("bazinga!", &data)); |
| Status status = Status::INTERNAL_ERROR; |
| const std::string id = "some id"; |
| ASSERT_EQ(ZX_OK, page_cloud->AddObject( |
| ToArray(id), std::move(data).ToTransport(), &status)); |
| EXPECT_EQ(Status::OK, status); |
| // Adding the same object again must succeed as per cloud provider contract. |
| fsl::SizedVmo more_data; |
| ASSERT_TRUE(fsl::VmoFromString("bazinga!", &more_data)); |
| ASSERT_EQ(ZX_OK, |
| page_cloud->AddObject(ToArray(id), |
| std::move(more_data).ToTransport(), &status)); |
| EXPECT_EQ(Status::OK, status); |
| } |
| |
| TEST_F(PageCloudTest, WatchAndReceiveCommits) { |
| PageCloudSyncPtr page_cloud; |
| ASSERT_TRUE(GetPageCloud(ToArray("app_id"), ToArray("page_id"), &page_cloud)); |
| Status status = Status::INTERNAL_ERROR; |
| fidl::Binding<PageCloudWatcher> binding(this); |
| PageCloudWatcherPtr watcher; |
| binding.Bind(watcher.NewRequest()); |
| ASSERT_EQ(ZX_OK, |
| page_cloud->SetWatcher(nullptr, std::move(watcher), &status)); |
| EXPECT_EQ(Status::OK, status); |
| |
| std::vector<CommitPackEntry> entries{{"id0", "data0"}, {"id1", "data1"}}; |
| CommitPack commit_pack; |
| ASSERT_TRUE(EncodeCommitPack(entries, &commit_pack)); |
| ASSERT_EQ(ZX_OK, page_cloud->AddCommits(std::move(commit_pack), &status)); |
| EXPECT_EQ(Status::OK, status); |
| |
| // The two commits could be delivered in one or two notifications. If they are |
| // delivered over two notifications, the second one can only be delivered |
| // after the client confirms having processed the first one by calling the |
| // notification callback. |
| while (on_new_commits_commits_.size() < 2u) { |
| ASSERT_EQ(ZX_OK, binding.WaitForMessage()); |
| on_new_commits_commits_callback_(); |
| } |
| EXPECT_TRUE(CheckThatCommitsContain(on_new_commits_commits_, "id0", "data0")); |
| EXPECT_TRUE(CheckThatCommitsContain(on_new_commits_commits_, "id1", "data1")); |
| } |
| |
| // Verifies that the pre-existing commits are also delivered when a watcher is |
| // registered. |
| TEST_F(PageCloudTest, WatchWithBacklog) { |
| PageCloudSyncPtr page_cloud; |
| ASSERT_TRUE(GetPageCloud(ToArray("app_id"), ToArray("page_id"), &page_cloud)); |
| Status status = Status::INTERNAL_ERROR; |
| |
| std::vector<CommitPackEntry> entries{{"id0", "data0"}, {"id1", "data1"}}; |
| CommitPack commit_pack; |
| ASSERT_TRUE(EncodeCommitPack(entries, &commit_pack)); |
| ASSERT_EQ(ZX_OK, page_cloud->AddCommits(std::move(commit_pack), &status)); |
| EXPECT_EQ(Status::OK, status); |
| |
| fidl::Binding<PageCloudWatcher> binding(this); |
| PageCloudWatcherPtr watcher; |
| binding.Bind(watcher.NewRequest()); |
| ASSERT_EQ(ZX_OK, |
| page_cloud->SetWatcher(nullptr, std::move(watcher), &status)); |
| EXPECT_EQ(Status::OK, status); |
| |
| while (on_new_commits_commits_.size() < 2u) { |
| ASSERT_EQ(ZX_OK, binding.WaitForMessage()); |
| on_new_commits_commits_callback_(); |
| } |
| EXPECT_TRUE(CheckThatCommitsContain(on_new_commits_commits_, "id0", "data0")); |
| EXPECT_TRUE(CheckThatCommitsContain(on_new_commits_commits_, "id1", "data1")); |
| } |
| |
| TEST_F(PageCloudTest, WatchWithPositionToken) { |
| PageCloudSyncPtr page_cloud; |
| ASSERT_TRUE(GetPageCloud(ToArray("app_id"), ToArray("page_id"), &page_cloud)); |
| |
| // Add two commits. |
| std::vector<CommitPackEntry> entries{{"id0", "data0"}, {"id1", "data1"}}; |
| CommitPack commit_pack; |
| ASSERT_TRUE(EncodeCommitPack(entries, &commit_pack)); |
| Status status = Status::INTERNAL_ERROR; |
| ASSERT_EQ(ZX_OK, page_cloud->AddCommits(std::move(commit_pack), &status)); |
| EXPECT_EQ(Status::OK, status); |
| |
| // Retrieve the position token of the newest of the two (`id1`). |
| std::unique_ptr<cloud_provider::Token> token; |
| ASSERT_TRUE(GetLatestPositionToken(&page_cloud, &token)); |
| EXPECT_TRUE(token); |
| |
| // Set the watcher with the position token of `id1`. |
| fidl::Binding<PageCloudWatcher> binding(this); |
| PageCloudWatcherPtr watcher; |
| binding.Bind(watcher.NewRequest()); |
| ASSERT_EQ(ZX_OK, page_cloud->SetWatcher(std::move(token), std::move(watcher), |
| &status)); |
| EXPECT_EQ(Status::OK, status); |
| |
| // Add one more commit. |
| std::vector<CommitPackEntry> more_entries{{"id2", "data2"}}; |
| ASSERT_TRUE(EncodeCommitPack(more_entries, &commit_pack)); |
| ASSERT_EQ(ZX_OK, page_cloud->AddCommits(std::move(commit_pack), &status)); |
| EXPECT_EQ(Status::OK, status); |
| |
| while (!CheckThatCommitsContain(on_new_commits_commits_, "id2", "data2")) { |
| ASSERT_EQ(ZX_OK, binding.WaitForMessage()); |
| on_new_commits_commits_callback_(); |
| } |
| |
| // Add one more commit. |
| more_entries = {{"id3", "data3"}}; |
| ASSERT_TRUE(EncodeCommitPack(more_entries, &commit_pack)); |
| ASSERT_EQ(ZX_OK, page_cloud->AddCommits(std::move(commit_pack), &status)); |
| EXPECT_EQ(Status::OK, status); |
| |
| while (!CheckThatCommitsContain(on_new_commits_commits_, "id3", "data3")) { |
| ASSERT_EQ(ZX_OK, binding.WaitForMessage()); |
| on_new_commits_commits_callback_(); |
| } |
| } |
| |
| TEST_F(PageCloudTest, WatchWithPositionTokenBatch) { |
| PageCloudSyncPtr page_cloud; |
| ASSERT_TRUE(GetPageCloud(ToArray("app_id"), ToArray("page_id"), &page_cloud)); |
| |
| // Add two commits. |
| std::vector<CommitPackEntry> entries{{"id0", "data0"}, {"id1", "data1"}}; |
| CommitPack commit_pack; |
| ASSERT_TRUE(EncodeCommitPack(entries, &commit_pack)); |
| Status status = Status::INTERNAL_ERROR; |
| ASSERT_EQ(ZX_OK, page_cloud->AddCommits(std::move(commit_pack), &status)); |
| EXPECT_EQ(Status::OK, status); |
| |
| // Retrieve the position token of the newest of the two (`id1`). |
| std::unique_ptr<cloud_provider::Token> token; |
| ASSERT_TRUE(GetLatestPositionToken(&page_cloud, &token)); |
| EXPECT_TRUE(token); |
| |
| // Set the watcher with the position token of `id1`. |
| fidl::Binding<PageCloudWatcher> binding(this); |
| PageCloudWatcherPtr watcher; |
| binding.Bind(watcher.NewRequest()); |
| ASSERT_EQ(ZX_OK, page_cloud->SetWatcher(std::move(token), std::move(watcher), |
| &status)); |
| EXPECT_EQ(Status::OK, status); |
| |
| // Add two commits at once. |
| std::vector<CommitPackEntry> more_entries{{"id2", "data2"}, {"id3", "data3"}}; |
| ASSERT_TRUE(EncodeCommitPack(more_entries, &commit_pack)); |
| ASSERT_EQ(ZX_OK, page_cloud->AddCommits(std::move(commit_pack), &status)); |
| EXPECT_EQ(Status::OK, status); |
| |
| while (!CheckThatCommitsContain(on_new_commits_commits_, "id2", "data2")) { |
| ASSERT_EQ(ZX_OK, binding.WaitForMessage()); |
| on_new_commits_commits_callback_(); |
| } |
| // The two commits must be delivered at the same time. |
| EXPECT_TRUE(CheckThatCommitsContain(on_new_commits_commits_, "id3", "data3")); |
| } |
| |
| } // namespace |
| } // namespace cloud_provider |