| // 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 "net/disk_cache/sql/sql_shared_cache.h" |
| |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/pickle.h" |
| #include "base/run_loop.h" |
| #include "base/task/thread_pool.h" |
| #include "base/test/bind.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_future.h" |
| #include "net/base/features.h" |
| #include "net/disk_cache/backend_cleanup_tracker.h" |
| #include "net/disk_cache/sql/mock_shared_cache_client_remote.h" |
| #include "net/disk_cache/sql/sql_persistent_store.h" |
| #include "net/disk_cache/sql/sql_shared_cache_handle.h" |
| #include "net/disk_cache/sql/sql_shared_cache_isolated_database.h" |
| #include "net/disk_cache/sql/sql_shared_cache_manager.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/http/http_response_info.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace disk_cache { |
| |
| namespace { |
| |
| constexpr SqlSharedCacheDbId kTestDbId(42); |
| |
| scoped_refptr<net::IOBufferWithSize> CreateHeadBuffer( |
| const net::HttpResponseInfo& response_info, |
| bool response_truncated = false) { |
| auto pickle = response_info.MakePickle(/*skip_transient_headers=*/true, |
| response_truncated); |
| auto head_buffer = |
| base::MakeRefCounted<net::IOBufferWithSize>(pickle->size()); |
| head_buffer->span().copy_from(pickle->AsBytes()); |
| return head_buffer; |
| } |
| |
| scoped_refptr<net::IOBufferWithSize> CreateDataBuffer(std::string_view data) { |
| auto data_buffer = base::MakeRefCounted<net::IOBufferWithSize>(data.size()); |
| data_buffer->span().copy_from(base::as_bytes(base::span(data))); |
| return data_buffer; |
| } |
| |
| net::HttpResponseInfo CreateTestHttpResponseInfo() { |
| net::HttpResponseInfo response_info; |
| response_info.headers = |
| base::MakeRefCounted<net::HttpResponseHeaders>("HTTP/1.1 200 OK"); |
| response_info.response_time = base::Time::Now(); |
| return response_info; |
| } |
| |
| } // namespace |
| |
| class SqlSharedCacheTest : public testing::TestWithParam<bool> { |
| public: |
| static std::string DescribeParams( |
| const testing::TestParamInfo<ParamType>& info) { |
| return info.param ? "WalEnabled" : "WalDisabled"; |
| } |
| |
| void SetUp() override { |
| ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); |
| cleanup_tracker_ = BackendCleanupTracker::TryCreate(temp_dir_.GetPath(), |
| base::DoNothing()); |
| CHECK(cleanup_tracker_); |
| if (GetParam()) { |
| feature_list_.InitWithFeaturesAndParameters( |
| {{net::features::kRendererAccessibleHttpCache, |
| {{net::features::kRendererAccessibleHttpCacheWalMode.name, |
| "true"}}}}, |
| {}); |
| } else { |
| feature_list_.InitWithFeaturesAndParameters( |
| {{net::features::kRendererAccessibleHttpCache, |
| {{net::features::kRendererAccessibleHttpCacheWalMode.name, |
| "false"}}}}, |
| {}); |
| } |
| task_runners_.push_back(base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::USER_BLOCKING, |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN})); |
| store_ = std::make_unique<SqlPersistentStore>( |
| temp_dir_.GetPath(), 1024 * 1024, net::DISK_CACHE, task_runners_, |
| async_task_manager_, cleanup_tracker_); |
| } |
| |
| void TearDown() override { |
| store_.reset(); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| WaitForCleanup(); |
| } |
| |
| protected: |
| void WaitForCleanup() { |
| if (!cleanup_tracker_) { |
| return; |
| } |
| base::RunLoop run_loop; |
| cleanup_tracker_->AddPostCleanupCallback(run_loop.QuitClosure()); |
| cleanup_tracker_ = nullptr; |
| run_loop.Run(); |
| cleanup_tracker_ = BackendCleanupTracker::TryCreate(temp_dir_.GetPath(), |
| base::DoNothing()); |
| CHECK(cleanup_tracker_); |
| } |
| |
| void PopulateStoreEntry(const CacheEntryKey& key, |
| const net::HttpResponseInfo& response_info, |
| std::string_view data, |
| bool response_truncated = false) { |
| base::test::TestFuture<SqlPersistentStore::EntryInfoOrError> create_future; |
| store_->OpenOrCreateEntry(key, create_future.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto create_result = create_future.Take(); |
| ASSERT_TRUE(create_result.has_value()); |
| auto res_id = create_result.value().res_id; |
| |
| auto head_buffer = CreateHeadBuffer(response_info, response_truncated); |
| base::test::TestFuture<SqlPersistentStore::ResIdOrError> header_future; |
| store_->WriteEntryDataAndMetadata( |
| key, res_id, /*old_body_end=*/std::nullopt, |
| EntryWriteBuffer(/*buffer=*/nullptr, /*size=*/0, /*offset=*/0), |
| base::Time::Now(), /*new_hints=*/std::nullopt, head_buffer, |
| head_buffer->size(), /*doomed_new_entry=*/false, |
| header_future.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| ASSERT_TRUE(header_future.Get().has_value()); |
| |
| if (!data.empty()) { |
| auto data_buffer = CreateDataBuffer(data); |
| base::test::TestFuture<SqlPersistentStore::ResIdOrError> write_future; |
| store_->WriteEntryData( |
| key, res_id, /*old_body_end=*/0, |
| EntryWriteBuffer(data_buffer, data.size(), /*offset=*/0), |
| /*truncate=*/false, /*doomed_new_entry=*/false, |
| /*sparse_write=*/false, /*header_size=*/0, |
| write_future.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| ASSERT_TRUE(write_future.Get().has_value()); |
| } |
| } |
| |
| scoped_refptr<SqlSharedCacheHandle> CreateAndInitStoreAndCache() { |
| base::test::TestFuture<SqlPersistentStore::Error> store_init_future; |
| store_->Initialize(store_init_future.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| EXPECT_EQ(store_init_future.Get(), SqlPersistentStore::Error::kOk); |
| |
| auto* manager = store_->shared_cache_manager_for_testing(); |
| EXPECT_TRUE(manager); |
| |
| net::NetworkIsolationKey nik(net::SchemefulSite(GURL("https://foo.test")), |
| net::SchemefulSite(GURL("https://bar.test"))); |
| base::test::TestFuture<scoped_refptr<SqlSharedCacheHandle>> handle_future; |
| manager->GetCacheByNik(nik, /*require_shared_cache_db_id=*/true, |
| handle_future.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| scoped_refptr<SqlSharedCacheHandle> handle = handle_future.Take(); |
| EXPECT_TRUE(handle); |
| return handle; |
| } |
| |
| SqlPersistentStore::SharedCacheEligibleEntry CreateEligibleEntry( |
| const CacheEntryKey& key, |
| const GURL& url, |
| const net::HttpResponseInfo& response_info) { |
| SqlPersistentStore::SharedCacheEligibleEntry entry; |
| entry.key = key; |
| entry.url = url; |
| entry.response_info = |
| std::make_unique<net::HttpResponseInfo>(response_info); |
| return entry; |
| } |
| |
| void VerifyIsolatedDatabaseEntryData(SqlSharedCache& cache, |
| const CacheEntryKey& key, |
| SqlSharedCacheRowId row_id, |
| std::string_view expected_data) { |
| auto read_buffer = |
| base::MakeRefCounted<net::IOBufferWithSize>(expected_data.size()); |
| base::test::TestFuture<SqlSharedCacheIsolatedDatabase::ReadResultOrError> |
| read_future; |
| cache.isolated_database_for_testing() |
| .AsyncCall(&SqlSharedCacheIsolatedDatabase::Read) |
| .WithArgs(key, row_id, static_cast<int>(expected_data.size()), |
| /*offset=*/0, read_buffer) |
| .Then(read_future.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto read_result = read_future.Take(); |
| ASSERT_TRUE(read_result.has_value()); |
| EXPECT_EQ(read_result.value().read_bytes, |
| static_cast<int>(expected_data.size())); |
| if (!expected_data.empty()) { |
| EXPECT_EQ(std::string_view(read_buffer->data(), expected_data.size()), |
| expected_data); |
| } |
| } |
| |
| void VerifyIsolatedDatabaseEntryNotFound(SqlSharedCache& cache, |
| const CacheEntryKey& key, |
| SqlSharedCacheRowId row_id) { |
| auto read_buffer = base::MakeRefCounted<net::IOBufferWithSize>(100); |
| base::test::TestFuture<SqlSharedCacheIsolatedDatabase::ReadResultOrError> |
| read_future; |
| cache.isolated_database_for_testing() |
| .AsyncCall(&SqlSharedCacheIsolatedDatabase::Read) |
| .WithArgs(key, row_id, /*body_size=*/100, /*offset=*/0, read_buffer) |
| .Then(read_future.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto read_result = read_future.Take(); |
| EXPECT_FALSE(read_result.has_value()); |
| EXPECT_EQ(read_result.error(), |
| SqlSharedCacheIsolatedDatabase::Error::kEntryNotFound); |
| } |
| |
| SqlPersistentStore::EntryInfoOrError OpenStoreEntry( |
| const CacheEntryKey& key) { |
| base::test::TestFuture<SqlPersistentStore::EntryInfoOrError> future; |
| store_->OpenEntry(key, future.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| return future.Take(); |
| } |
| |
| void VerifyStoreEntrySharedCacheResourceId( |
| const CacheEntryKey& key, |
| SqlSharedCacheDbId expected_db_id, |
| SqlSharedCacheRowId expected_row_id) { |
| auto open_result = OpenStoreEntry(key); |
| ASSERT_TRUE(open_result.has_value()); |
| ASSERT_TRUE(open_result->shared_cache_resource_id.has_value()); |
| EXPECT_EQ(open_result->shared_cache_resource_id->db_id, expected_db_id); |
| EXPECT_EQ(open_result->shared_cache_resource_id->row_id, expected_row_id); |
| } |
| |
| base::test::ScopedFeatureList feature_list_; |
| base::test::TaskEnvironment task_environment_; |
| base::ScopedTempDir temp_dir_; |
| std::vector<scoped_refptr<base::SequencedTaskRunner>> task_runners_; |
| SqlAsyncTaskManager async_task_manager_; |
| std::unique_ptr<SqlPersistentStore> store_; |
| scoped_refptr<BackendCleanupTracker> cleanup_tracker_; |
| }; |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SqlSharedCacheTest, |
| testing::Bool(), |
| &SqlSharedCacheTest::DescribeParams); |
| |
| TEST_P(SqlSharedCacheTest, BasicLifecycleAndHandleCount) { |
| bool unreferenced_called = false; |
| SqlSharedCache* unreferenced_cache_ptr = nullptr; |
| |
| auto cache = std::make_unique<SqlSharedCache>( |
| "test_nik", *store_, temp_dir_.GetPath(), |
| base::BindLambdaForTesting([&](SqlSharedCache& c) { |
| unreferenced_called = true; |
| unreferenced_cache_ptr = &c; |
| }), |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::USER_BLOCKING, |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN}), |
| /*read_cache_memory_monitor=*/nullptr, cleanup_tracker_); |
| |
| EXPECT_EQ(cache->nik_string(), "test_nik"); |
| EXPECT_FALSE(cache->shared_cache_db_id().has_value()); |
| EXPECT_FALSE(cache->IsReferenced()); |
| |
| { |
| scoped_refptr<SqlSharedCacheHandle> handle1 = cache->CreateHandle(); |
| EXPECT_TRUE(cache->IsReferenced()); |
| EXPECT_TRUE(handle1->get() != nullptr); |
| EXPECT_EQ(handle1->get(), cache.get()); |
| |
| { |
| scoped_refptr<SqlSharedCacheHandle> handle2 = cache->CreateHandle(); |
| EXPECT_TRUE(cache->IsReferenced()); |
| EXPECT_FALSE(unreferenced_called); |
| } |
| |
| EXPECT_TRUE(cache->IsReferenced()); |
| EXPECT_FALSE(unreferenced_called); |
| } |
| EXPECT_FALSE(cache->IsReferenced()); |
| EXPECT_TRUE(unreferenced_called); |
| EXPECT_EQ(unreferenced_cache_ptr, cache.get()); |
| } |
| |
| TEST_P(SqlSharedCacheTest, InitIsolatedDatabaseAndCleanup) { |
| auto cache = std::make_unique<SqlSharedCache>( |
| "test_nik", *store_, temp_dir_.GetPath(), base::DoNothing(), |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::USER_BLOCKING, |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN}), |
| /*read_cache_memory_monitor=*/nullptr, cleanup_tracker_); |
| |
| bool init_success = false; |
| |
| cache->InitIsolatedDatabase( |
| kTestDbId, base::BindLambdaForTesting( |
| [&](bool success) { init_success = success; })); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| |
| EXPECT_EQ(cache->shared_cache_db_id(), kTestDbId); |
| EXPECT_TRUE(init_success); |
| |
| bool cleanup_done = false; |
| cache->Cleanup(base::BindLambdaForTesting([&]() { cleanup_done = true; })); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| |
| EXPECT_TRUE(cleanup_done); |
| } |
| |
| TEST_P(SqlSharedCacheTest, CleanupWithoutIsolatedDatabase) { |
| auto cache = std::make_unique<SqlSharedCache>( |
| "test_nik", *store_, temp_dir_.GetPath(), base::DoNothing(), |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::USER_BLOCKING, |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN}), |
| /*read_cache_memory_monitor=*/nullptr, cleanup_tracker_); |
| |
| bool cleanup_done = false; |
| cache->Cleanup(base::BindLambdaForTesting([&]() { cleanup_done = true; })); |
| |
| EXPECT_TRUE(cleanup_done); |
| } |
| |
| TEST_P(SqlSharedCacheTest, DestructionTriggersCleanup) { |
| { |
| auto cache = std::make_unique<SqlSharedCache>( |
| "test_nik", *store_, temp_dir_.GetPath(), base::DoNothing(), |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::USER_BLOCKING, |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN}), |
| /*read_cache_memory_monitor=*/nullptr, cleanup_tracker_); |
| |
| bool init_success = false; |
| cache->InitIsolatedDatabase( |
| kTestDbId, base::BindLambdaForTesting( |
| [&](bool success) { init_success = success; })); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| EXPECT_TRUE(init_success); |
| // `cache` goes out of scope here. Its destruction triggers the destruction |
| // of `isolated_database_` (SqlTrackedSequenceBound), which posts a deletion |
| // task and registers it with `SqlAsyncTaskManager`. |
| } |
| |
| // Verify that `RunUntilAllTasksCompleteForTest()` waits for the cleanup task |
| // to complete. |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| |
| // Re-initializing another `SqlSharedCache` with the same db_id should succeed |
| // without database access conflicts. |
| auto new_cache = std::make_unique<SqlSharedCache>( |
| "test_nik", *store_, temp_dir_.GetPath(), base::DoNothing(), |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::USER_BLOCKING, |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN}), |
| /*read_cache_memory_monitor=*/nullptr, cleanup_tracker_); |
| |
| bool reinit_success = false; |
| new_cache->InitIsolatedDatabase( |
| kTestDbId, base::BindLambdaForTesting( |
| [&](bool success) { reinit_success = success; })); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| EXPECT_TRUE(reinit_success); |
| } |
| |
| TEST_P(SqlSharedCacheTest, CopyEntries) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKey("credential_key/post_key/https://www.example.com/"); |
| const std::string kData = "example data"; |
| auto response_info = CreateTestHttpResponseInfo(); |
| |
| PopulateStoreEntry(kKey, response_info, kData); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry(kKey, GURL("https://www.example.com/"), |
| response_info)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_TRUE(unprocessed.empty()); |
| |
| VerifyIsolatedDatabaseEntryData(*cache, kKey, SqlSharedCacheRowId(1), kData); |
| VerifyStoreEntrySharedCacheResourceId(kKey, *cache->shared_cache_db_id(), |
| SqlSharedCacheRowId(1)); |
| } |
| |
| TEST_P(SqlSharedCacheTest, CopyEntriesMultiple) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKey1( |
| "credential_key/post_key/https://example.com/1.png"); |
| const CacheEntryKey kKey2( |
| "credential_key/post_key/https://example.com/2.html"); |
| const CacheEntryKey kKey3( |
| "credential_key/post_key/https://example.com/3.bin"); |
| |
| const std::string kData1 = "image_bytes_1234"; |
| const std::string kData2 = ""; // 0-byte body |
| const std::string kData3(600 * 1024, 'a'); // Multi-chunk body > 512KB |
| |
| auto response_info1 = CreateTestHttpResponseInfo(); |
| auto response_info2 = CreateTestHttpResponseInfo(); |
| auto response_info3 = CreateTestHttpResponseInfo(); |
| |
| PopulateStoreEntry(kKey1, response_info1, kData1); |
| PopulateStoreEntry(kKey2, response_info2, kData2); |
| PopulateStoreEntry(kKey3, response_info3, kData3); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry(kKey1, GURL("https://example.com/1.png"), |
| response_info1)); |
| entries.push(CreateEligibleEntry(kKey2, GURL("https://example.com/2.html"), |
| response_info2)); |
| entries.push(CreateEligibleEntry(kKey3, GURL("https://example.com/3.bin"), |
| response_info3)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_TRUE(unprocessed.empty()); |
| |
| VerifyIsolatedDatabaseEntryData(*cache, kKey1, SqlSharedCacheRowId(1), |
| kData1); |
| VerifyIsolatedDatabaseEntryData(*cache, kKey2, SqlSharedCacheRowId(2), |
| kData2); |
| VerifyIsolatedDatabaseEntryData(*cache, kKey3, SqlSharedCacheRowId(3), |
| kData3); |
| VerifyStoreEntrySharedCacheResourceId(kKey1, *cache->shared_cache_db_id(), |
| SqlSharedCacheRowId(1)); |
| VerifyStoreEntrySharedCacheResourceId(kKey2, *cache->shared_cache_db_id(), |
| SqlSharedCacheRowId(2)); |
| VerifyStoreEntrySharedCacheResourceId(kKey3, *cache->shared_cache_db_id(), |
| SqlSharedCacheRowId(3)); |
| } |
| |
| TEST_P(SqlSharedCacheTest, |
| CopyEntriesMoveBlobsToSharedCacheFailureCleansUpPartialEntry) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKey( |
| "credential_key/post_key/https://example.com/fail_move"); |
| std::string kData = "payload for move blobs failure test"; |
| auto response_info = CreateTestHttpResponseInfo(); |
| |
| PopulateStoreEntry(kKey, response_info, kData); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry(kKey, GURL("https://example.com/fail_move"), |
| response_info)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| // OpenEntry task is already enqueued on store runner. Enabling simulate |
| // failure now will cause MoveBlobsToSharedCache (which is enqueued later) to |
| // fail. |
| store_->SetSimulateDbFailureForTesting(true); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_TRUE(unprocessed.empty()); |
| |
| // Verify that row 1 was cleaned up (deleted) from isolated database upon |
| // failure. |
| base::test::TestFuture<bool> has_row_future; |
| cache->isolated_database_for_testing() |
| .AsyncCall(&SqlSharedCacheIsolatedDatabase::HasRowForTesting) |
| .WithArgs(SqlSharedCacheRowId(1)) |
| .Then(has_row_future.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| EXPECT_FALSE(has_row_future.Get()); |
| } |
| |
| TEST_P(SqlSharedCacheTest, CopyEntriesExceedingMaxCopySizeSkipped) { |
| base::test::ScopedFeatureList custom_feature_list; |
| custom_feature_list.InitAndEnableFeatureWithParameters( |
| net::features::kDiskCacheBackendExperiment, |
| {{net::features::kSqlDiskCacheMaxSharedCacheCopyEntrySize.name, "100"}}); |
| |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKeySmall( |
| "credential_key/post_key/https://example.com/small"); |
| const CacheEntryKey kKeyLarge( |
| "credential_key/post_key/https://example.com/large"); |
| |
| const std::string kSmallData = "50 bytes payload data here..."; |
| const std::string kLargeData(200, |
| 'x'); // 200 bytes exceeds 100-byte threshold |
| |
| auto response_info_small = CreateTestHttpResponseInfo(); |
| auto response_info_large = CreateTestHttpResponseInfo(); |
| |
| PopulateStoreEntry(kKeySmall, response_info_small, kSmallData); |
| PopulateStoreEntry(kKeyLarge, response_info_large, kLargeData); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry(kKeySmall, GURL("https://example.com/small"), |
| response_info_small)); |
| entries.push(CreateEligibleEntry(kKeyLarge, GURL("https://example.com/large"), |
| response_info_large)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_TRUE(unprocessed.empty()); |
| |
| VerifyIsolatedDatabaseEntryData(*cache, kKeySmall, SqlSharedCacheRowId(1), |
| kSmallData); |
| VerifyIsolatedDatabaseEntryNotFound(*cache, kKeyLarge, |
| SqlSharedCacheRowId(2)); |
| } |
| |
| TEST_P(SqlSharedCacheTest, CopyEntriesOpenEntryFailed) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kNonExistentKey( |
| "credential_key/post_key/https://example.com/non_existent"); |
| auto response_info = CreateTestHttpResponseInfo(); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry(kNonExistentKey, |
| GURL("https://example.com/non_existent"), |
| response_info)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_TRUE(unprocessed.empty()); |
| |
| VerifyIsolatedDatabaseEntryNotFound(*cache, kNonExistentKey, |
| SqlSharedCacheRowId(1)); |
| } |
| |
| TEST_P(SqlSharedCacheTest, CopyEntriesParseResponseInfoMismatch) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKey( |
| "credential_key/post_key/https://example.com/mismatch"); |
| auto response_info_store = CreateTestHttpResponseInfo(); |
| PopulateStoreEntry(kKey, response_info_store, "test data"); |
| |
| // Create response info for entry with mismatched response_time |
| auto response_info_mismatched = CreateTestHttpResponseInfo(); |
| response_info_mismatched.response_time = |
| response_info_store.response_time + base::Seconds(10); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry(kKey, GURL("https://example.com/mismatch"), |
| response_info_mismatched)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_TRUE(unprocessed.empty()); |
| |
| VerifyIsolatedDatabaseEntryNotFound(*cache, kKey, SqlSharedCacheRowId(1)); |
| } |
| |
| TEST_P(SqlSharedCacheTest, |
| CopyEntriesPartialFailureNotifiesOnlySuccessfulHashes) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| auto client = std::make_unique<MockSharedCacheClientRemote>(); |
| auto* client_ptr = client.get(); |
| cache->RegisterClient(std::move(client)); |
| |
| client_ptr->WaitUntilInitialized(); |
| client_ptr->WaitUntilOnResourcesAdded(1); |
| EXPECT_TRUE(client_ptr->new_hashes().empty()); |
| |
| const CacheEntryKey kKeyValid( |
| "credential_key/post_key/https://example.com/valid"); |
| const CacheEntryKey kKeyInvalid( |
| "credential_key/post_key/https://example.com/invalid"); |
| const std::string kData = "valid data"; |
| auto response_info = CreateTestHttpResponseInfo(); |
| |
| PopulateStoreEntry(kKeyValid, response_info, kData); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry(kKeyValid, GURL("https://example.com/valid"), |
| response_info)); |
| // kKeyInvalid does not exist in store, so opening it will fail. |
| entries.push(CreateEligibleEntry( |
| kKeyInvalid, GURL("https://example.com/invalid"), response_info)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| |
| client_ptr->WaitUntilOnResourcesAdded(2); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_TRUE(unprocessed.empty()); |
| |
| EXPECT_EQ(client_ptr->on_resources_added_call_count(), 2u); |
| EXPECT_THAT(client_ptr->new_hashes(), |
| testing::ElementsAre(static_cast<uint32_t>( |
| kKeyValid.resource_url_hash().value()))); |
| |
| client_ptr->RunDisconnectHandler(); |
| } |
| |
| TEST_P(SqlSharedCacheTest, CopyEntriesResponseTruncatedSkipped) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKey( |
| "credential_key/post_key/https://example.com/truncated"); |
| auto response_info = CreateTestHttpResponseInfo(); |
| PopulateStoreEntry(kKey, response_info, "truncated payload", |
| /*response_truncated=*/true); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry(kKey, GURL("https://example.com/truncated"), |
| response_info)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_TRUE(unprocessed.empty()); |
| |
| VerifyIsolatedDatabaseEntryNotFound(*cache, kKey, SqlSharedCacheRowId(1)); |
| } |
| |
| TEST_P(SqlSharedCacheTest, CopyEntriesAlreadyInSharedCacheSkipped) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKey("credential_key/post_key/https://www.example.com/"); |
| const std::string kData = "example data"; |
| auto response_info = CreateTestHttpResponseInfo(); |
| |
| PopulateStoreEntry(kKey, response_info, kData); |
| |
| // Initial copy: should succeed and populate the isolated database. |
| { |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry(kKey, GURL("https://www.example.com/"), |
| response_info)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, |
| copy_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_TRUE(unprocessed.empty()); |
| |
| VerifyIsolatedDatabaseEntryData(*cache, kKey, SqlSharedCacheRowId(1), |
| kData); |
| VerifyStoreEntrySharedCacheResourceId(kKey, *cache->shared_cache_db_id(), |
| SqlSharedCacheRowId(1)); |
| } |
| |
| // Second copy: the entry is already in the shared cache |
| // (shared_cache_resource_id is set). It should be skipped and not re-inserted |
| // with a zero-filled body. |
| { |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry(kKey, GURL("https://www.example.com/"), |
| response_info)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, |
| copy_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_TRUE(unprocessed.empty()); |
| |
| // A second entry must not have been created in the isolated database. |
| VerifyIsolatedDatabaseEntryNotFound(*cache, kKey, SqlSharedCacheRowId(2)); |
| |
| // The original entry data must remain intact and not overwritten with |
| // zeros. |
| VerifyIsolatedDatabaseEntryData(*cache, kKey, SqlSharedCacheRowId(1), |
| kData); |
| VerifyStoreEntrySharedCacheResourceId(kKey, *cache->shared_cache_db_id(), |
| SqlSharedCacheRowId(1)); |
| } |
| } |
| |
| TEST_P(SqlSharedCacheTest, CopyEntriesReadSuccessAndFailure) { |
| base::test::ScopedFeatureList custom_feature_list; |
| custom_feature_list.InitAndEnableFeatureWithParameters( |
| net::features::kDiskCacheBackendExperiment, |
| {{net::features::kSqlDiskCacheMaxSharedCacheCopyEntrySize.name, "100"}}); |
| |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kSuccessKey( |
| "credential_key/post_key/https://example.com/success"); |
| const CacheEntryKey kExceededKey( |
| "credential_key/post_key/https://example.com/exceeded"); |
| |
| const std::string kSuccessData = "small valid payload"; |
| const std::string kExceededData(200, 'z'); |
| |
| auto response_info_success = CreateTestHttpResponseInfo(); |
| auto response_info_exceeded = CreateTestHttpResponseInfo(); |
| |
| PopulateStoreEntry(kSuccessKey, response_info_success, kSuccessData); |
| PopulateStoreEntry(kExceededKey, response_info_exceeded, kExceededData); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry( |
| kSuccessKey, GURL("https://example.com/success"), response_info_success)); |
| entries.push(CreateEligibleEntry(kExceededKey, |
| GURL("https://example.com/exceeded"), |
| response_info_exceeded)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_TRUE(unprocessed.empty()); |
| |
| // 1. Verify succeeded entry can be read from isolated database. |
| VerifyIsolatedDatabaseEntryData(*cache, kSuccessKey, SqlSharedCacheRowId(1), |
| kSuccessData); |
| |
| // 2. Verify failed/skipped entry cannot be read from isolated database. |
| VerifyIsolatedDatabaseEntryNotFound(*cache, kExceededKey, |
| SqlSharedCacheRowId(999)); |
| } |
| |
| TEST_P(SqlSharedCacheTest, CopyEntriesExceedingReadBufferSize) { |
| base::test::ScopedFeatureList custom_feature_list; |
| custom_feature_list.InitAndEnableFeatureWithParameters( |
| net::features::kDiskCacheBackendExperiment, |
| {{net::features::kSqlDiskCacheSharedCacheReadBufferSize.name, "50"}}); |
| |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKey( |
| "credential_key/post_key/https://example.com/chunked"); |
| // 120 bytes body will be read in 3 chunks (50 + 50 + 20 bytes). |
| // Use sequential, varying characters so any offset misalignment is detected. |
| std::string kData; |
| kData.reserve(120); |
| for (int i = 0; i < 120; ++i) { |
| kData.push_back(static_cast<char>('a' + (i % 26))); |
| } |
| auto response_info = CreateTestHttpResponseInfo(); |
| |
| PopulateStoreEntry(kKey, response_info, kData); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry(kKey, GURL("https://example.com/chunked"), |
| response_info)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_TRUE(unprocessed.empty()); |
| |
| VerifyIsolatedDatabaseEntryData(*cache, kKey, SqlSharedCacheRowId(1), kData); |
| } |
| |
| TEST_P(SqlSharedCacheTest, CopyEntriesAborted) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry( |
| CacheEntryKey("credential_key/post_key/https://www.example.com/"), |
| GURL("https://www.example.com/"), net::HttpResponseInfo())); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, true); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_EQ(unprocessed.size(), 1u); |
| } |
| |
| TEST_P(SqlSharedCacheTest, CopyEntriesWriteBodyFailureCleansUpPartialEntry) { |
| base::test::ScopedFeatureList custom_feature_list; |
| custom_feature_list.InitAndEnableFeatureWithParameters( |
| net::features::kDiskCacheBackendExperiment, |
| {{net::features::kSqlDiskCacheSharedCacheReadBufferSize.name, "50"}}); |
| |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKey( |
| "credential_key/post_key/https://example.com/fail_write"); |
| std::string kData(120, 'a'); |
| auto response_info = CreateTestHttpResponseInfo(); |
| |
| PopulateStoreEntry(kKey, response_info, kData); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry(kKey, GURL("https://example.com/fail_write"), |
| response_info)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| |
| // Simulate DB failure specifically for WriteBody so Insert succeeds but body |
| // write fails. |
| cache->isolated_database_for_testing() |
| .AsyncCall(&SqlSharedCacheIsolatedDatabase:: |
| SetSimulateDbFailureCallbackForTesting) |
| .WithArgs(base::BindRepeating( |
| [](SqlSharedCacheIsolatedDatabase::OperationForTesting op) { |
| return op == SqlSharedCacheIsolatedDatabase::OperationForTesting:: |
| kWriteBody; |
| })); |
| |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_TRUE(unprocessed.empty()); |
| |
| // Verify that row 1 was cleaned up (deleted) from isolated database upon |
| // failure. |
| base::test::TestFuture<bool> has_row_future; |
| cache->isolated_database_for_testing() |
| .AsyncCall(&SqlSharedCacheIsolatedDatabase::HasRowForTesting) |
| .WithArgs(SqlSharedCacheRowId(1)) |
| .Then(has_row_future.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| EXPECT_FALSE(has_row_future.Get()); |
| } |
| |
| TEST_P(SqlSharedCacheTest, DeleteEntries) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKey1("credential_key/post_key/https://example.com/1"); |
| const CacheEntryKey kKey2("credential_key/post_key/https://example.com/2"); |
| std::string kData = "test_data"; |
| auto response_info = CreateTestHttpResponseInfo(); |
| |
| PopulateStoreEntry(kKey1, response_info, kData); |
| PopulateStoreEntry(kKey2, response_info, kData); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push( |
| CreateEligibleEntry(kKey1, GURL("https://example.com/1"), response_info)); |
| entries.push( |
| CreateEligibleEntry(kKey2, GURL("https://example.com/2"), response_info)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| EXPECT_TRUE(copy_future.Take().empty()); |
| |
| // Verify row 1 and row 2 exist in isolated database. |
| VerifyIsolatedDatabaseEntryData(*cache, kKey1, SqlSharedCacheRowId(1), kData); |
| VerifyIsolatedDatabaseEntryData(*cache, kKey2, SqlSharedCacheRowId(2), kData); |
| |
| // Delete row 1. |
| base::test::TestFuture< |
| base::expected<void, SqlSharedCacheIsolatedDatabase::Error>> |
| delete_future1; |
| cache->DeleteEntries({SqlSharedCacheRowId(1)}, delete_future1.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| EXPECT_TRUE(delete_future1.Get().has_value()); |
| |
| // Delete row 2. |
| base::test::TestFuture< |
| base::expected<void, SqlSharedCacheIsolatedDatabase::Error>> |
| delete_future2; |
| cache->DeleteEntries({SqlSharedCacheRowId(2)}, delete_future2.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| EXPECT_TRUE(delete_future2.Get().has_value()); |
| } |
| |
| TEST_P(SqlSharedCacheTest, DeleteMultipleEntriesAtOnce) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKey1("credential_key/post_key/https://example.com/1"); |
| const CacheEntryKey kKey2("credential_key/post_key/https://example.com/2"); |
| const CacheEntryKey kKey3("credential_key/post_key/https://example.com/3"); |
| std::string kData = "test_data"; |
| auto response_info = CreateTestHttpResponseInfo(); |
| |
| PopulateStoreEntry(kKey1, response_info, kData); |
| PopulateStoreEntry(kKey2, response_info, kData); |
| PopulateStoreEntry(kKey3, response_info, kData); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push( |
| CreateEligibleEntry(kKey1, GURL("https://example.com/1"), response_info)); |
| entries.push( |
| CreateEligibleEntry(kKey2, GURL("https://example.com/2"), response_info)); |
| entries.push( |
| CreateEligibleEntry(kKey3, GURL("https://example.com/3"), response_info)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| EXPECT_TRUE(copy_future.Take().empty()); |
| |
| // Delete row 1 and row 2 simultaneously. |
| base::test::TestFuture< |
| base::expected<void, SqlSharedCacheIsolatedDatabase::Error>> |
| delete_future1; |
| cache->DeleteEntries({SqlSharedCacheRowId(1), SqlSharedCacheRowId(2)}, |
| delete_future1.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| EXPECT_TRUE(delete_future1.Get().has_value()); |
| |
| // Delete remaining row 3. |
| base::test::TestFuture< |
| base::expected<void, SqlSharedCacheIsolatedDatabase::Error>> |
| delete_future2; |
| cache->DeleteEntries({SqlSharedCacheRowId(3)}, delete_future2.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| EXPECT_TRUE(delete_future2.Get().has_value()); |
| } |
| |
| TEST_P(SqlSharedCacheTest, DeleteNonExistentEntries) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKey1("credential_key/post_key/https://example.com/1"); |
| std::string kData = "test_data"; |
| auto response_info = CreateTestHttpResponseInfo(); |
| |
| PopulateStoreEntry(kKey1, response_info, kData); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push( |
| CreateEligibleEntry(kKey1, GURL("https://example.com/1"), response_info)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| EXPECT_TRUE(copy_future.Take().empty()); |
| |
| // Delete non-existent row 999. |
| base::test::TestFuture< |
| base::expected<void, SqlSharedCacheIsolatedDatabase::Error>> |
| delete_future1; |
| cache->DeleteEntries({SqlSharedCacheRowId(999)}, |
| delete_future1.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| EXPECT_TRUE(delete_future1.Get().has_value()); |
| |
| // Delete existing row 1 and non-existent row 999 together. |
| base::test::TestFuture< |
| base::expected<void, SqlSharedCacheIsolatedDatabase::Error>> |
| delete_future2; |
| cache->DeleteEntries({SqlSharedCacheRowId(1), SqlSharedCacheRowId(999)}, |
| delete_future2.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| EXPECT_TRUE(delete_future2.Get().has_value()); |
| } |
| |
| TEST_P(SqlSharedCacheTest, DeleteEntriesWithoutIsolatedDatabase) { |
| auto cache = std::make_unique<SqlSharedCache>( |
| "test_nik", *store_, temp_dir_.GetPath(), base::DoNothing(), |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::USER_BLOCKING, |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN}), |
| /*read_cache_memory_monitor=*/nullptr, cleanup_tracker_); |
| |
| base::test::TestFuture< |
| base::expected<void, SqlSharedCacheIsolatedDatabase::Error>> |
| delete_future; |
| cache->DeleteEntries({SqlSharedCacheRowId(1)}, delete_future.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto result = delete_future.Take(); |
| ASSERT_FALSE(result.has_value()); |
| EXPECT_EQ( |
| result.error(), |
| SqlSharedCacheIsolatedDatabase::Error::kIsolatedDatabaseNotAvailable); |
| } |
| |
| TEST_P(SqlSharedCacheTest, GetBlobHandleSuccess) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKey( |
| "credential_key/post_key/https://example.com/blob_test"); |
| auto response_info = CreateTestHttpResponseInfo(); |
| std::string body_data = "Blob data test"; |
| PopulateStoreEntry(kKey, response_info, body_data); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry(kKey, GURL("https://example.com/blob_test"), |
| response_info)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_TRUE(unprocessed.empty()); |
| |
| base::test::TestFuture<base::expected<scoped_refptr<SqlSharedCacheBlobHandle>, |
| SqlSharedCacheIsolatedDatabase::Error>> |
| blob_future; |
| cache->GetBlobHandle(kKey, SqlSharedCacheRowId(1), body_data.size(), |
| blob_future.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto result = blob_future.Take(); |
| ASSERT_TRUE(result.has_value()); |
| EXPECT_TRUE(result.value()); |
| } |
| |
| TEST_P(SqlSharedCacheTest, GetBlobHandleWithoutIsolatedDatabase) { |
| auto cache = std::make_unique<SqlSharedCache>( |
| "test_nik", *store_, temp_dir_.GetPath(), base::DoNothing(), |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::USER_BLOCKING, |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN}), |
| /*read_cache_memory_monitor=*/nullptr, cleanup_tracker_); |
| |
| const CacheEntryKey kKey("https://example.com/blob_test"); |
| base::test::TestFuture<base::expected<scoped_refptr<SqlSharedCacheBlobHandle>, |
| SqlSharedCacheIsolatedDatabase::Error>> |
| blob_future; |
| cache->GetBlobHandle(kKey, SqlSharedCacheRowId(1), 10, |
| blob_future.GetCallback()); |
| auto result = blob_future.Take(); |
| ASSERT_FALSE(result.has_value()); |
| EXPECT_EQ( |
| result.error(), |
| SqlSharedCacheIsolatedDatabase::Error::kIsolatedDatabaseNotAvailable); |
| } |
| |
| TEST_P(SqlSharedCacheTest, ReadSuccess) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKey( |
| "credential_key/post_key/https://example.com/read_test"); |
| auto response_info = CreateTestHttpResponseInfo(); |
| std::string body_data = "Read data test"; |
| PopulateStoreEntry(kKey, response_info, body_data); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry(kKey, GURL("https://example.com/read_test"), |
| response_info)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_TRUE(unprocessed.empty()); |
| |
| auto read_buf = base::MakeRefCounted<net::IOBufferWithSize>(body_data.size()); |
| base::test::TestFuture<SqlPersistentStore::ReadResultOrError> read_future; |
| cache->Read(kKey, SqlSharedCacheRowId(1), body_data.size(), /*offset=*/0, |
| read_buf, read_future.GetCallback()); |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| |
| auto result = read_future.Take(); |
| ASSERT_TRUE(result.has_value()); |
| EXPECT_EQ(result->read_bytes, static_cast<int>(body_data.size())); |
| EXPECT_EQ(std::string_view(read_buf->data(), body_data.size()), body_data); |
| } |
| |
| TEST_P(SqlSharedCacheTest, ReadWithoutIsolatedDatabase) { |
| auto cache = std::make_unique<SqlSharedCache>( |
| "test_nik", *store_, temp_dir_.GetPath(), base::DoNothing(), |
| base::ThreadPool::CreateSequencedTaskRunner( |
| {base::MayBlock(), base::TaskPriority::USER_BLOCKING, |
| base::TaskShutdownBehavior::BLOCK_SHUTDOWN}), |
| /*read_cache_memory_monitor=*/nullptr, cleanup_tracker_); |
| |
| const CacheEntryKey kKey("https://example.com/read_test"); |
| auto read_buf = base::MakeRefCounted<net::IOBufferWithSize>(10); |
| base::test::TestFuture<SqlPersistentStore::ReadResultOrError> read_future; |
| cache->Read(kKey, SqlSharedCacheRowId(1), /*body_size=*/10, /*offset=*/0, |
| read_buf, read_future.GetCallback()); |
| |
| auto result = read_future.Take(); |
| ASSERT_FALSE(result.has_value()); |
| EXPECT_EQ(result.error(), SqlPersistentStore::Error::kNotFound); |
| } |
| |
| TEST_P(SqlSharedCacheTest, ReadOffsetExceedsIntMax) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKey("https://example.com/read_test"); |
| auto read_buf = base::MakeRefCounted<net::IOBufferWithSize>(10); |
| base::test::TestFuture<SqlPersistentStore::ReadResultOrError> read_future; |
| cache->Read( |
| kKey, SqlSharedCacheRowId(1), /*body_size=*/10, |
| /*offset=*/static_cast<int64_t>(std::numeric_limits<int>::max()) + 1, |
| read_buf, read_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto result = read_future.Take(); |
| ASSERT_FALSE(result.has_value()); |
| EXPECT_EQ(result.error(), SqlPersistentStore::Error::kFailedToExecute); |
| } |
| |
| TEST_P(SqlSharedCacheTest, ReadEntryNotFound) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKey("https://example.com/read_test"); |
| auto read_buf = base::MakeRefCounted<net::IOBufferWithSize>(10); |
| base::test::TestFuture<SqlPersistentStore::ReadResultOrError> read_future; |
| cache->Read(kKey, SqlSharedCacheRowId(9999), /*body_size=*/10, /*offset=*/0, |
| read_buf, read_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto result = read_future.Take(); |
| ASSERT_FALSE(result.has_value()); |
| EXPECT_EQ(result.error(), SqlPersistentStore::Error::kNotFound); |
| } |
| |
| TEST_P(SqlSharedCacheTest, ReadOtherError) { |
| auto handle = CreateAndInitStoreAndCache(); |
| auto* cache = handle->get(); |
| |
| const CacheEntryKey kKey( |
| "credential_key/post_key/https://example.com/read_other_error"); |
| auto response_info = CreateTestHttpResponseInfo(); |
| std::string body_data = "Read error data"; |
| PopulateStoreEntry(kKey, response_info, body_data); |
| |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry> entries; |
| entries.push(CreateEligibleEntry( |
| kKey, GURL("https://example.com/read_other_error"), response_info)); |
| |
| auto abort_flag = |
| base::MakeRefCounted<base::RefCountedData<std::atomic_bool>>( |
| std::in_place, false); |
| base::test::TestFuture< |
| base::queue<SqlPersistentStore::SharedCacheEligibleEntry>> |
| copy_future; |
| cache->CopyEntries(std::move(entries), abort_flag, copy_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto unprocessed = copy_future.Take(); |
| EXPECT_TRUE(unprocessed.empty()); |
| |
| // Pass a buffer larger than body_data.size() (100 > 15) so offset + buf_len > |
| // body_size, triggering Error::kInvalidReadRange in |
| // SqlSharedCacheIsolatedDatabase::Read. |
| auto invalid_buf = base::MakeRefCounted<net::IOBufferWithSize>(100); |
| base::test::TestFuture<SqlPersistentStore::ReadResultOrError> read_future; |
| cache->Read(kKey, SqlSharedCacheRowId(1), body_data.size(), /*offset=*/0, |
| invalid_buf, read_future.GetCallback()); |
| |
| async_task_manager_.RunUntilAllTasksCompleteForTest(); |
| auto result = read_future.Take(); |
| ASSERT_FALSE(result.has_value()); |
| EXPECT_EQ(result.error(), SqlPersistentStore::Error::kFailedToExecute); |
| } |
| |
| } // namespace disk_cache |