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

#include "storage/browser/fileapi/sandbox_directory_database.h"

#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <limits>

#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "content/browser/fileapi/sandbox_database_test_helper.h"
#include "storage/common/fileapi/file_system_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/leveldatabase/src/include/leveldb/db.h"

#define FPL(x) FILE_PATH_LITERAL(x)

using storage::FilePathToString;
using storage::SandboxDirectoryDatabase;

namespace content {

namespace {
const base::FilePath::CharType kDirectoryDatabaseName[] = FPL("Paths");
}

class SandboxDirectoryDatabaseTest : public testing::Test {
 public:
  typedef SandboxDirectoryDatabase::FileId FileId;
  typedef SandboxDirectoryDatabase::FileInfo FileInfo;

  SandboxDirectoryDatabaseTest() {
    EXPECT_TRUE(base_.CreateUniqueTempDir());
    InitDatabase();
  }

  SandboxDirectoryDatabase* db() {
    return db_.get();
  }

  void InitDatabase() {
    // Call CloseDatabase() to avoid having multiple database instances for
    // single directory at once.
    CloseDatabase();
    db_.reset(new SandboxDirectoryDatabase(path(), NULL));
  }

  void CloseDatabase() {
    db_.reset();
  }

  base::File::Error AddFileInfo(
      FileId parent_id, const base::FilePath::StringType& name) {
    FileId file_id;
    FileInfo info;
    info.parent_id = parent_id;
    info.name = name;
    return db_->AddFileInfo(info, &file_id);
  }

  void CreateDirectory(FileId parent_id,
                       const base::FilePath::StringType& name,
                       FileId* file_id_out) {
    FileInfo info;
    info.parent_id = parent_id;
    info.name = name;
    ASSERT_EQ(base::File::FILE_OK, db_->AddFileInfo(info, file_id_out));
  }

  void CreateFile(FileId parent_id,
                  const base::FilePath::StringType& name,
                  const base::FilePath::StringType& data_path,
                  FileId* file_id_out) {
    FileId file_id;

    FileInfo info;
    info.parent_id = parent_id;
    info.name = name;
    info.data_path = base::FilePath(data_path).NormalizePathSeparators();
    ASSERT_EQ(base::File::FILE_OK, db_->AddFileInfo(info, &file_id));

    base::FilePath local_path = path().Append(data_path);
    if (!base::DirectoryExists(local_path.DirName()))
      ASSERT_TRUE(base::CreateDirectory(local_path.DirName()));

    base::File file(local_path,
                    base::File::FLAG_CREATE | base::File::FLAG_WRITE);
    ASSERT_TRUE(file.IsValid());
    ASSERT_TRUE(file.created());

    if (file_id_out)
      *file_id_out = file_id;
  }

  void ClearDatabaseAndDirectory() {
    db_.reset();
    ASSERT_TRUE(base::DeleteFile(path(), true /* recursive */));
    ASSERT_TRUE(base::CreateDirectory(path()));
    db_.reset(new SandboxDirectoryDatabase(path(), NULL));
  }

  bool RepairDatabase() {
    return db()->RepairDatabase(
        FilePathToString(path().Append(kDirectoryDatabaseName)));
  }

  const base::FilePath& path() {
    return base_.path();
  }

  // Makes link from |parent_id| to |child_id| with |name|.
  void MakeHierarchyLink(FileId parent_id,
                         FileId child_id,
                         const base::FilePath::StringType& name) {
    ASSERT_TRUE(db()->db_->Put(
        leveldb::WriteOptions(),
        "CHILD_OF:" + base::Int64ToString(parent_id) + ":" +
        FilePathToString(base::FilePath(name)),
        base::Int64ToString(child_id)).ok());
  }

  // Deletes link from parent of |file_id| to |file_id|.
  void DeleteHierarchyLink(FileId file_id) {
    FileInfo file_info;
    ASSERT_TRUE(db()->GetFileInfo(file_id, &file_info));
    ASSERT_TRUE(db()->db_->Delete(
        leveldb::WriteOptions(),
        "CHILD_OF:" + base::Int64ToString(file_info.parent_id) + ":" +
        FilePathToString(base::FilePath(file_info.name))).ok());
  }

 protected:
  // Common temp base for nondestructive uses.
  base::ScopedTempDir base_;
  scoped_ptr<SandboxDirectoryDatabase> db_;

