// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_DOWNLOAD_DOWNLOAD_BROWSERTEST_UTILS_H_
#define CHROME_BROWSER_DOWNLOAD_DOWNLOAD_BROWSERTEST_UTILS_H_

#include <stddef.h>

#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/memory/raw_ptr.h"
#include "chrome/browser/download/download_test_file_activity_observer.h"
#include "chrome/browser/web_applications/test/os_integration_test_override_impl.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/slow_download_http_response.h"
#include "content/public/test/test_download_http_response.h"
#include "content/public/test/test_file_error_injector.h"
#include "extensions/browser/install_verifier.h"
#include "extensions/browser/scoped_ignore_content_verifier_for_test.h"
#include "ui/base/window_open_disposition.h"

class BrowserWindowInterface;
class DownloadPrefs;

// Gets the download manager for a browser.
content::DownloadManager* DownloadManagerForBrowser(
    BrowserWindowInterface* browser);

// Sets the kPromptForDownload pref on `browser`. Generally this should be used
// with `prompt_for_download` false, as prompting for download location in a
// browser test will make the download time out.
void SetPromptForDownload(BrowserWindowInterface* browser,
                          bool prompt_for_download);

// DownloadTestObserver subclass that observes one download until it transitions
// from a non-resumable state to a resumable state a specified number of
// times. Note that this observer can only observe a single download.
class DownloadTestObserverResumable : public content::DownloadTestObserver {
 public:
  // Construct a new observer. |transition_count| is the number of times the
  // download should transition from a non-resumable state to a resumable state.
  DownloadTestObserverResumable(content::DownloadManager* download_manager,
                                size_t transition_count);

  DownloadTestObserverResumable(const DownloadTestObserverResumable&) = delete;
  DownloadTestObserverResumable& operator=(
      const DownloadTestObserverResumable&) = delete;

  ~DownloadTestObserverResumable() override;

 private:
  bool IsDownloadInFinalState(download::DownloadItem* download) override;

  bool was_previously_resumable_ = false;
  size_t transitions_left_;
};

// DownloadTestObserver subclass that observes a download until it transitions
// from IN_PROGRESS to another state, but only after StartObserving() is called.
class DownloadTestObserverNotInProgress : public content::DownloadTestObserver {
 public:
  DownloadTestObserverNotInProgress(content::DownloadManager* download_manager,
                                    size_t count);

  DownloadTestObserverNotInProgress(const DownloadTestObserverNotInProgress&) =
      delete;
  DownloadTestObserverNotInProgress& operator=(
      const DownloadTestObserverNotInProgress&) = delete;

  ~DownloadTestObserverNotInProgress() override;

  void StartObserving();

 private:
  bool IsDownloadInFinalState(download::DownloadItem* download) override;

  bool started_observing_;
};

class DownloadTestBase : public InProcessBrowserTest {
 public:
  // Choice of navigation or direct fetch.  Used by |DownloadFileCheckErrors()|.
  enum DownloadMethod { DOWNLOAD_NAVIGATE, DOWNLOAD_DIRECT };

  // Information passed in to |DownloadFileCheckErrors()|.
  struct DownloadInfo {
    const char* starting_url;           // URL for initiating the download.
    const char* expected_download_url;  // Expected value of DI::GetURL(). Can
                                        // be different if |starting_url|
                                        // initiates a download from another
                                        // URL.
    DownloadMethod download_method;     // Navigation or Direct.
    // Download interrupt reason (NONE is OK).
    download::DownloadInterruptReason reason;
    bool show_download_item;  // True if the download item appears on the shelf.
    bool should_redirect_to_documents;  // True if we save it in "My Documents".
  };

  struct FileErrorInjectInfo {
    DownloadInfo download_info;
    content::TestFileErrorInjector::FileErrorInfo error_info;
  };

  static constexpr char kDownloadTest1Path[] = "download-test1.lib";
#if BUILDFLAG(IS_WIN)
  static constexpr char kDangerousMockFilePath[] =
      "/downloads/dangerous/dangerous.exe";
#elif BUILDFLAG(IS_POSIX)
  static constexpr char kDangerousMockFilePath[] =
      "/downloads/dangerous/dangerous.sh";
#endif

  DownloadTestBase();
  ~DownloadTestBase() override;

  void SetUpOnMainThread() override;
  void SetUpCommandLine(base::CommandLine* command_line) override;
  void TearDownOnMainThread() override;

  bool CheckTestDir();

  // Returning false indicates a failure of the setup, and should be asserted
  // in the caller.
  bool InitialSetup();

 protected:
  enum SizeTestType {
    SIZE_TEST_TYPE_KNOWN,
    SIZE_TEST_TYPE_UNKNOWN,
  };

  base::FilePath GetTestDataDirectory();

  // Location of the file source (the place from which it is downloaded).
  base::FilePath OriginFile(const base::FilePath& file);

  // Location of the file destination (place to which it is downloaded).
  base::FilePath DestinationFile(BrowserWindowInterface* browser,
                                 const base::FilePath& file);

  content::TestDownloadResponseHandler* test_response_handler();

  DownloadPrefs* GetDownloadPrefs(BrowserWindowInterface* browser);

  base::FilePath GetDownloadDirectory(BrowserWindowInterface* browser);

  // Create a DownloadTestObserverTerminal that will wait for the
  // specified number of downloads to finish.
  content::DownloadTestObserver* CreateWaiter(BrowserWindowInterface* browser,
                                              int num_downloads);

  // Create a DownloadTestObserverInProgress that will wait for the
  // specified number of downloads to start.
  content::DownloadTestObserver* CreateInProgressWaiter(
      BrowserWindowInterface* browser,
      int num_downloads);

