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

#include "components/sqlite_vfs/sqlite_sandboxed_vfs.h"

#include <memory>
#include <utility>

#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/test/gmock_expected_support.h"
#include "base/test/gtest_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/types/expected_macros.h"
#include "components/sqlite_vfs/client.h"
#include "components/sqlite_vfs/file_type.h"
#include "components/sqlite_vfs/pending_file_set.h"
#include "components/sqlite_vfs/sqlite_database_vfs_file_set.h"
#include "components/sqlite_vfs/vfs_utils.h"
#include "sql/database.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/sqlite/sqlite3.h"

namespace sqlite_vfs {

namespace {

const char kNonExistentVirtualFilePath[] = "NotFound";

}  // namespace

class SqliteSandboxedVfsTest : public testing::Test {
 protected:
  static constexpr int kOpenFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_MAIN_DB;

  void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }

  std::optional<SqliteVfsFileSet> CreateFilesAndBuildVfsFileSet(
      bool single_connection = false) {
    std::optional<SqliteVfsFileSet> file_set;
    if (auto pending_file_set = MakePendingFileSet(
            Client::kTest, temp_dir_.GetPath(), base::FilePath(kTestBaseName),
            single_connection, /*journal_mode_wal=*/false);
        !pending_file_set.has_value()) {
      ADD_FAILURE() << "Failed creating pending file set";
    } else {
      file_set =
          SqliteVfsFileSet::Bind(Client::kTest, *std::move(pending_file_set));
      EXPECT_NE(file_set, std::nullopt) << "Failed binding to pending file set";
    }
    return file_set;
  }

  // Returns a read-only view of the same database as `file_set`.
  std::optional<SqliteVfsFileSet> GetReadOnlyVfsFileSet(
      const SqliteVfsFileSet& file_set) {
    std::optional<SqliteVfsFileSet> read_only_file_set;
    if (auto pending_file_set = ShareConnection(
            temp_dir_.GetPath(), base::FilePath(kTestBaseName), file_set,
            /*read_write=*/false);
        !pending_file_set.has_value()) {
      ADD_FAILURE() << "Failed sharing file set";
    } else {
      read_only_file_set =
          SqliteVfsFileSet::Bind(Client::kTest, *std::move(pending_file_set));
      EXPECT_NE(read_only_file_set, std::nullopt)
          << "Failed binding to pending file set";
    }
    return read_only_file_set;
  }

 private:
  static constexpr base::FilePath::StringViewType kTestBaseName =
      FILE_PATH_LITERAL("TEST");

  base::ScopedTempDir temp_dir_;
};

TEST_F(SqliteSandboxedVfsTest, NoAccessWithoutRegistering) {
  ASSERT_OK_AND_ASSIGN(SqliteVfsFileSet vfs_file_set,
                       CreateFilesAndBuildVfsFileSet());
  EXPECT_FALSE(
      SqliteSandboxedVfsDelegate::GetInstance()
          ->OpenFile(base::FilePath::FromASCII(kNonExistentVirtualFilePath),
                     kOpenFlags)
          .IsValid());
}

TEST_F(SqliteSandboxedVfsTest, AccessAfterRegistering) {
  ASSERT_OK_AND_ASSIGN(SqliteVfsFileSet vfs_file_set,
                       CreateFilesAndBuildVfsFileSet());

  SqliteSandboxedVfsDelegate::UnregisterRunner unregister_runner =
      SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
          vfs_file_set);

  EXPECT_TRUE(SqliteSandboxedVfsDelegate::GetInstance()
                  ->OpenFile(vfs_file_set.GetDbVirtualFilePath(),
                             SQLITE_OPEN_READWRITE | SQLITE_OPEN_MAIN_DB)
                  .IsValid());
  EXPECT_TRUE(SqliteSandboxedVfsDelegate::GetInstance()
                  ->OpenFile(vfs_file_set.GetJournalVirtualFilePath(),
                             SQLITE_OPEN_READWRITE | SQLITE_OPEN_MAIN_JOURNAL)
                  .IsValid());
}

