Skip to content

Commit

Permalink
boca: Register invalidation workflow for boca.
Browse files Browse the repository at this point in the history
Test: Unit tested. E2E test manually.
Bug: b:354769102
Low-Coverage-Reason: OTHER session_client_impl.cc is just proxy and has no business logic.
Change-Id: I7c0ffc322550305bec97b04ce668c8a21cbeba7b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5854834
Reviewed-by: Benjamin Zielinski <[email protected]>
Commit-Queue: April Zhou <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1356170}
  • Loading branch information
zhouyuzhe authored and Chromium LUCI CQ committed Sep 16, 2024
1 parent 02edfdf commit 07e0e5e
Show file tree
Hide file tree
Showing 9 changed files with 335 additions and 2 deletions.
3 changes: 3 additions & 0 deletions chromeos/ash/components/boca/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ static_library("invalidations") {
sources = [
"invalidations/fcm_handler.cc",
"invalidations/fcm_handler.h",
"invalidations/invalidation_service_impl.cc",
"invalidations/invalidation_service_impl.h",
]
deps = [
":boca",
Expand All @@ -53,6 +55,7 @@ source_set("unit_tests") {
"boca_session_manager_unittest.cc",
"boca_session_util_unittest.cc",
"invalidations/fcm_handler_unittest.cc",
"invalidations/invalidation_service_impl_unittest.cc",
]

deps = [
Expand Down
2 changes: 1 addition & 1 deletion chromeos/ash/components/boca/boca_session_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ class BocaSessionManager
void RemoveObserver(Observer* observer);

void StartSessionPolling();
void LoadCurrentSession();
virtual void LoadCurrentSession();
void ParseSessionResponse(base::expected<std::unique_ptr<::boca::Session>,
google_apis::ApiErrorCode> result);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ class MockBocaAppClient : public BocaAppClient {

constexpr char kTestGaiaId[] = "123";
constexpr char kTestUserEmail[] = "[email protected]";
} // namespace

class BocaSessionManagerTest : public testing::Test {
public:
Expand Down Expand Up @@ -649,4 +648,5 @@ TEST_F(BocaSessionManagerTest, NotifyLocalCaptionConfigWhenLocalChange) {
BocaAppClient::Get()->GetSessionManager()->NotifyLocalCaptionEvents(config);
}

} // namespace
} // namespace ash::boca
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/boca/invalidations/invalidation_service_impl.h"

#include "base/time/time.h"
#include "chromeos/ash/components/boca/boca_session_manager.h"
#include "chromeos/ash/components/boca/session_api/session_client_impl.h"
#include "chromeos/ash/components/boca/session_api/upload_token_request.h"
#include "components/gcm_driver/gcm_driver.h"
#include "components/gcm_driver/instance_id/instance_id_driver.h"

namespace ash::boca {

InvalidationServiceImpl::InvalidationServiceImpl(
gcm::GCMDriver* gcm_driver,
instance_id::InstanceIDDriver* instance_id_driver,
AccountId account_id,
BocaSessionManager* boca_session_manager,
SessionClientImpl* session_client_impl)
: account_id_(account_id),
boca_session_manager_(boca_session_manager),
session_client_impl_(session_client_impl) {
fcm_handler_ = std::make_unique<FCMHandler>(gcm_driver, instance_id_driver,
kSenderId, kApplicationId);
// Add token refresh observer.
fcm_handler_->AddTokenObserver(this);
// Add invalidation message observer.
fcm_handler_->AddListener(this);
// Register app handler and start token fetch.
fcm_handler_->StartListening();
}

InvalidationServiceImpl::~InvalidationServiceImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
fcm_handler_->RemoveTokenObserver(this);
fcm_handler_->RemoveListener(this);
}

void InvalidationServiceImpl::OnInvalidationReceived(
const std::string& payload) {
// TODO(b/354769102): Potentially validate FCM payload before dispatching. And
// implement a thread-safe approach to skip loading when there is already
// active loading in progress.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// FCM message will be delivered even user not logged in. But
// LoadCurrentSession has a validation to skip load if the current active user
// doesn't match the profile user.
boca_session_manager_->LoadCurrentSession();
}

void InvalidationServiceImpl::OnFCMRegistrationTokenChanged() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!fcm_handler_->GetFCMRegistrationToken().has_value()) {
return;
} else {
auto request = std::make_unique<UploadTokenRequest>(
session_client_impl_->sender(), account_id_.GetGaiaId(),
fcm_handler_->GetFCMRegistrationToken().value(),
base::BindOnce(&InvalidationServiceImpl::OnTokenUploaded,
weak_factory_.GetWeakPtr()));
session_client_impl_->UploadToken(std::move(request));
}
}

