blob: 5c374e55a9b4971e6051c8d96afb0bb0e5b247f4 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "remoting/signaling/ftl_messaging_client.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "remoting/base/fake_oauth_token_getter.h"
#include "remoting/base/http_status.h"
#include "remoting/base/protobuf_http_client.h"
#include "remoting/base/protobuf_http_test_responder.h"
#include "remoting/base/scoped_protobuf_http_request.h"
#include "remoting/proto/ftl/v1/ftl_messages.pb.h"
#include "remoting/signaling/ftl_message_channel_strategy.h"
#include "remoting/signaling/ftl_services_context.h"
#include "remoting/signaling/registration_manager.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace remoting {
namespace {
using ::testing::_;
using ::testing::Property;
using ::testing::Return;
using ::testing::Truly;
constexpr char kFakeServerEndpoint[] = "test.com";
constexpr char kFakeSenderId[] = "fake_sender@gmail.com";
constexpr char kFakeSenderRegId[] = "fake_sender_reg_id";
constexpr char kFakeReceiverId[] = "fake_receiver@gmail.com";
constexpr char kMessage1Id[] = "msg_1";
constexpr char kMessage2Id[] = "msg_2";
constexpr char kMessage1Text[] = "Message 1";
constexpr char kMessage2Text[] = "Message 2";
MATCHER(IsFakeSenderId, "") {
return arg.id() == kFakeSenderId;
}
ftl::ChromotingMessage CreateXmppMessage(const std::string& message_text) {
ftl::ChromotingMessage crd_message;
crd_message.mutable_xmpp()->set_stanza(message_text);
return crd_message;
}
ftl::InboxMessage CreateInboxMessage(const std::string& message_id,
const std::string& message_text) {
ftl::InboxMessage message;
message.mutable_sender_id()->set_id(kFakeSenderId);
message.mutable_receiver_id()->set_id(kFakeReceiverId);
message.set_sender_registration_id(kFakeSenderRegId);
message.set_message_type(ftl::InboxMessage_MessageType_CHROMOTING_MESSAGE);
message.set_message_id(message_id);
ftl::ChromotingMessage crd_message = CreateXmppMessage(message_text);
std::string serialized_message;
bool succeeded = crd_message.SerializeToString(&serialized_message);
EXPECT_TRUE(succeeded);
message.set_message(serialized_message);
return message;
}
base::OnceCallback<void(const HttpStatus&)> CheckStatusThenQuitRunLoopCallback(
const base::Location& from_here,
HttpStatus::Code expected_status_code,
base::RunLoop* run_loop) {
return base::BindLambdaForTesting([=](const HttpStatus& status) {
ASSERT_EQ(status.error_code(), expected_status_code)
<< "Incorrect status code. Location: " << from_here.ToString();
run_loop->QuitWhenIdle();
});
}
std::string GetChromotingMessageText(const ftl::InboxMessage& message) {
EXPECT_EQ(message.message_type(),
ftl::InboxMessage_MessageType_CHROMOTING_MESSAGE);
ftl::ChromotingMessage chromoting_message;
chromoting_message.ParseFromString(message.message());
return chromoting_message.xmpp().stanza();
}
class MockRegistrationManager : public RegistrationManager {
public:
MockRegistrationManager() = default;
~MockRegistrationManager() override = default;
MOCK_METHOD1(SignInGaia, void(DoneCallback));
MOCK_METHOD0(SignOut, void());
MOCK_CONST_METHOD0(IsSignedIn, bool());
MOCK_CONST_METHOD0(GetRegistrationId, std::string());
MOCK_CONST_METHOD0(GetFtlAuthToken, std::string());
};
decltype(auto) StanzaTextMatches(const std::string& expected_stanza) {
return Truly([=](const ftl::ChromotingMessage& message) {
return expected_stanza == message.xmpp().stanza();
});
}
} // namespace
class FtlMessagingClientTest : public testing::Test {
public:
void SetUp() override;
void TearDown() override;
protected:
ProtobufHttpTestResponder test_responder_;
FakeOAuthTokenGetter token_getter_{OAuthTokenGetter::Status::SUCCESS,
OAuthTokenInfo()};
std::unique_ptr<FtlMessagingClient> messaging_client_;
private:
base::test::TaskEnvironment task_environment_;
MockRegistrationManager mock_registration_manager_;
};
void FtlMessagingClientTest::SetUp() {
EXPECT_CALL(mock_registration_manager_, GetFtlAuthToken())
.WillRepeatedly(Return("fake_auth_token"));
messaging_client_ = std::unique_ptr<FtlMessagingClient>(
new FtlMessagingClient(std::make_unique<ProtobufHttpClient>(
kFakeServerEndpoint, &token_getter_,
test_responder_.GetUrlLoaderFactory()),
&mock_registration_manager_,
/*signaling_tracker=*/nullptr,
std::make_unique<FtlMessageChannelStrategy>()));
}
void FtlMessagingClientTest::TearDown() {
messaging_client_.reset();
}
TEST_F(FtlMessagingClientTest, TestSendMessage_Unauthenticated) {
base::RunLoop run_loop;
messaging_client_->SendMessage(
kFakeReceiverId, kFakeSenderRegId, CreateXmppMessage(kMessage1Text),
CheckStatusThenQuitRunLoopCallback(
FROM_HERE, HttpStatus::Code::UNAUTHENTICATED, &run_loop));
test_responder_.AddErrorToMostRecentRequestUrl(
HttpStatus(HttpStatus::Code::UNAUTHENTICATED, "Unauthenticated"));
run_loop.Run();
}
TEST_F(FtlMessagingClientTest, TestSendMessage_SendOneMessageWithoutRegId) {
base::RunLoop run_loop;
messaging_client_->SendMessage(
kFakeReceiverId, "", CreateXmppMessage(kMessage1Text),
CheckStatusThenQuitRunLoopCallback(FROM_HERE, HttpStatus::Code::OK,
&run_loop));
ftl::InboxSendRequest request;
ASSERT_TRUE(test_responder_.GetMostRecentRequestMessage(&request));
EXPECT_EQ(request.dest_registration_ids_size(), 0);
EXPECT_GE(request.time_to_live(), 0);
EXPECT_EQ(request.dest_id().id(), kFakeReceiverId);
EXPECT_FALSE(request.message().message_id().empty());
EXPECT_EQ(GetChromotingMessageText(request.message()), kMessage1Text);
test_responder_.AddResponseToMostRecentRequestUrl(ftl::InboxSendResponse());
run_loop.Run();
}
TEST_F(FtlMessagingClientTest, TestSendMessage_SendOneMessageWithRegId) {
base::RunLoop run_loop;
messaging_client_->SendMessage(
kFakeReceiverId, kFakeSenderRegId, CreateXmppMessage(kMessage1Text),
CheckStatusThenQuitRunLoopCallback(FROM_HERE, HttpStatus::Code::OK,
&run_loop));
ftl::InboxSendRequest request;
ASSERT_TRUE(test_responder_.GetMostRecentRequestMessage(&request));
EXPECT_EQ(request.dest_registration_ids_size(), 1);
EXPECT_EQ(request.dest_registration_ids(0), kFakeSenderRegId);
EXPECT_GE(request.time_to_live(), 0);
EXPECT_EQ(request.dest_id().id(), kFakeReceiverId);
EXPECT_FALSE(request.message().message_id().empty());
EXPECT_EQ(GetChromotingMessageText(request.message()), kMessage1Text);
test_responder_.AddResponseToMostRecentRequestUrl(ftl::InboxSendResponse());
run_loop.Run();
}
TEST_F(FtlMessagingClientTest, TestStartReceivingMessages_CallbacksForwarded) {
base::RunLoop run_loop;
base::MockCallback<base::OnceClosure> mock_on_ready_closure;
EXPECT_CALL(mock_on_ready_closure, Run()).WillOnce(Return());
messaging_client_->StartReceivingMessages(
mock_on_ready_closure.Get(),
CheckStatusThenQuitRunLoopCallback(FROM_HERE, HttpStatus::Code::UNKNOWN,
&run_loop));
ftl::ReceiveMessagesResponse start_of_batch;
start_of_batch.mutable_start_of_batch();
// Using `UNKNOWN` because it is a non-retriable error.
test_responder_.AddStreamResponseToMostRecentRequestUrl(
{&start_of_batch}, HttpStatus(HttpStatus::Code::UNKNOWN, ""));
run_loop.Run();
}
TEST_F(FtlMessagingClientTest,
TestStreamOpener_StreamsTwoMessagesThenClosedByServer) {
base::RunLoop run_loop;
base::MockCallback<FtlMessagingClient::MessageCallback> mock_on_incoming_msg;
EXPECT_CALL(mock_on_incoming_msg, Run(_, _, _))
.WillOnce([&](const ftl::Id&, const std::string&,
const ftl::ChromotingMessage& message) {
EXPECT_EQ(message.xmpp().stanza(), kMessage1Text);
})
.WillOnce([&](const ftl::Id&, const std::string&,
const ftl::ChromotingMessage& message) {
EXPECT_EQ(message.xmpp().stanza(), kMessage2Text);
run_loop.Quit();
});
base::CallbackListSubscription subscription =
messaging_client_->RegisterMessageCallback(mock_on_incoming_msg.Get());
messaging_client_->StartReceivingMessages(base::DoNothing(),
base::DoNothing());
ftl::ReceiveMessagesResponse start_of_batch;
start_of_batch.mutable_start_of_batch();
ftl::ReceiveMessagesResponse inbox_message_1;
*inbox_message_1.mutable_inbox_message() =
CreateInboxMessage(kMessage1Id, kMessage1Text);
ftl::ReceiveMessagesResponse inbox_message_2;
*inbox_message_2.mutable_inbox_message() =
CreateInboxMessage(kMessage2Id, kMessage2Text);
test_responder_.AddStreamResponseToMostRecentRequestUrl(
{&start_of_batch, &inbox_message_1, &inbox_message_2},
HttpStatus(HttpStatus::Code::OK, ""));
run_loop.Run();
}
TEST_F(FtlMessagingClientTest,
TestOnMessageReceived_MessagePassedToSubscriberAndAcked) {
base::RunLoop run_loop;
base::MockCallback<FtlMessagingClient::MessageCallback> mock_on_incoming_msg;
EXPECT_CALL(mock_on_incoming_msg, Run(IsFakeSenderId(), kFakeSenderRegId,
StanzaTextMatches(kMessage1Text)))
.WillOnce(Return());
base::CallbackListSubscription subscription =
messaging_client_->RegisterMessageCallback(mock_on_incoming_msg.Get());
messaging_client_->StartReceivingMessages(base::DoNothing(),
base::DoNothing());
ftl::ReceiveMessagesResponse start_of_batch;
start_of_batch.mutable_start_of_batch();
ftl::ReceiveMessagesResponse inbox_message;
*inbox_message.mutable_inbox_message() =
CreateInboxMessage(kMessage1Id, kMessage1Text);
test_responder_.AddStreamResponseToMostRecentRequestUrl(
{&start_of_batch, &inbox_message}, HttpStatus(HttpStatus::Code::OK, ""));
EXPECT_CALL(test_responder_.GetMockInterceptor(), Run(_))
.WillRepeatedly([&](const network::ResourceRequest& request) {
ftl::BatchAckMessagesRequest message;
ProtobufHttpTestResponder::ParseRequestMessage(request, &message);
if (message.message_ids_size() < 1) {
// Ignore non-batch messages.
return;
}
EXPECT_EQ(message.message_ids_size(), 1);
EXPECT_EQ(message.message_ids(0), kMessage1Id);
run_loop.Quit();
});
run_loop.Run();
}
TEST_F(FtlMessagingClientTest, ReceivedDuplicatedMessage_AckAndDrop) {
base::RunLoop run_loop;
base::MockCallback<FtlMessagingClient::MessageCallback> mock_on_incoming_msg;
EXPECT_CALL(mock_on_incoming_msg, Run(IsFakeSenderId(), kFakeSenderRegId, _))
.WillOnce([](const ftl::Id&, const std::string&,
const ftl::ChromotingMessage& message) {
EXPECT_EQ(message.xmpp().stanza(), kMessage1Text);
})
.WillOnce([](const ftl::Id&, const std::string&,
const ftl::ChromotingMessage& message) {
EXPECT_EQ(message.xmpp().stanza(), kMessage2Text);
});
int ack_count = 0;
EXPECT_CALL(test_responder_.GetMockInterceptor(), Run(_))
.WillRepeatedly([&](const network::ResourceRequest& request) {
ftl::BatchAckMessagesRequest message;
ProtobufHttpTestResponder::ParseRequestMessage(request, &message);
if (message.message_ids_size() < 1) {
// Ignore non-batch messages.
return;
}
ack_count++;
EXPECT_EQ(message.message_ids_size(), 1);
if (ack_count == 1) {
EXPECT_EQ(message.message_ids(0), kMessage1Id);
} else if (ack_count == 2) {
EXPECT_EQ(message.message_ids(0), kMessage1Id);
} else if (ack_count == 3) {
EXPECT_EQ(message.message_ids(0), kMessage2Id);
run_loop.Quit();
}
});
base::CallbackListSubscription subscription =
messaging_client_->RegisterMessageCallback(mock_on_incoming_msg.Get());
messaging_client_->StartReceivingMessages(base::DoNothing(),
base::DoNothing());
ftl::ReceiveMessagesResponse start_of_batch;
start_of_batch.mutable_start_of_batch();
ftl::ReceiveMessagesResponse inbox_message_1;
*inbox_message_1.mutable_inbox_message() =
CreateInboxMessage(kMessage1Id, kMessage1Text);
ftl::ReceiveMessagesResponse inbox_message_1_resend;
*inbox_message_1_resend.mutable_inbox_message() =
CreateInboxMessage(kMessage1Id, kMessage1Text);
ftl::ReceiveMessagesResponse inbox_message_2;
*inbox_message_2.mutable_inbox_message() =
CreateInboxMessage(kMessage2Id, kMessage2Text);
test_responder_.AddStreamResponseToMostRecentRequestUrl(
{&start_of_batch, &inbox_message_1, &inbox_message_1_resend,
&inbox_message_2},
HttpStatus(HttpStatus::Code::OK, ""));
run_loop.Run();
}
} // namespace remoting