// Copyright 2013 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 "content/browser/indexed_db/indexed_db_database.h"

#include <stdint.h>
#include <set>
#include <utility>

#include "base/auto_reset.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/indexed_db/fake_indexed_db_metadata_coding.h"
#include "content/browser/indexed_db/indexed_db.h"
#include "content/browser/indexed_db/indexed_db_backing_store.h"
#include "content/browser/indexed_db/indexed_db_callbacks.h"
#include "content/browser/indexed_db/indexed_db_class_factory.h"
#include "content/browser/indexed_db/indexed_db_connection.h"
#include "content/browser/indexed_db/indexed_db_cursor.h"
#include "content/browser/indexed_db/indexed_db_fake_backing_store.h"
#include "content/browser/indexed_db/indexed_db_leveldb_coding.h"
#include "content/browser/indexed_db/indexed_db_transaction.h"
#include "content/browser/indexed_db/indexed_db_value.h"
#include "content/browser/indexed_db/mock_indexed_db_callbacks.h"
#include "content/browser/indexed_db/mock_indexed_db_database_callbacks.h"
#include "content/browser/indexed_db/mock_indexed_db_factory.h"
#include "content/browser/indexed_db/scopes/disjoint_range_lock_manager.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::ASCIIToUTF16;
using blink::IndexedDBDatabaseMetadata;
using blink::IndexedDBIndexKeys;
using blink::IndexedDBKey;
using blink::IndexedDBKeyPath;

namespace {
const int kFakeChildProcessId = 0;
}

namespace content {

class IndexedDBDatabaseTest : public ::testing::Test {
 public:
  IndexedDBDatabaseTest() : lock_manager_(kIndexedDBLockLevelCount) {}

  void SetUp() override {
    backing_store_ = new IndexedDBFakeBackingStore();
    factory_ = new MockIndexedDBFactory();
    std::unique_ptr<FakeIndexedDBMetadataCoding> metadata_coding =
        std::make_unique<FakeIndexedDBMetadataCoding>();
    metadata_coding_ = metadata_coding.get();
    EXPECT_TRUE(backing_store_->HasOneRef());
    leveldb::Status s;

    std::tie(db_, s) = IndexedDBDatabase::Create(
        ASCIIToUTF16("db"), backing_store_.get(), factory_.get(),
        std::move(metadata_coding), IndexedDBDatabase::Identifier(),
        &lock_manager_);
    ASSERT_TRUE(s.ok());
    EXPECT_FALSE(backing_store_->HasOneRef());  // local and db
  }

 protected:
  scoped_refptr<IndexedDBFakeBackingStore> backing_store_;
  scoped_refptr<MockIndexedDBFactory> factory_;
  scoped_refptr<IndexedDBDatabase> db_;
  FakeIndexedDBMetadataCoding* metadata_coding_;

