// 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.

#import "ios/chrome/browser/sessions/model/session_internal_util.h"

#import "base/files/file_enumerator.h"
#import "base/files/scoped_temp_dir.h"
#import "ios/chrome/browser/sessions/model/proto/storage.pb.h"
#import "ios/chrome/browser/sessions/model/proto_util.h"
#import "testing/gtest_mac.h"
#import "testing/platform_test.h"

using SessionInternalUtilTest = PlatformTest;

namespace {

// Constants used to construct filenames.
const base::FilePath::CharType kFilename[] = FILE_PATH_LITERAL("file");
const base::FilePath::CharType kDirname1[] = FILE_PATH_LITERAL("dir1");
const base::FilePath::CharType kDirname2[] = FILE_PATH_LITERAL("dir2");
const base::FilePath::CharType kDirname3[] = FILE_PATH_LITERAL("dir3");
const base::FilePath::CharType kFromName[] = FILE_PATH_LITERAL("from");
const base::FilePath::CharType kDestName[] = FILE_PATH_LITERAL("dest");

// Returns the list of item at `path`.
std::set<base::FilePath> DirectoryContent(const base::FilePath& path) {
  using FileEnumerator = base::FileEnumerator;
  constexpr int all_items =
      FileEnumerator::FileType::FILES | FileEnumerator::FileType::DIRECTORIES;

  std::set<base::FilePath> result;
  FileEnumerator e(path, false, all_items);
  for (base::FilePath name = e.Next(); !name.empty(); name = e.Next()) {
    result.insert(e.GetInfo().GetName());
  }

  return result;
}

// Compares the content of two path and check they are identical (recursively).
bool PathAreIdentical(const base::FilePath& lhs, const base::FilePath& rhs) {
  // If both path are file, check whether they have the same content.
  if (ios::sessions::FileExists(lhs) && ios::sessions::FileExists(rhs)) {
    NSData* lhs_data = ios::sessions::ReadFile(lhs);
    NSData* rhs_data = ios::sessions::ReadFile(rhs);
    return [lhs_data isEqualToData:rhs_data];
  }

  // If either path is not a directory, then they content is not identical.
  if (!ios::sessions::DirectoryExists(lhs) ||
      !ios::sessions::DirectoryExists(rhs)) {
    return false;
  }

  // Both paths are directory, check the content recursively.
  const std::set<base::FilePath> lhs_names = DirectoryContent(lhs);
  const std::set<base::FilePath> rhs_names = DirectoryContent(rhs);
  if (lhs_names != rhs_names) {
    return false;
  }

  // If the list of items are identical, compare them recursively.
  for (const base::FilePath& name : lhs_names) {
    if (!PathAreIdentical(lhs.Append(name), rhs.Append(name))) {
      return false;
    }
  }
  return true;
}

}  // namespace

// Tests that `FileExists` return true if the path corresponds to an existing
// file or false otherwise (e.g. corresponds to a directory, or does not exist).
TEST_F(SessionInternalUtilTest, FileExists) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  // Check that FileExists() returns false if the file does not exist.
  EXPECT_FALSE(ios::sessions::FileExists(root.Append(kFilename)));

  // Create a file and check that FileExists() returns true.
  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  EXPECT_TRUE(ios::sessions::WriteFile(root.Append(kFilename), data));
  EXPECT_TRUE(ios::sessions::FileExists(root.Append(kFilename)));

  // Create a directory and check that FileExists() returns false.
  EXPECT_TRUE(ios::sessions::CreateDirectory(root.Append(kDirname1)));
  EXPECT_FALSE(ios::sessions::FileExists(root.Append(kDirname1)));
}

// Tests that `DirectoryExists` return true if the path corresponds to an
// existing directory or false otherwise (e.g. corresponds to a file, or does
// not exist).
TEST_F(SessionInternalUtilTest, DirectoryExists) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  // Check that DirectoryExists() returns false if the file does not exist.
  EXPECT_FALSE(ios::sessions::DirectoryExists(root.Append(kDirname1)));

  // Create a directory and check that DirectoryExists() returns true.
  EXPECT_TRUE(ios::sessions::CreateDirectory(root.Append(kDirname1)));
  EXPECT_TRUE(ios::sessions::DirectoryExists(root.Append(kDirname1)));

  // Create a file and check that DirectoryExists() returns true.
  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  EXPECT_TRUE(ios::sessions::WriteFile(root.Append(kFilename), data));
  EXPECT_FALSE(ios::sessions::DirectoryExists(root.Append(kFilename)));
}

