blob: bb5a8932021a82da337ac787a937879423c864bc [file] [log] [blame]
// 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 "chrome/browser/ash/app_mode/load_profile.h"
#include <functional>
#include <memory>
#include <string>
#include <string_view>
#include <utility>
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/types/expected.h"
#include "chrome/browser/ash/app_mode/cancellable_job.h"
#include "chrome/browser/ash/app_mode/kiosk_app_launch_error.h"
#include "chrome/browser/ash/app_mode/kiosk_app_types.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/test/base/testing_profile.h"
#include "chromeos/ash/components/login/auth/public/auth_failure.h"
#include "chromeos/ash/components/login/auth/public/user_context.h"
#include "components/account_id/account_id.h"
#include "components/user_manager/user_type.h"
#include "content/public/test/browser_task_environment.h"
#include "google_apis/gaia/gaia_id.h"
#include "testing/gtest/include/gtest/gtest.h"
using ash::kiosk::CryptohomeMountState;
using ash::kiosk::CryptohomeMountStateCallback;
using ash::kiosk::LoadProfileResult;
using ash::kiosk::LoadProfileWithCallbacks;
using ash::kiosk::PerformSigninResultCallback;
using ash::kiosk::StartSessionResultCallback;
using base::test::TestFuture;
namespace ash {
namespace {
constexpr std::string_view kKioskEmail = "kiosk@example.com";
// App type used in tests. Could be any `KioskAppType`.
constexpr KioskAppType kTestAppType = KioskAppType::kWebApp;
// Helper to post a task with less boilerplate.
void PostTask(base::OnceClosure task) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(task)));
}
// Helper to return a handle to a `CancellableJob`.
std::unique_ptr<CancellableJob> NewCancellableJob() {
return std::unique_ptr<CancellableJob>{};
}
// Account ID used in tests. Could be any `AccountId`.
AccountId TestAccountId() {
return AccountId::FromUserEmailGaiaId(/*user_email=*/std::string(kKioskEmail),
GaiaId("a fake gaia id"));
}
// User context used in tests. Could be any `UserContext` .
UserContext TestUserContext() {
return UserContext(user_manager::UserType::kKioskWebApp, TestAccountId());
}
} // namespace
class LoadProfileTest : public testing::Test {
protected:
// Set task environment with mock time to call `PostDelayedTask`.
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
LoadProfileTest() = default;
};
TEST_F(LoadProfileTest, ReturnsProfileOnSuccess) {
TestingProfile profile;
profile.set_profile_name(std::string(kKioskEmail));
auto fake_check_cryptohome = [](CryptohomeMountStateCallback cb) {
PostTask(base::BindOnce(std::move(cb), CryptohomeMountState::kNotMounted));
return NewCancellableJob();
};
auto fake_signin = [](KioskAppType app_type, AccountId id,
PerformSigninResultCallback cb) {
PostTask(base::BindOnce(std::move(cb), TestUserContext()));
return NewCancellableJob();
};
auto fake_start_session = [&](const UserContext& user_context,
StartSessionResultCallback cb) {
PostTask(base::BindOnce(std::move(cb), std::ref(profile)));
return NewCancellableJob();
};
TestFuture<LoadProfileResult> future;
auto handle = LoadProfileWithCallbacks(
TestAccountId(), kTestAppType, base::BindOnce(fake_check_cryptohome),
base::BindOnce(fake_signin),
base::BindLambdaForTesting(fake_start_session), future.GetCallback());
auto result = future.Take();
ASSERT_TRUE(result.has_value());
ASSERT_EQ(profile.GetProfileUserName(), result.value()->GetProfileUserName());
}
TEST_F(LoadProfileTest, ForwardsResultsBetweenStepsCorrectly) {
TestingProfile profile;
profile.set_profile_name(std::string(kKioskEmail));
AccountId seen_account_id;
KioskAppType seen_app_type;
UserContext seen_user_context;
auto fake_check_cryptohome = [](CryptohomeMountStateCallback cb) {
PostTask(base::BindOnce(std::move(cb), CryptohomeMountState::kNotMounted));
return NewCancellableJob();
};
auto fake_signin = [&](KioskAppType app_type, AccountId id,
PerformSigninResultCallback cb) {
seen_app_type = app_type;
seen_account_id = TestAccountId();
PostTask(base::BindOnce(std::move(cb), TestUserContext()));
return NewCancellableJob();
};
auto fake_start_session = [&](const UserContext& user_context,
StartSessionResultCallback cb) {
seen_user_context = user_context;
PostTask(base::BindOnce(std::move(cb), std::ref(profile)));
return NewCancellableJob();
};
TestFuture<LoadProfileResult> future;
auto handle = LoadProfileWithCallbacks(
TestAccountId(), kTestAppType, base::BindOnce(fake_check_cryptohome),
base::BindLambdaForTesting(fake_signin),
base::BindLambdaForTesting(fake_start_session), future.GetCallback());
auto result = future.Take();
ASSERT_TRUE(result.has_value());
EXPECT_EQ(seen_account_id, TestAccountId());
EXPECT_EQ(seen_app_type, kTestAppType);
EXPECT_EQ(seen_user_context, TestUserContext());
EXPECT_EQ(result.value()->GetProfileUserName(), profile.GetProfileUserName());
}
TEST_F(LoadProfileTest, ReturnsCryptohomeCheckError) {
bool did_call_jobs_after_failure = false;
auto fake_check_cryptohome = [](CryptohomeMountStateCallback cb) {
PostTask(base::BindOnce(std::move(cb), CryptohomeMountState::kMounted));
return NewCancellableJob();
};
auto fake_signin = [&](KioskAppType app_type, AccountId id,
PerformSigninResultCallback cb) {
did_call_jobs_after_failure = true;
return NewCancellableJob();
};
auto fake_start_session = [&](const UserContext& user_context,
StartSessionResultCallback cb) {
did_call_jobs_after_failure = true;
return NewCancellableJob();
};
TestFuture<LoadProfileResult> future;
auto handle = LoadProfileWithCallbacks(
TestAccountId(), kTestAppType, base::BindOnce(fake_check_cryptohome),
base::BindLambdaForTesting(fake_signin),
base::BindLambdaForTesting(fake_start_session), future.GetCallback());
auto result = future.Take();
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error(), KioskAppLaunchError::Error::kAlreadyMounted);
EXPECT_FALSE(did_call_jobs_after_failure);
}
TEST_F(LoadProfileTest, ReturnsSigninError) {
bool did_call_jobs_after_failure = false;
auto fake_check_cryptohome = [](CryptohomeMountStateCallback cb) {
PostTask(base::BindOnce(std::move(cb), CryptohomeMountState::kNotMounted));
return NewCancellableJob();
};
auto fake_signin = [](KioskAppType app_type, AccountId id,
PerformSigninResultCallback cb) {
PostTask(base::BindOnce(
std::move(cb),
base::unexpected(AuthFailure(AuthFailure::UNRECOVERABLE_CRYPTOHOME))));
return NewCancellableJob();
};
auto fake_start_session = [&](const UserContext& user_context,
StartSessionResultCallback cb) {
did_call_jobs_after_failure = true;
return NewCancellableJob();
};
TestFuture<LoadProfileResult> future;
auto handle = LoadProfileWithCallbacks(
TestAccountId(), kTestAppType, base::BindOnce(fake_check_cryptohome),
base::BindOnce(fake_signin),
base::BindLambdaForTesting(fake_start_session), future.GetCallback());
auto result = future.Take();
ASSERT_FALSE(result.has_value());
EXPECT_EQ(result.error(), KioskAppLaunchError::Error::kUnableToMount);
EXPECT_FALSE(did_call_jobs_after_failure);
}
TEST_F(LoadProfileTest, StopsWhenHandleIsCancelled) {
TestingProfile profile;
profile.set_profile_name(std::string(kKioskEmail));
TestFuture<void> signin_future;
bool did_call_jobs_after_cancellation = false;
auto fake_check_cryptohome = [](CryptohomeMountStateCallback cb) {
PostTask(base::BindOnce(std::move(cb), CryptohomeMountState::kNotMounted));
return NewCancellableJob();
};
auto fake_signin = [&](KioskAppType app_type, AccountId id,
PerformSigninResultCallback cb) {
signin_future.SetValue();
PostTask(base::BindOnce(std::move(cb), TestUserContext()));
return NewCancellableJob();
};
auto fake_start_session = [&](const UserContext& user_context,
StartSessionResultCallback cb) {
did_call_jobs_after_cancellation = true;
PostTask(base::BindOnce(std::move(cb), std::ref(profile)));
return NewCancellableJob();
};
TestFuture<LoadProfileResult> future;
auto handle = LoadProfileWithCallbacks(
TestAccountId(), kTestAppType, base::BindOnce(fake_check_cryptohome),
base::BindLambdaForTesting(fake_signin),
base::BindLambdaForTesting(fake_start_session), future.GetCallback());
// Cancel the job handle after signin.
ASSERT_NE(handle, nullptr);
ASSERT_TRUE(signin_future.Wait()) << "Did not signin";
handle.reset();
base::RunLoop().RunUntilIdle(); // Wait any ongoing tasks to complete.
ASSERT_FALSE(did_call_jobs_after_cancellation);
ASSERT_FALSE(future.IsReady());
}
} // namespace ash