// 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/updater/net/fallback_net_fetcher.h"

#include <cstdint>
#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/test/bind.h"
#include "components/update_client/network.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

namespace updater {
namespace {

class FakeFetcher : public update_client::NetworkFetcher {
 public:
  FakeFetcher() = delete;
  FakeFetcher(const FakeFetcher&) = delete;
  FakeFetcher& operator=(const FakeFetcher&) = delete;
  FakeFetcher(
      base::OnceCallback<
          void(update_client::NetworkFetcher::PostRequestCompleteCallback)>
          post_request,
      base::OnceCallback<base::OnceClosure(
          update_client::NetworkFetcher::DownloadToFileCompleteCallback)>
          download_to_file)
      : post_request_(std::move(post_request)),
        download_to_file_(std::move(download_to_file)) {}

  // Overrides for update_client::NetworkFetcher
  void PostRequest(
      const GURL& url,
      const std::string& post_data,
      const std::string& content_type,
      const base::flat_map<std::string, std::string>& post_additional_headers,
      update_client::NetworkFetcher::ResponseStartedCallback
          response_started_callback,
      update_client::NetworkFetcher::ProgressCallback progress_callback,
      update_client::NetworkFetcher::PostRequestCompleteCallback
          post_request_complete_callback) override {
    std::move(post_request_).Run(std::move(post_request_complete_callback));
  }

  base::OnceClosure DownloadToFile(
      const GURL& url,
      const base::FilePath& file_path,
      update_client::NetworkFetcher::ResponseStartedCallback
          response_started_callback,
      update_client::NetworkFetcher::ProgressCallback progress_callback,
      update_client::NetworkFetcher::DownloadToFileCompleteCallback
          download_to_file_complete_callback) override {
    return std::move(download_to_file_)
        .Run(std::move(download_to_file_complete_callback));
  }