// Tests that `RenameFile` correctly move a file.
TEST_F(SessionInternalUtilTest, RenameFile) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath from = root.Append(kDirname1).Append(kFilename);
  const base::FilePath dest = root.Append(kDirname2).Append(kFilename);

  // Create a file in a sub-directory.
  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  EXPECT_TRUE(ios::sessions::WriteFile(from, data));

  // Check that moving the file is a success, and that the file content
  // is the expected one.
  EXPECT_TRUE(ios::sessions::RenameFile(from, dest));
  NSData* read = ios::sessions::ReadFile(dest);
  EXPECT_NSEQ(read, data);
}

// Tests that `RenameFile` fails if it cannot create destination directory.
TEST_F(SessionInternalUtilTest, RenameFile_FailureCreatingDirectory) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath from = root.Append(kDirname1).Append(kFilename);
  const base::FilePath dest = root.Append(kDirname2).Append(kFilename);

  // Create a file in a sub-directory.
  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  EXPECT_TRUE(ios::sessions::WriteFile(from, data));

  // Create a file at the path `dest.DirName()` which will cause the
  // call to `CreateDirectory` to fail.
  EXPECT_TRUE(ios::sessions::WriteFile(dest.DirName(), data));

  // Check that trying to move fail while trying to create the directory.
  EXPECT_FALSE(ios::sessions::RenameFile(from, dest));
}

// Tests that `RenameFile` fails if it cannot write the destination file.
TEST_F(SessionInternalUtilTest, RenameFile_FailureRenamingFile) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath from = root.Append(kDirname1).Append(kFilename);
  const base::FilePath dest = root.Append(kDirname2).Append(kFilename);

  // Check that trying to move a non-existent file fails.
  EXPECT_FALSE(ios::sessions::RenameFile(from, dest));
}

// Tests that `CreateDirectory` correctly create the directory, or return
// a success if the destination exists and is a directory.
TEST_F(SessionInternalUtilTest, CreateDirectory) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath dir = root.Append(kDirname1).Append(kDirname2);
  EXPECT_FALSE(ios::sessions::DirectoryExists(dir));
  EXPECT_FALSE(ios::sessions::DirectoryExists(dir.DirName()));

  // Check that creating the directory succeed and that the directory
  // exists after the successful creation. If should have created the
  // parent directory too.
  EXPECT_TRUE(ios::sessions::CreateDirectory(dir));
  EXPECT_TRUE(ios::sessions::DirectoryExists(dir));
  EXPECT_TRUE(ios::sessions::DirectoryExists(dir.DirName()));

  // Check that trying to create an existing directory result in a success.
  EXPECT_TRUE(ios::sessions::CreateDirectory(dir));
}

// Tests that `CreateDirectory` returns false in case of failure.
TEST_F(SessionInternalUtilTest, CreateDirectory_Failure) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath file = root.Append(kFilename);

  // Create a file at the path where we will try to create the directory.
  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  EXPECT_TRUE(ios::sessions::WriteFile(file, data));
  EXPECT_TRUE(ios::sessions::FileExists(file));

  // Check that trying to create a directory at a path where a file exists
  // results in a failure to create the directory.
  const base::FilePath dir = file.Append(kDirname2);
  EXPECT_FALSE(ios::sessions::CreateDirectory(dir));
  EXPECT_FALSE(ios::sessions::DirectoryExists(dir));
}

// Tests that `DirectoryEmpty` returns true if the directory exists and is
// empty, false otherwise (not a directory or a directory that is not empty).
TEST_F(SessionInternalUtilTest, DirectoryEmpty) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath dir = root.Append(kDirname1);
  const base::FilePath file = dir.Append(kFilename);

  // Check that `DirectoryEmpty` returns false if the path is not a directory
  // (e.g. the path does not exists).
  EXPECT_FALSE(ios::sessions::DirectoryEmpty(dir));

  // Check that `DirectoryEmpty` returns true if the path is a directory and
  // the directory is empty (i.e. just created with no file inside).
  EXPECT_TRUE(ios::sessions::CreateDirectory(dir));
  EXPECT_TRUE(ios::sessions::DirectoryEmpty(dir));

  // Create a file inside `dir` and check that `DirectoryEmpty` now returns
  // false.
  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  EXPECT_TRUE(ios::sessions::WriteFile(file, data));
  EXPECT_TRUE(ios::sessions::FileExists(file));
  EXPECT_FALSE(ios::sessions::DirectoryEmpty(dir));
}

