// 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/policy/skyvault/odfs_skyvault_uploader.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/test_future.h"
#include "chrome/browser/ash/file_manager/file_manager_test_util.h"
#include "chrome/browser/ash/file_manager/volume_manager.h"
#include "chrome/browser/ash/policy/skyvault/policy_utils.h"
#include "chrome/browser/ash/policy/skyvault/signin_notification_helper.h"
#include "chrome/browser/ash/policy/skyvault/skyvault_test_base.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/test/browser_test.h"
#include "storage/browser/file_system/file_system_url.h"
#include "ui/message_center/public/cpp/notification.h"
using policy::local_user_files::SkyvaultOneDriveTest;
namespace ash::cloud_upload {
// Tests the OneDrive upload workflow using the static
// `OdfsSkyvaultUploader::Upload` method. Ensures that the upload completes
// with the expected results.
class OdfsSkyvaultUploaderTest : public SkyvaultOneDriveTest {
public:
OdfsSkyvaultUploaderTest() = default;
OdfsSkyvaultUploaderTest(const OdfsSkyvaultUploaderTest&) = delete;
OdfsSkyvaultUploaderTest& operator=(const OdfsSkyvaultUploaderTest&) = delete;
void SetUpOnMainThread() override {
SkyvaultOneDriveTest::SetUpOnMainThread();
display_service_tester_ =
std::make_unique<NotificationDisplayServiceTester>(profile());
}
protected:
std::unique_ptr<NotificationDisplayServiceTester> display_service_tester_;
// Used to observe skyvault notifications during tests.
base::RepeatingCallback<void(const message_center::Notification&)>
on_notification_displayed_callback_;
};
IN_PROC_BROWSER_TEST_F(OdfsSkyvaultUploaderTest, SuccessfulUpload) {
SetUpMyFiles();
SetUpODFS();
const std::string test_file_name = "video_long.ogv";
base::FilePath source_file_path =
CopyTestFile(test_file_name, my_files_dir());
// Start the upload workflow and end the test once the upload callback is run.
base::MockCallback<base::RepeatingCallback<void(int64_t)>> progress_callback;
base::test::TestFuture<bool, storage::FileSystemURL> upload_callback;
EXPECT_CALL(progress_callback, Run(/*bytes_transferred=*/230096));
OdfsSkyvaultUploader::Upload(
profile(), source_file_path, OdfsSkyvaultUploader::FileType::kDownload,
progress_callback.Get(), upload_callback.GetCallback());
EXPECT_EQ(upload_callback.Get<bool>(), true);
// Check that the source file has been moved to OneDrive.
CheckPathExistsOnODFS(base::FilePath("/").AppendASCII(test_file_name));
}
IN_PROC_BROWSER_TEST_F(OdfsSkyvaultUploaderTest, SuccessfulUploadWithTarget) {
SetUpMyFiles();
SetUpODFS();
const std::string test_file_name = "video_long.ogv";
base::FilePath source_file_path =
CopyTestFile(test_file_name, my_files_dir());
const std::string target_path = "ChromeOS Device";
// Start the upload workflow and end the test once the upload callback is run.
base::MockCallback<base::RepeatingCallback<void(int64_t)>> progress_callback;
base::test::TestFuture<
storage::FileSystemURL,
std::optional<policy::local_user_files::MigrationUploadError>>
upload_callback;
OdfsSkyvaultUploader::Upload(
profile(), source_file_path, OdfsSkyvaultUploader::FileType::kMigration,
progress_callback.Get(), upload_callback.GetCallback(),
base::FilePath(target_path));
auto [url, error] = upload_callback.Get();
EXPECT_FALSE(error.has_value());
EXPECT_TRUE(url.is_valid());
// Check that the source file has been moved to OneDrive.
CheckPathExistsOnODFS(
base::FilePath("/").AppendASCII(target_path).AppendASCII(test_file_name));
}
IN_PROC_BROWSER_TEST_F(OdfsSkyvaultUploaderTest, CancelledUpload) {
SetUpMyFiles();
SetUpODFS();
const std::string test_file_name = "video_long.ogv";
base::FilePath source_file_path =
CopyTestFile(test_file_name, my_files_dir());
// Start the upload workflow and cancel the upload immediately.
base::MockCallback<base::RepeatingCallback<void(int64_t)>> progress_callback;
base::test::TestFuture<bool, storage::FileSystemURL> upload_callback;
base::WeakPtr<OdfsSkyvaultUploader> uploader = OdfsSkyvaultUploader::Upload(
profile(), source_file_path, OdfsSkyvaultUploader::FileType::kDownload,
progress_callback.Get(), upload_callback.GetCallback());
uploader->Cancel();
EXPECT_EQ(upload_callback.Get<bool>(), false);
// Check that the source file has not been moved to OneDrive.
CheckPathNotFoundOnODFS(base::FilePath("/").AppendASCII(test_file_name));
}
IN_PROC_BROWSER_TEST_F(OdfsSkyvaultUploaderTest, FailToUploadDueToMemoryError) {
SetUpMyFiles();
SetUpODFS();
// Ensure Upload fails due to memory error and that reauthentication to
// OneDrive is not required.
provided_file_system_->SetCreateFileError(
base::File::Error::FILE_ERROR_NO_MEMORY);
provided_file_system_->SetReauthenticationRequired(false);
const std::string test_file_name = "id3Audio.mp3";
base::FilePath source_file_path =
CopyTestFile(test_file_name, my_files_dir());
// Start the upload workflow and end the test once the upload callback is run.
base::MockCallback<base::RepeatingCallback<void(int64_t)>> progress_callback;
base::test::TestFuture<bool, storage::FileSystemURL> upload_callback;
OdfsSkyvaultUploader::Upload(
profile(), source_file_path, OdfsSkyvaultUploader::FileType::kDownload,
progress_callback.Get(), upload_callback.GetCallback());
EXPECT_EQ(upload_callback.Get<bool>(), false);
// Check that the source file has not been moved to OneDrive.
CheckPathNotFoundOnODFS(base::FilePath("/").AppendASCII(test_file_name));
}
// Test that when the reauthentication to ODFS is required, the sign-in required
// notification is shown. When the sign-in is complete, the upload is continued.
IN_PROC_BROWSER_TEST_F(OdfsSkyvaultUploaderTest,
UploadAfterReauthenticationRequired) {
SetUpMyFiles();
SetUpODFS();
provided_file_system_->SetReauthenticationRequired(true);
const std::string test_file_name = "text.docx";
base::FilePath source_file_path =
CopyTestFile(test_file_name, my_files_dir());
// Start the upload workflow and simulate a successful mount() request
// (indicating interactive auth has succeeded).
file_manager::test::GetFakeProviderOneDrive(profile())->SetRequestMountImpl(
base::BindLambdaForTesting(
[&](ash::file_system_provider::RequestMountCallback callback) {
// The second check of reauth required after the mount succeeds
// should be OK so we attempt upload.
provided_file_system_->SetReauthenticationRequired(false);
std::move(callback).Run(base::File::Error::FILE_OK);
}));
// Start the upload workflow and wait till the sign-in notification is shown.
base::RunLoop added_run_loop;
display_service_tester_->SetNotificationAddedClosure(
added_run_loop.QuitClosure());
base::MockCallback<base::RepeatingCallback<void(int64_t)>> progress_callback;
base::test::TestFuture<bool, storage::FileSystemURL> upload_callback;
OdfsSkyvaultUploader::Upload(
profile(), source_file_path, OdfsSkyvaultUploader::FileType::kDownload,
progress_callback.Get(), upload_callback.GetCallback());
added_run_loop.Run();
// Click on the sign-in button to initiate the auth flow.
auto notification_id = base::StrCat(
{policy::skyvault_ui_utils::kDownloadSignInNotificationPrefix, "1"});
ASSERT_TRUE(
display_service_tester_->GetNotification(notification_id).has_value());
display_service_tester_->SimulateClick(
NotificationHandler::Type::TRANSIENT, notification_id,
policy::skyvault_ui_utils::NotificationButtonIndex::kSignInButton,
/*reply=*/std::nullopt);
EXPECT_EQ(upload_callback.Get<bool>(), true);
ASSERT_FALSE(
display_service_tester_->GetNotification(notification_id).has_value());
// Check that the source file has been moved to OneDrive.
CheckPathExistsOnODFS(base::FilePath("/").AppendASCII(test_file_name));
}
// Test that when the OneDrive file system isn't mounted, the sign-in required
// notification is shown. When the sign-in notification is cancelled, the upload
// fails.
IN_PROC_BROWSER_TEST_F(OdfsSkyvaultUploaderTest,
FailToUploadDueToReauthenticationRequired) {
SetUpMyFiles();
const std::string test_file_name = "text.docx";
base::FilePath source_file_path =
CopyTestFile(test_file_name, my_files_dir());
// Start the upload workflow and wait till the sign-in notification is shown.
base::RunLoop added_run_loop;
display_service_tester_->SetNotificationAddedClosure(
added_run_loop.QuitClosure());
base::MockCallback<base::RepeatingCallback<void(int64_t)>> progress_callback;
base::test::TestFuture<bool, storage::FileSystemURL> upload_callback;
OdfsSkyvaultUploader::Upload(
profile(), source_file_path, OdfsSkyvaultUploader::FileType::kDownload,
progress_callback.Get(), upload_callback.GetCallback());
added_run_loop.Run();
// Click on the cancel so the upload will fail.
auto notification_id = base::StrCat(
{policy::skyvault_ui_utils::kDownloadSignInNotificationPrefix, "1"});
ASSERT_TRUE(
display_service_tester_->GetNotification(notification_id).has_value());
display_service_tester_->SimulateClick(
NotificationHandler::Type::TRANSIENT, notification_id,
policy::skyvault_ui_utils::NotificationButtonIndex::kCancelButton,
/*reply=*/std::nullopt);
EXPECT_EQ(upload_callback.Get<bool>(), false);
ASSERT_FALSE(
display_service_tester_->GetNotification(notification_id).has_value());
}
} // namespace ash::cloud_upload