 private:
  DISALLOW_COPY_AND_ASSIGN(SandboxDirectoryDatabaseTest);
};

TEST_F(SandboxDirectoryDatabaseTest, TestMissingFileGetInfo) {
  FileId file_id = 888;
  FileInfo info;
  EXPECT_FALSE(db()->GetFileInfo(file_id, &info));
}

TEST_F(SandboxDirectoryDatabaseTest, TestGetRootFileInfoBeforeCreate) {
  FileId file_id = 0;
  FileInfo info;
  EXPECT_TRUE(db()->GetFileInfo(file_id, &info));
  EXPECT_EQ(0, info.parent_id);
  EXPECT_TRUE(info.name.empty());
  EXPECT_TRUE(info.data_path.empty());
}

TEST_F(SandboxDirectoryDatabaseTest, TestMissingParentAddFileInfo) {
  FileId parent_id = 7;
  EXPECT_EQ(base::File::FILE_ERROR_NOT_A_DIRECTORY,
            AddFileInfo(parent_id, FILE_PATH_LITERAL("foo")));
}

TEST_F(SandboxDirectoryDatabaseTest, TestAddNameClash) {
  FileInfo info;
  FileId file_id;
  info.parent_id = 0;
  info.name = FILE_PATH_LITERAL("dir 0");
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id));

  // Check for name clash in the root directory.
  base::FilePath::StringType name = info.name;
  EXPECT_EQ(base::File::FILE_ERROR_EXISTS, AddFileInfo(0, name));
  name = FILE_PATH_LITERAL("dir 1");
  EXPECT_EQ(base::File::FILE_OK, AddFileInfo(0, name));

  name = FILE_PATH_LITERAL("subdir 0");
  EXPECT_EQ(base::File::FILE_OK, AddFileInfo(file_id, name));

  // Check for name clash in a subdirectory.
  EXPECT_EQ(base::File::FILE_ERROR_EXISTS, AddFileInfo(file_id, name));
  name = FILE_PATH_LITERAL("subdir 1");
  EXPECT_EQ(base::File::FILE_OK, AddFileInfo(file_id, name));
}

TEST_F(SandboxDirectoryDatabaseTest, TestRenameNoMoveNameClash) {
  FileInfo info;
  FileId file_id0;
  base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo");
  base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar");
  base::FilePath::StringType name2 = FILE_PATH_LITERAL("bas");
  info.parent_id = 0;
  info.name = name0;
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id0));
  EXPECT_EQ(base::File::FILE_OK, AddFileInfo(0, name1));
  info.name = name1;
  EXPECT_FALSE(db()->UpdateFileInfo(file_id0, info));
  info.name = name2;
  EXPECT_TRUE(db()->UpdateFileInfo(file_id0, info));
}

TEST_F(SandboxDirectoryDatabaseTest, TestMoveSameNameNameClash) {
  FileInfo info;
  FileId file_id0;
  FileId file_id1;
  base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo");
  base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar");
  info.parent_id = 0;
  info.name = name0;
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id0));
  info.parent_id = file_id0;
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id1));
  info.parent_id = 0;
  EXPECT_FALSE(db()->UpdateFileInfo(file_id1, info));
  info.name = name1;
  EXPECT_TRUE(db()->UpdateFileInfo(file_id1, info));
}

TEST_F(SandboxDirectoryDatabaseTest, TestMoveRenameNameClash) {
  FileInfo info;
  FileId file_id0;
  FileId file_id1;
  base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo");
  base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar");
  base::FilePath::StringType name2 = FILE_PATH_LITERAL("bas");
  info.parent_id = 0;
  info.name = name0;
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id0));
  info.parent_id = file_id0;
  info.name = name1;
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id1));
  info.parent_id = 0;
  info.name = name0;
  EXPECT_FALSE(db()->UpdateFileInfo(file_id1, info));
  info.name = name1;
  EXPECT_TRUE(db()->UpdateFileInfo(file_id1, info));
  // Also test a successful move+rename.
  info.parent_id = file_id0;
  info.name = name2;
  EXPECT_TRUE(db()->UpdateFileInfo(file_id1, info));
}

TEST_F(SandboxDirectoryDatabaseTest, TestRemoveWithChildren) {
  FileInfo info;
  FileId file_id0;
  FileId file_id1;
  info.parent_id = 0;
  info.name = FILE_PATH_LITERAL("foo");
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id0));
  info.parent_id = file_id0;
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id1));
  EXPECT_FALSE(db()->RemoveFileInfo(file_id0));
  EXPECT_TRUE(db()->RemoveFileInfo(file_id1));
  EXPECT_TRUE(db()->RemoveFileInfo(file_id0));
}