// Tests that `DeleteRecursively` correctly remove a file/directory and in
// the case of a directory, all its content recursively.
TEST_F(SessionInternalUtilTest, DeleteRecursively) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath dir = root.Append(kDirname1);
  const base::FilePath file = dir.Append(kDirname2).Append(kFilename);

  // Create a file deeply nested.
  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  EXPECT_TRUE(ios::sessions::WriteFile(file, data));
  EXPECT_TRUE(ios::sessions::FileExists(file));

  // Check that deleting `dir` delete everything below.
  EXPECT_TRUE(ios::sessions::DeleteRecursively(dir));
  EXPECT_FALSE(ios::sessions::DirectoryExists(dir));
  EXPECT_FALSE(ios::sessions::FileExists(file));

  // Create a file deeply nested.
  EXPECT_TRUE(ios::sessions::WriteFile(file, data));
  EXPECT_TRUE(ios::sessions::FileExists(file));

  // Check that deleting `file` only delete the file and nothing else.
  EXPECT_TRUE(ios::sessions::DeleteRecursively(file));
  EXPECT_TRUE(ios::sessions::DirectoryExists(dir.DirName()));
  EXPECT_TRUE(ios::sessions::DirectoryExists(dir));
  EXPECT_FALSE(ios::sessions::FileExists(file));
}

// Tests that `DeleteRecursively` returns false in case of failure.
TEST_F(SessionInternalUtilTest, DeleteRecursively_Failure) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath dir = root.Append(kDirname1);
  const base::FilePath file = dir.Append(kDirname2).Append(kFilename);

  // Check that trying to delete a non-existent file fails.
  EXPECT_FALSE(ios::sessions::FileExists(file));
  EXPECT_FALSE(ios::sessions::DeleteRecursively(file));
}

// Tests that `CopyDirectory` correctly copy the directory structure
// recursively.
TEST_F(SessionInternalUtilTest, CopyDirectory) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  // Create the source directory with some sub-directories and files.
  const base::FilePath from = root.Append(kFromName);
  const base::FilePath from_dir1 = from.Append(kDirname1);
  const base::FilePath from_dir2 = from.Append(kDirname2);

  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  ASSERT_TRUE(ios::sessions::WriteFile(from_dir1.Append(kFilename), data));
  ASSERT_TRUE(ios::sessions::WriteFile(from_dir2.Append(kFilename), data));

  // Check that copying recursively to inexistent destination works.
  const base::FilePath dest = root.Append(kDestName);
  EXPECT_TRUE(ios::sessions::CopyDirectory(from, dest));
  EXPECT_TRUE(PathAreIdentical(from, dest));
}

// Tests that `CopyDirectory` correctly replaces the content of the target
// directory if it exists.
TEST_F(SessionInternalUtilTest, CopyDirectory_OverExistingDirectory) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  // Create the source directory with some sub-directories and files.
  const base::FilePath from = root.Append(kFromName);
  const base::FilePath from_dir1 = from.Append(kDirname1);
  const base::FilePath from_dir2 = from.Append(kDirname2);

  NSData* data0 = [@"data0" dataUsingEncoding:NSUTF8StringEncoding];
  ASSERT_TRUE(ios::sessions::WriteFile(from_dir1.Append(kFilename), data0));
  ASSERT_TRUE(ios::sessions::WriteFile(from_dir2.Append(kFilename), data0));

  // Create the target directory with a different content.
  const base::FilePath dest = root.Append(kDestName);
  const base::FilePath dest_dir3 = from.Append(kDirname3);

  NSData* data1 = [@"data1" dataUsingEncoding:NSUTF8StringEncoding];
  ASSERT_TRUE(ios::sessions::WriteFile(dest_dir3.Append(kFilename), data1));
  ASSERT_TRUE(ios::sessions::WriteFile(dest.Append(kFilename), data1));

  // Check that both directories have distinct content.
  ASSERT_FALSE(PathAreIdentical(from, dest));

  // Check that copying recursively to existing directory works and erase
  // all content in des.
  EXPECT_TRUE(ios::sessions::CopyDirectory(from, dest));
  EXPECT_TRUE(PathAreIdentical(from, dest));
}