void InvalidationServiceImpl::OnTokenUploaded(
base::expected<bool, google_apis::ApiErrorCode> result) {
if (result.has_value()) {
// TODO(b/366316261):Add metrics for token failure.
LOG(WARNING) << "[Boca]Failed to upload token, skipping";
return;
}
}

} // namespace ash::boca
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROMEOS_ASH_COMPONENTS_BOCA_INVALIDATIONS_INVALIDATION_SERVICE_IMPL_H_
#define CHROMEOS_ASH_COMPONENTS_BOCA_INVALIDATIONS_INVALIDATION_SERVICE_IMPL_H_

#include <memory>

#include "base/time/time.h"
#include "base/types/expected.h"
#include "chromeos/ash/components/boca/invalidations/fcm_handler.h"
#include "components/account_id/account_id.h"
#include "google_apis/common/api_error_codes.h"

namespace instance_id {
class InstanceIDDriver;
}

namespace gcm {
class GCMDriver;
}

namespace ash::boca {
class BocaSessionManager;
class SessionClientImpl;
class InvalidationServiceImpl : public InvalidationsListener,
FCMRegistrationTokenObserver {
public:
inline static constexpr char kSenderId[] = "947897361853";
inline static constexpr char kApplicationId[] =
"com.google.chrome.boca.fcm.invalidations";
InvalidationServiceImpl(gcm::GCMDriver* gcm_driver,
instance_id::InstanceIDDriver* instance_id_driver,
AccountId account_id,
BocaSessionManager* boca_session_manager_,
SessionClientImpl* session_client_impl_);
~InvalidationServiceImpl() override;

// InvalidationsListener implementation.
void OnInvalidationReceived(const std::string& payload) override;

// FCMRegistrationTokenObserver implementation.
void OnFCMRegistrationTokenChanged() override;

void OnTokenUploaded(base::expected<bool, google_apis::ApiErrorCode> result);

FCMHandler* fcm_handler() { return fcm_handler_.get(); }

private:
SEQUENCE_CHECKER(sequence_checker_);
std::unique_ptr<FCMHandler> fcm_handler_;
AccountId account_id_;
raw_ptr<BocaSessionManager> boca_session_manager_;
raw_ptr<SessionClientImpl> session_client_impl_;
base::WeakPtrFactory<InvalidationServiceImpl> weak_factory_{this};
};

} // namespace ash::boca
#endif // CHROMEOS_ASH_COMPONENTS_BOCA_INVALIDATIONS_INVALIDATION_SERVICE_IMPL_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/boca/invalidations/invalidation_service_impl.h"

#include <memory>

#include "base/test/gmock_callback_support.h"
#include "base/test/task_environment.h"
#include "chromeos/ash/components/boca/boca_session_manager.h"
#include "chromeos/ash/components/boca/invalidations/fcm_handler.h"
#include "chromeos/ash/components/boca/session_api/session_client_impl.h"
#include "chromeos/ash/components/boca/session_api/upload_token_request.h"
#include "components/account_id/account_id.h"
#include "components/gcm_driver/fake_gcm_driver.h"
#include "components/gcm_driver/gcm_driver.h"
#include "components/gcm_driver/instance_id/instance_id_driver.h"
#include "google_apis/common/request_sender.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::base::test::RunOnceCallback;
using ::testing::_;
using ::testing::NiceMock;
using ::testing::Return;
using ::testing::SaveArg;

