chromium/ios/chrome/browser/download/ui_bundled/download_manager_mediator_unittest.mm

// Copyright 2018 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/download/ui_bundled/download_manager_mediator.h"

#import <UIKit/UIKit.h>

#import "base/apple/foundation_util.h"
#import "base/run_loop.h"
#import "base/test/ios/wait_util.h"
#import "base/test/scoped_feature_list.h"
#import "ios/chrome/browser/download/model/document_download_tab_helper.h"
#import "ios/chrome/browser/download/model/download_directory_util.h"
#import "ios/chrome/browser/download/model/download_manager_tab_helper.h"
#import "ios/chrome/browser/download/model/external_app_util.h"
#import "ios/chrome/browser/shared/public/features/features.h"
#import "ios/chrome/test/fakes/fake_download_manager_consumer.h"
#import "ios/web/public/test/fakes/fake_download_task.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#import "ios/web/public/test/web_task_environment.h"
#import "net/base/net_errors.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"
#import "third_party/ocmock/OCMock/OCMock.h"

using base::test::ios::WaitUntilConditionOrTimeout;

namespace {

// Constants for configuring a fake download task.
const char kTestUrl[] = "https://chromium.test/download.txt";
const char kTestMimeType[] = "text/html";
const int64_t kTestTotalBytes = 10;
const int64_t kTestReceivedBytes = 0;
const base::FilePath::CharType kTestSuggestedFileName[] =
    FILE_PATH_LITERAL("important_file.zip");

}  // namespace

// Test fixture for testing DownloadManagerMediator class.
class DownloadManagerMediatorTest : public PlatformTest {
 protected:
  DownloadManagerMediatorTest()
      : consumer_([[FakeDownloadManagerConsumer alloc] init]),
        application_(OCMClassMock([UIApplication class])),
        task_(GURL(kTestUrl), kTestMimeType) {
    OCMStub([application_ sharedApplication]).andReturn(application_);
  }
  ~DownloadManagerMediatorTest() override {
    [application_ stopMocking];
  }

  web::FakeDownloadTask* task() { return &task_; }

  DownloadManagerMediator mediator_;
  FakeDownloadManagerConsumer* consumer_;
  id application_;

 private:
  web::WebTaskEnvironment task_environment_;
  web::FakeDownloadTask task_;
};

// Tests starting the download and immediately destroying the task.
// DownloadManagerMediator should not crash.
TEST_F(DownloadManagerMediatorTest, DestoryTaskAfterStart) {
  auto task =
      std::make_unique<web::FakeDownloadTask>(GURL(kTestUrl), kTestMimeType);
  mediator_.SetDownloadTask(task.get());
  mediator_.StartDownloading();
  task.reset();
}

// Tests starting the download. Verifies that download task is started and its
// file writer is configured to write into Chrome's temporary download
// directory.
TEST_F(DownloadManagerMediatorTest, StartTempDownload) {
  task()->SetGeneratedFileName(base::FilePath(kTestSuggestedFileName));
  mediator_.SetDownloadTask(task());
  mediator_.SetConsumer(consumer_);
  mediator_.StartDownloading();

  // Starting download is async for task and sync for consumer.
  EXPECT_EQ(kDownloadManagerStateInProgress, consumer_.state);
  EXPECT_FALSE(consumer_.installDriveButtonVisible);
  ASSERT_TRUE(
      WaitUntilConditionOrTimeout(base::test::ios::kWaitForDownloadTimeout, ^{
        base::RunLoop().RunUntilIdle();
        return task()->GetState() == web::DownloadTask::State::kInProgress;
      }));

  // Download file should be located in tmp directory during the download.
  base::FilePath tmp_dir;
  ASSERT_TRUE(GetTempDownloadsDirectory(&tmp_dir));
  EXPECT_TRUE(tmp_dir.IsParent(task()->GetResponsePath()));

  // Once downloaded, the file should be located in download directory.
  task()->SetDone(true);
  base::FilePath download_dir;
  GetDownloadsDirectory(&download_dir);
  EXPECT_EQ(kDownloadManagerStateSucceeded, consumer_.state);
  EXPECT_TRUE(download_dir.IsParent(mediator_.GetDownloadPath()));
}

