// 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.
#import "ios/chrome/browser/drive/model/drive_upload_task.h"
#import "base/apple/foundation_util.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/metrics/histogram_tester.h"
#import "base/test/task_environment.h"
#import "ios/chrome/browser/download/model/download_mimetype_util.h"
#import "ios/chrome/browser/drive/model/drive_metrics.h"
#import "ios/chrome/browser/drive/model/test_drive_file_uploader.h"
#import "ios/chrome/browser/drive/model/test_upload_task_observer.h"
#import "ios/chrome/browser/signin/model/fake_system_identity.h"
#import "net/base/url_util.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#import "url/gurl.h"
using State = UploadTask::State;
// DriveUploadTask unit tests.
class DriveUploadTaskTest : public PlatformTest {
protected:
void SetUp() final {
PlatformTest::SetUp();
uploading_identity_ = [FakeSystemIdentity fakeIdentity1];
auto uploader =
std::make_unique<TestDriveFileUploader>(uploading_identity_);
uploader_ = uploader.get();
task_ = std::make_unique<DriveUploadTask>(std::move(uploader));
observer_ = std::make_unique<TestUploadTaskObserver>();
task_->AddObserver(observer_.get());
}
using TaskEnvironment = base::test::TaskEnvironment;
TaskEnvironment task_environment_{TaskEnvironment::TimeSource::MOCK_TIME};
id<SystemIdentity> uploading_identity_;
raw_ptr<TestDriveFileUploader> uploader_;
std::unique_ptr<TestUploadTaskObserver> observer_;
std::unique_ptr<DriveUploadTask> task_;
};
// Tests that upon first starting and later cancelling an upload task, the state
// of the task is correctly updated.
TEST_F(DriveUploadTaskTest, TaskCanBeStartedAndCancelled) {
EXPECT_EQ(0, task_->GetProgress());
EXPECT_EQ(State::kNotStarted, task_->GetState());
EXPECT_EQ(nil, task_->GetError());
EXPECT_EQ(std::nullopt, task_->GetResponseLink());
EXPECT_FALSE(task_->IsDone());
// Starting the task.
EXPECT_EQ(nullptr, observer_->GetUpdatedUpload());
task_->Start();
EXPECT_EQ(task_.get(), observer_->GetUpdatedUpload());
observer_->ResetUpdatedUpload();
EXPECT_EQ(State::kInProgress, task_->GetState());
// Cancelling the task.
EXPECT_EQ(nullptr, observer_->GetUpdatedUpload());
task_->Cancel();
EXPECT_EQ(task_.get(), observer_->GetUpdatedUpload());
EXPECT_EQ(State::kCancelled, task_->GetState());
EXPECT_TRUE(task_->IsDone());
}
// Tests that if the destination folder does *NOT* exist, the task will create
// it and upload the file as expected.
TEST_F(DriveUploadTaskTest, CreatesFolderIfNotFound) {
base::HistogramTester histogram_tester;
// Set up the uploader to simulate empty search results, a successful folder
// creation and file upload progress and successful completion.
uploader_->SetFolderSearchResult({.folder_identifier = nil, .error = nil});
uploader_->SetFolderCreationResult(
{.folder_identifier = @"test_folder_identifier", .error = nil});
const std::vector<DriveFileUploadProgress> progress_elements{
{0, 100}, {10, 100}, {25, 100}, {50, 100}, {99, 100}, {100, 100},
};
uploader_->SetFileUploadProgressElements(progress_elements);
const char* response_link_str = "https://test_response.file_link";
const GURL response_link(response_link_str);
const GURL response_link_with_identifier = net::AppendOrReplaceQueryParameter(
response_link, "huid",
base::SysNSStringToUTF8(uploader_->GetIdentity().hashedGaiaID));
uploader_->SetFileUploadResult(
{ .file_link = @(response_link_str), .error = nil });
// Set up the task with the name of the parent folder as well as the path,
// suggested name and MIME type of the file to upload.
task_->SetDestinationFolderName("test_folder_name");
const base::FilePath file_path_to_upload{"/test/path/of/file/to/upload"};
const base::FilePath file_suggested_name{"test_uploaded_file_name"};
task_->SetFileToUpload(file_path_to_upload, file_suggested_name,
"test_mime_type", 42);
// Test that a folder with appropriate name is searched.
uploader_->SetSearchFolderQuitClosure(task_environment_.QuitClosure());
EXPECT_EQ(State::kNotStarted, task_->GetState());
task_->Start();
EXPECT_EQ(State::kInProgress, task_->GetState());
task_environment_.RunUntilQuit();
EXPECT_NSEQ(@"test_folder_name", uploader_->GetSearchedFolderName());
// Test that a folder with appropriate name is created.
uploader_->SetCreateFolderQuitClosure(task_environment_.QuitClosure());
task_environment_.RunUntilQuit();
EXPECT_NSEQ(@"test_folder_name", uploader_->GetCreatedFolderName());
// Test that the file parameters provided earlier are forwarded to the
// uploader.
EXPECT_NSEQ(base::apple::FilePathToNSURL(file_path_to_upload),
uploader_->GetUploadedFileUrl());
EXPECT_NSEQ(base::apple::FilePathToNSString(file_suggested_name),
uploader_->GetUploadedFileName());
EXPECT_NSEQ(@"test_mime_type", uploader_->GetUploadedFileMimeType());
// Test that the parent folder identifier is the one returned by the uploader.
EXPECT_NSEQ(@"test_folder_identifier",
uploader_->GetUploadedFileFolderIdentifier());
// Test that progress is reported as expected.
for (const DriveFileUploadProgress& progress : progress_elements) {
uploader_->SetUploadFileProgressQuitClosure(
task_environment_.QuitClosure());
observer_->ResetUpdatedUpload();
task_environment_.RunUntilQuit();
EXPECT_EQ(task_.get(), observer_->GetUpdatedUpload());
const float progress_float =
static_cast<float>(progress.total_bytes_uploaded) /
progress.total_bytes_expected_to_upload;
EXPECT_EQ(progress_float, task_->GetProgress());
}
// Test that the result is reported as expected.
uploader_->SetUploadFileCompletionQuitClosure(
task_environment_.QuitClosure());
observer_->ResetUpdatedUpload();
task_environment_.RunUntilQuit();
EXPECT_EQ(task_.get(), observer_->GetUpdatedUpload());
EXPECT_EQ(response_link,
task_->GetResponseLink(/* add_user_identifier= */ false));
EXPECT_EQ(response_link_with_identifier,
task_->GetResponseLink(/* add_user_identifier= */ true));
EXPECT_NSEQ(nil, task_->GetError());
EXPECT_EQ(State::kComplete, task_->GetState());
// Test that expected histograms were recorded.
histogram_tester.ExpectUniqueSample(kDriveSearchFolderResultSuccessful, true,
1);
histogram_tester.ExpectUniqueSample(kDriveCreateFolderResultSuccessful, true,
1);
histogram_tester.ExpectUniqueSample(kDriveFileUploadResultSuccessful, true,
1);
}
// Tests that if the destination folder *DOES* exist, the task will use it as-is
// and upload the file as expected.
TEST_F(DriveUploadTaskTest, UsesExistingFolderIfFound) {
base::HistogramTester histogram_tester;
// Set up the uploader to simulate non-empty search result, and file upload
// progress and successful completion.
uploader_->SetFolderSearchResult(
{.folder_identifier = @"test_folder_identifier", .error = nil});
const std::vector<DriveFileUploadProgress> progress_elements{
{0, 100}, {10, 100}, {25, 100}, {50, 100}, {99, 100}, {100, 100},
};
uploader_->SetFileUploadProgressElements(progress_elements);
const char* response_link_str = "https://test_response.file_link";
const GURL response_link(response_link_str);
const GURL response_link_with_identifier = net::AppendOrReplaceQueryParameter(
response_link, "huid",
base::SysNSStringToUTF8(uploader_->GetIdentity().hashedGaiaID));
uploader_->SetFileUploadResult(
{ .file_link = @(response_link_str), .error = nil });
// Set up the task with the name of the parent folder as well as the path,
// suggested name and MIME type of the file to upload.
task_->SetDestinationFolderName("test_folder_name");
const base::FilePath file_path_to_upload{"/test/path/of/file/to/upload"};
const base::FilePath file_suggested_name{"test_uploaded_file_name"};
task_->SetFileToUpload(file_path_to_upload, file_suggested_name,
"test_mime_type", 42);
// Test that a folder with appropriate name is searched.
uploader_->SetSearchFolderQuitClosure(task_environment_.QuitClosure());
EXPECT_EQ(State::kNotStarted, task_->GetState());
task_->Start();
EXPECT_EQ(State::kInProgress, task_->GetState());
task_environment_.RunUntilQuit();
EXPECT_NSEQ(@"test_folder_name", uploader_->GetSearchedFolderName());
// Test that the file parameters provided earlier are forwarded to the
// uploader.
EXPECT_NSEQ(base::apple::FilePathToNSURL(file_path_to_upload),
uploader_->GetUploadedFileUrl());
EXPECT_NSEQ(base::apple::FilePathToNSString(file_suggested_name),
uploader_->GetUploadedFileName());
EXPECT_NSEQ(@"test_mime_type", uploader_->GetUploadedFileMimeType());
// Test that the parent folder identifier is the one returned by the uploader.
EXPECT_NSEQ(@"test_folder_identifier",
uploader_->GetUploadedFileFolderIdentifier());
// Test that progress is reported as expected.
for (const DriveFileUploadProgress& progress : progress_elements) {
uploader_->SetUploadFileProgressQuitClosure(
task_environment_.QuitClosure());
observer_->ResetUpdatedUpload();
task_environment_.RunUntilQuit();
EXPECT_EQ(task_.get(), observer_->GetUpdatedUpload());
const float progress_float =
static_cast<float>(progress.total_bytes_uploaded) /
progress.total_bytes_expected_to_upload;
EXPECT_EQ(progress_float, task_->GetProgress());
}
// Test that the result is reported as expected.
uploader_->SetUploadFileCompletionQuitClosure(
task_environment_.QuitClosure());
observer_->ResetUpdatedUpload();
task_environment_.RunUntilQuit();
EXPECT_EQ(task_.get(), observer_->GetUpdatedUpload());
EXPECT_EQ(response_link,
task_->GetResponseLink(/* add_user_identifier= */ false));
EXPECT_EQ(response_link_with_identifier,
task_->GetResponseLink(/* add_user_identifier= */ true));
EXPECT_NSEQ(nil, task_->GetError());
EXPECT_EQ(State::kComplete, task_->GetState());
// Test that expected histograms were recorded.
histogram_tester.ExpectUniqueSample(kDriveSearchFolderResultSuccessful, true,
1);
histogram_tester.ExpectUniqueSample(kDriveCreateFolderResultSuccessful, true,
0);
histogram_tester.ExpectUniqueSample(kDriveFileUploadResultSuccessful, true,
1);
}
// Tests that if the file upload fails, failure is correctly reported.
TEST_F(DriveUploadTaskTest, ReportsFileUploadFailure) {
base::HistogramTester histogram_tester;
// Set up the uploader to simulate non-empty search result, and file upload
// progress and unsuccessful completion.
uploader_->SetFolderSearchResult(
{.folder_identifier = @"test_folder_identifier", .error = nil});
const std::vector<DriveFileUploadProgress> progress_elements{
{0, 100},
{10, 100},
{25, 100},
{50, 100},
};
uploader_->SetFileUploadProgressElements(progress_elements);
NSError* file_upload_error = [NSError errorWithDomain:@"test_domain"
code:400
userInfo:nil];
uploader_->SetFileUploadResult(
{.file_link = nil, .error = file_upload_error});
// Set up the task with the name of the parent folder as well as the path,
// suggested name and MIME type of the file to upload.
task_->SetDestinationFolderName("test_folder_name");
const base::FilePath file_path_to_upload{"/test/path/of/file/to/upload"};
const base::FilePath file_suggested_name{"test_uploaded_file_name"};
task_->SetFileToUpload(file_path_to_upload, file_suggested_name,
"test_mime_type", 42);
// Test that a folder with appropriate name is searched.
uploader_->SetSearchFolderQuitClosure(task_environment_.QuitClosure());
EXPECT_EQ(State::kNotStarted, task_->GetState());
task_->Start();
EXPECT_EQ(State::kInProgress, task_->GetState());
task_environment_.RunUntilQuit();
EXPECT_NSEQ(@"test_folder_name", uploader_->GetSearchedFolderName());
// Test that the file parameters provided earlier are forwarded to the
// uploader.
EXPECT_NSEQ(base::apple::FilePathToNSURL(file_path_to_upload),
uploader_->GetUploadedFileUrl());
EXPECT_NSEQ(base::apple::FilePathToNSString(file_suggested_name),
uploader_->GetUploadedFileName());
EXPECT_NSEQ(@"test_mime_type", uploader_->GetUploadedFileMimeType());
// Test that the parent folder identifier is the one returned by the uploader.
EXPECT_NSEQ(@"test_folder_identifier",
uploader_->GetUploadedFileFolderIdentifier());
// Test that progress is reported as expected.
for (const DriveFileUploadProgress& progress : progress_elements) {
uploader_->SetUploadFileProgressQuitClosure(
task_environment_.QuitClosure());
observer_->ResetUpdatedUpload();
task_environment_.RunUntilQuit();
EXPECT_EQ(task_.get(), observer_->GetUpdatedUpload());
const float progress_float =
static_cast<float>(progress.total_bytes_uploaded) /
progress.total_bytes_expected_to_upload;
EXPECT_EQ(progress_float, task_->GetProgress());
}
// Test that the result is reported as expected.
uploader_->SetUploadFileCompletionQuitClosure(
task_environment_.QuitClosure());
observer_->ResetUpdatedUpload();
task_environment_.RunUntilQuit();
EXPECT_EQ(task_.get(), observer_->GetUpdatedUpload());
EXPECT_EQ(std::nullopt, task_->GetResponseLink());
EXPECT_NSEQ(file_upload_error, task_->GetError());
EXPECT_EQ(State::kFailed, task_->GetState());
// Test that expected histograms were recorded.
histogram_tester.ExpectUniqueSample(kDriveSearchFolderResultSuccessful, true,
1);
histogram_tester.ExpectUniqueSample(kDriveCreateFolderResultSuccessful, true,
0);
histogram_tester.ExpectUniqueSample(kDriveFileUploadResultSuccessful, false,
1);
histogram_tester.ExpectUniqueSample(kDriveFileUploadResultErrorCode, 400, 1);
}
// Tests that a task that is destroyed before being started records the expected
// histograms.
TEST_F(DriveUploadTaskTest, TaskNotStartedDestructorRecordsMetrics) {
base::HistogramTester histogram_tester;
// Give file to task.
task_->SetFileToUpload(base::FilePath("file_name.txt"),
base::FilePath("file_name.txt"), "text/plain",
500 * 1024); // 500 KB
// Destroy the upload task.
task_.reset();
// Check that all expected histograms are recorded.
histogram_tester.ExpectUniqueSample(kDriveUploadTaskFinalState,
UploadTaskStateHistogram::kNotStarted, 1);
histogram_tester.ExpectUniqueSample(
std::string(kDriveUploadTaskNumberOfAttempts) + ".NotStarted", 0, 1);
histogram_tester.ExpectUniqueSample(
std::string(kDriveUploadTaskMimeType) + ".NotStarted",
DownloadMimeTypeResult::Text, 1);
histogram_tester.ExpectUniqueSample(
std::string(kDriveUploadTaskFileSize) + ".NotStarted", 0 /* MB */, 1);
}
// Tests that a task that is destroyed before being started records the expected
// histograms.
TEST_F(DriveUploadTaskTest, TaskInProgressDestructorRecordsMetrics) {
base::HistogramTester histogram_tester;
// Give file to task.
task_->SetFileToUpload(base::FilePath("file_name.pdf"),
base::FilePath("file_name.pdf"), "application/pdf",
1 * 1024 * 1024); // 1 MB
// Start the task.
task_->Start();
// Destroy the upload task.
task_.reset();
// Check that all expected histograms are recorded.
histogram_tester.ExpectUniqueSample(kDriveUploadTaskFinalState,
UploadTaskStateHistogram::kCancelled, 1);
histogram_tester.ExpectUniqueSample(
std::string(kDriveUploadTaskNumberOfAttempts) + ".Cancelled", 1, 1);
histogram_tester.ExpectUniqueSample(
std::string(kDriveUploadTaskMimeType) + ".Cancelled",
DownloadMimeTypeResult::AdobePortableDocumentFormat, 1);
histogram_tester.ExpectUniqueSample(
std::string(kDriveUploadTaskFileSize) + ".Cancelled", 1 /* MB */, 1);
}
// Tests that a task that is destroyed after being started and then cancelled
// records the expected histograms.
TEST_F(DriveUploadTaskTest, TaskCancelledDestructorRecordsMetrics) {
base::HistogramTester histogram_tester;
// Give file to task.
task_->SetFileToUpload(base::FilePath("file_name.pdf"),
base::FilePath("file_name.pdf"), "application/pdf",
150 * 1024 * 1024); // 150 MB
// Start and then cancel the task.
task_->Start();
task_->Cancel();
// Destroy the upload task.
task_.reset();
// Check that all expected histograms are recorded.
histogram_tester.ExpectUniqueSample(kDriveUploadTaskFinalState,
UploadTaskStateHistogram::kCancelled, 1);
histogram_tester.ExpectUniqueSample(
std::string(kDriveUploadTaskNumberOfAttempts) + ".Cancelled", 1, 1);
histogram_tester.ExpectUniqueSample(
std::string(kDriveUploadTaskMimeType) + ".Cancelled",
DownloadMimeTypeResult::AdobePortableDocumentFormat, 1);
histogram_tester.ExpectUniqueSample(
std::string(kDriveUploadTaskFileSize) + ".Cancelled", 150 /* MB */, 1);
}
TEST_F(DriveUploadTaskTest, TaskCompleteDestructorRecordsMetrics) {
base::HistogramTester histogram_tester;
// Give file to task.
task_->SetFileToUpload(base::FilePath("file_name.pdf"),
base::FilePath("file_name.pdf"), "application/pdf",
5 * 1024 * 1024 * 1024LL); // 5 GB
// Start the task and wait until file upload completion.
task_->Start();
uploader_->SetUploadFileCompletionQuitClosure(
task_environment_.QuitClosure());
task_environment_.RunUntilQuit();
// Destroy the upload task.
task_.reset();
// Check that all expected histograms are recorded.
histogram_tester.ExpectUniqueSample(kDriveUploadTaskFinalState,
UploadTaskStateHistogram::kComplete, 1);
histogram_tester.ExpectUniqueSample(
std::string(kDriveUploadTaskNumberOfAttempts) + ".Complete", 1, 1);
histogram_tester.ExpectUniqueSample(
std::string(kDriveUploadTaskMimeType) + ".Complete",
DownloadMimeTypeResult::AdobePortableDocumentFormat, 1);
histogram_tester.ExpectUniqueSample(
std::string(kDriveUploadTaskFileSize) + ".Complete", 5 * 1024 /* MB */,
1);
}
TEST_F(DriveUploadTaskTest, TaskFailedDestructorRecordsMetrics) {
base::HistogramTester histogram_tester;
// Give file to task.
task_->SetFileToUpload(base::FilePath("file_name.pdf"),
base::FilePath("file_name.pdf"), "application/pdf",
1); // 1 B
// Start the task and wait until file upload fails.
task_->Start();
uploader_->SetUploadFileCompletionQuitClosure(
task_environment_.QuitClosure());
NSError* file_upload_error = [NSError errorWithDomain:@"test_domain"
code:400
userInfo:nil];
uploader_->SetFileUploadResult(
{.file_link = nil, .error = file_upload_error});
task_environment_.RunUntilQuit();
// Destroy the upload task.
task_.reset();
// Check that all expected histograms are recorded.
histogram_tester.ExpectUniqueSample(kDriveUploadTaskFinalState,
UploadTaskStateHistogram::kFailed, 1);
histogram_tester.ExpectUniqueSample(
std::string(kDriveUploadTaskNumberOfAttempts) + ".Failed", 1, 1);
histogram_tester.ExpectUniqueSample(
std::string(kDriveUploadTaskMimeType) + ".Failed",
DownloadMimeTypeResult::AdobePortableDocumentFormat, 1);
histogram_tester.ExpectUniqueSample(
std::string(kDriveUploadTaskFileSize) + ".Failed", 0 /* MB */, 1);
}