blob: 5911dba675f9b6992eb4eeb9ab5f352f3a12be16 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// 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 <string>
#include <utility>
#include "base/auto_reset.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "components/services/storage/indexed_db/locks/partitioned_lock_manager.h"
#include "components/services/storage/privileged/mojom/indexed_db_client_state_checker.mojom-forward.h"
#include "components/services/storage/public/cpp/buckets/bucket_locator.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_bucket_context.h"
#include "content/browser/indexed_db/indexed_db_class_factory.h"
#include "content/browser/indexed_db/indexed_db_client_state_checker_wrapper.h"
#include "content/browser/indexed_db/indexed_db_connection.h"
#include "content/browser/indexed_db/indexed_db_context_impl.h"
#include "content/browser/indexed_db/indexed_db_cursor.h"
#include "content/browser/indexed_db/indexed_db_database_callbacks.h"
#include "content/browser/indexed_db/indexed_db_factory.h"
#include "content/browser/indexed_db/indexed_db_factory_client.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_factory_client.h"
#include "content/browser/indexed_db/mock_mojo_indexed_db_database_callbacks.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "storage/browser/test/mock_quota_manager.h"
#include "storage/browser/test/mock_quota_manager_proxy.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
using blink::IndexedDBDatabaseMetadata;
using blink::IndexedDBIndexKeys;
using blink::IndexedDBKey;
using blink::IndexedDBKeyPath;
namespace content {
class IndexedDBDatabaseTest : public ::testing::Test {
public:
IndexedDBDatabaseTest() = default;
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
quota_manager_ = base::MakeRefCounted<storage::MockQuotaManager>(
/*is_incognito=*/false, temp_dir_.GetPath(),
base::SingleThreadTaskRunner::GetCurrentDefault(),
/*special_storage_policy=*/nullptr);
quota_manager_proxy_ = base::MakeRefCounted<storage::MockQuotaManagerProxy>(
quota_manager_.get(),
base::SingleThreadTaskRunner::GetCurrentDefault().get());
indexed_db_context_ = std::make_unique<IndexedDBContextImpl>(
temp_dir_.GetPath(), quota_manager_proxy_,
/*blob_storage_context=*/mojo::NullRemote(),
/*file_system_access_context=*/mojo::NullRemote(),
base::SequencedTaskRunner::GetCurrentDefault(),
base::SequencedTaskRunner::GetCurrentDefault());
IndexedDBBucketContext::Delegate delegate;
delegate.on_fatal_error = base::BindRepeating(
&IndexedDBDatabaseTest::OnFatalError, weak_factory_.GetWeakPtr());
delegate.on_ready_for_destruction = base::BindRepeating(
&IndexedDBDatabaseTest::OnBucketContextReadyForDestruction,
weak_factory_.GetWeakPtr());
bucket_context_ = std::make_unique<IndexedDBBucketContext>(
storage::BucketInfo(), std::make_unique<PartitionedLockManager>(),
std::move(delegate), std::make_unique<IndexedDBFakeBackingStore>(),
quota_manager_proxy_,
/*io_task_runner=*/base::SequencedTaskRunner::GetCurrentDefault(),
/*blob_storage_context=*/mojo::NullRemote(),
/*file_system_access_context=*/mojo::NullRemote(), base::DoNothing());
db_ = bucket_context_->AddDatabase(
u"db", std::make_unique<IndexedDBDatabase>(
u"db", *bucket_context_, IndexedDBDatabase::Identifier()));
}
void TearDown() override { db_ = nullptr; }
void OnFatalError(leveldb::Status s) { error_called_ = true; }
void OnBucketContextReadyForDestruction() { bucket_context_.reset(); }
void RunPostedTasks() {
base::RunLoop run_loop;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
}
scoped_refptr<IndexedDBClientStateCheckerWrapper>
CreateTestClientStateWrapper() {
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker> remote;
return base::MakeRefCounted<IndexedDBClientStateCheckerWrapper>(
std::move(remote));
}
protected:
base::test::TaskEnvironment task_environment_;
base::ScopedTempDir temp_dir_;
std::unique_ptr<IndexedDBContextImpl> indexed_db_context_;
std::unique_ptr<IndexedDBBucketContext> bucket_context_;
scoped_refptr<storage::MockQuotaManager> quota_manager_;
scoped_refptr<storage::MockQuotaManagerProxy> quota_manager_proxy_;
// As `this` is owned by `bucket_context_`, tests that cause the database to
// be destroyed must manually reset this to null to avoid triggering dangling
// pointer warnings.
raw_ptr<IndexedDBDatabase> db_ = nullptr;
bool error_called_ = false;
base::WeakPtrFactory<IndexedDBDatabaseTest> weak_factory_{this};
};
TEST_F(IndexedDBDatabaseTest, ConnectionLifecycle) {
MockMojoIndexedDBDatabaseCallbacks database_callbacks;
MockIndexedDBFactoryClient request1;
const int64_t transaction_id1 = 1;
auto connection1 = std::make_unique<IndexedDBPendingConnection>(
std::make_unique<ThunkFactoryClient>(request1),
std::make_unique<IndexedDBDatabaseCallbacks>(
database_callbacks.BindNewEndpointAndPassDedicatedRemote()),
transaction_id1, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection1),
CreateTestClientStateWrapper());
RunPostedTasks();
MockMojoIndexedDBDatabaseCallbacks database_callbacks2;
MockIndexedDBFactoryClient request2;
const int64_t transaction_id2 = 2;
auto connection2 = std::make_unique<IndexedDBPendingConnection>(
std::make_unique<ThunkFactoryClient>(request2),
std::make_unique<IndexedDBDatabaseCallbacks>(
database_callbacks2.BindNewEndpointAndPassDedicatedRemote()),
transaction_id2, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection2),
CreateTestClientStateWrapper());
RunPostedTasks();
db_ = nullptr;
EXPECT_TRUE(request1.connection());
request1.connection()->CloseAndReportForceClose();
EXPECT_FALSE(request1.connection()->IsConnected());
EXPECT_TRUE(request2.connection());
request2.connection()->CloseAndReportForceClose();
EXPECT_FALSE(request2.connection()->IsConnected());
RunPostedTasks();
EXPECT_TRUE(bucket_context_->databases().empty());
}
TEST_F(IndexedDBDatabaseTest, ForcedClose) {
MockMojoIndexedDBDatabaseCallbacks database_callbacks;
MockIndexedDBFactoryClient request;
const int64_t upgrade_transaction_id = 3;
auto connection = std::make_unique<IndexedDBPendingConnection>(
std::make_unique<ThunkFactoryClient>(request),
std::make_unique<IndexedDBDatabaseCallbacks>(
database_callbacks.BindNewEndpointAndPassDedicatedRemote()),
upgrade_transaction_id, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection),
CreateTestClientStateWrapper());
RunPostedTasks();
EXPECT_EQ(db_, request.connection()->database().get());
const int64_t transaction_id = 123;
const std::vector<int64_t> scope;
IndexedDBTransaction* transaction = request.connection()->CreateTransaction(
mojo::NullAssociatedReceiver(), transaction_id,
std::set<int64_t>(scope.begin(), scope.end()),
blink::mojom::IDBTransactionMode::ReadOnly,
new IndexedDBBackingStore::Transaction(
bucket_context_->backing_store()->AsWeakPtr(),
blink::mojom::IDBTransactionDurability::Relaxed,
blink::mojom::IDBTransactionMode::ReadWrite));
db_->RegisterAndScheduleTransaction(transaction);
db_ = nullptr;
base::RunLoop run_loop;
EXPECT_CALL(database_callbacks, ForcedClose)
.WillOnce(base::test::RunClosure(run_loop.QuitClosure()));
request.connection()->CloseAndReportForceClose();
run_loop.Run();
}
namespace {
class MockFactoryClient : public IndexedDBFactoryClient {
public:
MockFactoryClient() : IndexedDBFactoryClient(mojo::NullAssociatedRemote()) {}
~MockFactoryClient() override = default;
MockFactoryClient(const MockFactoryClient&) = delete;
MockFactoryClient& operator=(const MockFactoryClient&) = delete;
void OnBlocked(int64_t existing_version) override { blocked_called_ = true; }
void OnDeleteSuccess(int64_t old_version) 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:
bool blocked_called_ = false;
bool success_called_ = false;
bool error_called_ = false;
};
} // namespace
TEST_F(IndexedDBDatabaseTest, PendingDelete) {
MockIndexedDBFactoryClient request1;
const int64_t transaction_id1 = 1;
MockMojoIndexedDBDatabaseCallbacks database_callbacks1;
auto connection = std::make_unique<IndexedDBPendingConnection>(
std::make_unique<ThunkFactoryClient>(request1),
std::make_unique<IndexedDBDatabaseCallbacks>(
database_callbacks1.BindNewEndpointAndPassDedicatedRemote()),
transaction_id1, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection),
CreateTestClientStateWrapper());
RunPostedTasks();
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 0UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
bool deleted = false;
MockFactoryClient request2;
db_->ScheduleDeleteDatabase(
std::make_unique<ThunkFactoryClient>(request2),
base::BindLambdaForTesting([&]() { deleted = true; }));
RunPostedTasks();
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
EXPECT_FALSE(request2.blocked_called());
request1.connection()->VersionChangeIgnored();
EXPECT_TRUE(request2.blocked_called());
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
db_->ForceCloseAndRunTasks();
db_ = nullptr;
RunPostedTasks();
EXPECT_FALSE(db_);
EXPECT_TRUE(deleted);
EXPECT_TRUE(request2.success_called());
}
TEST_F(IndexedDBDatabaseTest, OpenDeleteClear) {
const int64_t kDatabaseVersion = 1;
MockIndexedDBFactoryClient request1(
/*expect_connection=*/true);
MockMojoIndexedDBDatabaseCallbacks database_callbacks1;
const int64_t transaction_id1 = 1;
auto connection1 = std::make_unique<IndexedDBPendingConnection>(
std::make_unique<ThunkFactoryClient>(request1),
std::make_unique<IndexedDBDatabaseCallbacks>(
database_callbacks1.BindNewEndpointAndPassDedicatedRemote()),
transaction_id1, kDatabaseVersion, mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection1),
CreateTestClientStateWrapper());
RunPostedTasks();
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
MockIndexedDBFactoryClient request2(
/*expect_connection=*/false);
MockMojoIndexedDBDatabaseCallbacks database_callbacks2;
const int64_t transaction_id2 = 2;
auto connection2 = std::make_unique<IndexedDBPendingConnection>(
std::make_unique<ThunkFactoryClient>(request2),
std::make_unique<IndexedDBDatabaseCallbacks>(
database_callbacks2.BindNewEndpointAndPassDedicatedRemote()),
transaction_id2, kDatabaseVersion, mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection2),
CreateTestClientStateWrapper());
RunPostedTasks();
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 1UL);
MockIndexedDBFactoryClient request3(
/*expect_connection=*/false);
MockMojoIndexedDBDatabaseCallbacks database_callbacks3;
const int64_t transaction_id3 = 3;
auto connection3 = std::make_unique<IndexedDBPendingConnection>(
std::make_unique<ThunkFactoryClient>(request3),
std::make_unique<IndexedDBDatabaseCallbacks>(
database_callbacks3.BindNewEndpointAndPassDedicatedRemote()),
transaction_id3, kDatabaseVersion, mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection3),
CreateTestClientStateWrapper());
RunPostedTasks();
EXPECT_TRUE(request1.upgrade_called());
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 2UL);
EXPECT_CALL(database_callbacks1, ForcedClose);
EXPECT_CALL(database_callbacks2, ForcedClose);
EXPECT_CALL(database_callbacks3, ForcedClose);
db_->ForceCloseAndRunTasks();
db_ = nullptr;
database_callbacks1.FlushForTesting();
EXPECT_TRUE(request1.error_called());
EXPECT_TRUE(request2.error_called());
EXPECT_TRUE(request3.error_called());
}
TEST_F(IndexedDBDatabaseTest, ForceDelete) {
MockIndexedDBFactoryClient request1;
MockMojoIndexedDBDatabaseCallbacks database_callbacks;
const int64_t transaction_id1 = 1;
auto connection = std::make_unique<IndexedDBPendingConnection>(
std::make_unique<ThunkFactoryClient>(request1),
std::make_unique<IndexedDBDatabaseCallbacks>(
database_callbacks.BindNewEndpointAndPassDedicatedRemote()),
transaction_id1, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection),
CreateTestClientStateWrapper());
RunPostedTasks();
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 0UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
bool deleted = false;
MockFactoryClient request2;
db_->ScheduleDeleteDatabase(
std::make_unique<ThunkFactoryClient>(request2),
base::BindLambdaForTesting([&]() { deleted = true; }));
RunPostedTasks();
EXPECT_FALSE(deleted);
db_->ForceCloseAndRunTasks();
db_ = nullptr;
RunPostedTasks();
EXPECT_FALSE(db_);
EXPECT_TRUE(deleted);
EXPECT_FALSE(request2.blocked_called());
EXPECT_TRUE(request2.success_called());
}
TEST_F(IndexedDBDatabaseTest, ForceCloseWhileOpenPending) {
// Verify that pending connection requests are handled correctly during a
// ForceClose.
MockIndexedDBFactoryClient request1;
MockMojoIndexedDBDatabaseCallbacks database_callbacks1;
const int64_t transaction_id1 = 1;
auto connection1 = std::make_unique<IndexedDBPendingConnection>(
std::make_unique<ThunkFactoryClient>(request1),
std::make_unique<IndexedDBDatabaseCallbacks>(
database_callbacks1.BindNewEndpointAndPassDedicatedRemote()),
transaction_id1, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection1),
CreateTestClientStateWrapper());
RunPostedTasks();
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 0UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
MockIndexedDBFactoryClient request2(
/*expect_connection=*/false);
MockMojoIndexedDBDatabaseCallbacks database_callbacks2;
const int64_t transaction_id2 = 2;
auto connection2 = std::make_unique<IndexedDBPendingConnection>(
std::make_unique<ThunkFactoryClient>(request2),
std::make_unique<IndexedDBDatabaseCallbacks>(
database_callbacks2.BindNewEndpointAndPassDedicatedRemote()),
transaction_id2, 3, mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection2),
CreateTestClientStateWrapper());
RunPostedTasks();
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
db_->ForceCloseAndRunTasks();
db_ = nullptr;
RunPostedTasks();
EXPECT_FALSE(db_);
}
TEST_F(IndexedDBDatabaseTest, ForceCloseWhileOpenAndDeletePending) {
// Verify that pending connection requests are handled correctly during a
// ForceClose.
MockIndexedDBFactoryClient request1;
MockMojoIndexedDBDatabaseCallbacks database_callbacks1;
const int64_t transaction_id1 = 1;
auto connection1 = std::make_unique<IndexedDBPendingConnection>(
std::make_unique<ThunkFactoryClient>(request1),
std::make_unique<IndexedDBDatabaseCallbacks>(
database_callbacks1.BindNewEndpointAndPassDedicatedRemote()),
transaction_id1, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection1),
CreateTestClientStateWrapper());
RunPostedTasks();
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 0UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 0UL);
MockIndexedDBFactoryClient request2(false);
MockMojoIndexedDBDatabaseCallbacks database_callbacks2;
const int64_t transaction_id2 = 2;
auto connection2 = std::make_unique<IndexedDBPendingConnection>(
std::make_unique<ThunkFactoryClient>(request2),
std::make_unique<IndexedDBDatabaseCallbacks>(
database_callbacks2.BindNewEndpointAndPassDedicatedRemote()),
transaction_id2, 3, mojo::NullAssociatedReceiver());
db_->ScheduleOpenConnection(std::move(connection2),
CreateTestClientStateWrapper());
RunPostedTasks();
bool deleted = false;
auto request3 = std::make_unique<MockFactoryClient>();
db_->ScheduleDeleteDatabase(
std::move(request3),
base::BindLambdaForTesting([&]() { deleted = true; }));
RunPostedTasks();
EXPECT_FALSE(deleted);
EXPECT_EQ(db_->ConnectionCount(), 1UL);
EXPECT_EQ(db_->ActiveOpenDeleteCount(), 1UL);
EXPECT_EQ(db_->PendingOpenDeleteCount(), 1UL);
db_->ForceCloseAndRunTasks();
db_ = nullptr;
RunPostedTasks();
EXPECT_TRUE(deleted);
}
leveldb::Status DummyOperation(IndexedDBTransaction* transaction) {
return leveldb::Status::OK();
}
class IndexedDBDatabaseOperationTest : public IndexedDBDatabaseTest {
public:
IndexedDBDatabaseOperationTest() : commit_success_(leveldb::Status::OK()) {}
IndexedDBDatabaseOperationTest(const IndexedDBDatabaseOperationTest&) =
delete;
IndexedDBDatabaseOperationTest& operator=(
const IndexedDBDatabaseOperationTest&) = delete;
void SetUp() override {
IndexedDBDatabaseTest::SetUp();
const int64_t transaction_id = 1;
auto connection = std::make_unique<IndexedDBPendingConnection>(
std::make_unique<ThunkFactoryClient>(request_),
std::make_unique<IndexedDBDatabaseCallbacks>(
mojo::NullAssociatedRemote()),
transaction_id, IndexedDBDatabaseMetadata::DEFAULT_VERSION,
mojo::NullAssociatedReceiver());
mojo::PendingRemote<storage::mojom::IndexedDBClientStateChecker> remote;
db_->ScheduleOpenConnection(
std::move(connection),
base::MakeRefCounted<IndexedDBClientStateCheckerWrapper>(
std::move(remote)));
RunPostedTasks();
EXPECT_EQ(IndexedDBDatabaseMetadata::NO_VERSION, db_->metadata().version);
EXPECT_TRUE(request_.connection());
transaction_ = request_.connection()->CreateVersionChangeTransaction(
transaction_id, /*scope=*/std::set<int64_t>(),
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));
// Run posted tasks to execute the dummy operation and ensure that it is
// stored in the connection.
RunPostedTasks();
}
protected:
MockIndexedDBFactoryClient request_;
raw_ptr<IndexedDBTransaction, AcrossTasksDanglingUntriaged> transaction_ =
nullptr;
leveldb::Status commit_success_;
};
TEST_F(IndexedDBDatabaseOperationTest, CreateObjectStore) {
EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
const int64_t store_id = 1001;
leveldb::Status s =
db_->CreateObjectStoreOperation(store_id, u"store", IndexedDBKeyPath(),
/*auto_increment=*/false, transaction_);
EXPECT_TRUE(s.ok());
transaction_->SetCommitFlag();
RunPostedTasks();
EXPECT_FALSE(error_called_);
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;
leveldb::Status s =
db_->CreateObjectStoreOperation(store_id, u"store", IndexedDBKeyPath(),
/*auto_increment=*/false, transaction_);
EXPECT_TRUE(s.ok());
EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
const int64_t index_id = 2002;
s = db_->CreateIndexOperation(store_id, index_id, u"index",
IndexedDBKeyPath(), /*unique=*/false,
/*multi_entry=*/false, transaction_);
EXPECT_TRUE(s.ok());
EXPECT_EQ(
1ULL,
db_->metadata().object_stores.find(store_id)->second.indexes.size());
transaction_->SetCommitFlag();
RunPostedTasks();
EXPECT_FALSE(error_called_);
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.");
}
IndexedDBDatabaseOperationAbortTest(
const IndexedDBDatabaseOperationAbortTest&) = delete;
IndexedDBDatabaseOperationAbortTest& operator=(
const IndexedDBDatabaseOperationAbortTest&) = delete;
};
TEST_F(IndexedDBDatabaseOperationAbortTest, CreateObjectStore) {
EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
const int64_t store_id = 1001;
leveldb::Status s =
db_->CreateObjectStoreOperation(store_id, u"store", IndexedDBKeyPath(),
/*auto_increment=*/false, transaction_);
EXPECT_TRUE(s.ok());
EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
transaction_->SetCommitFlag();
RunPostedTasks();
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;
leveldb::Status s =
db_->CreateObjectStoreOperation(store_id, u"store", IndexedDBKeyPath(),
/*auto_increment=*/false, transaction_);
EXPECT_TRUE(s.ok());
EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
const int64_t index_id = 2002;
s = db_->CreateIndexOperation(store_id, index_id, u"index",
IndexedDBKeyPath(), /*unique=*/false,
/*multi_entry=*/false, transaction_);
EXPECT_TRUE(s.ok());
EXPECT_EQ(
1ULL,
db_->metadata().object_stores.find(store_id)->second.indexes.size());
transaction_->SetCommitFlag();
RunPostedTasks();
EXPECT_TRUE(error_called_);
EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
db_ = nullptr;
}
TEST_F(IndexedDBDatabaseOperationTest, CreatePutDelete) {
EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
const int64_t store_id = 1001;
leveldb::Status s =
db_->CreateObjectStoreOperation(store_id, u"store", IndexedDBKeyPath(),
/*auto_increment=*/false, transaction_);
EXPECT_TRUE(s.ok());
EXPECT_EQ(1ULL, db_->metadata().object_stores.size());
IndexedDBValue value("value1", {});
std::unique_ptr<IndexedDBKey> key(std::make_unique<IndexedDBKey>("key"));
std::vector<IndexedDBIndexKeys> index_keys;
base::MockCallback<blink::mojom::IDBTransaction::PutCallback> callback;
// Set in-flight memory to a reasonably large number to prevent underflow in
// `PutOperation`
transaction_->in_flight_memory() += 1000;
auto put_params = std::make_unique<IndexedDBDatabase::PutOperationParams>();
put_params->object_store_id = store_id;
put_params->value = value;
put_params->key = std::move(key);
put_params->put_mode = blink::mojom::IDBPutMode::AddOnly;
put_params->callback = callback.Get();
put_params->index_keys = index_keys;
s = db_->PutOperation(std::move(put_params), transaction_);
EXPECT_TRUE(s.ok());
s = db_->DeleteObjectStoreOperation(store_id, transaction_);
EXPECT_TRUE(s.ok());
EXPECT_EQ(0ULL, db_->metadata().object_stores.size());
db_ = nullptr;
transaction_->SetCommitFlag();
RunPostedTasks();
EXPECT_FALSE(error_called_);
EXPECT_TRUE(s.ok());
}
} // namespace content