  // Create a DownloadTestObserverTerminal that will wait for the
  // specified number of downloads to finish, or for
  // a dangerous download warning to be shown.
  content::DownloadTestObserver* DangerousDownloadWaiter(
      BrowserWindowInterface* browser,
      int num_downloads,
      content::DownloadTestObserver::DangerousDownloadAction
          dangerous_download_action);

  void CheckDownloadStatesForBrowser(
      BrowserWindowInterface* browser,
      size_t num,
      download::DownloadItem::DownloadState state);

  void CheckDownloadStates(size_t num,
                           download::DownloadItem::DownloadState state);

  bool VerifyNoDownloads() const;

  // Download |url|, then wait for the download to finish.
  // |disposition| indicates where the navigation occurs (current tab, new
  // foreground tab, etc).
  // |browser_test_flags| indicate what to wait for, and is an OR of 0 or more
  // values in the ui_test_utils::BrowserTestWaitFlags enum.
  // |prompt_for_download| indicates whether to prompt for the download location
  // and should generally be false, since the download location prompt can
  // cause the browser test to time out.
  void DownloadAndWaitWithDisposition(BrowserWindowInterface* browser,
                                      const GURL& url,
                                      WindowOpenDisposition disposition,
                                      int browser_test_flags,
                                      bool prompt_for_download = false);

  // Download a file in the current tab, then wait for the download to finish.
  void DownloadAndWait(BrowserWindowInterface* browser,
                       const GURL& url,
                       bool prompt_for_download = false);

  // Should only be called when the download is known to have finished
  // (in error or not).
  // Returning false indicates a failure of the function, and should be asserted
  // in the caller.
  bool CheckDownload(BrowserWindowInterface* browser,
                     const base::FilePath& downloaded_filename,
                     const base::FilePath& origin_filename);

  // A version of CheckDownload that allows complete path specification.
  bool CheckDownloadFullPaths(BrowserWindowInterface* browser,
                              const base::FilePath& downloaded_file,
                              const base::FilePath& origin_file);

  // Creates an in-progress download and returns a pointer to its DownloadItem.
  // Either supply a `browser` or the `browser()` in the test fixture will be
  // used.
  download::DownloadItem* CreateSlowTestDownload(
      BrowserWindowInterface* browser = nullptr);

  bool RunSizeTest(BrowserWindowInterface* browser,
                   SizeTestType type,
                   const std::string& partial_indication,
                   const std::string& total_indication);

  void GetDownloads(
      BrowserWindowInterface* browser,
      std::vector<raw_ptr<download::DownloadItem, VectorExperimental>>*
          downloads) const;

  static void ExpectWindowCountAfterDownload(size_t expected);

  void EnableFileChooser(bool enable);
  bool DidShowFileChooser();

  // Allows the ChromeDownloadManagerDelegate to open downloads.
  void SetAllowOpenDownload(bool allow);

  // Checks that |path| is has |file_size| bytes, and matches the |value|
  // string.
  bool VerifyFile(const base::FilePath& path,
                  const std::string& value,
                  const int64_t file_size);

  // Attempts to download a file, based on information in |download_info|.
  // If a Select File dialog opens, will automatically choose the default.
  void DownloadFilesCheckErrorsSetup();

  // Attempts to download a set of files, based on information in the
  // |download_info| array.  |count| is the number of files.
  // If a Select File dialog appears, it will choose the default and return
  // immediately.
  void DownloadFilesCheckErrors(base::span<DownloadInfo> download_info);
  void DownloadFilesCheckErrorsLoopBody(const DownloadInfo& download_info,
                                        size_t i);

  void DownloadInsertFilesErrorCheckErrors(
      base::span<FileErrorInjectInfo> info);
  void DownloadInsertFilesErrorCheckErrorsLoopBody(
      scoped_refptr<content::TestFileErrorInjector> injector,
      const FileErrorInjectInfo& info,
      size_t i);

  // Attempts to download a file to a read-only folder, based on information
  // in |download_info|.
  void DownloadFilesToReadonlyFolder(base::span<DownloadInfo> download_info);

  // This method:
  // * Starts a mock download by navigating to embedded test server URL.
  // * Injects |error| on the first write using |error_injector|.
  // * Waits for the download to be interrupted.
  // * Clears the errors on |error_injector|.
  // * Returns the resulting interrupted download.
  download::DownloadItem* StartMockDownloadAndInjectError(
      content::TestFileErrorInjector* error_injector,
      download::DownloadInterruptReason error);

  // Provide equivalent to embedded_test_server() with a variant that uses HTTPS
  // to avoid insecure download warnings.
  net::EmbeddedTestServer* https_test_server() {
    return https_test_server_.get();
  }

 private:
  web_app::OsIntegrationTestOverrideBlockingRegistration faked_os_integration_;

  // Location of the test data.
  base::FilePath test_dir_;

  content::TestDownloadResponseHandler test_response_handler_;
  std::unique_ptr<DownloadTestFileActivityObserver> file_activity_observer_;
  extensions::ScopedIgnoreContentVerifierForTest ignore_content_verifier_;
  extensions::ScopedInstallVerifierBypassForTest ignore_install_verification_;

  // By default, the embedded test server uses HTTP. Keep an HTTPS server
  // as well so that we can avoid unexpected insecure download warnings.
  std::unique_ptr<net::EmbeddedTestServer> https_test_server_;
};

#endif  // CHROME_BROWSER_DOWNLOAD_DOWNLOAD_BROWSERTEST_UTILS_H_