// Tests starting the download. Verifies that download task is started and its
// file writer is configured to write into Chrome's Documents download
// directory.
TEST_F(DownloadManagerMediatorTest, StartDownload) {
  task()->SetGeneratedFileName(base::FilePath(kTestSuggestedFileName));
  mediator_.SetDownloadTask(task());
  mediator_.SetConsumer(consumer_);
  mediator_.StartDownloading();

  // Starting download is async for task and sync for consumer.
  EXPECT_EQ(kDownloadManagerStateInProgress, consumer_.state);
  EXPECT_FALSE(consumer_.installDriveButtonVisible);
  ASSERT_TRUE(
      WaitUntilConditionOrTimeout(base::test::ios::kWaitForDownloadTimeout, ^{
        base::RunLoop().RunUntilIdle();
        return task()->GetState() == web::DownloadTask::State::kInProgress;
      }));

  task()->SetDone(true);
  EXPECT_EQ(kDownloadManagerStateSucceeded, consumer_.state);
  // Download file should be located in download directory.
  base::FilePath download_dir;
  GetDownloadsDirectory(&download_dir);
  ASSERT_TRUE(
      WaitUntilConditionOrTimeout(base::test::ios::kWaitForDownloadTimeout, ^{
        base::RunLoop().RunUntilIdle();
        return download_dir.IsParent(mediator_.GetDownloadPath());
      }));

  // Updates the consumer once the file has been moved.
  mediator_.SetDownloadTask(task());
}

// Tests that consumer is updated right after it's set.
TEST_F(DownloadManagerMediatorTest, ConsumerInstantUpdate) {
  OCMStub([application_ canOpenURL:GetGoogleDriveAppUrl()]).andReturn(YES);

  task()->SetGeneratedFileName(base::FilePath(kTestSuggestedFileName));
  mediator_.SetDownloadTask(task());
  mediator_.SetConsumer(consumer_);
  mediator_.StartDownloading();

  // Starting download is async for task and sync for consumer.
  EXPECT_EQ(kDownloadManagerStateInProgress, consumer_.state);
  ASSERT_TRUE(
      WaitUntilConditionOrTimeout(base::test::ios::kWaitForDownloadTimeout, ^{
        base::RunLoop().RunUntilIdle();
        return task()->GetState() == web::DownloadTask::State::kInProgress;
      }));

  task()->SetDone(true);
  task()->SetTotalBytes(kTestTotalBytes);
  task()->SetReceivedBytes(kTestReceivedBytes);
  task()->SetPercentComplete(80);

  mediator_.SetDownloadTask(task());
  mediator_.SetConsumer(consumer_);

  EXPECT_EQ(kDownloadManagerStateSucceeded, consumer_.state);
  EXPECT_FALSE(consumer_.installDriveButtonVisible);
  EXPECT_EQ(base::FilePath(kTestSuggestedFileName),
            base::apple::NSStringToFilePath(consumer_.fileName));
  EXPECT_EQ(kTestTotalBytes, consumer_.countOfBytesExpectedToReceive);
  EXPECT_EQ(kTestReceivedBytes, consumer_.countOfBytesReceived);
  EXPECT_FLOAT_EQ(0.8f, consumer_.progress);
}

// Tests that consumer changes the state to kDownloadManagerStateFailed if task
// competed with an error.
TEST_F(DownloadManagerMediatorTest, ConsumerFailedStateUpdate) {
  mediator_.SetDownloadTask(task());
  mediator_.SetConsumer(consumer_);

  task()->SetErrorCode(net::ERR_INTERNET_DISCONNECTED);
  task()->SetState(web::DownloadTask::State::kFailed);
  EXPECT_EQ(kDownloadManagerStateFailed, consumer_.state);
  EXPECT_FALSE(consumer_.installDriveButtonVisible);
}

// Tests that consumer changes the state to kDownloadManagerStateSucceeded if
// task competed without an error.
TEST_F(DownloadManagerMediatorTest, ConsumerSuceededStateUpdate) {
  OCMStub([application_ canOpenURL:GetGoogleDriveAppUrl()]).andReturn(YES);

  task()->SetGeneratedFileName(base::FilePath(kTestSuggestedFileName));
  mediator_.SetDownloadTask(task());
  mediator_.SetConsumer(consumer_);
  mediator_.StartDownloading();

  // Starting download is async for task and sync for consumer.
  EXPECT_EQ(kDownloadManagerStateInProgress, consumer_.state);
  ASSERT_TRUE(
      WaitUntilConditionOrTimeout(base::test::ios::kWaitForDownloadTimeout, ^{
        base::RunLoop().RunUntilIdle();
        return task()->GetState() == web::DownloadTask::State::kInProgress;
      }));

  task()->SetDone(true);
  EXPECT_EQ(kDownloadManagerStateSucceeded, consumer_.state);
  EXPECT_FALSE(consumer_.installDriveButtonVisible);
}

