// 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/shared/public/features/features.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:
  DriveUploadTaskTest() = default;

  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, DanglingUntriaged> 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 file upload fails, failure is correctly reported.
TEST_F(DriveUploadTaskTest, ReportsFileUploadFailure) {
  base::HistogramTester histogram_tester;
  // Set up the uploader to simulate a successful client folder fetch.
  uploader_->SetClientFolderResult(
      {.folder_identifier = @"test_folder_identifier", .error = nil});

  // Set up the uploader to simulate file upload progress and unsuccessful
  // completion.
  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 the correct folder API is called.
  uploader_->SetFetchClientFolderQuitClosure(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_->GetFetchedClientFolderName());

  // 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(kDriveFetchClientFolderResultSuccessful,
                                      true, 1);
  histogram_tester.ExpectUniqueSample(kDriveFileUploadResultSuccessful, false,
                                      1);
  histogram_tester.ExpectUniqueSample(kDriveFileUploadResultErrorCode, 400, 1);
}

// Tests that `FetchSaveToDriveClientFolder()` is called on the uploader.
TEST_F(DriveUploadTaskTest, FetchesClientFolder) {
  base::HistogramTester histogram_tester;

  // Set up the uploader to simulate a successful client folder fetch.
  uploader_->SetClientFolderResult(
      {.folder_identifier = @"test_client_folder_identifier", .error = nil});

  // Set up the task.
  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 `FetchSaveToDriveClientFolder` is called.
  uploader_->SetFetchClientFolderQuitClosure(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_->GetFetchedClientFolderName());

  // Test that the file is uploaded to the client folder.
  EXPECT_NSEQ(@"test_client_folder_identifier",
              uploader_->GetUploadedFileFolderIdentifier());

  // Test that expected histograms were recorded.
  histogram_tester.ExpectUniqueSample(kDriveFetchClientFolderResultSuccessful,
                                      true, 1);
  histogram_tester.ExpectTotalCount(kDriveFetchClientFolderResultErrorCode, 0);
}

// Tests that if fetching the client folder fails, the task also fails.
TEST_F(DriveUploadTaskTest, FetchesClientFolderFailure) {
  base::HistogramTester histogram_tester;

  // Set up the uploader to simulate a failed client folder fetch.
  NSError* client_folder_error = [NSError errorWithDomain:@"test_domain"
                                                     code:401
                                                 userInfo:nil];
  uploader_->SetClientFolderResult(
      {.folder_identifier = nil, .error = client_folder_error});

  // Set up the task.
  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 `FetchSaveToDriveClientFolder` is called and that the task fails.
  uploader_->SetFetchClientFolderQuitClosure(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_->GetFetchedClientFolderName());
  EXPECT_EQ(State::kFailed, task_->GetState());
  EXPECT_NSEQ(client_folder_error, task_->GetError());

  // Test that expected histograms were recorded.
  histogram_tester.ExpectUniqueSample(kDriveFetchClientFolderResultSuccessful,
                                      false, 1);
  histogram_tester.ExpectUniqueSample(kDriveFetchClientFolderResultErrorCode,
                                      401, 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);
}

// Tests that if GetResponseLink fails, the failure is correctly reported in
// histograms.
TEST_F(DriveUploadTaskTest, ReportsGetResponseLinkFailure) {
  base::HistogramTester histogram_tester;
  // Set up the uploader to simulate successful upload but with missing file
  // link.
  const std::vector<DriveFileUploadProgress> progress_elements{
      {100, 100},
  };
  uploader_->SetFileUploadProgressElements(progress_elements);

  // Case 1: missing result.
  // Set up the task.
  task_->SetDestinationFolderName("test_folder_name");
  task_->SetFileToUpload(base::FilePath("/test/path"), base::FilePath("name"),
                         "test_mime_type", 42);

  task_->Start();
  uploader_->SetFileUploadResult({.file_link = nil, .error = nil});
  uploader_->SetUploadFileCompletionQuitClosure(
      task_environment_.QuitClosure());
  task_environment_.RunUntilQuit();

  // Case 1: The upload succeeds but the file link is missing.
  // `kMissingFileLink` should be recorded.
  EXPECT_EQ(std::nullopt, task_->GetResponseLink());
  histogram_tester.ExpectBucketCount(
      "IOS.SaveToDrive.UploadTask.GetResponseLinkFailure",
      GetResponseLinkFailure::kMissingFileLink, 1);

  // Case 2: The upload result is missing (task is reset).
  // `kMissingResult` should be recorded.
  task_ = std::make_unique<DriveUploadTask>(
      std::make_unique<TestDriveFileUploader>(uploading_identity_));
  EXPECT_EQ(std::nullopt, task_->GetResponseLink());
  histogram_tester.ExpectBucketCount(
      "IOS.SaveToDrive.UploadTask.GetResponseLinkFailure",
      GetResponseLinkFailure::kMissingResult, 1);
}