 private:
  TestBrowserThreadBundle thread_bundle_;
  DisjointRangeLockManager lock_manager_;
};

TEST_F(IndexedDBDatabaseTest, BackingStoreRetention) {
  EXPECT_FALSE(backing_store_->HasOneRef());  // local and db
  db_ = nullptr;
  EXPECT_TRUE(backing_store_->HasOneRef());  // local
}

TEST_F(IndexedDBDatabaseTest, ConnectionLifecycle) {
  scoped_refptr<MockIndexedDBCallbacks> request1(new MockIndexedDBCallbacks());
  scoped_refptr<MockIndexedDBDatabaseCallbacks> callbacks1(
      new MockIndexedDBDatabaseCallbacks());
  const int64_t transaction_id1 = 1;
  std::unique_ptr<IndexedDBPendingConnection> connection1(
      std::make_unique<IndexedDBPendingConnection>(
          request1, callbacks1, kFakeChildProcessId, transaction_id1,
          IndexedDBDatabaseMetadata::DEFAULT_VERSION));
  db_->OpenConnection(std::move(connection1));

  EXPECT_FALSE(backing_store_->HasOneRef());  // db, connection count > 0

  scoped_refptr<MockIndexedDBCallbacks> request2(new MockIndexedDBCallbacks());
  scoped_refptr<MockIndexedDBDatabaseCallbacks> callbacks2(
      new MockIndexedDBDatabaseCallbacks());
  const int64_t transaction_id2 = 2;
  std::unique_ptr<IndexedDBPendingConnection> connection2(
      std::make_unique<IndexedDBPendingConnection>(
          request2, callbacks2, kFakeChildProcessId, transaction_id2,
          IndexedDBDatabaseMetadata::DEFAULT_VERSION));
  db_->OpenConnection(std::move(connection2));

  EXPECT_FALSE(backing_store_->HasOneRef());  // local and connection

  request1->connection()->ForceClose();
  EXPECT_FALSE(request1->connection()->IsConnected());

  EXPECT_FALSE(backing_store_->HasOneRef());  // local and connection

  request2->connection()->ForceClose();
  EXPECT_FALSE(request2->connection()->IsConnected());

  EXPECT_TRUE(backing_store_->HasOneRef());
  EXPECT_FALSE(db_->backing_store());

  db_ = nullptr;
}

TEST_F(IndexedDBDatabaseTest, ForcedClose) {
  scoped_refptr<MockIndexedDBDatabaseCallbacks> callbacks(
      new MockIndexedDBDatabaseCallbacks());
  scoped_refptr<MockIndexedDBCallbacks> request(new MockIndexedDBCallbacks());
  const int64_t upgrade_transaction_id = 3;
  std::unique_ptr<IndexedDBPendingConnection> connection(
      std::make_unique<IndexedDBPendingConnection>(
          request, callbacks, kFakeChildProcessId, upgrade_transaction_id,
          IndexedDBDatabaseMetadata::DEFAULT_VERSION));
  db_->OpenConnection(std::move(connection));
  EXPECT_EQ(db_.get(), request->connection()->database());

  const int64_t transaction_id = 123;
  const std::vector<int64_t> scope;
  IndexedDBTransaction* transaction = request->connection()->CreateTransaction(
      transaction_id, std::set<int64_t>(scope.begin(), scope.end()),
      blink::mojom::IDBTransactionMode::ReadOnly,
      new IndexedDBBackingStore::Transaction(backing_store_.get()));
  db_->RegisterAndScheduleTransaction(transaction);

  request->connection()->ForceClose();

  EXPECT_TRUE(backing_store_->HasOneRef());  // local
  EXPECT_TRUE(callbacks->abort_called());
}

class MockCallbacks : public IndexedDBCallbacks {
 public:
  MockCallbacks()
      : IndexedDBCallbacks(nullptr,
                           url::Origin(),
                           nullptr,
                           base::ThreadTaskRunnerHandle::Get()) {}

  void OnBlocked(int64_t existing_version) override { blocked_called_ = true; }
  void OnSuccess(int64_t result) override { success_called_ = true; }
  void OnError(const IndexedDBDatabaseError& error) override {
    error_called_ = true;
  }

  bool blocked_called() const { return blocked_called_; }
  bool success_called() const { return success_called_; }
  bool error_called() const { return error_called_; }

 private:
  ~MockCallbacks() override {}

  bool blocked_called_ = false;
  bool success_called_ = false;
  bool error_called_ = false;

  DISALLOW_COPY_AND_ASSIGN(MockCallbacks);
};

TEST_F(IndexedDBDatabaseTest, PendingDelete) {
  scoped_refptr<MockIndexedDBCallbacks> request1(new MockIndexedDBCallbacks());
  scoped_refptr<MockIndexedDBDatabaseCallbacks> callbacks1(
      new MockIndexedDBDatabaseCallbacks());
  const int64_t transaction_id1 = 1;
  std::unique_ptr<IndexedDBPendingConnection> connection(
      std::make_unique<IndexedDBPendingConnection>(
          request1, callbacks1, kFakeChildProcessId, transaction_id1,
          IndexedDBDatabaseMetadata::DEFAULT_VERSION));
  db_->OpenConnection(std::move(connection));

  EXPECT_EQ(db_->ConnectionCount(), 1UL);
  EXPECT_EQ(db_->ActiveOpenDeleteCount(), 0UL);
  EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
  EXPECT_FALSE(backing_store_->HasOneRef());  // local and db

  scoped_refptr<MockCallbacks> request2(new MockCallbacks());
  db_->DeleteDatabase(request2, false /* force_delete */);
  EXPECT_EQ(db_->ConnectionCount(), 1UL);
  EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
  EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);

