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

#include "extensions/browser/file_reader.h"

#include <limits>
#include <optional>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "components/crx_file/id_util.h"
#include "extensions/common/extension_id.h"
#include "extensions/common/extension_paths.h"
#include "extensions/common/extension_resource.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace extensions {

class FileReaderTest : public testing::Test {
 public:
  FileReaderTest() {}

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

 private:
  base::test::TaskEnvironment task_environment_;
};

class Receiver {
 public:
  explicit Receiver(
      std::vector<ExtensionResource> resources,
      size_t max_resources_length = std::numeric_limits<size_t>::max())
      : file_reader_(base::MakeRefCounted<FileReader>(
            std::move(resources),
            max_resources_length,
            FileReader::OptionalFileSequenceTask(),
            base::BindOnce(&Receiver::DidReadFile, base::Unretained(this)))) {}

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

  void Run() {
    file_reader_->Start();
    run_loop_.Run();
  }

  // Removes the pointer indirection from the read data for use with
  // comparators.

  const std::optional<std::string>& error() const { return error_; }
  bool succeeded() const { return !error_; }
  const std::vector<std::string>& data() const { return data_; }

 private:
  void DidReadFile(std::vector<std::string> data,
                   std::optional<std::string> error) {
    error_ = std::move(error);
    data_ = std::move(data);
    run_loop_.QuitWhenIdle();
  }

  std::optional<std::string> error_;
  std::vector<std::string> data_;
  scoped_refptr<FileReader> file_reader_;
  base::RunLoop run_loop_;
};

void RunBasicTest(const std::vector<std::string>& filenames) {
  base::FilePath root_path;
  base::PathService::Get(DIR_TEST_DATA, &root_path);
  ExtensionId extension_id = crx_file::id_util::GenerateId("test");

  std::vector<ExtensionResource> resources;
  resources.reserve(filenames.size());
  std::vector<std::string> expected_contents;
  expected_contents.reserve(filenames.size());
  for (const auto& filename : filenames) {
    resources.emplace_back(extension_id, root_path,
                           base::FilePath().AppendASCII(filename));

    base::FilePath path = root_path.AppendASCII(filename);
    std::string file_contents;
    ASSERT_TRUE(base::ReadFileToString(path, &file_contents));
    expected_contents.push_back(std::move(file_contents));
  }

  Receiver receiver(resources);
  receiver.Run();

  EXPECT_TRUE(receiver.succeeded()) << *receiver.error();
  EXPECT_THAT(receiver.data(), ::testing::ElementsAreArray(expected_contents));
}

TEST_F(FileReaderTest, SmallFile) {
  RunBasicTest({"smallfile"});
}

TEST_F(FileReaderTest, BiggerFile) {
  RunBasicTest({"bigfile"});
}

TEST_F(FileReaderTest, MultiFile) {
  RunBasicTest({"smallfile", "bigfile"});
}

TEST_F(FileReaderTest, NonExistentFile) {
  base::FilePath path;
  base::PathService::Get(DIR_TEST_DATA, &path);
  ExtensionId extension_id = crx_file::id_util::GenerateId("test");
  ExtensionResource resource(
      extension_id, path,
      base::FilePath(FILE_PATH_LITERAL("file_that_does_not_exist")));

  Receiver receiver({resource});
  receiver.Run();

  EXPECT_FALSE(receiver.succeeded());
  EXPECT_EQ("Could not load file: 'file_that_does_not_exist'.",
            *receiver.error());
}

TEST_F(FileReaderTest, AboveSizeLimitFile) {
  base::FilePath path;
  base::PathService::Get(DIR_TEST_DATA, &path);
  ExtensionId extension_id = crx_file::id_util::GenerateId("test");

  ExtensionResource resource(extension_id, path,
                             base::FilePath().AppendASCII("bigfile"));

  Receiver receiver({resource}, /*max_resources_length=*/100u);
  receiver.Run();

  EXPECT_FALSE(receiver.succeeded());
  EXPECT_EQ("Could not load file: 'bigfile'. Resource size exceeded.",
            *receiver.error());
}

}  // namespace extensions
