// Copyright 2018 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/ios/facade/host_list_service.h"
#import <Foundation/Foundation.h>
#import "remoting/ios/facade/remoting_authentication.h"
#import "remoting/ios/facade/remoting_service.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#include "base/functional/bind.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "net/http/http_status_code.h"
#include "remoting/base/directory_service_client.h"
#include "remoting/base/fake_oauth_token_getter.h"
#include "remoting/base/protobuf_http_status.h"
#include "remoting/base/protobuf_http_test_responder.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gtest_mac.h"
#include "testing/platform_test.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/display/screen.h"
#define EXPECT_HOST_LIST_STATE(expected) \
EXPECT_EQ(expected, host_list_service_.state())
#define EXPECT_NO_FETCH_FAILURE() \
EXPECT_TRUE(host_list_service_.last_fetch_failure() == nullptr)
namespace remoting {
namespace {
apis::v1::HostInfo CreateFakeHost(const std::string& host_id) {
apis::v1::HostInfo fake_host;
fake_host.set_host_id(host_id);
return fake_host;
}
apis::v1::GetHostListResponse CreateFakeHostListResponse(
const std::string& host_id = "fake_host") {
apis::v1::GetHostListResponse response;
*response.add_hosts() = CreateFakeHost(host_id);
return response;
}
} // namespace
class HostListServiceTest : public PlatformTest {
public:
HostListServiceTest();
~HostListServiceTest() override;
protected:
// Respond to a GetHostList request and block until the host list state is
// changed.
void BlockAndRespondGetHostList(
const ProtobufHttpStatus& status,
const apis::v1::GetHostListResponse& response);
void NotifyUserUpdate(bool is_signed_in);
base::test::TaskEnvironment task_environment_;
ProtobufHttpTestResponder test_responder_;
FakeOAuthTokenGetter oauth_token_getter_{OAuthTokenGetter::Status::SUCCESS,
"", ""};
HostListService host_list_service_;
int on_fetch_failed_call_count_ = 0;
int on_host_list_state_changed_call_count_ = 0;
id remoting_authentication_mock_;
id remoting_service_mock_;
private:
base::CallbackListSubscription host_list_state_subscription_;
base::CallbackListSubscription fetch_failure_subscription_;
display::ScopedNativeScreen screen_;
};
HostListServiceTest::HostListServiceTest()
: host_list_service_(base::SequenceBound<DirectoryServiceClient>(
task_environment_.GetMainThreadTaskRunner(),
&oauth_token_getter_,
test_responder_.GetUrlLoaderFactory())) {
static const char use_cocoa_locale[] = "";
l10n_util::OverrideLocaleWithCocoaLocale();
ui::ResourceBundle::InitSharedInstanceWithLocale(
use_cocoa_locale, NULL, ui::ResourceBundle::DO_NOT_LOAD_COMMON_RESOURCES);
on_fetch_failed_call_count_ = 0;
on_host_list_state_changed_call_count_ = 0;
remoting_authentication_mock_ =
OCMProtocolMock(@protocol(RemotingAuthentication));
remoting_service_mock_ = OCMClassMock([RemotingService class]);
OCMStub([remoting_service_mock_ instance]).andReturn(remoting_service_mock_);
OCMStub([remoting_service_mock_ authentication])
.andReturn(remoting_authentication_mock_);
host_list_state_subscription_ =
host_list_service_.RegisterHostListStateCallback(base::BindRepeating(
[](HostListServiceTest* that) {
that->on_host_list_state_changed_call_count_++;
},
base::Unretained(this)));
fetch_failure_subscription_ =
host_list_service_.RegisterFetchFailureCallback(base::BindRepeating(
[](HostListServiceTest* that) {
that->on_fetch_failed_call_count_++;
},
base::Unretained(this)));
}
HostListServiceTest::~HostListServiceTest() {
ui::ResourceBundle::CleanupSharedInstance();
}
void HostListServiceTest::BlockAndRespondGetHostList(
const ProtobufHttpStatus& status,
const apis::v1::GetHostListResponse& response) {
base::RunLoop run_loop;
auto subscription =
host_list_service_.RegisterHostListStateCallback(run_loop.QuitClosure());
if (status.ok()) {
test_responder_.AddResponseToMostRecentRequestUrl(response);
} else {
test_responder_.AddErrorToMostRecentRequestUrl(status);
}
run_loop.Run();
}
void HostListServiceTest::NotifyUserUpdate(bool is_signed_in) {
NSDictionary* user_info =
is_signed_in ? @{kUserInfo : [[UserInfo alloc] init]} : @{};
[NSNotificationCenter.defaultCenter postNotificationName:kUserDidUpdate
object:nil
userInfo:user_info];
}
TEST_F(HostListServiceTest, SuccessfullyFetchedOneHost) {
EXPECT_HOST_LIST_STATE(HostListService::State::NOT_FETCHED);
EXPECT_NO_FETCH_FAILURE();
host_list_service_.RequestFetch();
EXPECT_HOST_LIST_STATE(HostListService::State::FETCHING);
EXPECT_NO_FETCH_FAILURE();
BlockAndRespondGetHostList(ProtobufHttpStatus::OK(),
CreateFakeHostListResponse("fake_host_1"));
EXPECT_HOST_LIST_STATE(HostListService::State::FETCHED);
EXPECT_NO_FETCH_FAILURE();
EXPECT_EQ(1u, host_list_service_.hosts().size());
EXPECT_EQ("fake_host_1", host_list_service_.hosts()[0].host_id());
EXPECT_EQ(2, on_host_list_state_changed_call_count_);
EXPECT_EQ(0, on_fetch_failed_call_count_);
}
TEST_F(HostListServiceTest, SuccessfullyFetchedTwoHosts_HostListSorted) {
EXPECT_HOST_LIST_STATE(HostListService::State::NOT_FETCHED);
EXPECT_NO_FETCH_FAILURE();
host_list_service_.RequestFetch();
EXPECT_HOST_LIST_STATE(HostListService::State::FETCHING);
EXPECT_NO_FETCH_FAILURE();
apis::v1::GetHostListResponse response;
response.add_hosts()->set_host_name("Host 2");
response.add_hosts()->set_host_name("Host 1");
BlockAndRespondGetHostList(ProtobufHttpStatus::OK(), response);
EXPECT_HOST_LIST_STATE(HostListService::State::FETCHED);
EXPECT_NO_FETCH_FAILURE();
EXPECT_EQ(2u, host_list_service_.hosts().size());
EXPECT_EQ("Host 1", host_list_service_.hosts()[0].host_name());
EXPECT_EQ("Host 2", host_list_service_.hosts()[1].host_name());
EXPECT_EQ(2, on_host_list_state_changed_call_count_);
EXPECT_EQ(0, on_fetch_failed_call_count_);
}
TEST_F(HostListServiceTest, FetchHostListRequestFailed) {
EXPECT_HOST_LIST_STATE(HostListService::State::NOT_FETCHED);
EXPECT_NO_FETCH_FAILURE();
host_list_service_.RequestFetch();
EXPECT_HOST_LIST_STATE(HostListService::State::FETCHING);
EXPECT_NO_FETCH_FAILURE();
BlockAndRespondGetHostList(
ProtobufHttpStatus(ProtobufHttpStatus::Code::INTERNAL, "Internal error"),
{});
EXPECT_HOST_LIST_STATE(HostListService::State::NOT_FETCHED);
EXPECT_EQ(HostListService::FetchFailureReason::UNKNOWN_ERROR,
host_list_service_.last_fetch_failure()->reason);
EXPECT_EQ("Internal error",
host_list_service_.last_fetch_failure()->localized_description);
EXPECT_TRUE(host_list_service_.hosts().empty());
EXPECT_EQ(2, on_host_list_state_changed_call_count_);
EXPECT_EQ(1, on_fetch_failed_call_count_);
}
TEST_F(HostListServiceTest, FetchHostListRequestUnauthenticated_signOut) {
EXPECT_HOST_LIST_STATE(HostListService::State::NOT_FETCHED);
EXPECT_NO_FETCH_FAILURE();
host_list_service_.RequestFetch();
EXPECT_HOST_LIST_STATE(HostListService::State::FETCHING);
EXPECT_NO_FETCH_FAILURE();
OCMExpect([remoting_authentication_mock_ logout]);
BlockAndRespondGetHostList(
ProtobufHttpStatus(ProtobufHttpStatus::Code::UNAUTHENTICATED,
"Unauthenticated"),
{});
EXPECT_HOST_LIST_STATE(HostListService::State::NOT_FETCHED);
[remoting_authentication_mock_ verifyAtLocation:nil];
EXPECT_EQ(2, on_host_list_state_changed_call_count_);
EXPECT_EQ(1, on_fetch_failed_call_count_);
}
TEST_F(HostListServiceTest, RequestFetchWhileFetching_ignoreSecondRequest) {
EXPECT_HOST_LIST_STATE(HostListService::State::NOT_FETCHED);
host_list_service_.RequestFetch();
EXPECT_HOST_LIST_STATE(HostListService::State::FETCHING);
host_list_service_.RequestFetch();
EXPECT_HOST_LIST_STATE(HostListService::State::FETCHING);
EXPECT_EQ(1, on_host_list_state_changed_call_count_);
EXPECT_EQ(0, on_fetch_failed_call_count_);
}
TEST_F(HostListServiceTest, UserLogOut_cancelFetch) {
EXPECT_HOST_LIST_STATE(HostListService::State::NOT_FETCHED);
host_list_service_.RequestFetch();
EXPECT_HOST_LIST_STATE(HostListService::State::FETCHING);
NotifyUserUpdate(false);
EXPECT_HOST_LIST_STATE(HostListService::State::NOT_FETCHED);
EXPECT_EQ(2, on_host_list_state_changed_call_count_);
EXPECT_EQ(0, on_fetch_failed_call_count_);
}
TEST_F(HostListServiceTest, UserSwitchAccount_cancelThenRequestNewFetch) {
EXPECT_HOST_LIST_STATE(HostListService::State::NOT_FETCHED);
host_list_service_.RequestFetch();
EXPECT_HOST_LIST_STATE(HostListService::State::FETCHING);
ASSERT_EQ(1, test_responder_.GetNumPending());
NotifyUserUpdate(true);
// The HostListService should have cancelled the previous request and created
// a new request.
ASSERT_EQ(2, test_responder_.GetNumPending());
ASSERT_FALSE(test_responder_.GetPendingRequest(0).client.is_connected());
ASSERT_TRUE(test_responder_.GetPendingRequest(1).client.is_connected());
EXPECT_HOST_LIST_STATE(HostListService::State::FETCHING);
BlockAndRespondGetHostList(ProtobufHttpStatus::OK(),
CreateFakeHostListResponse("fake_host_id"));
EXPECT_HOST_LIST_STATE(HostListService::State::FETCHED);
EXPECT_EQ(1u, host_list_service_.hosts().size());
EXPECT_EQ("fake_host_id", host_list_service_.hosts()[0].host_id());
// Note that there is an extra FETCHING->NOT_FETCH change during
// NotifyUserUpdate(true).
EXPECT_EQ(4, on_host_list_state_changed_call_count_);
EXPECT_EQ(0, on_fetch_failed_call_count_);
}
} // namespace remoting