  EXPECT_FALSE(request2->blocked_called());
  db_->VersionChangeIgnored();
  EXPECT_TRUE(request2->blocked_called());
  EXPECT_EQ(db_->ConnectionCount(), 1UL);
  EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
  EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);

  EXPECT_FALSE(backing_store_->HasOneRef());  // local and db

  db_->Close(request1->connection(), true /* forced */);
  EXPECT_EQ(db_->ConnectionCount(), 0UL);
  EXPECT_EQ(db_->ActiveOpenDeleteCount(), 0UL);
  EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);

  EXPECT_FALSE(db_->backing_store());
  EXPECT_TRUE(backing_store_->HasOneRef());  // local
  EXPECT_TRUE(request2->success_called());
}

TEST_F(IndexedDBDatabaseTest, OpenDeleteClear) {
  const int64_t kDatabaseVersion = 1;

  scoped_refptr<MockIndexedDBCallbacks> request1(
      new MockIndexedDBCallbacks(true));
  scoped_refptr<MockIndexedDBDatabaseCallbacks> callbacks1(
      new MockIndexedDBDatabaseCallbacks());
  const int64_t transaction_id1 = 1;
  std::unique_ptr<IndexedDBPendingConnection> connection1(
      std::make_unique<IndexedDBPendingConnection>(
          request1, callbacks1, kFakeChildProcessId, transaction_id1,
          kDatabaseVersion));
  db_->OpenConnection(std::move(connection1));

  EXPECT_EQ(db_->ConnectionCount(), 1UL);
  EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
  EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
  EXPECT_FALSE(backing_store_->HasOneRef());  // local and db

  scoped_refptr<MockIndexedDBCallbacks> request2(
      new MockIndexedDBCallbacks(false));
  scoped_refptr<MockIndexedDBDatabaseCallbacks> callbacks2(
      new MockIndexedDBDatabaseCallbacks());
  const int64_t transaction_id2 = 2;
  std::unique_ptr<IndexedDBPendingConnection> connection2(
      std::make_unique<IndexedDBPendingConnection>(
          request2, callbacks2, kFakeChildProcessId, transaction_id2,
          kDatabaseVersion));
  db_->OpenConnection(std::move(connection2));

  EXPECT_EQ(db_->ConnectionCount(), 1UL);
  EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
  EXPECT_EQ(db_->PendingOpenDeleteCount(), 1UL);
  EXPECT_FALSE(backing_store_->HasOneRef());  // local and db

  scoped_refptr<MockIndexedDBCallbacks> request3(
      new MockIndexedDBCallbacks(false));
  scoped_refptr<MockIndexedDBDatabaseCallbacks> callbacks3(
      new MockIndexedDBDatabaseCallbacks());
  const int64_t transaction_id3 = 3;
  std::unique_ptr<IndexedDBPendingConnection> connection3(
      std::make_unique<IndexedDBPendingConnection>(
          request3, callbacks3, kFakeChildProcessId, transaction_id3,
          kDatabaseVersion));
  db_->OpenConnection(std::move(connection3));

  // This causes the active request to call OnUpgradeNeeded on its callbacks.
  // The Abort() triggered by ForceClose() assumes that the transaction was
  // started and passed the connection along to the front end.
  db_->CallUpgradeTransactionStartedForTesting(
      IndexedDBDatabaseMetadata::DEFAULT_VERSION);
  EXPECT_TRUE(request1->upgrade_called());

  EXPECT_EQ(db_->ConnectionCount(), 1UL);
  EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
  EXPECT_EQ(db_->PendingOpenDeleteCount(), 2UL);
  EXPECT_FALSE(backing_store_->HasOneRef());

  db_->ForceClose();

  EXPECT_TRUE(backing_store_->HasOneRef());  // local
  EXPECT_TRUE(callbacks1->forced_close_called());
  EXPECT_TRUE(request1->error_called());
  EXPECT_TRUE(callbacks2->forced_close_called());
  EXPECT_FALSE(request2->error_called());
  EXPECT_TRUE(callbacks3->forced_close_called());
  EXPECT_FALSE(request3->error_called());
}