TEST_F(SandboxDirectoryDatabaseTest, TestGetChildWithName) {
  FileInfo info;
  FileId file_id0;
  FileId file_id1;
  base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo");
  base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar");
  info.parent_id = 0;
  info.name = name0;
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id0));
  info.parent_id = file_id0;
  info.name = name1;
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id1));
  EXPECT_NE(file_id0, file_id1);

  FileId check_file_id;
  EXPECT_FALSE(db()->GetChildWithName(0, name1, &check_file_id));
  EXPECT_TRUE(db()->GetChildWithName(0, name0, &check_file_id));
  EXPECT_EQ(file_id0, check_file_id);
  EXPECT_FALSE(db()->GetChildWithName(file_id0, name0, &check_file_id));
  EXPECT_TRUE(db()->GetChildWithName(file_id0, name1, &check_file_id));
  EXPECT_EQ(file_id1, check_file_id);
}

TEST_F(SandboxDirectoryDatabaseTest, TestGetFileWithPath) {
  FileInfo info;
  FileId file_id0;
  FileId file_id1;
  FileId file_id2;
  base::FilePath::StringType name0 = FILE_PATH_LITERAL("foo");
  base::FilePath::StringType name1 = FILE_PATH_LITERAL("bar");
  base::FilePath::StringType name2 = FILE_PATH_LITERAL("dog");

  info.parent_id = 0;
  info.name = name0;
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id0));
  info.parent_id = file_id0;
  info.name = name1;
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id1));
  EXPECT_NE(file_id0, file_id1);
  info.parent_id = file_id1;
  info.name = name2;
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id2));
  EXPECT_NE(file_id0, file_id2);
  EXPECT_NE(file_id1, file_id2);

  FileId check_file_id;
  base::FilePath path = base::FilePath(name0);
  EXPECT_TRUE(db()->GetFileWithPath(path, &check_file_id));
  EXPECT_EQ(file_id0, check_file_id);

  path = path.Append(name1);
  EXPECT_TRUE(db()->GetFileWithPath(path, &check_file_id));
  EXPECT_EQ(file_id1, check_file_id);

  path = path.Append(name2);
  EXPECT_TRUE(db()->GetFileWithPath(path, &check_file_id));
  EXPECT_EQ(file_id2, check_file_id);
}

TEST_F(SandboxDirectoryDatabaseTest, TestListChildren) {
  // No children in the root.
  std::vector<FileId> children;
  EXPECT_TRUE(db()->ListChildren(0, &children));
  EXPECT_TRUE(children.empty());

  // One child in the root.
  FileId file_id0;
  FileInfo info;
  info.parent_id = 0;
  info.name = FILE_PATH_LITERAL("foo");
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id0));
  EXPECT_TRUE(db()->ListChildren(0, &children));
  EXPECT_EQ(children.size(), 1UL);
  EXPECT_EQ(children[0], file_id0);

  // Two children in the root.
  FileId file_id1;
  info.name = FILE_PATH_LITERAL("bar");
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id1));
  EXPECT_TRUE(db()->ListChildren(0, &children));
  EXPECT_EQ(2UL, children.size());
  if (children[0] == file_id0) {
    EXPECT_EQ(children[1], file_id1);
  } else {
    EXPECT_EQ(children[1], file_id0);
    EXPECT_EQ(children[0], file_id1);
  }

  // No children in a subdirectory.
  EXPECT_TRUE(db()->ListChildren(file_id0, &children));
  EXPECT_TRUE(children.empty());

  // One child in a subdirectory.
  info.parent_id = file_id0;
  info.name = FILE_PATH_LITERAL("foo");
  FileId file_id2;
  FileId file_id3;
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id2));
  EXPECT_TRUE(db()->ListChildren(file_id0, &children));
  EXPECT_EQ(1UL, children.size());
  EXPECT_EQ(children[0], file_id2);

  // Two children in a subdirectory.
  info.name = FILE_PATH_LITERAL("bar");
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info, &file_id3));
  EXPECT_TRUE(db()->ListChildren(file_id0, &children));
  EXPECT_EQ(2UL, children.size());
  if (children[0] == file_id2) {
    EXPECT_EQ(children[1], file_id3);
  } else {
    EXPECT_EQ(children[1], file_id2);
    EXPECT_EQ(children[0], file_id3);
  }
}