// Tests that `CopyDirectory` succeeds even if the destination requires
// creating the parent directories.
TEST_F(SessionInternalUtilTest, CopyDirectory_TargetNestedInNonExistentDir) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  // Create the source directory with some sub-directories and files.
  const base::FilePath from = root.Append(kFromName);
  const base::FilePath from_dir1 = from.Append(kDirname1);
  const base::FilePath from_dir2 = from.Append(kDirname2);

  NSData* data0 = [@"data0" dataUsingEncoding:NSUTF8StringEncoding];
  ASSERT_TRUE(ios::sessions::WriteFile(from_dir1.Append(kFilename), data0));
  ASSERT_TRUE(ios::sessions::WriteFile(from_dir2.Append(kFilename), data0));

  // Use a destination directory that is deeply nested and change that the
  // copy succeed (and has the same content as the source).
  const base::FilePath deep = root.Append(kDirname1).Append(kDirname2);
  ASSERT_FALSE(ios::sessions::DirectoryExists(deep));
  ASSERT_FALSE(ios::sessions::DirectoryExists(deep.DirName()));

  const base::FilePath dest = deep.Append(kDestName);
  EXPECT_TRUE(ios::sessions::CopyDirectory(from, dest));
  EXPECT_TRUE(PathAreIdentical(from, dest));
}

// Tests that `CopyDirectory` fails if target is a file.
TEST_F(SessionInternalUtilTest, CopyDirectory_FailureDestinationIsAFile) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  // Create the source directory with some sub-directories and files.
  const base::FilePath from = root.Append(kFromName);
  const base::FilePath from_dir1 = from.Append(kDirname1);
  const base::FilePath from_dir2 = from.Append(kDirname2);

  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  ASSERT_TRUE(ios::sessions::WriteFile(from_dir1.Append(kFilename), data));
  ASSERT_TRUE(ios::sessions::WriteFile(from_dir2.Append(kFilename), data));

  // Create a file with the same name as target directory.
  const base::FilePath dest = root.Append(kDestName);
  ASSERT_TRUE(ios::sessions::WriteFile(dest, data));

  // Check that trying to copy source over a file fails.
  EXPECT_FALSE(ios::sessions::CopyDirectory(from, dest));
}

// Tests that `CopyDirectory` fails if source is a file.
TEST_F(SessionInternalUtilTest, CopyDirectory_FailureSourceNotADirectory) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  // Create a file named like source.
  const base::FilePath from = root.Append(kFromName);
  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  ASSERT_TRUE(ios::sessions::WriteFile(from, data));

  // Check that CopyDirectory fails when the source is a file.
  const base::FilePath dest = root.Append(kDestName);
  EXPECT_FALSE(ios::sessions::CopyDirectory(from, dest));
}

// Tests that `CopyDirectory` fails if it cannot create the parent of the
// target directory.
TEST_F(SessionInternalUtilTest, CopyDirectory_FailureCannotCreateTargetParent) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  // Create the source directory with some sub-directories and files.
  const base::FilePath from = root.Append(kFromName);
  const base::FilePath from_dir1 = from.Append(kDirname1);
  const base::FilePath from_dir2 = from.Append(kDirname2);

  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  ASSERT_TRUE(ios::sessions::WriteFile(from_dir1.Append(kFilename), data));
  ASSERT_TRUE(ios::sessions::WriteFile(from_dir2.Append(kFilename), data));

  // Use a destination directory that is deeply nested.
  const base::FilePath deep = root.Append(kDirname1).Append(kDirname2);
  const base::FilePath dest = deep.Append(kDestName);
  ASSERT_FALSE(ios::sessions::DirectoryExists(deep));
  ASSERT_FALSE(ios::sessions::DirectoryExists(deep.DirName()));

  // Create a file in the location of the target parent directory. This
  // should cause the creation of the parent directory to fail and thus
  // the failure of the copy.
  ASSERT_TRUE(ios::sessions::WriteFile(deep, data));

  // Check that the copy failed and that the file that was in the way
  // has not been modified.
  EXPECT_FALSE(ios::sessions::CopyDirectory(from, dest));
  EXPECT_NSEQ(ios::sessions::ReadFile(deep), data);
}