TEST_F(IndexedDBDatabaseTest, ForceDelete) {
  scoped_refptr<MockIndexedDBCallbacks> request1(new MockIndexedDBCallbacks());
  scoped_refptr<MockIndexedDBDatabaseCallbacks> callbacks1(
      new MockIndexedDBDatabaseCallbacks());
  const int64_t transaction_id1 = 1;
  std::unique_ptr<IndexedDBPendingConnection> connection(
      std::make_unique<IndexedDBPendingConnection>(
          request1, callbacks1, kFakeChildProcessId, transaction_id1,
          IndexedDBDatabaseMetadata::DEFAULT_VERSION));
  db_->OpenConnection(std::move(connection));

  EXPECT_EQ(db_->ConnectionCount(), 1UL);
  EXPECT_EQ(db_->ActiveOpenDeleteCount(), 0UL);
  EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
  EXPECT_FALSE(backing_store_->HasOneRef());  // local and db

  scoped_refptr<MockCallbacks> request2(new MockCallbacks());
  db_->DeleteDatabase(request2, true /* force_delete */);
  EXPECT_EQ(db_->ConnectionCount(), 0UL);
  EXPECT_EQ(db_->ActiveOpenDeleteCount(), 0UL);
  EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
  EXPECT_FALSE(request2->blocked_called());

  EXPECT_FALSE(db_->backing_store());
  EXPECT_TRUE(backing_store_->HasOneRef());  // local
  EXPECT_TRUE(request2->success_called());
}

leveldb::Status DummyOperation(IndexedDBTransaction* transaction) {
  return leveldb::Status::OK();
}

class IndexedDBDatabaseOperationTest : public testing::Test {
 public:
  IndexedDBDatabaseOperationTest()
      : lock_manager_(kIndexedDBLockLevelCount),
        commit_success_(leveldb::Status::OK()),
        factory_(new MockIndexedDBFactory()) {}

  void SetUp() override {
    backing_store_ = new IndexedDBFakeBackingStore();
    std::unique_ptr<FakeIndexedDBMetadataCoding> metadata_coding =
        std::make_unique<FakeIndexedDBMetadataCoding>();
    metadata_coding_ = metadata_coding.get();
    leveldb::Status s;
    std::tie(db_, s) = IndexedDBDatabase::Create(
        ASCIIToUTF16("db"), backing_store_.get(), factory_.get(),
        std::move(metadata_coding), IndexedDBDatabase::Identifier(),
        &lock_manager_);
    ASSERT_TRUE(s.ok());

    request_ = new MockIndexedDBCallbacks();
    callbacks_ = new MockIndexedDBDatabaseCallbacks();
    const int64_t transaction_id = 1;
    std::unique_ptr<IndexedDBPendingConnection> connection(
        std::make_unique<IndexedDBPendingConnection>(
            request_, callbacks_, kFakeChildProcessId, transaction_id,
            IndexedDBDatabaseMetadata::DEFAULT_VERSION));
    db_->OpenConnection(std::move(connection));
    EXPECT_EQ(IndexedDBDatabaseMetadata::NO_VERSION, db_->metadata().version);

    connection_ = std::make_unique<IndexedDBConnection>(kFakeChildProcessId,
                                                        db_, callbacks_);
    transaction_ = connection_->CreateTransaction(
        transaction_id, std::set<int64_t>() /*scope*/,
        blink::mojom::IDBTransactionMode::VersionChange,
        new IndexedDBFakeBackingStore::FakeTransaction(commit_success_));
    db_->RegisterAndScheduleTransaction(transaction_);

    // Add a dummy task which takes the place of the VersionChangeOperation
    // which kicks off the upgrade. This ensures that the transaction has
    // processed at least one task before the CreateObjectStore call.
    transaction_->ScheduleTask(base::BindOnce(&DummyOperation));
  }

  void RunPostedTasks() { base::RunLoop().RunUntilIdle(); }

private:
  // Needs to outlive |db_|.
  content::TestBrowserThreadBundle thread_bundle_;

 protected:
  scoped_refptr<IndexedDBFakeBackingStore> backing_store_;
  scoped_refptr<IndexedDBDatabase> db_;
  FakeIndexedDBMetadataCoding* metadata_coding_;
  scoped_refptr<MockIndexedDBCallbacks> request_;
  scoped_refptr<MockIndexedDBDatabaseCallbacks> callbacks_;
  IndexedDBTransaction* transaction_;
  std::unique_ptr<IndexedDBConnection> connection_;
  DisjointRangeLockManager lock_manager_;

