blob: d27c886a6d191a2d8291471d4f4b14cfb4961623 [file] [log] [blame]
// Copyright 2018 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 "chromeos/services/assistant/service.h"
#include <utility>
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/time/tick_clock.h"
#include "base/time/time.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/fake_power_manager_client.h"
#include "chromeos/services/assistant/fake_assistant_manager_service_impl.h"
#include "chromeos/services/assistant/public/mojom/constants.mojom.h"
#include "services/identity/public/mojom/identity_manager.mojom.h"
#include "services/service_manager/public/cpp/service_context.h"
#include "services/service_manager/public/cpp/service_test.h"
#include "services/service_manager/public/mojom/service_factory.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace assistant {
namespace {
constexpr base::TimeDelta kDefaultTokenExpirationDelay =
base::TimeDelta::FromMilliseconds(1000);
}
class FakeIdentityManager : identity::mojom::IdentityManager {
public:
FakeIdentityManager()
: binding_(this),
access_token_expriation_delay_(kDefaultTokenExpirationDelay) {}
identity::mojom::IdentityManagerPtr CreateInterfacePtrAndBind() {
identity::mojom::IdentityManagerPtr ptr;
binding_.Bind(mojo::MakeRequest(&ptr));
return ptr;
}
void SetAccessTokenExpirationDelay(base::TimeDelta delay) {
access_token_expriation_delay_ = delay;
}
void SetShouldFail(bool fail) { should_fail_ = fail; }
int get_access_token_count() const { return get_access_token_count_; }
private:
// identity::mojom::IdentityManager:
void GetPrimaryAccountInfo(GetPrimaryAccountInfoCallback callback) override {
AccountInfo account_info;
account_info.account_id = "account_id";
account_info.gaia = "fakegaiaid";
account_info.email = "fake@email";
account_info.full_name = "full name";
account_info.given_name = "given name";
account_info.hosted_domain = "hosted_domain";
account_info.locale = "en";
account_info.picture_url = "http://fakepicture";
identity::AccountState account_state;
account_state.has_refresh_token = true;
account_state.is_primary_account = true;
std::move(callback).Run(account_info, account_state);
}
void GetPrimaryAccountWhenAvailable(
GetPrimaryAccountWhenAvailableCallback callback) override {}
void GetAccountInfoFromGaiaId(
const std::string& gaia_id,
GetAccountInfoFromGaiaIdCallback callback) override {}
void GetAccounts(GetAccountsCallback callback) override {}
void GetAccessToken(const std::string& account_id,
const ::identity::ScopeSet& scopes,
const std::string& consumer_id,
GetAccessTokenCallback callback) override {
GoogleServiceAuthError auth_error(
should_fail_ ? GoogleServiceAuthError::CONNECTION_FAILED
: GoogleServiceAuthError::NONE);
std::move(callback).Run(
should_fail_ ? base::nullopt
: base::Optional<std::string>("fake access token"),
base::Time::Now() + access_token_expriation_delay_, auth_error);
++get_access_token_count_;
}
mojo::Binding<identity::mojom::IdentityManager> binding_;
base::TimeDelta access_token_expriation_delay_;
int get_access_token_count_ = 0;
bool should_fail_ = false;
DISALLOW_COPY_AND_ASSIGN(FakeIdentityManager);
};
class FakeAssistantClient : mojom::Client {
public:
FakeAssistantClient() : binding_(this) {}
mojom::ClientPtr CreateInterfacePtrAndBind() {
mojom::ClientPtr ptr;
binding_.Bind(mojo::MakeRequest(&ptr));
return ptr;
}
private:
// mojom::Client:
void OnAssistantStatusChanged(bool running) override {}
void RequestAssistantStructure(
RequestAssistantStructureCallback callback) override {}
mojo::Binding<mojom::Client> binding_;
DISALLOW_COPY_AND_ASSIGN(FakeAssistantClient);
};
class FakeAudioInput : mojom::AudioInput {
public:
FakeAudioInput() : binding_(this) {}
mojom::AudioInputPtr CreateInterfacePtrAndBind() {
mojom::AudioInputPtr ptr;
binding_.Bind(mojo::MakeRequest(&ptr));
return ptr;
}
private:
// mojom::AudioInput:
void AddObserver(mojom::AudioInputObserverPtr observer) override {}
mojo::Binding<mojom::AudioInput> binding_;
DISALLOW_COPY_AND_ASSIGN(FakeAudioInput);
};
class ServiceTestClient : public service_manager::test::ServiceTestClient,
public service_manager::mojom::ServiceFactory {
public:
ServiceTestClient(service_manager::test::ServiceTest* test,
FakeIdentityManager* fake_identity_manager,
FakeAssistantManagerServiceImpl* fake_assistant_manager_ptr,
base::TestMockTimeTaskRunner* mock_task_runner)
: service_manager::test::ServiceTestClient(test),
fake_identity_manager_(fake_identity_manager),
fake_assistant_manager_ptr_(fake_assistant_manager_ptr),
mock_task_runner_(mock_task_runner) {
registry_.AddInterface<service_manager::mojom::ServiceFactory>(
base::BindRepeating(&ServiceTestClient::Create,
base::Unretained(this)));
}
protected:
void OnBindInterface(const service_manager::BindSourceInfo& source_info,
const std::string& interface_name,
mojo::ScopedMessagePipeHandle interface_pipe) override {
registry_.BindInterface(interface_name, std::move(interface_pipe));
}
void CreateService(
service_manager::mojom::ServiceRequest request,
const std::string& name,
service_manager::mojom::PIDReceiverPtr pid_receiver) override {
if (name == mojom::kServiceName) {
auto service = std::make_unique<chromeos::assistant::Service>();
auto mock_timer = std::make_unique<base::OneShotTimer>(
mock_task_runner_->GetMockTickClock());
mock_timer->SetTaskRunner(mock_task_runner_);
service->SetTimerForTesting(std::move(mock_timer));
service->SetIdentityManagerForTesting(
fake_identity_manager_->CreateInterfacePtrAndBind());
service->SetAssistantManagerForTesting(
base::WrapUnique(fake_assistant_manager_ptr_));
service_context_ = std::make_unique<service_manager::ServiceContext>(
std::move(service), std::move(request));
}
}
void Create(service_manager::mojom::ServiceFactoryRequest request) {
service_factory_bindings_.AddBinding(this, std::move(request));
}
private:
service_manager::BinderRegistry registry_;
mojo::BindingSet<service_manager::mojom::ServiceFactory>
service_factory_bindings_;
FakeIdentityManager* fake_identity_manager_;
FakeAssistantManagerServiceImpl* fake_assistant_manager_ptr_;
base::TestMockTimeTaskRunner* mock_task_runner_;
std::unique_ptr<service_manager::ServiceContext> service_context_;
DISALLOW_COPY_AND_ASSIGN(ServiceTestClient);
};
class ServiceTest : public service_manager::test::ServiceTest {
public:
ServiceTest() : service_manager::test::ServiceTest("assistant_unittests") {}
void SetUp() override {
service_manager::test::ServiceTest::SetUp();
GetService()->Init(fake_assistant_client_->CreateInterfacePtrAndBind());
platform_service_.FlushForTesting();
base::RunLoop().RunUntilIdle();
}
void TearDown() override { service_manager::test::ServiceTest::TearDown(); }
// service_manager::test::ServiceTest:
std::unique_ptr<service_manager::Service> CreateService() override {
fake_identity_manager_ = std::make_unique<FakeIdentityManager>();
fake_assistant_client_ = std::make_unique<FakeAssistantClient>();
fake_audio_input_ = std::make_unique<FakeAudioInput>();
fake_assistant_manager_ptr_ = new FakeAssistantManagerServiceImpl();
std::unique_ptr<chromeos::DBusThreadManagerSetter> dbus_setter =
chromeos::DBusThreadManager::GetSetterForTesting();
auto power_manager_client =
std::make_unique<chromeos::FakePowerManagerClient>();
power_manager_client->SetTabletMode(
chromeos::PowerManagerClient::TabletMode::OFF, base::TimeTicks());
power_manager_client_ = power_manager_client.get();
dbus_setter->SetPowerManagerClient(std::move(power_manager_client));
mock_task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>(
base::Time::Now(), base::TimeTicks::Now());
return std::make_unique<ServiceTestClient>(this, identity_manager(),
assistant_manager_service(),
mock_task_runner());
}
mojom::AssistantPlatform* GetService() {
if (!platform_service_)
connector()->BindInterface(mojom::kServiceName, &platform_service_);
return platform_service_.get();
}
FakeIdentityManager* identity_manager() {
return fake_identity_manager_.get();
}
FakeAssistantManagerServiceImpl* assistant_manager_service() {
return fake_assistant_manager_ptr_;
}
chromeos::FakePowerManagerClient* power_manager_client() {
return power_manager_client_;
}
base::TestMockTimeTaskRunner* mock_task_runner() {
return mock_task_runner_.get();
}
private:
mojom::AssistantPlatformPtr platform_service_;
std::unique_ptr<FakeIdentityManager> fake_identity_manager_;
std::unique_ptr<FakeAssistantClient> fake_assistant_client_;
std::unique_ptr<FakeAudioInput> fake_audio_input_;
FakeAssistantManagerServiceImpl* fake_assistant_manager_ptr_;
chromeos::FakePowerManagerClient* power_manager_client_;
scoped_refptr<base::TestMockTimeTaskRunner> mock_task_runner_;
std::unique_ptr<base::OneShotTimer> mock_timer_;
DISALLOW_COPY_AND_ASSIGN(ServiceTest);
};
TEST_F(ServiceTest, RefreshTokenAfterExpire) {
auto current_count = identity_manager()->get_access_token_count();
mock_task_runner()->FastForwardBy(kDefaultTokenExpirationDelay / 2);
base::RunLoop().RunUntilIdle();
// Before token expire, should not request new token.
EXPECT_EQ(identity_manager()->get_access_token_count(), current_count);
mock_task_runner()->FastForwardBy(kDefaultTokenExpirationDelay);
base::RunLoop().RunUntilIdle();
// After token expire, should request once.
EXPECT_EQ(identity_manager()->get_access_token_count(), ++current_count);
}
TEST_F(ServiceTest, RetryRefreshTokenAfterFailure) {
auto current_count = identity_manager()->get_access_token_count();
identity_manager()->SetShouldFail(true);
mock_task_runner()->FastForwardBy(kDefaultTokenExpirationDelay);
base::RunLoop().RunUntilIdle();
// Token request failed.
EXPECT_EQ(identity_manager()->get_access_token_count(), ++current_count);
base::RunLoop().RunUntilIdle();
// Token request automatically retry.
identity_manager()->SetShouldFail(false);
// The failure delay has jitter so fast forward a bit more.
mock_task_runner()->FastForwardBy(kDefaultTokenExpirationDelay * 2);
base::RunLoop().RunUntilIdle();
EXPECT_EQ(identity_manager()->get_access_token_count(), ++current_count);
}
TEST_F(ServiceTest, RetryRefreshTokenAfterDeviceWakeup) {
auto current_count = identity_manager()->get_access_token_count();
power_manager_client()->SendSuspendDone();
base::RunLoop().RunUntilIdle();
// Token requested immediately after suspend done.
EXPECT_EQ(identity_manager()->get_access_token_count(), ++current_count);
}
} // namespace assistant
} // namespace chromeos