blob: 9814e1b509253ab792f6536c424032ba19800c8a [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 <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