blob: a55239fecc2013bb0643505d9f04ca74c704ecdf [file] [log] [blame]
// Copyright 2016 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/lib/firebase/firebase_impl.h"
#include <memory>
#include <utility>
#include <lib/async/cpp/task.h>
#include <lib/callback/capture.h>
#include <lib/callback/set_when_called.h>
#include <lib/fsl/socket/strings.h>
#include <src/lib/fxl/macros.h>
#include <src/lib/fxl/memory/ref_ptr.h>
#include <lib/gtest/test_loop_fixture.h>
#include <lib/network_wrapper/fake_network_wrapper.h>
#include <lib/network_wrapper/network_wrapper_impl.h>
#include <rapidjson/document.h>
#include "peridot/lib/socket/socket_pair.h"
namespace firebase {
namespace {
class FirebaseImplTest : public gtest::TestLoopFixture, public WatchClient {
public:
FirebaseImplTest()
: fake_network_wrapper_(dispatcher()),
firebase_(&fake_network_wrapper_, "example", "pre/fix") {}
~FirebaseImplTest() override {}
protected:
// WatchClient:
void OnPut(const std::string& path, const rapidjson::Value& value) override {
put_count_++;
put_paths_.push_back(path);
put_data_.emplace_back(value, document_.GetAllocator());
}
void OnPatch(const std::string& path,
const rapidjson::Value& value) override {
patch_count_++;
patch_paths_.push_back(path);
patch_data_.emplace_back(value, document_.GetAllocator());
}
void OnCancel() override { cancel_count_++; }
void OnAuthRevoked(const std::string& reason) override {
auth_revoked_count_++;
auth_revoked_reasons_.push_back(reason);
}
void OnMalformedEvent() override { malformed_event_count_++; }
void OnConnectionError() override { connection_error_count_++; }
std::vector<std::string> put_paths_;
std::vector<rapidjson::Value> put_data_;
unsigned int put_count_ = 0u;
std::vector<std::string> patch_paths_;
std::vector<rapidjson::Value> patch_data_;
unsigned int patch_count_ = 0u;
unsigned int cancel_count_ = 0u;
std::vector<std::string> auth_revoked_reasons_;
unsigned int auth_revoked_count_ = 0u;
unsigned int malformed_event_count_ = 0u;
unsigned int connection_error_count_ = 0u;
network_wrapper::FakeNetworkWrapper fake_network_wrapper_;
FirebaseImpl firebase_;
private:
// Used for its allocator which we use to make copies of rapidjson Values.
rapidjson::Document document_;
FXL_DISALLOW_COPY_AND_ASSIGN(FirebaseImplTest);
};
// Verifies that GET requests are handled correctly.
TEST_F(FirebaseImplTest, Get) {
fake_network_wrapper_.SetStringResponse("\"content\"", 200);
bool called;
Status status;
std::unique_ptr<rapidjson::Value> value;
firebase_.Get(
"bazinga", {},
callback::Capture(callback::SetWhenCalled(&called), &status, &value));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::OK, status);
ASSERT_TRUE(value);
EXPECT_TRUE(value->IsString());
EXPECT_EQ("content", *value);
EXPECT_EQ("https://example.firebaseio.com/pre/fix/bazinga.json",
fake_network_wrapper_.GetRequest()->url);
EXPECT_EQ("GET", fake_network_wrapper_.GetRequest()->method);
}
TEST_F(FirebaseImplTest, GetError) {
fake_network_wrapper_.SetStringResponse("\"content\"", 404);
bool called;
Status status;
std::unique_ptr<rapidjson::Value> value;
firebase_.Get(
"bazinga", {},
callback::Capture(callback::SetWhenCalled(&called), &status, &value));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_NE(Status::OK, status);
ASSERT_TRUE(value);
EXPECT_TRUE(value->IsNull());
}
TEST_F(FirebaseImplTest, GetWithSingleQueryParam) {
fake_network_wrapper_.SetStringResponse("content", 200);
bool called;
Status status;
firebase_.Get("bazinga", {"orderBy=\"timestamp\""},
callback::Capture(callback::SetWhenCalled(&called), &status,
&std::ignore));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::PARSE_ERROR, status);
EXPECT_EQ(
"https://example.firebaseio.com/pre/fix/"
"bazinga.json?orderBy=\"timestamp\"",
fake_network_wrapper_.GetRequest()->url);
EXPECT_EQ("GET", fake_network_wrapper_.GetRequest()->method);
}
TEST_F(FirebaseImplTest, GetWithTwoQueryParams) {
fake_network_wrapper_.SetStringResponse("content", 200);
bool called;
Status status;
firebase_.Get("bazinga", {"one_param", "other_param=bla"},
callback::Capture(callback::SetWhenCalled(&called), &status,
&std::ignore));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::PARSE_ERROR, status);
EXPECT_EQ(
"https://example.firebaseio.com/pre/fix/"
"bazinga.json?one_param&other_param=bla",
fake_network_wrapper_.GetRequest()->url);
EXPECT_EQ("GET", fake_network_wrapper_.GetRequest()->method);
}
// Verifies that request urls for root of the db are correctly formed.
TEST_F(FirebaseImplTest, Root) {
fake_network_wrapper_.SetStringResponse("42", 200);
bool called;
Status status;
firebase_.Get("", {},
callback::Capture(callback::SetWhenCalled(&called), &status,
&std::ignore));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::OK, status);
EXPECT_EQ("https://example.firebaseio.com/pre/fix/.json",
fake_network_wrapper_.GetRequest()->url);
}
// Verifies that PUT requests are handled correctly.
TEST_F(FirebaseImplTest, Put) {
// Firebase server seems to respond with the data we sent to it. This is not
// useful for the client so our API doesn't expose it to the client.
fake_network_wrapper_.SetStringResponse("\"Alice\"", 200);
bool called;
Status status;
firebase_.Put("name", {}, "\"Alice\"",
callback::Capture(callback::SetWhenCalled(&called), &status));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::OK, status);
EXPECT_EQ("https://example.firebaseio.com/pre/fix/name.json",
fake_network_wrapper_.GetRequest()->url);
EXPECT_EQ("PUT", fake_network_wrapper_.GetRequest()->method);
}
// Verifies that PATCH requests are handled correctly.
TEST_F(FirebaseImplTest, Patch) {
fake_network_wrapper_.SetStringResponse("\"ok\"", 200);
bool called;
Status status;
std::string data = R"({"name":"Alice"})";
firebase_.Patch("person", {}, data,
callback::Capture(callback::SetWhenCalled(&called), &status));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::OK, status);
EXPECT_EQ("https://example.firebaseio.com/pre/fix/person.json",
fake_network_wrapper_.GetRequest()->url);
EXPECT_EQ("PATCH", fake_network_wrapper_.GetRequest()->method);
}
// Verifies that DELETE requests are made correctly.
TEST_F(FirebaseImplTest, Delete) {
fake_network_wrapper_.SetStringResponse("", 200);
bool called;
Status status;
firebase_.Delete(
"name", {}, callback::Capture(callback::SetWhenCalled(&called), &status));
RunLoopUntilIdle();
EXPECT_TRUE(called);
EXPECT_EQ(Status::OK, status);
EXPECT_EQ("https://example.firebaseio.com/pre/fix/name.json",
fake_network_wrapper_.GetRequest()->url);
EXPECT_EQ("DELETE", fake_network_wrapper_.GetRequest()->method);
}
// Verifies that event-stream requests are correctly formed.
TEST_F(FirebaseImplTest, WatchRequest) {
fake_network_wrapper_.SetStringResponse("", 200);
firebase_.Watch("some/path", {}, this);
RunLoopUntilIdle();
EXPECT_EQ("https://example.firebaseio.com/pre/fix/some/path.json",
fake_network_wrapper_.GetRequest()->url);
EXPECT_EQ("GET", fake_network_wrapper_.GetRequest()->method);
EXPECT_EQ(1u, fake_network_wrapper_.GetRequest()->headers->size());
EXPECT_EQ("Accept", fake_network_wrapper_.GetRequest()->headers->at(0).name);
EXPECT_EQ("text/event-stream",
fake_network_wrapper_.GetRequest()->headers->at(0).value);
}
TEST_F(FirebaseImplTest, WatchRequestWithQuery) {
fake_network_wrapper_.SetStringResponse("", 200);
firebase_.Watch("some/path", {"orderBy=\"timestamp\""}, this);
RunLoopUntilIdle();
EXPECT_EQ(
"https://example.firebaseio.com/pre/fix/some/path.json"
"?orderBy=\"timestamp\"",
fake_network_wrapper_.GetRequest()->url);
EXPECT_EQ("GET", fake_network_wrapper_.GetRequest()->method);
EXPECT_EQ(1u, fake_network_wrapper_.GetRequest()->headers->size());
EXPECT_EQ("Accept", fake_network_wrapper_.GetRequest()->headers->at(0).name);
EXPECT_EQ("text/event-stream",
fake_network_wrapper_.GetRequest()->headers->at(0).value);
}
TEST_F(FirebaseImplTest, WatchPut) {
std::string stream_body = std::string(
"event: put\n"
"data: {\"path\":\"/\",\"data\":\"Alice\"}\n"
"\n"
"event: put\n"
"data: {\"path\":\"/bla/\",\"data\":{\"name\":\"Bob\"}}\n"
"\n"
"event: put\n"
"data: {\"path\":\"/\",\"data\":42.5}\n"
"\n");
fake_network_wrapper_.SetStringResponse(stream_body, 200);
firebase_.Watch("/", {}, this);
RunLoopUntilIdle();
EXPECT_EQ(3u, put_count_);
EXPECT_EQ(0u, patch_count_);
EXPECT_EQ(0u, cancel_count_);
EXPECT_EQ(0u, auth_revoked_count_);
EXPECT_EQ(0u, malformed_event_count_);
EXPECT_EQ("/", put_paths_[0]);
EXPECT_EQ("Alice", put_data_[0]);
EXPECT_EQ("/bla/", put_paths_[1]);
EXPECT_EQ("Bob", put_data_[1]["name"]);
EXPECT_EQ("/", put_paths_[2]);
EXPECT_EQ(42.5, put_data_[2]);
}
TEST_F(FirebaseImplTest, WatchPatch) {
std::string stream_body = std::string(
"event: patch\n"
"data: "
"{\"path\":\"/bla/\",\"data\":{\"name1\":\"Alice\",\"name2\":\"Bob\"}}\n"
"\n");
fake_network_wrapper_.SetStringResponse(stream_body, 200);
firebase_.Watch("/", {}, this);
RunLoopUntilIdle();
EXPECT_EQ(0u, put_count_);
EXPECT_EQ(1u, patch_count_);
EXPECT_EQ(0u, cancel_count_);
EXPECT_EQ(0u, auth_revoked_count_);
EXPECT_EQ(0u, malformed_event_count_);
EXPECT_EQ("/bla/", patch_paths_[0]);
EXPECT_EQ("Alice", patch_data_[0]["name1"]);
EXPECT_EQ("Bob", patch_data_[0]["name2"]);
}
TEST_F(FirebaseImplTest, WatchKeepAlive) {
std::string stream_body = std::string(
"event: keep-alive\n"
"data: null\n"
"\n");
fake_network_wrapper_.SetStringResponse(stream_body, 200);
firebase_.Watch("name", {}, this);
RunLoopUntilIdle();
EXPECT_EQ(0u, put_count_);
EXPECT_EQ(0u, patch_count_);
EXPECT_EQ(0u, cancel_count_);
EXPECT_EQ(0u, auth_revoked_count_);
EXPECT_EQ(0u, malformed_event_count_);
}
TEST_F(FirebaseImplTest, WatchCancel) {
std::string stream_body = std::string(
"event: cancel\n"
"data: null\n"
"\n");
fake_network_wrapper_.SetStringResponse(stream_body, 200);
firebase_.Watch("/", {}, this);
RunLoopUntilIdle();
EXPECT_EQ(0u, put_count_);
EXPECT_EQ(0u, patch_count_);
EXPECT_EQ(1u, cancel_count_);
EXPECT_EQ(0u, auth_revoked_count_);
EXPECT_EQ(0u, malformed_event_count_);
}
TEST_F(FirebaseImplTest, WatchAuthRevoked) {
std::string stream_body = std::string(
"event: auth_revoked\n"
"data: credential is no longer valid\n"
"\n");
fake_network_wrapper_.SetStringResponse(stream_body, 200);
firebase_.Watch("/", {}, this);
RunLoopUntilIdle();
EXPECT_EQ(0u, put_count_);
EXPECT_EQ(0u, patch_count_);
EXPECT_EQ(0u, cancel_count_);
EXPECT_EQ(1u, auth_revoked_count_);
EXPECT_EQ(0u, malformed_event_count_);
EXPECT_EQ("credential is no longer valid", auth_revoked_reasons_[0]);
}
TEST_F(FirebaseImplTest, WatchErrorUnknownEvent) {
std::string stream_body = std::string(
"event: wild-animal-appears\n"
"data: null\n"
"\n");
fake_network_wrapper_.SetStringResponse(stream_body, 200);
firebase_.Watch("/", {}, this);
RunLoopUntilIdle();
EXPECT_EQ(0u, put_count_);
EXPECT_EQ(0u, patch_count_);
EXPECT_EQ(0u, cancel_count_);
EXPECT_EQ(0u, auth_revoked_count_);
EXPECT_EQ(1u, malformed_event_count_);
}
TEST_F(FirebaseImplTest, WatchHttpError) {
fake_network_wrapper_.SetStringResponse("", 404);
firebase_.Watch("/", {}, this);
RunLoopUntilIdle();
EXPECT_EQ(0u, put_count_);
EXPECT_EQ(0u, patch_count_);
EXPECT_EQ(0u, cancel_count_);
EXPECT_EQ(0u, auth_revoked_count_);
EXPECT_EQ(0u, malformed_event_count_);
EXPECT_EQ(1u, connection_error_count_);
}
TEST_F(FirebaseImplTest, UnWatch) {
std::string event = std::string(
"event: put\n"
"data: {\"path\":\"/\",\"data\":\"Alice\"}\n"
"\n");
socket::SocketPair socket;
fake_network_wrapper_.SetSocketResponse(std::move(socket.socket1), 200);
firebase_.Watch("/", {}, this);
EXPECT_TRUE(fsl::BlockingCopyFromString(event, socket.socket2));
RunLoopUntilIdle();
EXPECT_EQ(1u, put_count_);
EXPECT_EQ(0u, patch_count_);
EXPECT_EQ(0u, cancel_count_);
EXPECT_EQ(0u, auth_revoked_count_);
EXPECT_EQ(0u, malformed_event_count_);
EXPECT_EQ(0u, connection_error_count_);
EXPECT_TRUE(fsl::BlockingCopyFromString(event, socket.socket2));
RunLoopUntilIdle();
EXPECT_EQ(2u, put_count_);
EXPECT_EQ(0u, patch_count_);
EXPECT_EQ(0u, cancel_count_);
EXPECT_EQ(0u, auth_revoked_count_);
EXPECT_EQ(0u, malformed_event_count_);
EXPECT_EQ(0u, connection_error_count_);
// Unregister the watch client and make sure that we are *not* notified about
// the next event.
firebase_.UnWatch(this);
EXPECT_TRUE(fsl::BlockingCopyFromString(event, socket.socket2));
RunLoopUntilIdle();
EXPECT_EQ(2u, put_count_);
EXPECT_EQ(0u, patch_count_);
EXPECT_EQ(0u, cancel_count_);
EXPECT_EQ(0u, auth_revoked_count_);
EXPECT_EQ(0u, malformed_event_count_);
EXPECT_EQ(0u, connection_error_count_);
}
} // namespace
} // namespace firebase