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