// Tests that `CopyFile` returns success if the file can be copied.
TEST_F(SessionInternalUtilTest, CopyFile) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath from = root.Append(kDirname1).Append(kFromName);
  const base::FilePath dest = root.Append(kDirname2).Append(kDestName);

  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  ASSERT_TRUE(ios::sessions::WriteFile(from, data));

  // Check that copying the file leave the source file intact, creates
  // the directory structure for destination file, and both files have
  // the same content.
  EXPECT_TRUE(ios::sessions::CopyFile(from, dest));

  EXPECT_TRUE(ios::sessions::FileExists(from));
  EXPECT_NSEQ(ios::sessions::ReadFile(from), data);

  EXPECT_TRUE(ios::sessions::FileExists(dest));
  EXPECT_NSEQ(ios::sessions::ReadFile(dest), data);
}

// Tests that `CopyFile` returns success and overwritten destination if
// it exists and is a file.
TEST_F(SessionInternalUtilTest, CopyFile_OverwriteDestination) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath from = root.Append(kDirname1).Append(kFromName);
  const base::FilePath dest = root.Append(kDirname2).Append(kDestName);

  NSData* data1 = [@"data1" dataUsingEncoding:NSUTF8StringEncoding];
  ASSERT_TRUE(ios::sessions::WriteFile(from, data1));

  NSData* data2 = [@"data2" dataUsingEncoding:NSUTF8StringEncoding];
  ASSERT_TRUE(ios::sessions::WriteFile(dest, data2));
  ASSERT_NSEQ(ios::sessions::ReadFile(dest), data2);

  // Check that copying the file leave the source file intact, overwrite the
  // destination file, and both files have the same content.
  EXPECT_TRUE(ios::sessions::CopyFile(from, dest));

  EXPECT_TRUE(ios::sessions::FileExists(from));
  EXPECT_NSEQ(ios::sessions::ReadFile(from), data1);

  EXPECT_TRUE(ios::sessions::FileExists(dest));
  EXPECT_NSEQ(ios::sessions::ReadFile(dest), data1);
}

// Tests that `CopyFile` fails if the source is not a file.
TEST_F(SessionInternalUtilTest, CopyFile_FailureSourceIsADirectory) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath from = root.Append(kDirname1).Append(kFromName);
  const base::FilePath dest = root.Append(kDirname2).Append(kDestName);

  ASSERT_TRUE(ios::sessions::CreateDirectory(from));

  // Check that trying to copy `from` which is a directory using CopyFile()`
  // fails with an error.
  EXPECT_FALSE(ios::sessions::CopyFile(from, dest));
}

// Tests that `CopyFile` fails if the source does not exist.
TEST_F(SessionInternalUtilTest, CopyFile_FailureSourceMissing) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath from = root.Append(kDirname1).Append(kFromName);
  const base::FilePath dest = root.Append(kDirname2).Append(kDestName);

  // Check that trying to copy `from` which is a directory using CopyFile()`
  // fails with an error.
  EXPECT_FALSE(ios::sessions::CopyFile(from, dest));
}

// Tests that `CopyFile` fails if the destination path is a directory.
TEST_F(SessionInternalUtilTest, CopyFile_FailureDestinationIsADirectory) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath from = root.Append(kDirname1).Append(kFromName);
  const base::FilePath dest = root.Append(kDirname2).Append(kDestName);

  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  ASSERT_TRUE(ios::sessions::WriteFile(from, data));
  ASSERT_TRUE(ios::sessions::CreateDirectory(dest));

  // Check that trying to copy a file to a path that is a directory fails.
  EXPECT_FALSE(ios::sessions::CopyFile(from, dest));
}

// Tests that `WriteFile` returns success when the file is created and the
// data written to disk.
TEST_F(SessionInternalUtilTest, WriteFile) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath dir = root.Append(kDirname1);
  const base::FilePath file = dir.Append(kFilename);

  EXPECT_FALSE(ios::sessions::FileExists(file));

  // Check that creating a file creates its parent directory (recursively)
  // and correctly write the data to the disk.
  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  EXPECT_TRUE(ios::sessions::WriteFile(file, data));
  EXPECT_TRUE(ios::sessions::DirectoryExists(dir));
  EXPECT_TRUE(ios::sessions::FileExists(file));
  NSData* read = ios::sessions::ReadFile(file);
  EXPECT_NSEQ(read, data);
}

// Tests that `WriteFile` fails if it cannot create the parent directory.
TEST_F(SessionInternalUtilTest, WriteFile_FailureCreateDirectory) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath dir = root.Append(kDirname1);
  const base::FilePath file = dir.Append(kFilename);

  // Create a file named `dir` which should prevent creating a directory
  // with the same path in the next call to `WriteFile`.
  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  EXPECT_TRUE(ios::sessions::WriteFile(dir, data));

  // Check that creating a file named `file` will fail because the parent
  // directory cannot be created.
  EXPECT_FALSE(ios::sessions::WriteFile(file, data));
}