TEST_F(SandboxDirectoryDatabaseTest, TestUpdateModificationTime) {
  FileInfo info0;
  FileId file_id;
  info0.parent_id = 0;
  info0.name = FILE_PATH_LITERAL("name");
  info0.data_path = base::FilePath(FILE_PATH_LITERAL("fake path"));
  info0.modification_time = base::Time::Now();
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info0, &file_id));
  FileInfo info1;
  EXPECT_TRUE(db()->GetFileInfo(file_id, &info1));
  EXPECT_EQ(info0.name, info1.name);
  EXPECT_EQ(info0.parent_id, info1.parent_id);
  EXPECT_EQ(info0.data_path, info1.data_path);
  EXPECT_EQ(
      floor(info0.modification_time.ToDoubleT()),
      info1.modification_time.ToDoubleT());

  EXPECT_TRUE(db()->UpdateModificationTime(file_id, base::Time::UnixEpoch()));
  EXPECT_TRUE(db()->GetFileInfo(file_id, &info1));
  EXPECT_EQ(info0.name, info1.name);
  EXPECT_EQ(info0.parent_id, info1.parent_id);
  EXPECT_EQ(info0.data_path, info1.data_path);
  EXPECT_NE(info0.modification_time, info1.modification_time);
  EXPECT_EQ(
      info1.modification_time.ToDoubleT(),
      floor(base::Time::UnixEpoch().ToDoubleT()));

  EXPECT_FALSE(db()->UpdateModificationTime(999, base::Time::UnixEpoch()));
}

TEST_F(SandboxDirectoryDatabaseTest, TestSimpleFileOperations) {
  FileId file_id = 888;
  FileInfo info0;
  EXPECT_FALSE(db()->GetFileInfo(file_id, &info0));
  info0.parent_id = 0;
  info0.data_path = base::FilePath(FILE_PATH_LITERAL("foo"));
  info0.name = FILE_PATH_LITERAL("file name");
  info0.modification_time = base::Time::Now();
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info0, &file_id));
  FileInfo info1;
  EXPECT_TRUE(db()->GetFileInfo(file_id, &info1));
  EXPECT_EQ(info0.parent_id, info1.parent_id);
  EXPECT_EQ(info0.data_path, info1.data_path);
  EXPECT_EQ(info0.name, info1.name);
  EXPECT_EQ(
      floor(info0.modification_time.ToDoubleT()),
      info1.modification_time.ToDoubleT());
}

TEST_F(SandboxDirectoryDatabaseTest, TestOverwritingMoveFileSrcDirectory) {
  FileId directory_id;
  FileInfo info0;
  info0.parent_id = 0;
  info0.name = FILE_PATH_LITERAL("directory");
  info0.modification_time = base::Time::Now();
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info0, &directory_id));

  FileId file_id;
  FileInfo info1;
  info1.parent_id = 0;
  info1.data_path = base::FilePath(FILE_PATH_LITERAL("bar"));
  info1.name = FILE_PATH_LITERAL("file");
  info1.modification_time = base::Time::UnixEpoch();
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info1, &file_id));

  EXPECT_FALSE(db()->OverwritingMoveFile(directory_id, file_id));
}

TEST_F(SandboxDirectoryDatabaseTest, TestOverwritingMoveFileDestDirectory) {
  FileId file_id;
  FileInfo info0;
  info0.parent_id = 0;
  info0.name = FILE_PATH_LITERAL("file");
  info0.data_path = base::FilePath(FILE_PATH_LITERAL("bar"));
  info0.modification_time = base::Time::Now();
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info0, &file_id));

  FileId directory_id;
  FileInfo info1;
  info1.parent_id = 0;
  info1.name = FILE_PATH_LITERAL("directory");
  info1.modification_time = base::Time::UnixEpoch();
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info1, &directory_id));

  EXPECT_FALSE(db()->OverwritingMoveFile(file_id, directory_id));
}