  leveldb::Status commit_success_;

 private:
  scoped_refptr<MockIndexedDBFactory> factory_;

  DISALLOW_COPY_AND_ASSIGN(IndexedDBDatabaseOperationTest);
};

TEST_F(IndexedDBDatabaseOperationTest, CreateObjectStore) {
  EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
  const int64_t store_id = 1001;
  db_->CreateObjectStore(transaction_, store_id, ASCIIToUTF16("store"),
                         IndexedDBKeyPath(), false /*auto_increment*/);
  EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
  RunPostedTasks();
  transaction_->Commit();
  EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
}

TEST_F(IndexedDBDatabaseOperationTest, CreateIndex) {
  EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
  const int64_t store_id = 1001;
  db_->CreateObjectStore(transaction_, store_id, ASCIIToUTF16("store"),
                         IndexedDBKeyPath(), false /*auto_increment*/);
  EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
  const int64_t index_id = 2002;
  db_->CreateIndex(transaction_, store_id, index_id, ASCIIToUTF16("index"),
                   IndexedDBKeyPath(), false /*unique*/, false /*multi_entry*/);
  EXPECT_EQ(
      1ULL,
      db_->metadata().object_stores.find(store_id)->second.indexes.size());
  RunPostedTasks();
  transaction_->Commit();
  EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
  EXPECT_EQ(
      1ULL,
      db_->metadata().object_stores.find(store_id)->second.indexes.size());
}

class IndexedDBDatabaseOperationAbortTest
    : public IndexedDBDatabaseOperationTest {
 public:
  IndexedDBDatabaseOperationAbortTest() {
    commit_success_ = leveldb::Status::NotFound("Bummer.");
  }

 private:
  DISALLOW_COPY_AND_ASSIGN(IndexedDBDatabaseOperationAbortTest);
};

TEST_F(IndexedDBDatabaseOperationAbortTest, CreateObjectStore) {
  EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
  const int64_t store_id = 1001;
  db_->CreateObjectStore(transaction_, store_id, ASCIIToUTF16("store"),
                         IndexedDBKeyPath(), false /*auto_increment*/);
  EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
  RunPostedTasks();
  transaction_->Commit();
  EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
}

TEST_F(IndexedDBDatabaseOperationAbortTest, CreateIndex) {
  EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
  const int64_t store_id = 1001;
  db_->CreateObjectStore(transaction_, store_id, ASCIIToUTF16("store"),
                         IndexedDBKeyPath(), false /*auto_increment*/);
  EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
  const int64_t index_id = 2002;
  db_->CreateIndex(transaction_, store_id, index_id, ASCIIToUTF16("index"),
                   IndexedDBKeyPath(), false /*unique*/, false /*multi_entry*/);
  EXPECT_EQ(
      1ULL,
      db_->metadata().object_stores.find(store_id)->second.indexes.size());
  RunPostedTasks();
  transaction_->Commit();
  EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
}

TEST_F(IndexedDBDatabaseOperationTest, CreatePutDelete) {
  EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
  const int64_t store_id = 1001;

  // Creation is synchronous.
  db_->CreateObjectStore(transaction_, store_id, ASCIIToUTF16("store"),
                         IndexedDBKeyPath(), false /*auto_increment*/);
  EXPECT_EQ(1ULL, db_->metadata().object_stores.size());


  // Put is asynchronous
  IndexedDBValue value("value1", std::vector<IndexedDBBlobInfo>());
  std::unique_ptr<IndexedDBKey> key(std::make_unique<IndexedDBKey>("key"));
  std::vector<IndexedDBIndexKeys> index_keys;
  scoped_refptr<MockIndexedDBCallbacks> request(
      new MockIndexedDBCallbacks(false));
  db_->Put(transaction_, store_id, &value, std::move(key),
           blink::mojom::IDBPutMode::AddOnly, request, index_keys);

  // Deletion is asynchronous.
  db_->DeleteObjectStore(transaction_, store_id);
  EXPECT_EQ(1ULL, db_->metadata().object_stores.size());

  // This will execute the Put then Delete.
  RunPostedTasks();
  EXPECT_EQ(0ULL, db_->metadata().object_stores.size());

  transaction_->Commit();  // Cleans up the object hierarchy.
}

}  // namespace content