 private:
  base::OnceCallback<void(
      update_client::NetworkFetcher::PostRequestCompleteCallback)>
      post_request_;
  base::OnceCallback<base::OnceClosure(
      update_client::NetworkFetcher::DownloadToFileCompleteCallback)>
      download_to_file_;
};

std::unique_ptr<FakeFetcher> MakeFakeFetcherForPost(
    base::OnceCallback<int(void)> error_supplier) {
  return std::make_unique<FakeFetcher>(
      base::BindOnce(
          [](base::OnceCallback<int(void)> error_supplier,
             update_client::NetworkFetcher::PostRequestCompleteCallback
                 callback) {
            std::move(callback).Run(
                std::nullopt, std::move(error_supplier).Run(), {}, {}, {}, 0);
          },
          std::move(error_supplier)),
      base::BindOnce(
          [](update_client::NetworkFetcher::DownloadToFileCompleteCallback
                 callback) {
            std::move(callback).Run(0, 0);
            return base::BindOnce([] {});
          }));
}

std::unique_ptr<FakeFetcher> MakeFakeFetcherForDownload(
    base::OnceCallback<int(void)> error_supplier) {
  return std::make_unique<FakeFetcher>(
      base::BindOnce(
          [](update_client::NetworkFetcher::PostRequestCompleteCallback
                 callback) {
            std::move(callback).Run(std::nullopt, 0, {}, {}, {}, 0);
          }),
      base::BindOnce(
          [](base::OnceCallback<int(void)> error_supplier,
             update_client::NetworkFetcher::DownloadToFileCompleteCallback
                 callback) {
            std::move(callback).Run(std::move(error_supplier).Run(), 0);
            return base::BindOnce([] {});
          },
          std::move(error_supplier)));
}

}  // namespace

TEST(FallbackNetFetcher, NoFallbackOnSuccess_Post) {
  bool ran1 = false;
  bool ran2 = false;
  bool called_back = false;
  FallbackNetFetcher(MakeFakeFetcherForPost(base::BindLambdaForTesting([&] {
                       ran1 = true;
                       return 0;
                     })),
                     MakeFakeFetcherForPost(base::BindLambdaForTesting([&] {
                       ran2 = true;
                       return 0;
                     })))
      .PostRequest({}, {}, {}, {}, base::DoNothing(), base::DoNothing(),
                   base::BindLambdaForTesting(
                       [&](std::optional<std::string>, int, const std::string&,
                           const std::string&, const std::string&,
                           int64_t) { called_back = true; }));
  EXPECT_TRUE(ran1);
  EXPECT_FALSE(ran2);
  EXPECT_TRUE(called_back);
}

TEST(FallbackNetFetcher, NoFallbackOnSuccess_Download) {
  bool ran1 = false;
  bool ran2 = false;
  bool called_back = false;
  FallbackNetFetcher(MakeFakeFetcherForDownload(base::BindLambdaForTesting([&] {
                       ran1 = true;
                       return 0;
                     })),
                     MakeFakeFetcherForDownload(base::BindLambdaForTesting([&] {
                       ran2 = true;
                       return 0;
                     })))
      .DownloadToFile({}, {}, base::DoNothing(), base::DoNothing(),
                      base::BindLambdaForTesting(
                          [&](int, int64_t) { called_back = true; }));
  EXPECT_TRUE(ran1);
  EXPECT_FALSE(ran2);
  EXPECT_TRUE(called_back);
}

TEST(FallbackNetFetcher, FallbackOnFailure_Post) {
  bool ran1 = false;
  bool ran2 = false;
  bool called_back = false;
  FallbackNetFetcher(MakeFakeFetcherForPost(base::BindLambdaForTesting([&] {
                       ran1 = true;
                       return 1;
                     })),
                     MakeFakeFetcherForPost(base::BindLambdaForTesting([&] {
                       ran2 = true;
                       return 0;
                     })))
      .PostRequest({}, {}, {}, {}, base::DoNothing(), base::DoNothing(),
                   base::BindLambdaForTesting(
                       [&](std::optional<std::string>, int, const std::string&,
                           const std::string&, const std::string&,
                           int64_t) { called_back = true; }));
  EXPECT_TRUE(ran1);
  EXPECT_TRUE(ran2);
  EXPECT_TRUE(called_back);
}

TEST(FallbackNetFetcher, FallbackOnFailure_Download) {
  bool ran1 = false;
  bool ran2 = false;
  bool called_back = false;
  FallbackNetFetcher(MakeFakeFetcherForDownload(base::BindLambdaForTesting([&] {
                       ran1 = true;
                       return 1;
                     })),
                     MakeFakeFetcherForDownload(base::BindLambdaForTesting([&] {
                       ran2 = true;
                       return 0;
                     })))
      .DownloadToFile({}, {}, base::DoNothing(), base::DoNothing(),
                      base::BindLambdaForTesting(
                          [&](int, int64_t) { called_back = true; }));
  EXPECT_TRUE(ran1);
  EXPECT_TRUE(ran2);
  EXPECT_TRUE(called_back);
}

TEST(FallbackNetFetcher, NoCrashOnNullptr_Post) {
  bool ran1 = false;
  bool called_back = false;
  FallbackNetFetcher(MakeFakeFetcherForPost(base::BindLambdaForTesting([&] {
                       ran1 = true;
                       return 1;
                     })),
                     nullptr)
      .PostRequest({}, {}, {}, {}, base::DoNothing(), base::DoNothing(),
                   base::BindLambdaForTesting(
                       [&](std::optional<std::string>, int, const std::string&,
                           const std::string&, const std::string&,
                           int64_t) { called_back = true; }));
  EXPECT_TRUE(ran1);
  EXPECT_TRUE(called_back);
}

TEST(FallbackNetFetcher, NoCrashOnNullptr_Download) {
  bool ran1 = false;
  bool called_back = false;
  FallbackNetFetcher(MakeFakeFetcherForDownload(base::BindLambdaForTesting([&] {
                       ran1 = true;
                       return 1;
                     })),
                     nullptr)
      .DownloadToFile({}, {}, base::DoNothing(), base::DoNothing(),
                      base::BindLambdaForTesting(
                          [&](int, int64_t) { called_back = true; }));
  EXPECT_TRUE(ran1);
  EXPECT_TRUE(called_back);
}

}  // namespace updater