namespace ash::boca {

namespace {
constexpr char kTestEmail[] = "testemail";
constexpr char kGaiaId[] = "123";
constexpr int kTokenValidationPeriodMinutesDefault = 60 * 24;

class MockSessionClientImpl : public SessionClientImpl {
public:
explicit MockSessionClientImpl(
std::unique_ptr<google_apis::RequestSender> sender)
: SessionClientImpl(std::move(sender)) {}
MOCK_METHOD(void,
UploadToken,
(std::unique_ptr<UploadTokenRequest>),
(override));
};

class MockSessionManager : public BocaSessionManager {
public:
explicit MockSessionManager(SessionClientImpl* session_client_impl)
: BocaSessionManager(
session_client_impl,
AccountId::FromUserEmailGaiaId(kTestEmail, kGaiaId)) {}
MOCK_METHOD(void, LoadCurrentSession, (), (override));
~MockSessionManager() override = default;
};

class MockGCMDriver : public gcm::GCMDriver {
public:
MockGCMDriver(base::FilePath& path,
scoped_refptr<base::SequencedTaskRunner>& sequence_runner)
: GCMDriver(/*store_path=*/base::FilePath(),
/*blocking_task_runner=*/nullptr) {}
};

class MockInstanceID : public instance_id::InstanceID {
public:
MockInstanceID()
: instance_id::InstanceID(InvalidationServiceImpl::kApplicationId,
/*gcm_driver=*/nullptr) {}
~MockInstanceID() override = default;
MOCK_METHOD(void, GetID, (GetIDCallback callback), (override));
MOCK_METHOD(void,
GetCreationTime,
(GetCreationTimeCallback callback),
(override));
MOCK_METHOD(void,
GetToken,
(const std::string& authorized_entity,
const std::string& scope,
base::TimeDelta time_to_live,
std::set<Flags> flags,
GetTokenCallback callback),
(override));
MOCK_METHOD(void,
ValidateToken,
(const std::string& authorized_entity,
const std::string& scope,
const std::string& token,
ValidateTokenCallback callback),
(override));

protected:
MOCK_METHOD(void,
DeleteTokenImpl,
(const std::string& authorized_entity,
const std::string& scope,
DeleteTokenCallback callback),
(override));
MOCK_METHOD(void, DeleteIDImpl, (DeleteIDCallback callback), (override));
};

class MockInstanceIDDriver : public instance_id::InstanceIDDriver {
public:
MockInstanceIDDriver() : InstanceIDDriver(/*gcm_driver=*/nullptr) {}
~MockInstanceIDDriver() override = default;
MOCK_METHOD(instance_id::InstanceID*,
GetInstanceID,
(const std::string& app_id),
(override));
MOCK_METHOD(void, RemoveInstanceID, (const std::string& app_id), (override));
MOCK_METHOD(bool,
ExistsInstanceID,
(const std::string& app_id),
(const override));
};

class InvalidationServiceImplTest : public testing::Test {
protected:
InvalidationServiceImplTest() = default;
~InvalidationServiceImplTest() override = default;

void SetUp() override {
mock_instance_id_driver_ =
std::make_unique<NiceMock<MockInstanceIDDriver>>();
ON_CALL(*mock_instance_id_driver_,
GetInstanceID(InvalidationServiceImpl::kApplicationId))
.WillByDefault(Return(&mock_instance_id_));

ON_CALL(mock_instance_id_, GetToken)
.WillByDefault(RunOnceCallback<4>(
"default_token", instance_id::InstanceID::Result::SUCCESS));

session_client_impl_ =
std::make_unique<NiceMock<MockSessionClientImpl>>(nullptr);
ON_CALL(*session_client_impl_, UploadToken(_)).WillByDefault(Return());

boca_session_manager_ = std::make_unique<NiceMock<MockSessionManager>>(
session_client_impl_.get());
invalidation_service_impl_ = std::make_unique<InvalidationServiceImpl>(
&fake_gcm_driver_, mock_instance_id_driver_.get(),
AccountId::FromUserEmailGaiaId(kTestEmail, kGaiaId),
boca_session_manager_.get(), session_client_impl_.get());
}

base::test::SingleThreadTaskEnvironment task_environment_{
base::test::SingleThreadTaskEnvironment::TimeSource::MOCK_TIME};
NiceMock<MockInstanceID> mock_instance_id_;
gcm::FakeGCMDriver fake_gcm_driver_;
std::unique_ptr<NiceMock<MockInstanceIDDriver>> mock_instance_id_driver_;
std::unique_ptr<NiceMock<MockSessionClientImpl>> session_client_impl_;
std::unique_ptr<NiceMock<MockSessionManager>> boca_session_manager_;
std::unique_ptr<InvalidationServiceImpl> invalidation_service_impl_;
};

TEST_F(InvalidationServiceImplTest, HandleInvalidation) {
EXPECT_CALL(*boca_session_manager_, LoadCurrentSession()).Times(1);
const std::string kPayloadValue = "payload_1";
gcm::IncomingMessage gcm_message;
gcm_message.raw_data = kPayloadValue;
invalidation_service_impl_->fcm_handler()->OnMessage(
InvalidationServiceImpl::kApplicationId, gcm_message);
}

TEST_F(InvalidationServiceImplTest, HandleTokenUpload) {
// Check that the handler gets the token through GetToken.
const char token[] = "token_2";
EXPECT_CALL(mock_instance_id_, GetToken)
.WillOnce(
RunOnceCallback<4>(token, instance_id::InstanceID::Result::SUCCESS));
std::unique_ptr<UploadTokenRequest> request;
EXPECT_CALL(*session_client_impl_, UploadToken(_))
.WillOnce([&](std::unique_ptr<UploadTokenRequest> request_1) {
request = std::move(request_1);
});
// Adjust the time and check that validation will happen in time.
// The old token is invalid, so token observer should be informed.
task_environment_.FastForwardBy(
base::Minutes(kTokenValidationPeriodMinutesDefault));

EXPECT_EQ(kGaiaId, request->gaia_id());
EXPECT_EQ(token, request->token());
}
} // namespace
} // namespace ash::boca
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,9 @@ void SessionClientImpl::GetSession(std::unique_ptr<GetSessionRequest> request) {
sender_->StartRequestWithAuthRetry(std::move(request));
}