// Tests that `WriteFile` fails if it cannot write the data.
TEST_F(SessionInternalUtilTest, WriteFile_FailureWritingFile) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath dir = root.Append(kDirname1);
  const base::FilePath file = dir.Append(kFilename);

  // Create a directory named `file` which should prevent creating a file
  // with the same path in the next call to `WriteFile`.
  EXPECT_TRUE(ios::sessions::CreateDirectory(file));

  // Check that creating a file named `file` will fail because the parent
  // directory cannot be created.
  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  EXPECT_FALSE(ios::sessions::WriteFile(file, data));
}

// Tests that `ReadFile` read the data from disk or return nil on failure.
TEST_F(SessionInternalUtilTest, ReadFile) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath dir = root.Append(kDirname1);
  const base::FilePath file = dir.Append(kFilename);

  // Check that reading from an inexistent file fails and return nil.
  EXPECT_FALSE(ios::sessions::FileExists(file));
  EXPECT_NSEQ(nil, ios::sessions::ReadFile(file));

  // Create a file with some data.
  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  EXPECT_TRUE(ios::sessions::WriteFile(file, data));

  // Check that reading the file return the written data.
  NSData* read = ios::sessions::ReadFile(file);
  EXPECT_NSEQ(read, data);
}

// Tests that `WriteProto` correctly write serialized protobuf message to disk.
TEST_F(SessionInternalUtilTest, WriteProto) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  // Create a protobuf message that is not empty.
  ios::proto::WebStateListStorage proto;
  proto.set_active_index(-1);

  const base::FilePath dir = root.Append(kDirname1);
  const base::FilePath file = dir.Append(kFilename);

  // Check that writing the protobuf message succeed and that some data
  // is written to disk.
  EXPECT_FALSE(ios::sessions::FileExists(file));
  EXPECT_TRUE(ios::sessions::WriteProto(file, proto));
  EXPECT_NSNE(nil, ios::sessions::ReadFile(file));
}

// Tests that `ParseProto` succeed when reading a protobuf message written
// using `WriteProto`.
TEST_F(SessionInternalUtilTest, ParseProto) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath dir = root.Append(kDirname1);
  const base::FilePath file = dir.Append(kFilename);

  // Serialize a non-empty protobuf to `file`.
  ios::proto::WebStateListStorage proto;
  proto.add_items()->set_identifier(10);
  proto.set_active_index(-1);
  EXPECT_TRUE(ios::sessions::WriteProto(file, proto));

  // Check that reading the protobuf message succeed and that the content
  // is identical.
  ios::proto::WebStateListStorage parsed;
  EXPECT_TRUE(ios::sessions::ParseProto(file, parsed));
  EXPECT_EQ(parsed, proto);
}

// Tests that `ParseProto` fails if it cannot read the file.
TEST_F(SessionInternalUtilTest, ParseProto_FailureReadFile) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath dir = root.Append(kDirname1);
  const base::FilePath file = dir.Append(kFilename);

  // Check that reading the protobuf message fails if the file does not exist.
  ios::proto::WebStateListStorage parsed;
  EXPECT_FALSE(ios::sessions::ParseProto(file, parsed));
}

// Tests that `ParseProto` fails if it cannot parse the file as a valid
// protobuf message.
TEST_F(SessionInternalUtilTest, ParseProto_FailureParseMessage) {
  base::ScopedTempDir scoped_temp_dir;
  ASSERT_TRUE(scoped_temp_dir.CreateUniqueTempDir());
  const base::FilePath root = scoped_temp_dir.GetPath();

  const base::FilePath dir = root.Append(kDirname1);
  const base::FilePath file = dir.Append(kFilename);

  // Write unstructured data to the file, that is not a valid serialized
  // protobuf message.
  NSData* data = [@"data" dataUsingEncoding:NSUTF8StringEncoding];
  EXPECT_TRUE(ios::sessions::WriteFile(file, data));

  // Check that reading the protobuf message fails if the data cannot be
  // parsed as a valid protobuf message.
  ios::proto::WebStateListStorage parsed;
  EXPECT_FALSE(ios::sessions::ParseProto(file, parsed));
}
