blob: a5dc829e3cbb16a67b86271300973368c5fb67b6 [file] [log] [blame]
// Copyright 2019 The Chromium 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 "remoting/signaling/ftl_messaging_client.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/check.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool.h"
#include "base/test/bind_test_util.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "remoting/base/grpc_support/grpc_async_executor.h"
#include "remoting/base/grpc_support/scoped_grpc_server_stream.h"
#include "remoting/base/grpc_test_support/grpc_async_test_server.h"
#include "remoting/base/grpc_test_support/grpc_test_util.h"
#include "remoting/proto/ftl/v1/ftl_services.grpc.pb.h"
#include "remoting/signaling/ftl_grpc_context.h"
#include "remoting/signaling/message_reception_channel.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::Invoke;
using ::testing::Property;
using ::testing::Return;
using ::testing::Truly;
using PullMessagesResponder =
test::GrpcServerResponder<ftl::PullMessagesResponse>;
using AckMessagesResponder =
test::GrpcServerResponder<ftl::AckMessagesResponse>;
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;
}
FtlMessagingClient::MessageCallback CreateNotReachedMessageCallback() {
return base::BindRepeating(
[](const ftl::Id& sender_id, const std::string& sender_registration_id,
const ftl::ChromotingMessage& message) { NOTREACHED(); });
}
std::string GetChromotingMessageText(const ftl::InboxMessage& message) {
EXPECT_EQ(ftl::InboxMessage_MessageType_CHROMOTING_MESSAGE,
message.message_type());
ftl::ChromotingMessage chromoting_message;
chromoting_message.ParseFromString(message.message());
return chromoting_message.xmpp().stanza();
}
class MockMessageReceptionChannel : public MessageReceptionChannel {
public:
MockMessageReceptionChannel() = default;
~MockMessageReceptionChannel() override = default;
// MessageReceptionChannel implementations.
void Initialize(const StreamOpener& stream_opener,
const MessageCallback& on_incoming_msg) override {
stream_opener_ = stream_opener;
on_incoming_msg_ = on_incoming_msg;
}
MOCK_METHOD2(StartReceivingMessages, void(base::OnceClosure, DoneCallback));
MOCK_METHOD0(StopReceivingMessages, void());
MOCK_CONST_METHOD0(IsReceivingMessages, bool());
StreamOpener* stream_opener() { return &stream_opener_; }
MessageCallback* on_incoming_msg() { return &on_incoming_msg_; }
private:
StreamOpener stream_opener_;
MessageCallback on_incoming_msg_;
DISALLOW_COPY_AND_ASSIGN(MockMessageReceptionChannel);
};
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:
using Messaging =
google::internal::communications::instantmessaging::v1::Messaging;
// Calls are scheduled sequentially and handled on the server thread.
void ServerWaitAndRespondToPullMessagesRequest(
const ftl::PullMessagesResponse& response,
const grpc::Status& status);
void ServerWaitAndRespondToInboxSendRequest(
base::OnceCallback<grpc::Status(const ftl::InboxSendRequest&)> handler,
base::OnceClosure on_done);
void ServerWaitAndRespondToAckMessagesRequest(
base::OnceCallback<grpc::Status(const ftl::AckMessagesRequest&)> handler,
base::OnceClosure on_done);
std::unique_ptr<test::GrpcAsyncTestServer> server_;
scoped_refptr<base::SequencedTaskRunner> server_task_runner_;
std::unique_ptr<FtlMessagingClient> messaging_client_;
MockMessageReceptionChannel* mock_message_reception_channel_;
private:
base::test::TaskEnvironment task_environment_;
MockRegistrationManager mock_registration_manager_;
};
void FtlMessagingClientTest::SetUp() {
server_task_runner_ =
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()});
server_ = std::make_unique<test::GrpcAsyncTestServer>(
std::make_unique<Messaging::AsyncService>());
FtlGrpcContext::SetChannelForTesting(server_->CreateInProcessChannel());
EXPECT_CALL(mock_registration_manager_, GetFtlAuthToken())
.WillRepeatedly(Return("fake_auth_token"));
auto channel = std::make_unique<MockMessageReceptionChannel>();
mock_message_reception_channel_ = channel.get();
messaging_client_ = std::unique_ptr<FtlMessagingClient>(
new FtlMessagingClient(std::make_unique<GrpcAsyncExecutor>(),
&mock_registration_manager_, std::move(channel)));
}
void FtlMessagingClientTest::TearDown() {
messaging_client_.reset();
FtlGrpcContext::SetChannelForTesting(nullptr);
server_task_runner_->DeleteSoon(FROM_HERE, std::move(server_));
}
void FtlMessagingClientTest::ServerWaitAndRespondToPullMessagesRequest(
const ftl::PullMessagesResponse& response,
const grpc::Status& status) {
server_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
[](const ftl::PullMessagesResponse& response,
const grpc::Status& status, test::GrpcAsyncTestServer* server) {
ftl::PullMessagesRequest request;
auto responder = server->HandleRequest(
&Messaging::AsyncService::RequestPullMessages, &request);
responder->Respond(response, status);
},
response, status, server_.get()));
}
void FtlMessagingClientTest::ServerWaitAndRespondToInboxSendRequest(
base::OnceCallback<grpc::Status(const ftl::InboxSendRequest&)> handler,
base::OnceClosure on_done) {
server_task_runner_->PostTaskAndReply(
FROM_HERE,
base::BindOnce(
[](base::OnceCallback<grpc::Status(const ftl::InboxSendRequest&)>
handler,
test::GrpcAsyncTestServer* server) {
ftl::InboxSendRequest request;
auto responder = server->HandleRequest(
&Messaging::AsyncService::RequestSendMessage, &request);
grpc::Status status = std::move(handler).Run(request);
responder->Respond(ftl::InboxSendResponse(), status);
},
std::move(handler), server_.get()),
std::move(on_done));
}
void FtlMessagingClientTest::ServerWaitAndRespondToAckMessagesRequest(
base::OnceCallback<grpc::Status(const ftl::AckMessagesRequest&)> handler,
base::OnceClosure on_done) {
server_task_runner_->PostTaskAndReply(
FROM_HERE,
base::BindOnce(
[](base::OnceCallback<grpc::Status(const ftl::AckMessagesRequest&)>
handler,
test::GrpcAsyncTestServer* server) {
ftl::AckMessagesRequest request;
auto responder = server->HandleRequest(
&Messaging::AsyncService::RequestAckMessages, &request);
grpc::Status status = std::move(handler).Run(request);
responder->Respond(ftl::AckMessagesResponse(), status);
},
std::move(handler), server_.get()),
std::move(on_done));
}
TEST_F(FtlMessagingClientTest, TestPullMessages_ReturnsNoMessage) {
base::RunLoop run_loop;
auto subscription = messaging_client_->RegisterMessageCallback(
CreateNotReachedMessageCallback());
messaging_client_->PullMessages(test::CheckStatusThenQuitRunLoopCallback(
FROM_HERE, grpc::StatusCode::OK, &run_loop));
ServerWaitAndRespondToPullMessagesRequest(ftl::PullMessagesResponse(),
grpc::Status::OK);
run_loop.Run();
}
TEST_F(FtlMessagingClientTest, TestPullMessages_Unauthenticated) {
base::RunLoop run_loop;
auto subscription = messaging_client_->RegisterMessageCallback(
CreateNotReachedMessageCallback());
messaging_client_->PullMessages(test::CheckStatusThenQuitRunLoopCallback(
FROM_HERE, grpc::StatusCode::UNAUTHENTICATED, &run_loop));
ServerWaitAndRespondToPullMessagesRequest(
ftl::PullMessagesResponse(),
grpc::Status(grpc::StatusCode::UNAUTHENTICATED, "Unauthenticated"));
run_loop.Run();
}
TEST_F(FtlMessagingClientTest, TestPullMessages_IgnoresMessageWithoutRegId) {
base::RunLoop run_loop;
auto subscription = messaging_client_->RegisterMessageCallback(
CreateNotReachedMessageCallback());
messaging_client_->PullMessages(test::CheckStatusThenQuitRunLoopCallback(
FROM_HERE, grpc::StatusCode::OK, &run_loop));
ftl::PullMessagesResponse response;
ftl::InboxMessage* message = response.add_messages();
*message = CreateInboxMessage(kMessage1Id, kMessage1Text);
message->clear_sender_registration_id();
ServerWaitAndRespondToPullMessagesRequest(response, grpc::Status::OK);
ServerWaitAndRespondToAckMessagesRequest(
base::BindLambdaForTesting([&](const ftl::AckMessagesRequest& request) {
EXPECT_EQ(1, request.messages_size());
EXPECT_EQ(kFakeReceiverId, request.messages(0).receiver_id().id());
EXPECT_EQ(kMessage1Id, request.messages(0).message_id());
return grpc::Status::OK;
}),
base::DoNothing());
run_loop.Run();
}
TEST_F(FtlMessagingClientTest, TestPullMessages_IgnoresUnknownMessageType) {
base::RunLoop run_loop;
auto subscription = messaging_client_->RegisterMessageCallback(
CreateNotReachedMessageCallback());
messaging_client_->PullMessages(test::CheckStatusThenQuitRunLoopCallback(
FROM_HERE, grpc::StatusCode::OK, &run_loop));
ftl::PullMessagesResponse response;
ftl::InboxMessage* message = response.add_messages();
*message = CreateInboxMessage(kMessage1Id, kMessage1Text);
message->set_message_type(ftl::InboxMessage_MessageType_UNKNOWN);
ServerWaitAndRespondToPullMessagesRequest(response, grpc::Status::OK);
ServerWaitAndRespondToAckMessagesRequest(
base::BindLambdaForTesting([&](const ftl::AckMessagesRequest& request) {
EXPECT_EQ(1, request.messages_size());
EXPECT_EQ(kFakeReceiverId, request.messages(0).receiver_id().id());
EXPECT_EQ(kMessage1Id, request.messages(0).message_id());
return grpc::Status::OK;
}),
base::DoNothing());
run_loop.Run();
}
TEST_F(FtlMessagingClientTest, TestPullMessages_ReturnsAndAcksTwoMessages) {
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());
EXPECT_CALL(mock_on_incoming_msg, Run(IsFakeSenderId(), kFakeSenderRegId,
StanzaTextMatches(kMessage2Text)))
.WillOnce(Return());
auto subscription =
messaging_client_->RegisterMessageCallback(mock_on_incoming_msg.Get());
messaging_client_->PullMessages(test::CheckStatusThenQuitRunLoopCallback(
FROM_HERE, grpc::StatusCode::OK, &run_loop));
ftl::PullMessagesResponse pull_messages_response;
ftl::InboxMessage* message = pull_messages_response.add_messages();
*message = CreateInboxMessage(kMessage1Id, kMessage1Text);
message = pull_messages_response.add_messages();
*message = CreateInboxMessage(kMessage2Id, kMessage2Text);
ServerWaitAndRespondToPullMessagesRequest(pull_messages_response,
grpc::Status::OK);
ServerWaitAndRespondToAckMessagesRequest(
base::BindLambdaForTesting([&](const ftl::AckMessagesRequest& request) {
EXPECT_EQ(2, request.messages_size());
EXPECT_EQ(kFakeReceiverId, request.messages(0).receiver_id().id());
EXPECT_EQ(kFakeReceiverId, request.messages(1).receiver_id().id());
EXPECT_EQ(kMessage1Id, request.messages(0).message_id());
EXPECT_EQ(kMessage2Id, request.messages(1).message_id());
return grpc::Status::OK;
}),
base::DoNothing());
run_loop.Run();
}
TEST_F(FtlMessagingClientTest, TestSendMessage_Unauthenticated) {
base::RunLoop run_loop;
messaging_client_->SendMessage(
kFakeReceiverId, kFakeSenderRegId, CreateXmppMessage(kMessage1Text),
test::CheckStatusThenQuitRunLoopCallback(
FROM_HERE, grpc::StatusCode::UNAUTHENTICATED, &run_loop));
ServerWaitAndRespondToInboxSendRequest(
base::BindOnce([](const ftl::InboxSendRequest&) {
return grpc::Status(grpc::StatusCode::UNAUTHENTICATED,
"Unauthenticated");
}),
base::DoNothing());
run_loop.Run();
}
TEST_F(FtlMessagingClientTest, TestSendMessage_SendOneMessageWithoutRegId) {
base::RunLoop run_loop;
messaging_client_->SendMessage(
kFakeReceiverId, "", CreateXmppMessage(kMessage1Text),
test::CheckStatusThenQuitRunLoopCallback(FROM_HERE, grpc::StatusCode::OK,
&run_loop));
ServerWaitAndRespondToInboxSendRequest(
base::BindOnce([](const ftl::InboxSendRequest& request) {
EXPECT_EQ(0, request.dest_registration_ids_size());
EXPECT_LT(0, request.time_to_live());
EXPECT_EQ(kFakeReceiverId, request.dest_id().id());
EXPECT_FALSE(request.message().message_id().empty());
EXPECT_EQ(kMessage1Text, GetChromotingMessageText(request.message()));
return grpc::Status::OK;
}),
base::DoNothing());
run_loop.Run();
}
TEST_F(FtlMessagingClientTest, TestSendMessage_SendOneMessageWithRegId) {
base::RunLoop run_loop;
messaging_client_->SendMessage(
kFakeReceiverId, kFakeSenderRegId, CreateXmppMessage(kMessage1Text),
test::CheckStatusThenQuitRunLoopCallback(FROM_HERE, grpc::StatusCode::OK,
&run_loop));
ServerWaitAndRespondToInboxSendRequest(
base::BindOnce([](const ftl::InboxSendRequest& request) {
EXPECT_EQ(1, request.dest_registration_ids_size());
EXPECT_EQ(kFakeSenderRegId, request.dest_registration_ids(0));
EXPECT_LT(0, request.time_to_live());
EXPECT_EQ(kFakeReceiverId, request.dest_id().id());
EXPECT_FALSE(request.message().message_id().empty());
EXPECT_EQ(kMessage1Text, GetChromotingMessageText(request.message()));
return grpc::Status::OK;
}),
base::DoNothing());
run_loop.Run();
}
TEST_F(FtlMessagingClientTest, TestStartReceivingMessages_CallbacksForwarded) {
base::RunLoop run_loop;
EXPECT_CALL(*mock_message_reception_channel_, StartReceivingMessages(_, _))
.WillOnce(Invoke([&](base::OnceClosure on_ready,
FtlMessagingClient::DoneCallback on_closed) {
std::move(on_ready).Run();
std::move(on_closed).Run(
grpc::Status(grpc::StatusCode::UNAUTHENTICATED, ""));
}));
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(),
base::BindLambdaForTesting([&](const grpc::Status& status) {
ASSERT_EQ(grpc::StatusCode::UNAUTHENTICATED, status.error_code());
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(FtlMessagingClientTest, TestStopReceivingMessages_CallForwarded) {
EXPECT_CALL(*mock_message_reception_channel_, StopReceivingMessages())
.WillOnce(Return());
messaging_client_->StopReceivingMessages();
}
TEST_F(FtlMessagingClientTest,
TestStreamOpener_StreamsTwoMessagesThenCloseByClient) {
base::RunLoop run_loop;
std::unique_ptr<ScopedGrpcServerStream> scoped_stream;
ftl::ReceiveMessagesResponse response_1;
response_1.mutable_inbox_message()->set_message_id(kMessage1Id);
ftl::ReceiveMessagesResponse response_2;
response_2.mutable_pong();
base::MockCallback<base::RepeatingClosure> exit_runloop_when_called_twice;
EXPECT_CALL(exit_runloop_when_called_twice, Run())
.WillOnce(Return())
.WillOnce([&]() {
// Whoever calls last quits the run loop.
run_loop.QuitWhenIdle();
});
server_task_runner_->PostTaskAndReply(
FROM_HERE, base::BindLambdaForTesting([&]() {
ftl::ReceiveMessagesRequest request;
// This blocks the server thread until the client opens the stream.
auto responder = server_->HandleStreamRequest(
&Messaging::AsyncService::RequestReceiveMessages, &request);
responder->SendMessage(response_1);
ASSERT_TRUE(responder->WaitForSendMessageResult());
responder->SendMessage(response_2);
ASSERT_TRUE(responder->WaitForSendMessageResult());
}),
exit_runloop_when_called_twice.Get());
base::MockCallback<
base::RepeatingCallback<void(const ftl::ReceiveMessagesResponse&)>>
mock_on_incoming_msg;
EXPECT_CALL(mock_on_incoming_msg, Run(_))
.WillOnce([&](const ftl::ReceiveMessagesResponse& response) {
ASSERT_EQ(kMessage1Id, response.inbox_message().message_id());
})
.WillOnce([&](const ftl::ReceiveMessagesResponse& response) {
ASSERT_TRUE(response.has_pong());
DCHECK(scoped_stream);
scoped_stream.reset();
exit_runloop_when_called_twice.Get().Run();
});
base::MockCallback<base::OnceClosure> on_channel_ready;
EXPECT_CALL(on_channel_ready, Run()).Times(1);
scoped_stream = mock_message_reception_channel_->stream_opener()->Run(
on_channel_ready.Get(), mock_on_incoming_msg.Get(),
base::BindOnce([](const grpc::Status&) { NOTREACHED(); }));
run_loop.Run();
}
TEST_F(FtlMessagingClientTest,
TestOnMessageReceived_MessagePassedToSubscriberAndAcked) {
base::RunLoop run_loop;
ftl::InboxMessage message = CreateInboxMessage(kMessage1Id, kMessage1Text);
mock_message_reception_channel_->on_incoming_msg()->Run(message);
ServerWaitAndRespondToAckMessagesRequest(
base::BindLambdaForTesting([&](const ftl::AckMessagesRequest& request) {
EXPECT_EQ(1, request.messages_size());
EXPECT_EQ(kFakeReceiverId, request.messages(0).receiver_id().id());
EXPECT_EQ(kMessage1Id, request.messages(0).message_id());
return grpc::Status::OK;
}),
run_loop.QuitWhenIdleClosure());
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,
StanzaTextMatches(kMessage1Text)))
.WillOnce(Return());
auto subscription =
messaging_client_->RegisterMessageCallback(mock_on_incoming_msg.Get());
messaging_client_->PullMessages(test::CheckStatusThenQuitRunLoopCallback(
FROM_HERE, grpc::StatusCode::OK, &run_loop));
ftl::PullMessagesResponse pull_messages_response;
*pull_messages_response.add_messages() =
CreateInboxMessage(kMessage1Id, kMessage1Text);
*pull_messages_response.add_messages() =
CreateInboxMessage(kMessage1Id, kMessage1Text);
ServerWaitAndRespondToPullMessagesRequest(pull_messages_response,
grpc::Status::OK);
ServerWaitAndRespondToAckMessagesRequest(
base::BindLambdaForTesting([&](const ftl::AckMessagesRequest& request) {
EXPECT_EQ(2, request.messages_size());
EXPECT_EQ(kFakeReceiverId, request.messages(0).receiver_id().id());
EXPECT_EQ(kFakeReceiverId, request.messages(1).receiver_id().id());
EXPECT_EQ(kMessage1Id, request.messages(0).message_id());
EXPECT_EQ(kMessage1Id, request.messages(1).message_id());
return grpc::Status::OK;
}),
base::DoNothing());
run_loop.Run();
}
} // namespace remoting