TEST_F(SandboxDirectoryDatabaseTest, TestOverwritingMoveFileSuccess) {
  FileId file_id0;
  FileInfo info0;
  info0.parent_id = 0;
  info0.data_path = base::FilePath(FILE_PATH_LITERAL("foo"));
  info0.name = FILE_PATH_LITERAL("file name 0");
  info0.modification_time = base::Time::Now();
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info0, &file_id0));

  FileInfo dir_info;
  FileId dir_id;
  dir_info.parent_id = 0;
  dir_info.name = FILE_PATH_LITERAL("directory name");
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(dir_info, &dir_id));

  FileId file_id1;
  FileInfo info1;
  info1.parent_id = dir_id;
  info1.data_path = base::FilePath(FILE_PATH_LITERAL("bar"));
  info1.name = FILE_PATH_LITERAL("file name 1");
  info1.modification_time = base::Time::UnixEpoch();
  EXPECT_EQ(base::File::FILE_OK, db()->AddFileInfo(info1, &file_id1));

  EXPECT_TRUE(db()->OverwritingMoveFile(file_id0, file_id1));

  FileInfo check_info;
  FileId check_id;

  EXPECT_FALSE(db()->GetFileWithPath(base::FilePath(info0.name), &check_id));
  EXPECT_TRUE(db()->GetFileWithPath(
      base::FilePath(dir_info.name).Append(info1.name), &check_id));
  EXPECT_TRUE(db()->GetFileInfo(check_id, &check_info));

  EXPECT_EQ(info0.data_path, check_info.data_path);
}

TEST_F(SandboxDirectoryDatabaseTest, TestGetNextInteger) {
  int64_t next = -1;
  EXPECT_TRUE(db()->GetNextInteger(&next));
  EXPECT_EQ(0, next);
  EXPECT_TRUE(db()->GetNextInteger(&next));
  EXPECT_EQ(1, next);
  InitDatabase();
  EXPECT_TRUE(db()->GetNextInteger(&next));
  EXPECT_EQ(2, next);
  EXPECT_TRUE(db()->GetNextInteger(&next));
  EXPECT_EQ(3, next);
  InitDatabase();
  EXPECT_TRUE(db()->GetNextInteger(&next));
  EXPECT_EQ(4, next);
}

TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_Empty) {
  EXPECT_TRUE(db()->IsFileSystemConsistent());

  int64_t next = -1;
  EXPECT_TRUE(db()->GetNextInteger(&next));
  EXPECT_EQ(0, next);
  EXPECT_TRUE(db()->IsFileSystemConsistent());
}

TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_Consistent) {
  FileId dir_id;
  CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
  CreateDirectory(0, FPL("bar"), &dir_id);
  CreateFile(dir_id, FPL("baz"), FPL("fuga"), NULL);
  CreateFile(dir_id, FPL("fizz"), FPL("buzz"), NULL);

  EXPECT_TRUE(db()->IsFileSystemConsistent());
}

TEST_F(SandboxDirectoryDatabaseTest,
       TestConsistencyCheck_BackingMultiEntry) {
  const base::FilePath::CharType kBackingFileName[] = FPL("the celeb");
  CreateFile(0, FPL("foo"), kBackingFileName, NULL);

  EXPECT_TRUE(db()->IsFileSystemConsistent());
  ASSERT_TRUE(base::DeleteFile(path().Append(kBackingFileName), false));
  CreateFile(0, FPL("bar"), kBackingFileName, NULL);
  EXPECT_FALSE(db()->IsFileSystemConsistent());
}

TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_FileLost) {
  const base::FilePath::CharType kBackingFileName[] = FPL("hoge");
  CreateFile(0, FPL("foo"), kBackingFileName, NULL);

  EXPECT_TRUE(db()->IsFileSystemConsistent());
  ASSERT_TRUE(base::DeleteFile(path().Append(kBackingFileName), false));
  EXPECT_TRUE(db()->IsFileSystemConsistent());
}

TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_OrphanFile) {
  CreateFile(0, FPL("foo"), FPL("hoge"), NULL);

  EXPECT_TRUE(db()->IsFileSystemConsistent());

  base::File file(path().Append(FPL("Orphan File")),
                  base::File::FLAG_CREATE | base::File::FLAG_WRITE);
  ASSERT_TRUE(file.IsValid());
  ASSERT_TRUE(file.created());
  file.Close();

  EXPECT_TRUE(db()->IsFileSystemConsistent());
}

TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_RootLoop) {
  EXPECT_TRUE(db()->IsFileSystemConsistent());
  MakeHierarchyLink(0, 0, base::FilePath::StringType());
  EXPECT_FALSE(db()->IsFileSystemConsistent());
}

TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_DirectoryLoop) {
  FileId dir1_id;
  FileId dir2_id;
  base::FilePath::StringType dir1_name = FPL("foo");
  CreateDirectory(0, dir1_name, &dir1_id);
  CreateDirectory(dir1_id, FPL("bar"), &dir2_id);

  EXPECT_TRUE(db()->IsFileSystemConsistent());
  MakeHierarchyLink(dir2_id, dir1_id, dir1_name);
  EXPECT_FALSE(db()->IsFileSystemConsistent());
}

TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_NameMismatch) {
  FileId dir_id;
  FileId file_id;
  CreateDirectory(0, FPL("foo"), &dir_id);
  CreateFile(dir_id, FPL("bar"), FPL("hoge/fuga/piyo"), &file_id);

  EXPECT_TRUE(db()->IsFileSystemConsistent());
  DeleteHierarchyLink(file_id);
  MakeHierarchyLink(dir_id, file_id, FPL("baz"));
  EXPECT_FALSE(db()->IsFileSystemConsistent());
}

TEST_F(SandboxDirectoryDatabaseTest, TestConsistencyCheck_WreckedEntries) {
  FileId dir1_id;
  FileId dir2_id;
  CreateDirectory(0, FPL("foo"), &dir1_id);
  CreateDirectory(dir1_id, FPL("bar"), &dir2_id);
  CreateFile(dir2_id, FPL("baz"), FPL("fizz/buzz"), NULL);

  EXPECT_TRUE(db()->IsFileSystemConsistent());
  DeleteHierarchyLink(dir2_id);  // Delete link from |dir1_id| to |dir2_id|.
  EXPECT_FALSE(db()->IsFileSystemConsistent());
}

TEST_F(SandboxDirectoryDatabaseTest, TestRepairDatabase_Success) {
  base::FilePath::StringType kFileName = FPL("bar");

  FileId file_id_prev;
  CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
  CreateFile(0, kFileName, FPL("fuga"), &file_id_prev);

  const base::FilePath kDatabaseDirectory =
      path().Append(kDirectoryDatabaseName);
  CloseDatabase();
  CorruptDatabase(kDatabaseDirectory, leveldb::kDescriptorFile,
                  0, std::numeric_limits<size_t>::max());
  InitDatabase();
  EXPECT_FALSE(db()->IsFileSystemConsistent());

  FileId file_id;
  EXPECT_TRUE(db()->GetChildWithName(0, kFileName, &file_id));
  EXPECT_EQ(file_id_prev, file_id);

  EXPECT_TRUE(db()->IsFileSystemConsistent());
}

TEST_F(SandboxDirectoryDatabaseTest, TestRepairDatabase_Failure) {
  base::FilePath::StringType kFileName = FPL("bar");

  CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
  CreateFile(0, kFileName, FPL("fuga"), NULL);

  const base::FilePath kDatabaseDirectory =
      path().Append(kDirectoryDatabaseName);
  CloseDatabase();
  CorruptDatabase(kDatabaseDirectory, leveldb::kDescriptorFile,
                  0, std::numeric_limits<size_t>::max());
  CorruptDatabase(kDatabaseDirectory, leveldb::kLogFile,
                  -1, 1);
  InitDatabase();
  EXPECT_FALSE(db()->IsFileSystemConsistent());

  FileId file_id;
  EXPECT_FALSE(db()->GetChildWithName(0, kFileName, &file_id));
  EXPECT_TRUE(db()->IsFileSystemConsistent());
}

TEST_F(SandboxDirectoryDatabaseTest, TestRepairDatabase_MissingManifest) {
  base::FilePath::StringType kFileName = FPL("bar");

  FileId file_id_prev;
  CreateFile(0, FPL("foo"), FPL("hoge"), NULL);
  CreateFile(0, kFileName, FPL("fuga"), &file_id_prev);

  const base::FilePath kDatabaseDirectory =
      path().Append(kDirectoryDatabaseName);
  CloseDatabase();

  DeleteDatabaseFile(kDatabaseDirectory, leveldb::kDescriptorFile);

  InitDatabase();
  EXPECT_FALSE(db()->IsFileSystemConsistent());

  FileId file_id;
  EXPECT_TRUE(db()->GetChildWithName(0, kFileName, &file_id));
  EXPECT_EQ(file_id_prev, file_id);

  EXPECT_TRUE(db()->IsFileSystemConsistent());
}

}  // namespace content
