blob: 4b58a4f61ca8c82ee2d3614f8e6a86d51c7e1c62 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sqlite_encrypted_vfs/sqlite_encrypted_vfs.h"
#include <string>
#include <vector>
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "sql/database.h"
#include "sql/statement.h"
#include "sql/test/test_helpers.h"
#include "sql/transaction.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/sqlite/sqlite3.h"
namespace sqlite_encrypted_vfs {
class SqliteEncryptedVfsTest : public testing::Test {
public:
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
db_path_ = temp_dir_.GetPath().AppendASCII("encrypted_vfs_test.sqlite");
SqliteEncryptedVfs::Register();
}
sql::Database CreateDatabase() {
return sql::Database(
sql::DatabaseOptions()
.set_vfs_name_discouraged(SqliteEncryptedVfs::kSqliteVfsName)
.set_mmap_enabled(false),
sql::test::kTestTag);
}
std::vector<base::FilePath> GetFilesInTempDir() const {
std::vector<base::FilePath> files;
base::FileEnumerator enumerator(temp_dir_.GetPath(), /*recursive=*/false,
base::FileEnumerator::FILES);
for (base::FilePath name = enumerator.Next(); !name.empty();
name = enumerator.Next()) {
files.push_back(name.BaseName());
}
return files;
}
void VerifyDatabaseFiles() const {
EXPECT_TRUE(base::PathExists(db_path_));
EXPECT_THAT(GetFilesInTempDir(),
testing::UnorderedElementsAre(
db_path_.BaseName(),
sql::Database::JournalPath(db_path_).BaseName()));
}
protected:
base::ScopedTempDir temp_dir_;
base::FilePath db_path_;
};
TEST_F(SqliteEncryptedVfsTest, GetInstance) {
EXPECT_NE(SqliteEncryptedVfs::GetInstance(), nullptr);
}
TEST_F(SqliteEncryptedVfsTest, BasicOperations) {
{
sql::Database db = CreateDatabase();
ASSERT_TRUE(db.Open(db_path_)) << db.GetErrorMessage();
EXPECT_TRUE(
db.Execute("CREATE TABLE foo (id INTEGER PRIMARY KEY, title TEXT)"))
<< db.GetErrorMessage();
EXPECT_TRUE(
db.Execute("INSERT INTO foo (id, title) VALUES (1, 'hello world')"))
<< db.GetErrorMessage();
sql::Statement s(
db.GetUniqueStatement("SELECT title FROM foo WHERE id = 1"));
EXPECT_TRUE(s.is_valid()) << db.GetErrorMessage();
EXPECT_TRUE(s.Step()) << db.GetErrorMessage();
EXPECT_EQ(s.ColumnString(0), "hello world");
}
VerifyDatabaseFiles();
}
TEST_F(SqliteEncryptedVfsTest, TransactionAndRollback) {
{
sql::Database db = CreateDatabase();
ASSERT_TRUE(db.Open(db_path_)) << db.GetErrorMessage();
ASSERT_TRUE(
db.Execute("CREATE TABLE items (id INTEGER PRIMARY KEY, name TEXT)"));
// Rolled back transaction.
{
sql::Transaction transaction(&db);
ASSERT_TRUE(transaction.Begin());
ASSERT_TRUE(
db.Execute("INSERT INTO items (id, name) VALUES (1, 'apple')"));
transaction.Rollback();
}
{
sql::Statement s(
db.GetUniqueStatement("SELECT COUNT(*) FROM items WHERE id = 1"));
ASSERT_TRUE(s.Step());
EXPECT_EQ(s.ColumnInt(0), 0);
}
// Committed transaction.
{
sql::Transaction transaction(&db);
ASSERT_TRUE(transaction.Begin());
ASSERT_TRUE(
db.Execute("INSERT INTO items (id, name) VALUES (2, 'banana')"));
ASSERT_TRUE(transaction.Commit());
}
{
sql::Statement s(
db.GetUniqueStatement("SELECT name FROM items WHERE id = 2"));
ASSERT_TRUE(s.Step());
EXPECT_EQ(s.ColumnString(0), "banana");
}
}
VerifyDatabaseFiles();
}
TEST_F(SqliteEncryptedVfsTest, ReopenDatabase) {
{
sql::Database db = CreateDatabase();
ASSERT_TRUE(db.Open(db_path_)) << db.GetErrorMessage();
ASSERT_TRUE(db.Execute("CREATE TABLE kv (k TEXT PRIMARY KEY, v TEXT)"));
ASSERT_TRUE(db.Execute("INSERT INTO kv (k, v) VALUES ('key1', 'val1')"));
db.Close();
}
{
sql::Database db = CreateDatabase();
ASSERT_TRUE(db.Open(db_path_)) << db.GetErrorMessage();
sql::Statement s(
db.GetUniqueStatement("SELECT v FROM kv WHERE k = 'key1'"));
ASSERT_TRUE(s.Step());
EXPECT_EQ(s.ColumnString(0), "val1");
}
VerifyDatabaseFiles();
}
TEST_F(SqliteEncryptedVfsTest, MmapFallbackToStandardIO) {
{
sql::Database db(
sql::DatabaseOptions()
.set_vfs_name_discouraged(SqliteEncryptedVfs::kSqliteVfsName)
.set_mmap_enabled(true),
sql::test::kTestTag);
ASSERT_TRUE(db.Open(db_path_)) << db.GetErrorMessage();
EXPECT_TRUE(
db.Execute("CREATE TABLE data (id INTEGER PRIMARY KEY, content TEXT)"))
<< db.GetErrorMessage();
EXPECT_TRUE(db.Execute(
"INSERT INTO data (id, content) VALUES (1, 'fallback test')"))
<< db.GetErrorMessage();
sql::Statement s(
db.GetUniqueStatement("SELECT content FROM data WHERE id = 1"));
ASSERT_TRUE(s.Step()) << db.GetErrorMessage();
EXPECT_EQ(s.ColumnString(0), "fallback test");
}
VerifyDatabaseFiles();
}
TEST_F(SqliteEncryptedVfsTest, RegistrationIdempotencyAndMetadata) {
// Repeated registration should not crash or duplicate.
SqliteEncryptedVfs::Register();
SqliteEncryptedVfs::Register();
sqlite3_vfs* vfs = sqlite3_vfs_find(SqliteEncryptedVfs::kSqliteVfsName);
ASSERT_NE(vfs, nullptr);
EXPECT_STREQ(vfs->zName, SqliteEncryptedVfs::kSqliteVfsName);
EXPECT_GE(vfs->iVersion, 3);
EXPECT_GT(vfs->szOsFile, 0);
EXPECT_EQ(vfs->xDlOpen, nullptr); // Dynamic extensions disabled.
}
TEST_F(SqliteEncryptedVfsTest, MultiPageOperations) {
{
sql::Database db = CreateDatabase();
ASSERT_TRUE(db.Open(db_path_)) << db.GetErrorMessage();
ASSERT_TRUE(db.Execute(
"CREATE TABLE large (id INTEGER PRIMARY KEY, payload TEXT)"));
std::string large_string(1024, 'x');
sql::Statement s(
db.GetUniqueStatement("INSERT INTO large (payload) VALUES (?)"));
for (int i = 0; i < 50; ++i) {
s.Reset(/*clear_bound_vars=*/true);
s.BindString(0, large_string);
ASSERT_TRUE(s.Run());
}
}
{
sql::Database db = CreateDatabase();
ASSERT_TRUE(db.Open(db_path_)) << db.GetErrorMessage();
sql::Statement count_stmt(
db.GetUniqueStatement("SELECT COUNT(*) FROM large"));
ASSERT_TRUE(count_stmt.Step());
EXPECT_EQ(count_stmt.ColumnInt(0), 50);
}
VerifyDatabaseFiles();
}
} // namespace sqlite_encrypted_vfs