void SessionClientImpl::UploadToken(
std::unique_ptr<UploadTokenRequest> request) {
sender_->StartRequestWithAuthRetry(std::move(request));
}

} // namespace ash::boca
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "base/functional/callback_forward.h"
#include "chromeos/ash/components/boca/session_api/create_session_request.h"
#include "chromeos/ash/components/boca/session_api/get_session_request.h"
#include "chromeos/ash/components/boca/session_api/upload_token_request.h"

namespace ash::boca {
class SessionClientImpl {
Expand All @@ -22,6 +23,7 @@ class SessionClientImpl {
virtual std::unique_ptr<google_apis::RequestSender> CreateRequestSender();
virtual void CreateSession(std::unique_ptr<CreateSessionRequest> request);
virtual void GetSession(std::unique_ptr<GetSessionRequest> request);
virtual void UploadToken(std::unique_ptr<UploadTokenRequest> request);
google_apis::RequestSender* sender() { return sender_.get(); }

private:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ class UploadTokenRequest : public google_apis::UrlFetchRequestBase {

UploadTokenCallback callback() { return std::move(callback_); }

std::string gaia_id() { return gaia_id_; }
std::string token() { return token_; }

protected:
// UrlFetchRequestBase:
google_apis::HttpRequestMethod GetRequestType() const override;
Expand Down

0 comments on commit 07e0e5e

Please sign in to comment.