TEST_F(SqliteSandboxedVfsTest, NoAccessAfterUnregistering) {
  ASSERT_OK_AND_ASSIGN(SqliteVfsFileSet vfs_file_set,
                       CreateFilesAndBuildVfsFileSet());

  // Register and immediately unregister.
  {
    SqliteSandboxedVfsDelegate::UnregisterRunner unregister_runner =
        SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
            vfs_file_set);
  }

  EXPECT_FALSE(SqliteSandboxedVfsDelegate::GetInstance()
                   ->OpenFile(vfs_file_set.GetDbVirtualFilePath(),
                              SQLITE_OPEN_READWRITE | SQLITE_OPEN_MAIN_DB)
                   .IsValid());
  EXPECT_FALSE(SqliteSandboxedVfsDelegate::GetInstance()
                   ->OpenFile(vfs_file_set.GetJournalVirtualFilePath(),
                              SQLITE_OPEN_READWRITE | SQLITE_OPEN_MAIN_JOURNAL)
                   .IsValid());
}

TEST_F(SqliteSandboxedVfsTest, AccessAfterReRegistering) {
  ASSERT_OK_AND_ASSIGN(SqliteVfsFileSet vfs_file_set,
                       CreateFilesAndBuildVfsFileSet());

  // Register and immediately unregister.
  {
    SqliteSandboxedVfsDelegate::UnregisterRunner unregister_runner =
        SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
            vfs_file_set);
  }

  // Register again.
  SqliteSandboxedVfsDelegate::UnregisterRunner unregister_runner =
      SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
          vfs_file_set);

  EXPECT_TRUE(SqliteSandboxedVfsDelegate::GetInstance()
                  ->OpenFile(vfs_file_set.GetDbVirtualFilePath(),
                             SQLITE_OPEN_READWRITE | SQLITE_OPEN_MAIN_DB)
                  .IsValid());
  EXPECT_TRUE(SqliteSandboxedVfsDelegate::GetInstance()
                  ->OpenFile(vfs_file_set.GetJournalVirtualFilePath(),
                             SQLITE_OPEN_READWRITE | SQLITE_OPEN_MAIN_JOURNAL)
                  .IsValid());
}

TEST_F(SqliteSandboxedVfsTest, DeleteFile) {
  ASSERT_OK_AND_ASSIGN(SqliteVfsFileSet vfs_file_set,
                       CreateFilesAndBuildVfsFileSet());

  // Impossible to delete non-registered files.
  EXPECT_EQ(SqliteSandboxedVfsDelegate::GetInstance()->DeleteFile(
                vfs_file_set.GetDbVirtualFilePath(), /*sync_dir=*/true),
            SQLITE_NOTFOUND);
  EXPECT_EQ(SqliteSandboxedVfsDelegate::GetInstance()->DeleteFile(
                vfs_file_set.GetJournalVirtualFilePath(), /*sync_dir=*/true),
            SQLITE_NOTFOUND);

  SqliteSandboxedVfsDelegate::UnregisterRunner unregister_runner =
      SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
          vfs_file_set);

  // Get a file to test with and open it.
  const base::FilePath file_path_to_delete =
      vfs_file_set.GetDbVirtualFilePath();
  SandboxedFile* file_to_delete = vfs_file_set.GetSandboxedDbFile();
  file_to_delete->OnFileOpened(
      file_to_delete->TakeUnderlyingFile(FileType::kMainDb));
  EXPECT_TRUE(file_to_delete->IsValid());

  // Write something to the file.
  const std::string content = "hello";
  EXPECT_EQ(file_to_delete->Write(content.data(), content.size(), 0),
            SQLITE_OK);

  // Deleting the open file succeeds and empties the file.
  EXPECT_EQ(SqliteSandboxedVfsDelegate::GetInstance()->DeleteFile(
                file_path_to_delete, true),
            SQLITE_OK);
  EXPECT_EQ(file_to_delete->OpenedFileForTesting().GetLength(), 0);

  // Write to the file again, then close it.
  EXPECT_EQ(file_to_delete->Write(content.data(), content.size(), 0),
            SQLITE_OK);
  file_to_delete->Close();
  EXPECT_FALSE(file_to_delete->IsValid());

  // Deleting the closed file succeeds and empties the file.
  EXPECT_EQ(SqliteSandboxedVfsDelegate::GetInstance()->DeleteFile(
                file_path_to_delete, true),
            SQLITE_OK);
  EXPECT_EQ(file_to_delete->UnderlyingFileForTesting().GetLength(), 0);
}