// Tests that consumer changes the state to kDownloadManagerStateSucceeded if
// task competed without an error and Google Drive app is not installed.
TEST_F(DownloadManagerMediatorTest,
       ConsumerSuceededStateUpdateWithoutDriveAppInstalled) {
  OCMStub([application_ canOpenURL:GetGoogleDriveAppUrl()]).andReturn(NO);

  task()->SetGeneratedFileName(base::FilePath(kTestSuggestedFileName));
  mediator_.SetDownloadTask(task());
  mediator_.SetConsumer(consumer_);
  mediator_.StartDownloading();

  // Starting download is async for task and sync for consumer.
  EXPECT_EQ(kDownloadManagerStateInProgress, consumer_.state);
  ASSERT_TRUE(
      WaitUntilConditionOrTimeout(base::test::ios::kWaitForDownloadTimeout, ^{
        base::RunLoop().RunUntilIdle();
        return task()->GetState() == web::DownloadTask::State::kInProgress;
      }));

  task()->SetDone(true);
  EXPECT_EQ(kDownloadManagerStateSucceeded, consumer_.state);
  EXPECT_TRUE(consumer_.installDriveButtonVisible);
}

// Tests that consumer changes the state to kDownloadManagerStateInProgress if
// the task has started.
TEST_F(DownloadManagerMediatorTest, ConsumerInProgressStateUpdate) {
  mediator_.SetDownloadTask(task());
  mediator_.SetConsumer(consumer_);

  task()->Start(base::FilePath());
  EXPECT_EQ(kDownloadManagerStateInProgress, consumer_.state);
  EXPECT_FALSE(consumer_.installDriveButtonVisible);
  EXPECT_EQ(0.0, consumer_.progress);
}

// Tests that setting the consumer twice when the download is complete will only
// move it once.
TEST_F(DownloadManagerMediatorTest, SetConsumerAfterDownloadComplete) {
  task()->SetGeneratedFileName(base::FilePath(kTestSuggestedFileName));
  mediator_.SetDownloadTask(task());
  mediator_.SetConsumer(consumer_);
  mediator_.StartDownloading();

  // Starting download is async for task and sync for consumer.
  EXPECT_EQ(kDownloadManagerStateInProgress, consumer_.state);
  EXPECT_FALSE(consumer_.installDriveButtonVisible);
  ASSERT_TRUE(
      WaitUntilConditionOrTimeout(base::test::ios::kWaitForDownloadTimeout, ^{
        base::RunLoop().RunUntilIdle();
        return task()->GetState() == web::DownloadTask::State::kInProgress;
      }));

  // Download file should be located in tmp directory during the download.
  base::FilePath tmp_dir;
  ASSERT_TRUE(GetTempDownloadsDirectory(&tmp_dir));
  EXPECT_TRUE(tmp_dir.IsParent(task()->GetResponsePath()));

  // Once downloaded, the file should be located in download directory.
  task()->SetDone(true);
  base::FilePath download_dir;
  base::FilePath file_path = mediator_.GetDownloadPath();
  GetDownloadsDirectory(&download_dir);
  EXPECT_EQ(kDownloadManagerStateSucceeded, consumer_.state);
  EXPECT_TRUE(download_dir.IsParent(file_path));

  // Set the consumer a second time.
  mediator_.SetConsumer(consumer_);
  EXPECT_EQ(kDownloadManagerStateSucceeded, consumer_.state);
  EXPECT_TRUE(download_dir.IsParent(file_path));
  EXPECT_EQ(file_path, mediator_.GetDownloadPath());
}

// Tests that calling `mediator_.SetGoogleDriveAppInstalled()` does inform the
// consumer accordingly.
TEST_F(DownloadManagerMediatorTest, SetGoogleDriveAppInstalled) {
  base::test::ScopedFeatureList feature_list(kIOSSaveToDrive);

  // Add WebState to the task with the required tab helpers.
  web::FakeWebState web_state;
  DocumentDownloadTabHelper::CreateForWebState(&web_state);
  DownloadManagerTabHelper::CreateForWebState(&web_state);
  task()->SetWebState(&web_state);
  mediator_.SetDownloadTask(task());
  mediator_.SetConsumer(consumer_);

  // Set Google Drive app installed.
  mediator_.SetGoogleDriveAppInstalled(true);
  mediator_.UpdateConsumer();
  EXPECT_FALSE(consumer_.installDriveButtonVisible);

  // Set Google Drive app not installed.
  mediator_.SetGoogleDriveAppInstalled(false);
  mediator_.UpdateConsumer();
  EXPECT_TRUE(consumer_.installDriveButtonVisible);

  // Set Google Drive app installed again.
  mediator_.SetGoogleDriveAppInstalled(true);
  mediator_.UpdateConsumer();
  EXPECT_FALSE(consumer_.installDriveButtonVisible);
}