TEST_F(SqliteSandboxedVfsTest, DeleteFileFails) {
  // Get read/write and read-only views of a db and register them.
  ASSERT_OK_AND_ASSIGN(SqliteVfsFileSet vfs_file_set,
                       CreateFilesAndBuildVfsFileSet());
  SqliteSandboxedVfsDelegate::UnregisterRunner unregister_runner =
      SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
          vfs_file_set);

  ASSERT_OK_AND_ASSIGN(SqliteVfsFileSet read_only_vfs_file_set,
                       GetReadOnlyVfsFileSet(vfs_file_set));
  SqliteSandboxedVfsDelegate::UnregisterRunner read_only_unregister_runner =
      SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
          read_only_vfs_file_set);

  // Write some data to the db file and close it.
  {
    const base::FilePath file_path_to_delete =
        vfs_file_set.GetDbVirtualFilePath();
    SandboxedFile* file_to_delete = vfs_file_set.GetSandboxedDbFile();
    file_to_delete->OnFileOpened(
        file_to_delete->TakeUnderlyingFile(FileType::kMainDb));
    EXPECT_TRUE(file_to_delete->IsValid());
    const std::string_view content = "hello";
    EXPECT_EQ(file_to_delete->Write(content.data(), content.size(), 0),
              SQLITE_OK);
    file_to_delete->Close();
  }

  // Deleting from the read-only view should fail.
  base::HistogramTester histogram_tester;
  {
    const base::FilePath file_path_to_delete =
        read_only_vfs_file_set.GetDbVirtualFilePath();
    EXPECT_EQ(SqliteSandboxedVfsDelegate::GetInstance()->DeleteFile(
                  file_path_to_delete, true),
              SQLITE_IOERR_DELETE);
  }
  // The exact error reported differs by platform, so accept any value.
  histogram_tester.ExpectTotalCount("SandboxedVfs.DbFile.SetLengthResult.Test",
                                    1);
}

TEST_F(SqliteSandboxedVfsTest, OpenFile) {
  ASSERT_OK_AND_ASSIGN(SqliteVfsFileSet vfs_file_set,
                       CreateFilesAndBuildVfsFileSet());

  int64_t length = 0;
  vfs_file_set.GetSandboxedDbFile()->UnderlyingFileForTesting().SetLength(
      length += 100);
  vfs_file_set.GetSandboxedJournalFile()->UnderlyingFileForTesting().SetLength(
      length += 100);

  SqliteSandboxedVfsDelegate::UnregisterRunner unregister_runner =
      SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
          vfs_file_set);

  const struct {
    SandboxedFile* (SqliteVfsFileSet::*get_sandboxed_file_fn)() const;
    base::FilePath (SqliteVfsFileSet::*get_sandboxed_file_path_fn)() const;
    int open_flags;
  } kAccessors[] = {
      {&SqliteVfsFileSet::GetSandboxedDbFile,
       &SqliteVfsFileSet::GetDbVirtualFilePath,
       SQLITE_OPEN_READWRITE | SQLITE_OPEN_MAIN_DB},
      {&SqliteVfsFileSet::GetSandboxedJournalFile,
       &SqliteVfsFileSet::GetJournalVirtualFilePath,
       SQLITE_OPEN_READWRITE | SQLITE_OPEN_MAIN_JOURNAL},
  };

  for (const auto& accessors : kAccessors) {
    SandboxedFile* const sandboxed_file =
        (vfs_file_set.*accessors.get_sandboxed_file_fn)();
    const base::FilePath sandboxed_file_path =
        (vfs_file_set.*accessors.get_sandboxed_file_path_fn)();
    int open_flags = accessors.open_flags;
    base::File::Info info_from_map;
    sandboxed_file->UnderlyingFileForTesting().GetInfo(&info_from_map);

    // Simulate an open from the VFS.
    base::File from_delegate =
        SqliteSandboxedVfsDelegate::GetInstance()->OpenFile(sandboxed_file_path,
                                                            open_flags);

    base::File::Info info_from_delegate;
    from_delegate.GetInfo(&info_from_delegate);
    EXPECT_EQ(info_from_delegate.size, info_from_map.size);

    // Simulate the binding done by the VFS.
    sandboxed_file->OnFileOpened(std::move(from_delegate));

    base::File::Info info_from_opened_file;
    sandboxed_file->OpenedFileForTesting().GetInfo(&info_from_opened_file);
    EXPECT_EQ(info_from_opened_file.size, info_from_map.size);
  }
}

TEST_F(SqliteSandboxedVfsTest, SqliteIntegration) {
  ASSERT_OK_AND_ASSIGN(SqliteVfsFileSet vfs_file_set,
                       CreateFilesAndBuildVfsFileSet());
  SqliteSandboxedVfsDelegate::UnregisterRunner unregister_runner =
      SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
          vfs_file_set);

  sql::Database db(sql::DatabaseOptions().set_vfs_name_discouraged(
                       SqliteSandboxedVfsDelegate::kSqliteVfsName),
                   "Test");
  EXPECT_TRUE(db.Open(vfs_file_set.GetDbVirtualFilePath()));
}

TEST_F(SqliteSandboxedVfsTest, RegisterSandboxedFilesSharedSuccess) {
  ASSERT_OK_AND_ASSIGN(SqliteVfsFileSet vfs_file_set_1,
                       CreateFilesAndBuildVfsFileSet());
  ASSERT_OK_AND_ASSIGN(SqliteVfsFileSet vfs_file_set_2,
                       GetReadOnlyVfsFileSet(vfs_file_set_1));

  ASSERT_EQ(vfs_file_set_1.GetSharedLock().GetGUID(),
            vfs_file_set_2.GetSharedLock().GetGUID());

  SqliteSandboxedVfsDelegate::UnregisterRunner unregister_runner_1 =
      SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
          vfs_file_set_1);

  SqliteSandboxedVfsDelegate::UnregisterRunner unregister_runner_2 =
      SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
          vfs_file_set_2);
}

// These tests trigger a CHECK(NotFatalUntil), so they only crash unofficial
// builds.
#if !defined(OFFICIAL_BUILD)
using SqliteSandboxedVfsDeathTest = SqliteSandboxedVfsTest;

TEST_F(SqliteSandboxedVfsDeathTest, RegisterSandboxedFilesDuplicateFailure) {
  ASSERT_OK_AND_ASSIGN(SqliteVfsFileSet vfs_file_set_1,
                       CreateFilesAndBuildVfsFileSet());
  ASSERT_OK_AND_ASSIGN(SqliteVfsFileSet vfs_file_set_2,
                       CreateFilesAndBuildVfsFileSet());

  ASSERT_NE(vfs_file_set_1.GetSharedLock().GetGUID(),
            vfs_file_set_2.GetSharedLock().GetGUID());

  SqliteSandboxedVfsDelegate::UnregisterRunner unregister_runner_1 =
      SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
          vfs_file_set_1);

  EXPECT_CHECK_DEATH({
    auto runner =
        SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
            vfs_file_set_2);
  });
}

#if BUILDFLAG(IS_POSIX)
TEST_F(SqliteSandboxedVfsDeathTest,
       RegisterSandboxedFilesSingleConnectionFailure) {
  ASSERT_OK_AND_ASSIGN(
      SqliteVfsFileSet vfs_file_set_1,
      CreateFilesAndBuildVfsFileSet(/*single_connection=*/true));
  ASSERT_OK_AND_ASSIGN(
      SqliteVfsFileSet vfs_file_set_2,
      CreateFilesAndBuildVfsFileSet(/*single_connection=*/true));

  SqliteSandboxedVfsDelegate::UnregisterRunner unregister_runner_1 =
      SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
          vfs_file_set_1);

  EXPECT_CHECK_DEATH({
    auto runner =
        SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
            vfs_file_set_2);
  });
}

TEST_F(SqliteSandboxedVfsDeathTest,
       RegisterSandboxedFilesSingleAndSharedConnectionFailure) {
  ASSERT_OK_AND_ASSIGN(
      SqliteVfsFileSet vfs_file_set_1,
      CreateFilesAndBuildVfsFileSet(/*single_connection=*/false));
  ASSERT_OK_AND_ASSIGN(
      SqliteVfsFileSet vfs_file_set_2,
      CreateFilesAndBuildVfsFileSet(/*single_connection=*/true));

  SqliteSandboxedVfsDelegate::UnregisterRunner unregister_runner_1 =
      SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
          vfs_file_set_1);

  EXPECT_CHECK_DEATH({
    auto runner =
        SqliteSandboxedVfsDelegate::GetInstance()->RegisterSandboxedFiles(
            vfs_file_set_2);
  });
}
#endif  // BUILDFLAG(IS_POSIX)
#endif  // !defined(OFFICIAL_BUILD)

}  // namespace sqlite_vfs
