| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <cstdint> |
| #include <memory> |
| #include <set> |
| #include <sstream> |
| #include <tuple> |
| #include <vector> |
| |
| #include "base/containers/span.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/system/sys_info.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_expected_support.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/simple_test_clock.h" |
| #include "base/test/task_environment.h" |
| #include "base/test/test_future.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/time/time.h" |
| #include "components/services/storage/public/cpp/buckets/bucket_info.h" |
| #include "components/services/storage/public/cpp/buckets/bucket_locator.h" |
| #include "components/services/storage/public/cpp/buckets/constants.h" |
| #include "components/services/storage/public/mojom/quota_client.mojom.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "sql/test/test_helpers.h" |
| #include "storage/browser/quota/quota_client_type.h" |
| #include "storage/browser/quota/quota_database.h" |
| #include "storage/browser/quota/quota_features.h" |
| #include "storage/browser/quota/quota_internals.mojom.h" |
| #include "storage/browser/quota/quota_manager_impl.h" |
| #include "storage/browser/quota/quota_manager_proxy.h" |
| #include "storage/browser/quota/quota_override_handle.h" |
| #include "storage/browser/quota/storage_directory_util.h" |
| #include "storage/browser/test/mock_quota_client.h" |
| #include "storage/browser/test/mock_special_storage_policy.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| #include "third_party/blink/public/mojom/buckets/bucket_manager_host.mojom-shared.h" |
| #include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h" |
| #include "url/gurl.h" |
| |
| using ::blink::StorageKey; |
| using ::blink::mojom::QuotaStatusCode; |
| |
| namespace storage { |
| |
| namespace { |
| |
| // Values in bytes. |
| const int64_t kAvailableSpaceForApp = 13377331U; |
| const int64_t kMustRemainAvailableForSystem = kAvailableSpaceForApp / 2; |
| const int64_t kDefaultPoolSize = 1000; |
| const int64_t kDefaultPerStorageKeyQuota = 200 * 1024 * 1024; |
| |
| struct UsageAndQuotaResult { |
| QuotaStatusCode status; |
| int64_t usage; |
| int64_t quota; |
| }; |
| |
| struct GlobalUsageResult { |
| int64_t usage; |
| int64_t unlimited_usage; |
| }; |
| |
| struct StorageCapacityResult { |
| int64_t total_space; |
| int64_t available_space; |
| }; |
| |
| struct ClientBucketData { |
| const char* origin; |
| std::string name; |
| int64_t usage; |
| int64_t quota = 0; |
| }; |
| |
| struct UsageWithBreakdown { |
| int64_t usage; |
| blink::mojom::UsageBreakdownPtr breakdown; |
| }; |
| |
| struct UsageAndQuotaWithBreakdown { |
| QuotaStatusCode status; |
| int64_t usage; |
| int64_t quota; |
| blink::mojom::UsageBreakdownPtr breakdown; |
| }; |
| |
| // Returns a deterministic value for the amount of available disk space. |
| int64_t GetAvailableDiskSpaceForTest() { |
| return kAvailableSpaceForApp + kMustRemainAvailableForSystem; |
| } |
| |
| QuotaAvailability GetVolumeInfoForTests(const base::FilePath& unused) { |
| int64_t available = static_cast<uint64_t>(GetAvailableDiskSpaceForTest()); |
| int64_t total = available * 2; |
| return QuotaAvailability(total, available); |
| } |
| |
| StorageKey ToStorageKey(const std::string& url) { |
| return StorageKey::CreateFromStringForTesting(url); |
| } |
| |
| const storage::mojom::BucketTableEntry* FindBucketTableEntry( |
| const std::vector<storage::mojom::BucketTableEntryPtr>& bucket_entries, |
| BucketId& id) { |
| auto it = std::ranges::find(bucket_entries, id.value(), |
| &storage::mojom::BucketTableEntry::bucket_id); |
| if (it == bucket_entries.end()) { |
| return nullptr; |
| } |
| return it->get(); |
| } |
| |
| MATCHER_P2(MatchesBucketTableEntry, storage_key, use_count, "") { |
| return testing::ExplainMatchResult(storage_key, arg->storage_key, |
| result_listener) && |
| testing::ExplainMatchResult(use_count, arg->use_count, |
| result_listener); |
| } |
| |
| } // namespace |
| |
| class QuotaManagerImplTest : public testing::Test { |
| protected: |
| using BucketTableEntries = QuotaManagerImpl::BucketTableEntries; |
| |
| public: |
| QuotaManagerImplTest() : mock_time_counter_(0) {} |
| |
| QuotaManagerImplTest(const QuotaManagerImplTest&) = delete; |
| QuotaManagerImplTest& operator=(const QuotaManagerImplTest&) = delete; |
| |
| void SetUp() override { |
| ASSERT_TRUE(data_dir_.CreateUniqueTempDir()); |
| mock_special_storage_policy_ = |
| base::MakeRefCounted<MockSpecialStoragePolicy>(); |
| ResetQuotaManagerImpl(false /* is_incognito */); |
| } |
| |
| void TearDown() override { |
| // Make sure the quota manager cleans up correctly. |
| quota_manager_impl_ = nullptr; |
| task_environment_.RunUntilIdle(); |
| } |
| |
| protected: |
| void ResetQuotaManagerImpl(bool is_incognito) { |
| quota_manager_impl_ = base::MakeRefCounted<QuotaManagerImpl>( |
| is_incognito, data_dir_.GetPath(), |
| base::SingleThreadTaskRunner::GetCurrentDefault().get(), |
| mock_special_storage_policy_.get(), GetQuotaSettingsFunc()); |
| SetQuotaSettings(kDefaultPoolSize, kDefaultPerStorageKeyQuota, |
| is_incognito ? INT64_C(0) : kMustRemainAvailableForSystem); |
| |
| // Don't (automatically) start the eviction for testing. |
| quota_manager_impl_->eviction_disabled_ = true; |
| // Don't query the hard disk for remaining capacity. |
| quota_manager_impl_->get_volume_info_fn_ = &GetVolumeInfoForTests; |
| additional_callback_count_ = 0; |
| } |
| |
| MockQuotaClient* CreateAndRegisterClient( |
| QuotaClientType client_type, |
| base::span<const UnmigratedStorageKeyData> unmigrated_data = |
| base::span<const UnmigratedStorageKeyData>()) { |
| auto mock_quota_client = std::make_unique<storage::MockQuotaClient>( |
| quota_manager_impl_->proxy(), client_type, unmigrated_data); |
| MockQuotaClient* mock_quota_client_ptr = mock_quota_client.get(); |
| |
| mojo::PendingRemote<storage::mojom::QuotaClient> quota_client; |
| mojo::MakeSelfOwnedReceiver(std::move(mock_quota_client), |
| quota_client.InitWithNewPipeAndPassReceiver()); |
| quota_manager_impl_->RegisterClient(std::move(quota_client), client_type); |
| return mock_quota_client_ptr; |
| } |
| |
| // Creates buckets in QuotaDatabase if they don't exist yet, and sets usage |
| // to the `client`. |
| void RegisterClientBucketData( |
| MockQuotaClient* client, |
| base::span<const ClientBucketData> mock_data, |
| std::map<BucketLocator, int64_t>* buckets_data_out = nullptr) { |
| std::map<BucketLocator, int64_t> buckets_data; |
| for (const ClientBucketData& data : mock_data) { |
| base::test::TestFuture<QuotaErrorOr<BucketInfo>> future; |
| BucketInitParams params(ToStorageKey(data.origin), data.name); |
| params.quota = data.quota; |
| quota_manager_impl_->UpdateOrCreateBucket(params, future.GetCallback()); |
| ASSERT_OK_AND_ASSIGN(auto bucket, future.Take()); |
| buckets_data.insert(std::pair<BucketLocator, int64_t>( |
| bucket.ToBucketLocator(), data.usage)); |
| } |
| if (buckets_data_out) { |
| *buckets_data_out = buckets_data; |
| } |
| client->AddBucketsData(buckets_data); |
| } |
| |
| void OpenDatabase() { quota_manager_impl_->EnsureDatabaseOpened(); } |
| |
| QuotaErrorOr<BucketInfo> UpdateOrCreateBucket( |
| const BucketInitParams& params) { |
| base::test::TestFuture<QuotaErrorOr<BucketInfo>> future; |
| quota_manager_impl_->UpdateOrCreateBucket(params, future.GetCallback()); |
| return future.Take(); |
| } |
| |
| QuotaErrorOr<BucketInfo> CreateBucketForTesting( |
| const StorageKey& storage_key, |
| const std::string& bucket_name) { |
| base::test::TestFuture<QuotaErrorOr<BucketInfo>> future; |
| quota_manager_impl_->CreateBucketForTesting(storage_key, bucket_name, |
| future.GetCallback()); |
| return future.Take(); |
| } |
| |
| QuotaErrorOr<BucketInfo> GetBucket(const StorageKey& storage_key, |
| const std::string& bucket_name) { |
| base::test::TestFuture<QuotaErrorOr<BucketInfo>> future; |
| quota_manager_impl_->GetBucketByNameUnsafe(storage_key, bucket_name, |
| future.GetCallback()); |
| return future.Take(); |
| } |
| |
| QuotaErrorOr<BucketInfo> GetBucketById(const BucketId& bucket_id) { |
| base::test::TestFuture<QuotaErrorOr<BucketInfo>> future; |
| quota_manager_impl_->GetBucketById(bucket_id, future.GetCallback()); |
| return future.Take(); |
| } |
| |
| std::set<StorageKey> GetAllStorageKeys() { |
| base::test::TestFuture<std::set<StorageKey>> future; |
| quota_manager_impl_->GetAllStorageKeys( |
| future.GetCallback<const std::set<StorageKey>&>()); |
| return future.Take(); |
| } |
| |
| QuotaErrorOr<std::set<BucketInfo>> GetAllBuckets() { |
| base::test::TestFuture<QuotaErrorOr<std::set<BucketInfo>>> future; |
| quota_manager_impl_->GetAllBuckets(future.GetCallback()); |
| return future.Take(); |
| } |
| |
| QuotaErrorOr<std::set<BucketInfo>> GetBucketsForHost( |
| const std::string& host) { |
| base::test::TestFuture<QuotaErrorOr<std::set<BucketInfo>>> future; |
| quota_manager_impl_->GetBucketsForHost(host, future.GetCallback()); |
| return future.Take(); |
| } |
| |
| QuotaErrorOr<std::set<BucketInfo>> GetBucketsForStorageKey( |
| const StorageKey& storage_key, |
| bool delete_expired = false) { |
| base::test::TestFuture<QuotaErrorOr<std::set<BucketInfo>>> future; |
| quota_manager_impl_->GetBucketsForStorageKey( |
| storage_key, future.GetCallback(), delete_expired); |
| return future.Take(); |
| } |
| |
| UsageAndQuotaResult GetUsageAndQuotaForWebApps( |
| const StorageKey& storage_key) { |
| base::test::TestFuture<QuotaStatusCode, int64_t, int64_t> future; |
| quota_manager_impl_->GetUsageAndQuotaForWebApps(storage_key, |
| future.GetCallback()); |
| return {future.Get<0>(), future.Get<1>(), future.Get<2>()}; |
| } |
| |
| UsageAndQuotaResult GetUsageAndQuotaForBucket(const BucketInfo& bucket_info) { |
| base::test::TestFuture<QuotaStatusCode, int64_t, int64_t> future; |
| quota_manager_impl_->GetBucketUsageAndReportedQuota(bucket_info.id, |
| future.GetCallback()); |
| return {future.Get<0>(), future.Get<1>(), future.Get<2>()}; |
| } |
| |
| UsageAndQuotaWithBreakdown GetUsageAndQuotaWithBreakdown( |
| const StorageKey& storage_key) { |
| base::test::TestFuture<QuotaStatusCode, int64_t, int64_t, |
| blink::mojom::UsageBreakdownPtr> |
| future; |
| quota_manager_impl_->GetUsageAndReportedQuotaWithBreakdown( |
| storage_key, future.GetCallback()); |
| auto result = future.Take(); |
| return {std::get<0>(result), std::get<1>(result), std::get<2>(result), |
| std::move(std::get<3>(result))}; |
| } |
| |
| UsageAndQuotaResult GetUsageAndQuotaForStorageClient( |
| const StorageKey& storage_key) { |
| base::test::TestFuture<QuotaStatusCode, int64_t, int64_t> future; |
| quota_manager_impl_->GetUsageAndQuota(storage_key, future.GetCallback()); |
| return {future.Get<0>(), future.Get<1>(), future.Get<2>()}; |
| } |
| |
| bool CheckForSufficientSpace(const BucketLocator& bucket, |
| int64_t bytes_to_be_written) { |
| base::test::TestFuture<QuotaErrorOr<int64_t>> future; |
| quota_manager_impl_->GetBucketSpaceRemaining(bucket, future.GetCallback()); |
| auto result = future.Take(); |
| return result.has_value() && (result.value() >= bytes_to_be_written); |
| } |
| |
| void SetQuotaSettings(int64_t pool_size, |
| int64_t per_storage_key_quota, |
| int64_t must_remain_available) { |
| QuotaSettings settings; |
| settings.pool_size = pool_size; |
| settings.per_storage_key_quota = per_storage_key_quota; |
| settings.session_only_per_storage_key_quota = |
| (per_storage_key_quota > 0) ? (per_storage_key_quota - 1) : 0; |
| settings.must_remain_available = must_remain_available; |
| settings.refresh_interval = base::TimeDelta::Max(); |
| quota_manager_impl_->SetQuotaSettings(settings); |
| } |
| |
| using GetVolumeInfoFn = QuotaAvailability (*)(const base::FilePath&); |
| |
| void SetGetVolumeInfoFn(GetVolumeInfoFn fn) { |
| quota_manager_impl_->SetGetVolumeInfoFnForTesting(fn); |
| } |
| |
| GlobalUsageResult GetGlobalUsage() { |
| base::test::TestFuture<int64_t, int64_t> future; |
| quota_manager_impl_->GetGlobalUsage(future.GetCallback()); |
| return {future.Get<0>(), future.Get<1>()}; |
| } |
| |
| UsageWithBreakdown GetStorageKeyUsageWithBreakdown( |
| const blink::StorageKey& storage_key) { |
| base::test::TestFuture<int64_t, blink::mojom::UsageBreakdownPtr> future; |
| quota_manager_impl_->GetStorageKeyUsageWithBreakdown(storage_key, |
| future.GetCallback()); |
| auto result = future.Take(); |
| return {std::get<0>(result), std::move(std::get<1>(result))}; |
| } |
| |
| void RunAdditionalUsageAndQuotaTask(const StorageKey& storage_key) { |
| quota_manager_impl_->GetUsageAndQuota( |
| storage_key, |
| base::BindOnce(&QuotaManagerImplTest::DidGetUsageAndQuotaAdditional, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| int EvictBucketData(const BucketLocator& bucket) { |
| base::test::TestFuture<int> future; |
| quota_manager_impl_->EvictBucketData({bucket}, future.GetCallback()); |
| return future.Get(); |
| } |
| |
| QuotaStatusCode DeleteBucketData(const BucketLocator& bucket, |
| QuotaClientTypes quota_client_types) { |
| base::test::TestFuture<QuotaStatusCode> future; |
| quota_manager_impl_->DeleteBucketData(bucket, std::move(quota_client_types), |
| future.GetCallback()); |
| return future.Get(); |
| } |
| |
| QuotaStatusCode DeleteHostData(const std::string& host) { |
| base::test::TestFuture<QuotaStatusCode> future; |
| quota_manager_impl_->DeleteHostData(host, future.GetCallback()); |
| return future.Get(); |
| } |
| |
| QuotaStatusCode FindAndDeleteBucketData(const StorageKey& storage_key, |
| const std::string& bucket_name) { |
| base::test::TestFuture<QuotaStatusCode> future; |
| quota_manager_impl_->FindAndDeleteBucketData(storage_key, bucket_name, |
| future.GetCallback()); |
| return future.Get(); |
| } |
| |
| StorageCapacityResult GetStorageCapacity() { |
| base::test::TestFuture<int64_t, int64_t> future; |
| quota_manager_impl_->GetStorageCapacity(future.GetCallback()); |
| return {future.Get<0>(), future.Get<1>()}; |
| } |
| |
| void GetEvictionRoundInfo() { |
| quota_status_ = QuotaStatusCode::kUnknown; |
| settings_ = QuotaSettings(); |
| available_space_ = -1; |
| total_space_ = -1; |
| usage_ = -1; |
| quota_manager_impl_->GetEvictionRoundInfo( |
| base::BindOnce(&QuotaManagerImplTest::DidGetEvictionRoundInfo, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void NotifyDefaultBucketAccessed(const StorageKey& storage_key, |
| const base::Time& time) { |
| auto bucket = BucketLocator::ForDefaultBucket(storage_key); |
| quota_manager_impl_->NotifyBucketAccessed(bucket, time); |
| } |
| |
| void NotifyDefaultBucketAccessed(const StorageKey& storage_key) { |
| NotifyDefaultBucketAccessed(storage_key, IncrementMockTime()); |
| } |
| |
| void NotifyBucketAccessed(const BucketLocator& bucket) { |
| quota_manager_impl_->NotifyBucketAccessed(bucket, IncrementMockTime()); |
| } |
| |
| void ModifyDefaultBucketAndNotify(MockQuotaClient* client, |
| const StorageKey& storage_key, |
| int delta) { |
| auto bucket = BucketLocator::ForDefaultBucket(storage_key); |
| client->ModifyBucketAndNotify(bucket, delta); |
| } |
| |
| // Gets just one bucket for eviction. |
| std::optional<BucketLocator> GetEvictionBucket() { |
| base::test::TestFuture<const std::set<BucketLocator>&> future; |
| quota_manager_impl_->GetEvictionBuckets( |
| /*target_usage=*/1, future.GetCallback()); |
| |
| std::set<BucketLocator> bucket = future.Take(); |
| if (1u == bucket.size()) { |
| return *bucket.begin(); |
| } |
| EXPECT_TRUE(bucket.empty()); |
| return std::nullopt; |
| } |
| |
| std::set<BucketLocator> GetEvictionBuckets(int64_t target_usage) { |
| base::test::TestFuture<const std::set<BucketLocator>&> future; |
| quota_manager_impl_->GetEvictionBuckets(target_usage, future.GetCallback()); |
| return future.Take(); |
| } |
| |
| std::set<BucketLocator> GetBucketsModifiedBetween(base::Time begin, |
| base::Time end) { |
| base::test::TestFuture<std::set<BucketLocator>> future; |
| quota_manager_impl_->GetBucketsModifiedBetween( |
| begin, end, future.GetCallback<const std::set<BucketLocator>&>()); |
| return future.Get<0>(); |
| } |
| |
| BucketTableEntries DumpBucketTable() { |
| base::test::TestFuture<BucketTableEntries> future; |
| quota_manager_impl_->DumpBucketTable(future.GetCallback()); |
| return future.Take(); |
| } |
| |
| std::vector<storage::mojom::BucketTableEntryPtr> RetrieveBucketsTable() { |
| base::test::TestFuture<std::vector<storage::mojom::BucketTableEntryPtr>> |
| future; |
| quota_manager_impl_->RetrieveBucketsTable(future.GetCallback()); |
| return future.Take(); |
| } |
| |
| void DidGetEvictionRoundInfo(QuotaStatusCode status, |
| const QuotaSettings& settings, |
| int64_t available_space, |
| int64_t total_space, |
| int64_t global_usage, |
| bool global_usage_is_complete) { |
| quota_status_ = status; |
| settings_ = settings; |
| available_space_ = available_space; |
| total_space_ = total_space; |
| usage_ = global_usage; |
| } |
| |
| void SetStoragePressureCallback( |
| base::RepeatingCallback<void(const StorageKey&)> callback) { |
| quota_manager_impl_->SetStoragePressureCallback(callback); |
| } |
| |
| void MaybeRunStoragePressureCallback(const StorageKey& storage_key, |
| int64_t total, |
| int64_t available) { |
| quota_manager_impl_->MaybeRunStoragePressureCallback(storage_key, total, |
| available); |
| } |
| |
| void set_additional_callback_count(int c) { additional_callback_count_ = c; } |
| int additional_callback_count() const { return additional_callback_count_; } |
| void DidGetUsageAndQuotaAdditional(QuotaStatusCode status, |
| int64_t usage, |
| int64_t quota) { |
| ++additional_callback_count_; |
| } |
| |
| QuotaManagerImpl* quota_manager_impl() const { |
| return quota_manager_impl_.get(); |
| } |
| |
| void set_quota_manager_impl(QuotaManagerImpl* quota_manager_impl) { |
| quota_manager_impl_ = quota_manager_impl; |
| } |
| |
| MockSpecialStoragePolicy* mock_special_storage_policy() const { |
| return mock_special_storage_policy_.get(); |
| } |
| |
| std::unique_ptr<QuotaOverrideHandle> GetQuotaOverrideHandle() { |
| return quota_manager_impl_->proxy()->GetQuotaOverrideHandle(); |
| } |
| |
| void SetQuotaChangeCallback(base::RepeatingClosure cb) { |
| quota_manager_impl_->SetQuotaChangeCallbackForTesting(std::move(cb)); |
| } |
| |
| QuotaError CorruptDatabaseForTesting( |
| base::OnceCallback<void(const base::FilePath&)> corrupter) { |
| base::test::TestFuture<QuotaError> corruption_future; |
| quota_manager_impl_->CorruptDatabaseForTesting( |
| std::move(corrupter), corruption_future.GetCallback()); |
| return corruption_future.Get(); |
| } |
| |
| bool is_db_bootstrapping() { |
| return quota_manager_impl_->is_bootstrapping_database_for_testing(); |
| } |
| |
| bool is_db_disabled() { |
| return quota_manager_impl_->is_db_disabled_for_testing(); |
| } |
| |
| void DisableQuotaDatabase() { |
| base::RunLoop run_loop; |
| quota_manager_impl_->PostTaskAndReplyWithResultForDBThread( |
| base::BindLambdaForTesting([&](QuotaDatabase* db) { |
| db->SetDisabledForTesting(true); |
| return QuotaError::kNone; |
| }), |
| base::BindLambdaForTesting([&](QuotaError error) { run_loop.Quit(); }), |
| FROM_HERE, /*is_bootstrap_task=*/false); |
| run_loop.Run(); |
| } |
| |
| void disable_database_bootstrap(bool disable) { |
| quota_manager_impl_->SetBootstrapDisabledForTesting(disable); |
| } |
| |
| QuotaStatusCode status() const { return quota_status_; } |
| int64_t usage() const { return usage_; } |
| int64_t quota() const { return quota_; } |
| int64_t total_space() const { return total_space_; } |
| int64_t available_space() const { return available_space_; } |
| const QuotaSettings& settings() const { return settings_; } |
| |
| void SetupQuotaManagerObserver() { |
| quota_manager_observer_run_loop_ = std::make_unique<base::RunLoop>(); |
| quota_manager_observer_test_ = |
| std::make_unique<QuotaManagerObserverTest>(weak_factory_.GetWeakPtr()); |
| } |
| |
| void RunUntilObserverNotifies() { |
| quota_manager_observer_run_loop_->Run(); |
| quota_manager_observer_run_loop_ = std::make_unique<base::RunLoop>(); |
| } |
| |
| protected: |
| enum ObserverNotifyType { |
| kCreateOrUpdate, |
| kDelete, |
| }; |
| struct ObserverNotification { |
| explicit ObserverNotification(BucketInfo bucket) |
| : type(ObserverNotifyType::kCreateOrUpdate), bucket_info(bucket) {} |
| explicit ObserverNotification(BucketLocator locator) |
| : type(ObserverNotifyType::kDelete), bucket_locator(locator) {} |
| ObserverNotifyType type; |
| std::optional<BucketInfo> bucket_info; |
| std::optional<BucketLocator> bucket_locator; |
| }; |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| base::test::TaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME}; |
| base::ScopedTempDir data_dir_; |
| scoped_refptr<QuotaManagerImpl> quota_manager_impl_; |
| std::vector<ObserverNotification> observer_notifications_; |
| |
| private: |
| class QuotaManagerObserverTest : storage::mojom::QuotaManagerObserver { |
| public: |
| explicit QuotaManagerObserverTest(base::WeakPtr<QuotaManagerImplTest> owner) |
| : owner_(owner) { |
| owner_->quota_manager_impl_->AddObserver( |
| receiver_.BindNewPipeAndPassRemote()); |
| } |
| |
| QuotaManagerObserverTest(const QuotaManagerObserverTest&) = delete; |
| QuotaManagerObserverTest& operator=(const QuotaManagerObserverTest&) = |
| delete; |
| |
| ~QuotaManagerObserverTest() override = default; |
| |
| void OnCreateOrUpdateBucket( |
| const storage::BucketInfo& bucket_info) override { |
| owner_->observer_notifications_.emplace_back(bucket_info); |
| QuitRunLoop(); |
| } |
| |
| void OnDeleteBucket(const storage::BucketLocator& bucket_locator) override { |
| owner_->observer_notifications_.emplace_back(bucket_locator); |
| QuitRunLoop(); |
| } |
| |
| private: |
| void QuitRunLoop() { |
| if (owner_->quota_manager_observer_run_loop_) { |
| owner_->quota_manager_observer_run_loop_->Quit(); |
| } |
| } |
| base::WeakPtr<QuotaManagerImplTest> owner_; |
| mojo::Receiver<storage::mojom::QuotaManagerObserver> receiver_{this}; |
| }; |
| |
| base::Time IncrementMockTime() { |
| ++mock_time_counter_; |
| return base::Time::FromSecondsSinceUnixEpoch(mock_time_counter_ * 10.0); |
| } |
| |
| scoped_refptr<MockSpecialStoragePolicy> mock_special_storage_policy_; |
| |
| QuotaStatusCode quota_status_; |
| int64_t usage_; |
| int64_t quota_; |
| int64_t total_space_; |
| int64_t available_space_; |
| QuotaSettings settings_; |
| std::unique_ptr<QuotaManagerObserverTest> quota_manager_observer_test_; |
| std::unique_ptr<base::RunLoop> quota_manager_observer_run_loop_; |
| |
| int additional_callback_count_; |
| |
| int mock_time_counter_; |
| |
| base::WeakPtrFactory<QuotaManagerImplTest> weak_factory_{this}; |
| }; |
| |
| TEST_F(QuotaManagerImplTest, QuotaDatabaseBootstrap) { |
| quota_manager_impl_->eviction_disabled_ = false; |
| |
| static const UnmigratedStorageKeyData kData1[] = { |
| {"http://foo.com/", 10}, |
| {"http://foo.com:8080/", 15}, |
| }; |
| static const UnmigratedStorageKeyData kData2[] = { |
| {"https://foo.com/", 30}, |
| {"https://foo.com:8081/", 35}, |
| }; |
| CreateAndRegisterClient(QuotaClientType::kFileSystem, kData1); |
| CreateAndRegisterClient(QuotaClientType::kIndexedDatabase, kData2); |
| |
| // OpenDatabase should trigger database bootstrapping. |
| OpenDatabase(); |
| EXPECT_TRUE(is_db_bootstrapping()); |
| |
| // When bootstrapping is complete, queued calls to the QuotaDatabase |
| // should return successfully and buckets for registered storage keys should |
| // already exist. |
| ASSERT_TRUE(GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName) |
| .has_value()); |
| EXPECT_FALSE(is_db_bootstrapping()); |
| |
| ASSERT_TRUE( |
| GetBucket(ToStorageKey("http://foo.com:8080/"), kDefaultBucketName) |
| .has_value()); |
| |
| ASSERT_TRUE( |
| GetBucket(ToStorageKey("https://foo.com:8081/"), kDefaultBucketName) |
| .has_value()); |
| |
| // The first eviction round is initiated a few minutes after bootstrapping. |
| EXPECT_FALSE(quota_manager_impl_->temporary_storage_evictor_); |
| task_environment_.FastForwardBy(base::Minutes(10)); |
| EXPECT_TRUE(quota_manager_impl_->temporary_storage_evictor_); |
| } |
| |
| TEST_F(QuotaManagerImplTest, CorruptionRecovery) { |
| // Setup clients with both unmigrated and migrated data. Before corruption the |
| // bucket data will be used, while after corruption recovery data should be |
| // migrated again. |
| static const ClientBucketData kData1[] = { |
| {"http://foo.com/", kDefaultBucketName, 10}, |
| {"http://foo.com:8080/", kDefaultBucketName, 15}, |
| }; |
| static const UnmigratedStorageKeyData kUnmigratedData1[] = { |
| {"http://foo.com/", 10}, |
| {"http://foo.com:8080/", 15}, |
| }; |
| static const ClientBucketData kData2[] = { |
| {"https://foo.com/", kDefaultBucketName, 30}, |
| {"https://foo.com:8081/", kDefaultBucketName, 35}, |
| }; |
| static const UnmigratedStorageKeyData kUnmigratedData2[] = { |
| {"https://foo.com/", 30}, |
| {"https://foo.com:8081/", 35}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem, kUnmigratedData1); |
| MockQuotaClient* idb_client = CreateAndRegisterClient( |
| QuotaClientType::kIndexedDatabase, kUnmigratedData2); |
| RegisterClientBucketData(fs_client, kData1); |
| RegisterClientBucketData(idb_client, kData2); |
| |
| // Basic sanity checks, make sure setup worked correctly. |
| ASSERT_TRUE(GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName) |
| .has_value()); |
| ASSERT_TRUE( |
| GetBucket(ToStorageKey("http://foo.com:8080/"), kDefaultBucketName) |
| .has_value()); |
| ASSERT_TRUE( |
| GetBucket(ToStorageKey("https://foo.com:8081/"), kDefaultBucketName) |
| .has_value()); |
| |
| // Corrupt the database to make bucket lookup fail. |
| QuotaError corruption_error = CorruptDatabaseForTesting( |
| base::BindOnce([](const base::FilePath& db_path) { |
| ASSERT_TRUE( |
| sql::test::CorruptIndexRootPage(db_path, "buckets_by_storage_key")); |
| })); |
| ASSERT_EQ(QuotaError::kNone, corruption_error); |
| |
| // Try to lookup a bucket, this should report a failure. |
| EXPECT_FALSE(quota_manager_impl_->is_db_disabled_for_testing()); |
| EXPECT_FALSE(is_db_bootstrapping()); |
| |
| EXPECT_THAT(GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName), |
| base::test::ErrorIs(QuotaError::kDatabaseError)); |
| |
| // The last lookup attempt should have started another bootstrap attempt. |
| EXPECT_TRUE(is_db_bootstrapping()); |
| |
| // And with that bucket lookup should be working again. |
| ASSERT_TRUE(GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName) |
| .has_value()); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsageInfo) { |
| static const ClientBucketData kData1[] = { |
| {"http://foo.com/", kDefaultBucketName, 10}, |
| {"http://foo.com:8080/", kDefaultBucketName, 15}, |
| {"http://bar.com/", "logs", 20}, |
| }; |
| static const ClientBucketData kData2[] = { |
| {"https://foo.com/", kDefaultBucketName, 30}, |
| {"https://foo.com:8081/", kDefaultBucketName, 35}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| MockQuotaClient* idb_client = |
| CreateAndRegisterClient(QuotaClientType::kIndexedDatabase); |
| RegisterClientBucketData(fs_client, kData1); |
| RegisterClientBucketData(idb_client, kData2); |
| |
| base::test::TestFuture<UsageInfoEntries> future; |
| quota_manager_impl()->GetUsageInfo(future.GetCallback()); |
| auto entries = future.Get(); |
| |
| EXPECT_THAT(entries, testing::UnorderedElementsAre( |
| UsageInfo("foo.com", 10 + 15 + 30 + 35), |
| UsageInfo("bar.com", 20))); |
| } |
| |
| TEST_F(QuotaManagerImplTest, UpdateUsageInfo) { |
| static const ClientBucketData kData1[] = { |
| {"http://foo.com/", kDefaultBucketName, 10}, |
| {"http://bar.com/", kDefaultBucketName, 50}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| std::map<BucketLocator, int64_t> buckets_data; |
| RegisterClientBucketData(fs_client, kData1, &buckets_data); |
| ASSERT_EQ(buckets_data.size(), 2u); |
| BucketLocator first_bucket_locator = buckets_data.begin()->first; |
| |
| { |
| base::test::TestFuture<UsageInfoEntries> future; |
| quota_manager_impl()->GetUsageInfo(future.GetCallback()); |
| auto entries = future.Get(); |
| |
| EXPECT_THAT(entries, |
| testing::UnorderedElementsAre(UsageInfo("foo.com", 10), |
| UsageInfo("bar.com", 50))); |
| |
| // The quota client was queried once for each bucket. |
| EXPECT_EQ(2U, fs_client->get_bucket_usage_call_count()); |
| } |
| |
| // Notify of a change with a provided byte delta. |
| quota_manager_impl()->NotifyBucketModified( |
| QuotaClientType::kFileSystem, first_bucket_locator, /*delta=*/7, |
| base::Time::Now(), base::DoNothing()); |
| |
| { |
| base::test::TestFuture<UsageInfoEntries> future; |
| quota_manager_impl()->GetUsageInfo(future.GetCallback()); |
| auto entries = future.Get(); |
| |
| EXPECT_THAT(entries, |
| testing::UnorderedElementsAre(UsageInfo("foo.com", 17), |
| UsageInfo("bar.com", 50))); |
| |
| // The quota client was not queried any more times since the values were |
| // cached and then updated. |
| EXPECT_EQ(2U, fs_client->get_bucket_usage_call_count()); |
| } |
| |
| // Dirty the cache by passing a null delta. |
| quota_manager_impl()->NotifyBucketModified( |
| QuotaClientType::kFileSystem, first_bucket_locator, |
| /*delta=*/std::nullopt, base::Time::Now(), base::DoNothing()); |
| |
| { |
| base::test::TestFuture<UsageInfoEntries> future; |
| quota_manager_impl()->GetUsageInfo(future.GetCallback()); |
| auto entries = future.Get(); |
| |
| // Since the cache was tossed out, the mock quota client is consulted again |
| // for its usage. |
| EXPECT_THAT(entries, |
| testing::UnorderedElementsAre(UsageInfo("foo.com", 10), |
| UsageInfo("bar.com", 50))); |
| |
| // The quota client was queried one more time. |
| EXPECT_EQ(3U, fs_client->get_bucket_usage_call_count()); |
| } |
| } |
| |
| TEST_F(QuotaManagerImplTest, UpdateOrCreateBucket) { |
| StorageKey storage_key = ToStorageKey("http://a.com/"); |
| std::string bucket_name = "bucket_a"; |
| |
| ASSERT_OK_AND_ASSIGN(auto bucket, |
| UpdateOrCreateBucket({storage_key, bucket_name})); |
| |
| BucketId created_bucket_id = bucket.id; |
| |
| EXPECT_THAT(UpdateOrCreateBucket({storage_key, bucket_name}), |
| base::test::ValueIs( |
| ::testing::Field(&BucketInfo::id, created_bucket_id))); |
| } |
| |
| TEST_F(QuotaManagerImplTest, UpdateOrCreateBucket_Expiration) { |
| auto clock = std::make_unique<base::SimpleTestClock>(); |
| QuotaDatabase::SetClockForTesting(clock.get()); |
| clock->SetNow(base::Time::Now()); |
| |
| BucketInitParams params(ToStorageKey("http://a.com/"), "bucket_a"); |
| params.expiration = clock->Now() - base::Days(1); |
| |
| ASSERT_FALSE(UpdateOrCreateBucket(params).has_value()); |
| |
| // Create a new bucket. |
| params.expiration = clock->Now() + base::Days(1); |
| params.quota = 1000; |
| ASSERT_OK_AND_ASSIGN(auto bucket, UpdateOrCreateBucket(params)); |
| EXPECT_EQ(bucket.expiration, params.expiration); |
| EXPECT_EQ(bucket.quota, 1000); |
| |
| // Get/Update the same bucket. Verify expiration is updated, but quota is not. |
| params.expiration = clock->Now() + base::Days(5); |
| params.quota = 500; |
| ASSERT_OK_AND_ASSIGN(bucket, UpdateOrCreateBucket(params)); |
| EXPECT_EQ(bucket.expiration, params.expiration); |
| EXPECT_EQ(bucket.quota, 1000); |
| |
| // Verify that the bucket is clobbered due to being expired. In this case, the |
| // new quota is respected. |
| clock->Advance(base::Days(20)); |
| params.expiration = base::Time(); |
| ASSERT_OK_AND_ASSIGN(bucket, UpdateOrCreateBucket(params)); |
| EXPECT_EQ(bucket.expiration, params.expiration); |
| EXPECT_EQ(bucket.quota, 500); |
| |
| QuotaDatabase::SetClockForTesting(nullptr); |
| } |
| |
| TEST_F(QuotaManagerImplTest, UpdateOrCreateBucket_Overflow) { |
| const int kPoolSize = 100; |
| // This quota for the storage key implies only two buckets can be constructed. |
| const int kPerStorageKeyQuota = 40 * 1024 * 1024; |
| SetQuotaSettings(kPoolSize, kPerStorageKeyQuota, |
| kMustRemainAvailableForSystem); |
| |
| StorageKey storage_key = ToStorageKey("http://a.com/"); |
| |
| EXPECT_TRUE(UpdateOrCreateBucket({storage_key, "bucket_a"}).has_value()); |
| EXPECT_TRUE(UpdateOrCreateBucket({storage_key, "bucket_b"}).has_value()); |
| EXPECT_THAT(UpdateOrCreateBucket({storage_key, "bucket_c"}), |
| base::test::ErrorIs(QuotaError::kQuotaExceeded)); |
| |
| // Default bucket shouldn't be limited by the quota. |
| EXPECT_TRUE( |
| UpdateOrCreateBucket({storage_key, kDefaultBucketName}).has_value()); |
| } |
| |
| // Make sure `EvictExpiredBuckets` deletes expired buckets. |
| TEST_F(QuotaManagerImplTest, EvictExpiredBuckets) { |
| auto clock = std::make_unique<base::SimpleTestClock>(); |
| QuotaDatabase::SetClockForTesting(clock.get()); |
| clock->SetNow(base::Time::Now()); |
| |
| BucketInitParams params(ToStorageKey("http://a.com/"), "bucket_a"); |
| params.expiration = clock->Now() + base::Days(1); |
| ASSERT_OK_AND_ASSIGN(auto bucket, UpdateOrCreateBucket(params)); |
| |
| BucketInitParams params_b(ToStorageKey("http://b.com/"), "bucket_b"); |
| params_b.expiration = clock->Now() + base::Days(10); |
| ASSERT_OK_AND_ASSIGN(auto bucket_b, UpdateOrCreateBucket(params_b)); |
| |
| // No specified expiration. |
| BucketInitParams params_c(ToStorageKey("http://c.com/"), "bucket_c"); |
| ASSERT_OK_AND_ASSIGN(auto bucket_c, UpdateOrCreateBucket(params_c)); |
| |
| clock->Advance(base::Days(5)); |
| |
| // Evict expired buckets. |
| base::test::TestFuture<QuotaStatusCode> future; |
| quota_manager_impl_->EvictExpiredBuckets(future.GetCallback()); |
| EXPECT_EQ(QuotaStatusCode::kOk, future.Get()); |
| |
| EXPECT_FALSE(GetBucketById(bucket.id).has_value()); |
| EXPECT_TRUE(GetBucketById(bucket_b.id).has_value()); |
| EXPECT_TRUE(GetBucketById(bucket_c.id).has_value()); |
| |
| QuotaDatabase::SetClockForTesting(nullptr); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetOrCreateBucketSync) { |
| base::RunLoop loop; |
| // Post the function call on a different thread to ensure that the |
| // production DCHECK in GetOrCreateBucketSync passes. |
| base::ThreadPool::PostTask( |
| FROM_HERE, {base::MayBlock()}, base::BindLambdaForTesting([&]() { |
| base::ScopedAllowBaseSyncPrimitivesForTesting allow; |
| BucketInitParams params(ToStorageKey("http://b.com"), "bucket_b"); |
| // Ensure that the synchronous function returns a bucket. |
| ASSERT_OK_AND_ASSIGN( |
| auto bucket, |
| quota_manager_impl_->proxy()->GetOrCreateBucketSync(params)); |
| BucketId created_bucket_id = bucket.id; |
| |
| // Ensure that the synchronous function does not create a new bucket |
| // each time. |
| ASSERT_OK_AND_ASSIGN( |
| bucket, |
| quota_manager_impl_->proxy()->GetOrCreateBucketSync(params)); |
| EXPECT_EQ(bucket.id, created_bucket_id); |
| loop.Quit(); |
| })); |
| loop.Run(); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetBucket) { |
| StorageKey storage_key = ToStorageKey("http://a.com/"); |
| std::string bucket_name = "bucket_a"; |
| |
| ASSERT_OK_AND_ASSIGN(BucketInfo created_bucket, |
| CreateBucketForTesting(storage_key, bucket_name)); |
| |
| ASSERT_OK_AND_ASSIGN(BucketInfo retrieved_bucket, |
| GetBucket(storage_key, bucket_name)); |
| EXPECT_EQ(created_bucket.id, retrieved_bucket.id); |
| |
| EXPECT_THAT(GetBucket(storage_key, "bucket_b"), |
| base::test::ErrorIs(QuotaError::kNotFound)); |
| ASSERT_FALSE(is_db_disabled()); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetBucketById) { |
| StorageKey storage_key = ToStorageKey("http://a.com/"); |
| std::string bucket_name = "bucket_a"; |
| |
| ASSERT_OK_AND_ASSIGN(BucketInfo created_bucket, |
| CreateBucketForTesting(storage_key, bucket_name)); |
| |
| ASSERT_OK_AND_ASSIGN(BucketInfo retrieved_bucket, |
| GetBucketById(created_bucket.id)); |
| EXPECT_EQ(created_bucket.id, retrieved_bucket.id); |
| |
| EXPECT_THAT(GetBucketById(BucketId::FromUnsafeValue(0)), |
| base::test::ErrorIs(QuotaError::kNotFound)); |
| ASSERT_FALSE(is_db_disabled()); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetAllStorageKeys) { |
| StorageKey storage_key_a = ToStorageKey("http://a.com/"); |
| StorageKey storage_key_b = ToStorageKey("http://b.com/"); |
| |
| ASSERT_OK_AND_ASSIGN(BucketInfo bucket_a, |
| CreateBucketForTesting(storage_key_a, "bucket_a")); |
| |
| ASSERT_OK_AND_ASSIGN(BucketInfo bucket_b, |
| CreateBucketForTesting(storage_key_b, "bucket_b")); |
| |
| EXPECT_THAT(GetAllStorageKeys(), |
| testing::UnorderedElementsAre(storage_key_a, storage_key_b)); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetAllStorageKeysWithDatabaseError) { |
| disable_database_bootstrap(true); |
| OpenDatabase(); |
| |
| // Disable quota database for database error behavior. |
| DisableQuotaDatabase(); |
| |
| // Return empty set when error is encountered. |
| EXPECT_TRUE(GetAllStorageKeys().empty()); |
| } |
| |
| TEST_F(QuotaManagerImplTest, QuotaDatabaseResultHistogram) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 123}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(fs_client, kData); |
| base::HistogramTester histograms; |
| |
| ASSERT_TRUE(GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName) |
| .has_value()); |
| |
| histograms.ExpectTotalCount("Quota.QuotaDatabaseError", |
| /*expected_count=*/0); |
| |
| // Corrupt QuotaDatabase so any future request returns a QuotaError. |
| QuotaError corruption_error = CorruptDatabaseForTesting( |
| base::BindOnce([](const base::FilePath& db_path) { |
| ASSERT_TRUE( |
| sql::test::CorruptIndexRootPage(db_path, "buckets_by_storage_key")); |
| })); |
| ASSERT_EQ(QuotaError::kNone, corruption_error); |
| |
| // Refetching the bucket with a corrupted database should return an error. |
| EXPECT_THAT(GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName), |
| base::test::ErrorIs(QuotaError::kDatabaseError)); |
| |
| histograms.ExpectTotalCount("Quota.QuotaDatabaseError", |
| /*expected_count=*/1); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetAllBuckets) { |
| StorageKey storage_key_a = ToStorageKey("http://a.com/"); |
| StorageKey storage_key_b = ToStorageKey("http://b.com/"); |
| |
| ASSERT_OK_AND_ASSIGN(BucketInfo bucket_a, |
| CreateBucketForTesting(storage_key_a, "bucket_a")); |
| |
| ASSERT_OK_AND_ASSIGN(BucketInfo bucket_b, |
| CreateBucketForTesting(storage_key_b, "bucket_b")); |
| |
| ASSERT_OK_AND_ASSIGN(std::set<BucketInfo> buckets, GetAllBuckets()); |
| EXPECT_EQ(2U, buckets.size()); |
| EXPECT_THAT(buckets, testing::Contains(bucket_a)); |
| EXPECT_THAT(buckets, testing::Contains(bucket_b)); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetBucketsForHost) { |
| StorageKey host_a_storage_key_1 = ToStorageKey("http://a.com/"); |
| StorageKey host_a_storage_key_2 = ToStorageKey("https://a.com:123/"); |
| StorageKey host_b_storage_key = ToStorageKey("http://b.com/"); |
| |
| ASSERT_OK_AND_ASSIGN( |
| BucketInfo host_a_bucket_1, |
| CreateBucketForTesting(host_a_storage_key_1, kDefaultBucketName)); |
| |
| ASSERT_OK_AND_ASSIGN(BucketInfo host_a_bucket_2, |
| CreateBucketForTesting(host_a_storage_key_2, "test")); |
| |
| ASSERT_OK_AND_ASSIGN( |
| BucketInfo host_b_bucket, |
| CreateBucketForTesting(host_b_storage_key, kDefaultBucketName)); |
| |
| ASSERT_OK_AND_ASSIGN(std::set<BucketInfo> buckets, |
| GetBucketsForHost("a.com")); |
| EXPECT_EQ(2U, buckets.size()); |
| EXPECT_THAT(buckets, testing::Contains(host_a_bucket_1)); |
| EXPECT_THAT(buckets, testing::Contains(host_a_bucket_2)); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetBucketsForStorageKey) { |
| StorageKey storage_key_a = ToStorageKey("http://a.com/"); |
| StorageKey storage_key_b = ToStorageKey("http://b.com/"); |
| StorageKey storage_key_c = ToStorageKey("http://c.com/"); |
| |
| ASSERT_OK_AND_ASSIGN(BucketInfo bucket_a1, |
| CreateBucketForTesting(storage_key_a, "bucket_a1")); |
| |
| ASSERT_OK_AND_ASSIGN(BucketInfo bucket_a2, |
| CreateBucketForTesting(storage_key_a, "bucket_a2")); |
| |
| ASSERT_OK_AND_ASSIGN(BucketInfo bucket_b, |
| CreateBucketForTesting(storage_key_b, "bucket_b")); |
| |
| ASSERT_OK_AND_ASSIGN(std::set<BucketInfo> buckets, |
| GetBucketsForStorageKey(storage_key_a)); |
| EXPECT_EQ(2U, buckets.size()); |
| EXPECT_THAT(buckets, testing::Contains(bucket_a1)); |
| EXPECT_THAT(buckets, testing::Contains(bucket_a2)); |
| |
| ASSERT_OK_AND_ASSIGN(buckets, GetBucketsForStorageKey(storage_key_b)); |
| EXPECT_EQ(1U, buckets.size()); |
| EXPECT_THAT(buckets, testing::Contains(bucket_b)); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetBucketsForStorageKey_Expiration) { |
| StorageKey storage_key = ToStorageKey("http://a.com/"); |
| |
| auto clock = std::make_unique<base::SimpleTestClock>(); |
| QuotaDatabase::SetClockForTesting(clock.get()); |
| clock->SetNow(base::Time::Now()); |
| |
| BucketInitParams params(storage_key, "bucket_1"); |
| ASSERT_OK_AND_ASSIGN(BucketInfo bucket_1, UpdateOrCreateBucket(params)); |
| |
| params.name = "bucket_2"; |
| params.expiration = clock->Now() + base::Days(1); |
| ASSERT_OK_AND_ASSIGN(BucketInfo bucket_2, UpdateOrCreateBucket(params)); |
| |
| params.name = "bucket_3"; |
| ASSERT_OK_AND_ASSIGN(BucketInfo bucket_3, UpdateOrCreateBucket(params)); |
| |
| clock->Advance(base::Days(2)); |
| |
| ASSERT_OK_AND_ASSIGN( |
| std::set<BucketInfo> buckets, |
| GetBucketsForStorageKey(storage_key, /*delete_expired=*/true)); |
| ASSERT_EQ(1U, buckets.size()); |
| EXPECT_EQ(*buckets.begin(), bucket_1); |
| |
| QuotaDatabase::SetClockForTesting(nullptr); |
| } |
| |
| TEST_F(QuotaManagerImplTest, EnforceQuota) { |
| const int64_t kMbytes = 1024 * 1024; |
| const int64_t kPoolSize = 100 * kMbytes; |
| const int64_t kPerStorageKeyQuota = 50 * kMbytes; |
| SetQuotaSettings(kPoolSize, kPerStorageKeyQuota, |
| kMustRemainAvailableForSystem); |
| |
| static const ClientBucketData kData[] = { |
| {"https://foo.com/", "logs", /*usage=*/1000, /*quota=*/1025}, |
| {"https://foo.com/", "cache", /*usage=*/0}, |
| {"https://foo.com/", kDefaultBucketName, /*usage=*/3900}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(fs_client, kData); |
| |
| // Check a non-default bucket's custom quota is enforced. |
| auto logs_bucket = GetBucket(ToStorageKey("https://foo.com/"), "logs"); |
| EXPECT_TRUE(CheckForSufficientSpace(logs_bucket->ToBucketLocator(), 20)); |
| EXPECT_FALSE(CheckForSufficientSpace(logs_bucket->ToBucketLocator(), 26)); |
| |
| // Check the StorageKey quota is enforced for a non-default bucket. |
| auto cache_bucket = GetBucket(ToStorageKey("https://foo.com/"), "cache"); |
| EXPECT_TRUE( |
| CheckForSufficientSpace(cache_bucket->ToBucketLocator(), 10 * kMbytes)); |
| EXPECT_FALSE( |
| CheckForSufficientSpace(cache_bucket->ToBucketLocator(), 60 * kMbytes)); |
| |
| // Check the StorageKeyQuota is enforced for a default bucket. |
| BucketLocator default_bucket = |
| BucketLocator::ForDefaultBucket(ToStorageKey("https://foo.com/")); |
| EXPECT_TRUE(CheckForSufficientSpace(default_bucket, 10 * kMbytes)); |
| EXPECT_FALSE(CheckForSufficientSpace(default_bucket, 60 * kMbytes)); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsageAndQuota_Simple) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", "logs", 10}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(fs_client, kData); |
| |
| auto result = GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 10); |
| EXPECT_GT(result.quota, 0); |
| int64_t quota_returned_for_foo = result.quota; |
| |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://bar.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 0); |
| EXPECT_EQ(result.quota, quota_returned_for_foo); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsageAndQuota_SingleBucket) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", "logs", 10}, |
| {"http://foo.com/", "inbox", 60}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| |
| // Initialize the logs bucket with a non-default quota. |
| BucketInitParams params(ToStorageKey("http://foo.com/"), "logs"); |
| params.quota = 117; |
| ASSERT_TRUE(UpdateOrCreateBucket(params).has_value()); |
| |
| RegisterClientBucketData(fs_client, kData); |
| |
| { |
| ASSERT_OK_AND_ASSIGN( |
| BucketInfo bucket, |
| UpdateOrCreateBucket({ToStorageKey("http://foo.com/"), "logs"})); |
| auto result = GetUsageAndQuotaForBucket(bucket); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 10); |
| EXPECT_EQ(result.quota, params.quota); |
| } |
| |
| { |
| ASSERT_OK_AND_ASSIGN( |
| BucketInfo bucket, |
| UpdateOrCreateBucket({ToStorageKey("http://foo.com/"), "inbox"})); |
| auto result = GetUsageAndQuotaForBucket(bucket); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 60); |
| EXPECT_EQ(result.quota, kDefaultPerStorageKeyQuota); |
| } |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsage_NoClient) { |
| auto result = GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 0); |
| |
| EXPECT_EQ( |
| 0, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| |
| auto global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, 0); |
| EXPECT_EQ(global_usage_result.unlimited_usage, 0); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsage_EmptyClient) { |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| |
| auto result = GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 0); |
| |
| EXPECT_EQ( |
| 0, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| auto global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, 0); |
| EXPECT_EQ(global_usage_result.unlimited_usage, 0); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsageAndQuota_MultiStorageKeys) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 10}, |
| {"http://foo.com:8080/", kDefaultBucketName, 20}, |
| {"http://bar.com/", "logs", 5}, |
| {"https://bar.com/", "notes", 7}, |
| {"http://baz.com/", "songs", 30}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(fs_client, kData); |
| |
| // This time explicitly set a global quota. |
| const int kPoolSize = 100; |
| const int kPerStorageKeyQuota = 20; |
| SetQuotaSettings(kPoolSize, kPerStorageKeyQuota, |
| kMustRemainAvailableForSystem); |
| |
| auto result = GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 10); |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com:8080/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 20); |
| |
| // The host's quota should be its full portion of the global quota |
| // since there's plenty of diskspace. |
| EXPECT_EQ(result.quota, kPerStorageKeyQuota); |
| |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://bar.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 5); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuota); |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("https://bar.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 7); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuota); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsage_MultipleClients) { |
| static const ClientBucketData kData1[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| {"http://bar.com/", kDefaultBucketName, 2}, |
| }; |
| static const ClientBucketData kData2[] = { |
| {"https://foo.com/", kDefaultBucketName, 128}, |
| {"http://unlimited/", "logs", 512}, |
| }; |
| mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/")); |
| auto storage_capacity = GetStorageCapacity(); |
| |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| MockQuotaClient* idb_client = |
| CreateAndRegisterClient(QuotaClientType::kIndexedDatabase); |
| RegisterClientBucketData(fs_client, kData1); |
| RegisterClientBucketData(idb_client, kData2); |
| |
| const int64_t kPoolSize = GetAvailableDiskSpaceForTest(); |
| const int64_t kPerStorageKeyQuota = kPoolSize / 5; |
| SetQuotaSettings(kPoolSize, kPerStorageKeyQuota, |
| kMustRemainAvailableForSystem); |
| |
| auto result = GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 1); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuota); |
| |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("https://foo.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 128); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuota); |
| |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://unlimited/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 512); |
| EXPECT_EQ(result.quota, storage_capacity.available_space + result.usage); |
| |
| auto global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, 1 + 2 + 128 + 512); |
| EXPECT_EQ(global_usage_result.unlimited_usage, 512); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsageWithBreakdown_Simple) { |
| static const ClientBucketData kData1[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| }; |
| static const ClientBucketData kData2[] = { |
| {"http://foo.com/", kDefaultBucketName, 4}, |
| }; |
| static const ClientBucketData kData3[] = { |
| {"http://foo.com/", kDefaultBucketName, 8}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| MockQuotaClient* idb_client = |
| CreateAndRegisterClient(QuotaClientType::kIndexedDatabase); |
| MockQuotaClient* sw_client = |
| CreateAndRegisterClient(QuotaClientType::kServiceWorkerCache); |
| RegisterClientBucketData(fs_client, kData1); |
| RegisterClientBucketData(idb_client, kData2); |
| RegisterClientBucketData(sw_client, kData3); |
| |
| blink::mojom::UsageBreakdown usage_breakdown_expected = |
| blink::mojom::UsageBreakdown(); |
| auto result = GetUsageAndQuotaWithBreakdown(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(QuotaStatusCode::kOk, result.status); |
| EXPECT_EQ(1 + 4 + 8, result.usage); |
| usage_breakdown_expected.fileSystem = 1; |
| usage_breakdown_expected.indexedDatabase = 4; |
| usage_breakdown_expected.serviceWorkerCache = 8; |
| EXPECT_TRUE(usage_breakdown_expected.Equals(*result.breakdown)); |
| |
| result = GetUsageAndQuotaWithBreakdown(ToStorageKey("http://bar.com/")); |
| EXPECT_EQ(QuotaStatusCode::kOk, result.status); |
| EXPECT_EQ(0, result.usage); |
| usage_breakdown_expected.fileSystem = 0; |
| usage_breakdown_expected.indexedDatabase = 0; |
| usage_breakdown_expected.serviceWorkerCache = 0; |
| EXPECT_TRUE(usage_breakdown_expected.Equals(*result.breakdown)); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsageWithBreakdown_NoClient) { |
| blink::mojom::UsageBreakdown usage_breakdown_expected = |
| blink::mojom::UsageBreakdown(); |
| |
| auto result = GetUsageAndQuotaWithBreakdown(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(QuotaStatusCode::kOk, result.status); |
| EXPECT_EQ(0, result.usage); |
| EXPECT_TRUE(usage_breakdown_expected.Equals(*result.breakdown)); |
| |
| auto usage = GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(0, usage.usage); |
| EXPECT_TRUE(usage_breakdown_expected.Equals(*usage.breakdown)); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsageWithBreakdown_MultiStorageKeys) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 10}, |
| {"http://foo.com:8080/", "logs", 20}, |
| {"http://bar.com/", kDefaultBucketName, 5}, |
| {"https://bar.com/", kDefaultBucketName, 7}, |
| {"http://baz.com/", "logs", 30}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(fs_client, kData); |
| |
| blink::mojom::UsageBreakdown usage_breakdown_expected = |
| blink::mojom::UsageBreakdown(); |
| auto result = GetUsageAndQuotaWithBreakdown(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(QuotaStatusCode::kOk, result.status); |
| EXPECT_EQ(10, result.usage); |
| usage_breakdown_expected.fileSystem = 10; |
| EXPECT_TRUE(usage_breakdown_expected.Equals(*result.breakdown)); |
| result = GetUsageAndQuotaWithBreakdown(ToStorageKey("http://foo.com:8080/")); |
| EXPECT_EQ(QuotaStatusCode::kOk, result.status); |
| EXPECT_EQ(20, result.usage); |
| usage_breakdown_expected.fileSystem = 20; |
| EXPECT_TRUE(usage_breakdown_expected.Equals(*result.breakdown)); |
| |
| result = GetUsageAndQuotaWithBreakdown(ToStorageKey("http://bar.com/")); |
| EXPECT_EQ(QuotaStatusCode::kOk, result.status); |
| EXPECT_EQ(5, result.usage); |
| usage_breakdown_expected.fileSystem = 5; |
| EXPECT_TRUE(usage_breakdown_expected.Equals(*result.breakdown)); |
| result = GetUsageAndQuotaWithBreakdown(ToStorageKey("https://bar.com/")); |
| EXPECT_EQ(QuotaStatusCode::kOk, result.status); |
| EXPECT_EQ(7, result.usage); |
| usage_breakdown_expected.fileSystem = 7; |
| EXPECT_TRUE(usage_breakdown_expected.Equals(*result.breakdown)); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsageWithBreakdown_MultipleClients) { |
| static const ClientBucketData kData1[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| {"http://bar.com/", kDefaultBucketName, 2}, |
| }; |
| static const ClientBucketData kData2[] = { |
| {"https://foo.com/", kDefaultBucketName, 128}, |
| {"http://unlimited/", "logs", 512}, |
| }; |
| mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/")); |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| MockQuotaClient* idb_client = |
| CreateAndRegisterClient(QuotaClientType::kIndexedDatabase); |
| RegisterClientBucketData(fs_client, kData1); |
| RegisterClientBucketData(idb_client, kData2); |
| |
| blink::mojom::UsageBreakdown usage_breakdown_expected = |
| blink::mojom::UsageBreakdown(); |
| auto result = GetUsageAndQuotaWithBreakdown(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(QuotaStatusCode::kOk, result.status); |
| EXPECT_EQ(1, result.usage); |
| usage_breakdown_expected.fileSystem = 1; |
| usage_breakdown_expected.indexedDatabase = 0; |
| EXPECT_TRUE(usage_breakdown_expected.Equals(*result.breakdown)); |
| result = GetUsageAndQuotaWithBreakdown(ToStorageKey("https://foo.com/")); |
| EXPECT_EQ(QuotaStatusCode::kOk, result.status); |
| EXPECT_EQ(128, result.usage); |
| usage_breakdown_expected.fileSystem = 0; |
| usage_breakdown_expected.indexedDatabase = 128; |
| EXPECT_TRUE(usage_breakdown_expected.Equals(*result.breakdown)); |
| |
| result = GetUsageAndQuotaWithBreakdown(ToStorageKey("http://unlimited/")); |
| EXPECT_EQ(QuotaStatusCode::kOk, result.status); |
| EXPECT_EQ(512, result.usage); |
| usage_breakdown_expected.fileSystem = 0; |
| usage_breakdown_expected.indexedDatabase = 512; |
| EXPECT_TRUE(usage_breakdown_expected.Equals(*result.breakdown)); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsage_WithModify) { |
| const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 10}, |
| {"http://bar.com/", kDefaultBucketName, 0}, |
| {"http://foo.com:1/", kDefaultBucketName, 20}, |
| {"https://foo.com/", kDefaultBucketName, 0}, |
| }; |
| MockQuotaClient* client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(client, kData); |
| |
| auto result = GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 10); |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com:1/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 20); |
| |
| ModifyDefaultBucketAndNotify(client, ToStorageKey("http://foo.com/"), 30); |
| ModifyDefaultBucketAndNotify(client, ToStorageKey("http://foo.com:1/"), -5); |
| ModifyDefaultBucketAndNotify(client, ToStorageKey("https://foo.com/"), 1); |
| |
| // Database call to ensure modification calls have completed. |
| std::ignore = GetBucket(ToStorageKey("http://foo.com"), kDefaultBucketName); |
| |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 10 + 30); |
| int foo_usage = result.usage; |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com:1/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 20 - 5); |
| int foo1_usage = result.usage; |
| |
| ModifyDefaultBucketAndNotify(client, ToStorageKey("http://bar.com/"), 40); |
| |
| // Database call to ensure modification calls have completed. |
| std::ignore = GetBucket(ToStorageKey("http://foo.com"), kDefaultBucketName); |
| |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://bar.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 40); |
| |
| auto global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, foo_usage + foo1_usage + 40 + 1); |
| EXPECT_EQ(global_usage_result.unlimited_usage, 0); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsageAndQuota_WithAdditionalTasks) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 10}, |
| {"http://foo.com:8080/", kDefaultBucketName, 20}, |
| {"http://bar.com/", "logs", 13}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(fs_client, kData); |
| |
| const int kPoolSize = 100; |
| const int kPerStorageKeyQuota = 20; |
| SetQuotaSettings(kPoolSize, kPerStorageKeyQuota, |
| kMustRemainAvailableForSystem); |
| |
| GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com/")); |
| GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com/")); |
| auto result = GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 10); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuota); |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com:8080/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 20); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuota); |
| |
| set_additional_callback_count(0); |
| RunAdditionalUsageAndQuotaTask(ToStorageKey("http://foo.com/")); |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com/")); |
| RunAdditionalUsageAndQuotaTask(ToStorageKey("http://bar.com/")); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 10); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuota); |
| EXPECT_EQ(2, additional_callback_count()); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsageAndQuota_NukeManager) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 10}, |
| {"http://foo.com:8080/", kDefaultBucketName, 20}, |
| {"http://bar.com/", kDefaultBucketName, 13}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(fs_client, kData); |
| |
| const int kPoolSize = 100; |
| const int kPerStorageKeyQuota = 20; |
| SetQuotaSettings(kPoolSize, kPerStorageKeyQuota, |
| kMustRemainAvailableForSystem); |
| |
| set_additional_callback_count(0); |
| |
| GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com/")); |
| RunAdditionalUsageAndQuotaTask(ToStorageKey("http://foo.com/")); |
| RunAdditionalUsageAndQuotaTask(ToStorageKey("http://bar.com/")); |
| |
| base::test::TestFuture<QuotaStatusCode> future_foo; |
| base::test::TestFuture<QuotaStatusCode> future_bar; |
| quota_manager_impl()->DeleteHostData("foo.com", future_foo.GetCallback()); |
| quota_manager_impl()->DeleteHostData("bar.com", future_bar.GetCallback()); |
| |
| // Nuke before waiting for callbacks. |
| set_quota_manager_impl(nullptr); |
| |
| EXPECT_EQ(QuotaStatusCode::kErrorAbort, future_foo.Get()); |
| EXPECT_EQ(QuotaStatusCode::kErrorAbort, future_bar.Get()); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsageAndQuota_Overbudget) { |
| static const ClientBucketData kData[] = { |
| {"http://usage1/", kDefaultBucketName, 1}, |
| {"http://usage10/", kDefaultBucketName, 10}, |
| {"http://usage200/", kDefaultBucketName, 200}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(fs_client, kData); |
| |
| const int kPoolSize = 100; |
| const int kPerStorageKeyQuota = 20; |
| SetQuotaSettings(kPoolSize, kPerStorageKeyQuota, |
| kMustRemainAvailableForSystem); |
| |
| // Provided diskspace is not tight, global usage does not affect the |
| // quota calculations for an individual storage key, so despite global usage |
| // in excess of our poolsize, we still get the nominal quota value. |
| auto storage_capacity = GetStorageCapacity(); |
| EXPECT_LE(kMustRemainAvailableForSystem, storage_capacity.available_space); |
| |
| auto result = GetUsageAndQuotaForWebApps(ToStorageKey("http://usage1/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 1); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuota); |
| |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://usage10/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 10); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuota); |
| |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://usage200/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 200); |
| // Should be clamped to the nominal quota. |
| EXPECT_EQ(result.quota, kPerStorageKeyQuota); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsageAndQuota_Unlimited) { |
| static const ClientBucketData kData[] = { |
| {"http://usage10/", kDefaultBucketName, 10}, |
| {"http://usage50/", kDefaultBucketName, 50}, |
| {"http://unlimited/", "inbox", 4000}, |
| }; |
| mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/")); |
| auto storage_capacity = GetStorageCapacity(); |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(fs_client, kData); |
| |
| // Test when not overbugdet. |
| const int kPerStorageKeyQuotaFor1000 = 200; |
| SetQuotaSettings(1000, kPerStorageKeyQuotaFor1000, |
| kMustRemainAvailableForSystem); |
| auto global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, 10 + 50 + 4000); |
| EXPECT_EQ(global_usage_result.unlimited_usage, 4000); |
| |
| auto result = GetUsageAndQuotaForWebApps(ToStorageKey("http://usage10/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 10); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuotaFor1000); |
| |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://usage50/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 50); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuotaFor1000); |
| |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://unlimited/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 4000); |
| EXPECT_EQ(result.quota, storage_capacity.available_space + result.usage); |
| |
| auto client_result = |
| GetUsageAndQuotaForStorageClient(ToStorageKey("http://unlimited/")); |
| EXPECT_EQ(client_result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(client_result.usage, 0); |
| EXPECT_EQ(client_result.quota, QuotaManagerImpl::kNoLimit); |
| |
| // Test when overbudgeted. |
| const int kPerStorageKeyQuotaFor100 = 20; |
| SetQuotaSettings(100, kPerStorageKeyQuotaFor100, |
| kMustRemainAvailableForSystem); |
| |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://usage10/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 10); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuotaFor100); |
| |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://usage50/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 50); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuotaFor100); |
| |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://unlimited/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 4000); |
| EXPECT_EQ(result.quota, storage_capacity.available_space + result.usage); |
| |
| client_result = |
| GetUsageAndQuotaForStorageClient(ToStorageKey("http://unlimited/")); |
| EXPECT_EQ(client_result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(client_result.usage, 0); |
| EXPECT_EQ(client_result.quota, QuotaManagerImpl::kNoLimit); |
| |
| // Revoke the unlimited rights and make sure the change is noticed. |
| mock_special_storage_policy()->Reset(); |
| mock_special_storage_policy()->NotifyCleared(); |
| |
| global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, 10 + 50 + 4000); |
| EXPECT_EQ(global_usage_result.unlimited_usage, 0); |
| |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://usage10/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 10); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuotaFor100); |
| |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://usage50/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 50); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuotaFor100); |
| |
| result = GetUsageAndQuotaForWebApps(ToStorageKey("http://unlimited/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 4000); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuotaFor100); |
| |
| client_result = |
| GetUsageAndQuotaForStorageClient(ToStorageKey("http://unlimited/")); |
| EXPECT_EQ(client_result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(client_result.usage, 4000); |
| EXPECT_EQ(client_result.quota, kPerStorageKeyQuotaFor100); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetQuotaLowAvailableDiskSpace) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 100000}, |
| {"http://unlimited/", kDefaultBucketName, 4000000}, |
| }; |
| |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(fs_client, kData); |
| |
| const int kPoolSize = 10000000; |
| const int kPerStorageKeyQuota = kPoolSize / 5; |
| |
| // In here, we expect the low available space logic branch |
| // to be ignored. Doing so should have QuotaManagerImpl return the same |
| // per-host quota as what is set in QuotaSettings, despite being in a state of |
| // low available space. |
| const int kMustRemainAvailable = |
| static_cast<int>(GetAvailableDiskSpaceForTest() - 65536); |
| SetQuotaSettings(kPoolSize, kPerStorageKeyQuota, kMustRemainAvailable); |
| |
| auto result = GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 100000); |
| EXPECT_EQ(result.quota, kPerStorageKeyQuota); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsage_Simple) { |
| static const ClientBucketData kData[] = { |
| {"http://bar.com/", kDefaultBucketName, 300}, |
| {"https://buz.com/", kDefaultBucketName, 4000}, |
| {"http://buz.com/", kDefaultBucketName, 50000}, |
| {"http://foo.com/", kDefaultBucketName, 7000000}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(fs_client, kData); |
| |
| auto global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(300 + 4000 + 50000 + 7000000, global_usage_result.usage); |
| EXPECT_EQ(global_usage_result.unlimited_usage, 0); |
| |
| EXPECT_EQ( |
| 4000, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("https://buz.com/")).usage); |
| EXPECT_EQ( |
| 50000, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://buz.com/")).usage); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsage_WithModification) { |
| static const ClientBucketData kData[] = { |
| {"http://bar.com/", kDefaultBucketName, 300}, |
| {"https://buz.com/", kDefaultBucketName, 4000}, |
| {"http://buz.com/", kDefaultBucketName, 50000}, |
| {"http://foo.com/", kDefaultBucketName, 7000000}, |
| }; |
| |
| MockQuotaClient* client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(client, kData); |
| |
| auto global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, 300 + 4000 + 50000 + 7000000); |
| EXPECT_EQ(global_usage_result.unlimited_usage, 0); |
| |
| ModifyDefaultBucketAndNotify(client, ToStorageKey("http://foo.com/"), 1); |
| |
| global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, 300 + 4000 + 50000 + 7000000 + 1); |
| EXPECT_EQ(global_usage_result.unlimited_usage, 0); |
| |
| EXPECT_EQ( |
| 4000, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("https://buz.com/")).usage); |
| EXPECT_EQ( |
| 50000, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://buz.com/")).usage); |
| |
| ModifyDefaultBucketAndNotify(client, ToStorageKey("http://buz.com/"), |
| 900000000); |
| |
| EXPECT_EQ( |
| 4000, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("https://buz.com/")).usage); |
| EXPECT_EQ( |
| 50000 + 900000000, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://buz.com/")).usage); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsage_WithBucketModification) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| {"http://bar.com/", "logs", 100}, |
| }; |
| |
| MockQuotaClient* client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(client, kData); |
| |
| ASSERT_OK_AND_ASSIGN( |
| auto foo_bucket, |
| GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName)); |
| client->ModifyBucketAndNotify(foo_bucket.ToBucketLocator(), 80000000); |
| |
| auto global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, 1 + 100 + 80000000); |
| EXPECT_EQ(global_usage_result.unlimited_usage, 0); |
| |
| EXPECT_EQ( |
| 100, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://bar.com/")).usage); |
| |
| ASSERT_OK_AND_ASSIGN(auto bar_bucket, |
| GetBucket(ToStorageKey("http://bar.com/"), "logs")); |
| client->ModifyBucketAndNotify(bar_bucket.ToBucketLocator(), 900000000); |
| |
| EXPECT_EQ( |
| 100 + 900000000, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://bar.com/")).usage); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsage_WithDeleteBucket) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| {"http://foo.com/", "secondbucket", 10000}, |
| {"http://foo.com:1/", kDefaultBucketName, 20}, |
| {"http://bar.com/", kDefaultBucketName, 4000}, |
| }; |
| MockQuotaClient* client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(client, kData); |
| |
| auto global_usage_result = GetGlobalUsage(); |
| int64_t predelete_global_tmp = global_usage_result.usage; |
| |
| const int64_t predelete_storage_key_tmp = |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage; |
| |
| ASSERT_OK_AND_ASSIGN(auto bucket, GetBucket(ToStorageKey("http://foo.com/"), |
| kDefaultBucketName)); |
| |
| auto status = DeleteBucketData(bucket.ToBucketLocator(), |
| {QuotaClientType::kFileSystem}); |
| EXPECT_EQ(status, QuotaStatusCode::kOk); |
| |
| global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, predelete_global_tmp - 1); |
| |
| EXPECT_EQ( |
| predelete_storage_key_tmp - 1, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetStorageCapacity) { |
| auto storage_capacity = GetStorageCapacity(); |
| EXPECT_GE(storage_capacity.total_space, 0); |
| EXPECT_GE(storage_capacity.available_space, 0); |
| } |
| |
| TEST_F(QuotaManagerImplTest, EvictBucketData) { |
| static const ClientBucketData kData1[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| {"http://foo.com:1/", "logs", 800000}, |
| {"http://foo.com/", "logs", 20}, |
| {"http://bar.com/", kDefaultBucketName, 4000}, |
| }; |
| static const ClientBucketData kData2[] = { |
| {"http://foo.com/", kDefaultBucketName, 50000}, |
| {"http://foo.com:1/", "logs", 6000}, |
| {"https://foo.com/", kDefaultBucketName, 80}, |
| {"http://bar.com/", kDefaultBucketName, 9}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| MockQuotaClient* idb_client = |
| CreateAndRegisterClient(QuotaClientType::kIndexedDatabase); |
| RegisterClientBucketData(fs_client, kData1); |
| RegisterClientBucketData(idb_client, kData2); |
| |
| auto global_usage_result = GetGlobalUsage(); |
| int64_t predelete_global_tmp = global_usage_result.usage; |
| |
| const int64_t predelete_storage_key_tmp = |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage; |
| |
| for (const ClientBucketData& data : kData1) { |
| NotifyDefaultBucketAccessed(ToStorageKey(data.origin), base::Time::Now()); |
| } |
| for (const ClientBucketData& data : kData2) { |
| NotifyDefaultBucketAccessed(ToStorageKey(data.origin), base::Time::Now()); |
| } |
| task_environment_.RunUntilIdle(); |
| |
| // Default bucket eviction. |
| ASSERT_OK_AND_ASSIGN(auto bucket, GetBucket(ToStorageKey("http://foo.com/"), |
| kDefaultBucketName)); |
| |
| EvictBucketData(bucket.ToBucketLocator()); |
| |
| ASSERT_THAT(GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName), |
| base::test::ErrorIs(QuotaError::kNotFound)); |
| |
| global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(predelete_global_tmp - (1 + 50000), global_usage_result.usage); |
| |
| EXPECT_EQ( |
| predelete_storage_key_tmp - (1 + 50000), |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| |
| // Non default bucket eviction. |
| ASSERT_OK_AND_ASSIGN(bucket, |
| GetBucket(ToStorageKey("http://foo.com"), "logs")); |
| |
| EvictBucketData(bucket.ToBucketLocator()); |
| |
| EXPECT_THAT(GetBucket(ToStorageKey("http://foo.com"), "logs"), |
| base::test::ErrorIs(QuotaError::kNotFound)); |
| |
| global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(predelete_global_tmp - (1 + 20 + 50000), global_usage_result.usage); |
| |
| EXPECT_EQ( |
| predelete_storage_key_tmp - (1 + 20 + 50000), |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| } |
| |
| TEST_F(QuotaManagerImplTest, EvictBucketDataHistogram) { |
| base::HistogramTester histograms; |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| {"http://bar.com/", kDefaultBucketName, 1}, |
| }; |
| MockQuotaClient* client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(client, kData); |
| |
| GetGlobalUsage(); |
| |
| ASSERT_OK_AND_ASSIGN(auto bucket, GetBucket(ToStorageKey("http://foo.com"), |
| kDefaultBucketName)); |
| |
| EvictBucketData(bucket.ToBucketLocator()); |
| |
| // Ensure use count and time since access are recorded. |
| histograms.ExpectTotalCount( |
| QuotaManagerImpl::kEvictedBucketAccessedCountHistogram, 1); |
| histograms.ExpectBucketCount( |
| QuotaManagerImpl::kEvictedBucketAccessedCountHistogram, 0, 1); |
| histograms.ExpectTotalCount( |
| QuotaManagerImpl::kEvictedBucketDaysSinceAccessHistogram, 1); |
| |
| // Change the use count. |
| NotifyDefaultBucketAccessed(ToStorageKey("http://bar.com/"), |
| base::Time::Now()); |
| task_environment_.RunUntilIdle(); |
| |
| GetGlobalUsage(); |
| |
| ASSERT_OK_AND_ASSIGN( |
| bucket, GetBucket(ToStorageKey("http://bar.com"), kDefaultBucketName)); |
| |
| EvictBucketData(bucket.ToBucketLocator()); |
| |
| // The new use count should be logged. |
| histograms.ExpectTotalCount( |
| QuotaManagerImpl::kEvictedBucketAccessedCountHistogram, 2); |
| histograms.ExpectBucketCount( |
| QuotaManagerImpl::kEvictedBucketAccessedCountHistogram, 1, 1); |
| histograms.ExpectTotalCount( |
| QuotaManagerImpl::kEvictedBucketDaysSinceAccessHistogram, 2); |
| } |
| |
| TEST_F(QuotaManagerImplTest, EvictBucketDataWithDeletionError) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| {"http://foo.com:1/", kDefaultBucketName, 20}, |
| {"http://bar.com/", kDefaultBucketName, 4000}, |
| }; |
| static const int kNumberOfBuckets = 3; |
| MockQuotaClient* client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(client, kData); |
| |
| auto global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, (1 + 20 + 4000)); |
| |
| EXPECT_EQ( |
| 1, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| EXPECT_EQ( |
| 20, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com:1/")).usage); |
| |
| for (const ClientBucketData& data : kData) { |
| NotifyDefaultBucketAccessed(ToStorageKey(data.origin)); |
| } |
| task_environment_.RunUntilIdle(); |
| |
| ASSERT_OK_AND_ASSIGN(auto bucket, GetBucket(ToStorageKey("http://foo.com/"), |
| kDefaultBucketName)); |
| client->AddBucketToErrorSet(bucket.ToBucketLocator()); |
| |
| for (int i = 0; i < QuotaManagerImpl::kThresholdOfErrorsToBeDenylisted + 1; |
| ++i) { |
| EvictBucketData(bucket.ToBucketLocator()); |
| } |
| |
| // The default bucket for "http://foo.com/" should still be in the database. |
| EXPECT_TRUE(GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName) |
| .has_value()); |
| |
| for (size_t i = 0; i < kNumberOfBuckets - 1; ++i) { |
| std::optional<BucketLocator> eviction_bucket = GetEvictionBucket(); |
| EXPECT_TRUE(eviction_bucket.has_value()); |
| // "http://foo.com/" should not be in the LRU list. |
| EXPECT_NE(std::string("http://foo.com/"), |
| eviction_bucket->storage_key.origin().GetURL().spec()); |
| DeleteBucketData(*eviction_bucket, AllQuotaClientTypes()); |
| } |
| |
| // Now the LRU list must be empty. |
| std::optional<BucketLocator> eviction_bucket = GetEvictionBucket(); |
| EXPECT_FALSE(eviction_bucket.has_value()); |
| |
| global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, 1); |
| |
| EXPECT_EQ( |
| 1, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| EXPECT_EQ( |
| 0, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com:1/")).usage); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetEvictionRoundInfo) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| {"http://foo.com:1/", kDefaultBucketName, 20}, |
| {"http://unlimited/", kDefaultBucketName, 4000}, |
| }; |
| |
| mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/")); |
| MockQuotaClient* client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(client, kData); |
| |
| const int kPoolSize = 10000000; |
| const int kPerStorageKeyQuota = kPoolSize / 5; |
| SetQuotaSettings(kPoolSize, kPerStorageKeyQuota, |
| kMustRemainAvailableForSystem); |
| |
| GetEvictionRoundInfo(); |
| task_environment_.RunUntilIdle(); |
| EXPECT_EQ(QuotaStatusCode::kOk, status()); |
| EXPECT_EQ(21, usage()); |
| EXPECT_EQ(kPoolSize, settings().pool_size); |
| EXPECT_LE(0, available_space()); |
| } |
| |
| TEST_F(QuotaManagerImplTest, DeleteHostDataNoClients) { |
| EXPECT_EQ(DeleteHostData(std::string()), QuotaStatusCode::kOk); |
| } |
| |
| TEST_F(QuotaManagerImplTest, DeleteHostDataSimple) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| }; |
| MockQuotaClient* client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(client, kData); |
| |
| auto global_usage_result = GetGlobalUsage(); |
| const int64_t predelete_global = global_usage_result.usage; |
| |
| const int64_t predelete_storage_key = |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage; |
| |
| EXPECT_EQ(DeleteHostData(std::string()), QuotaStatusCode::kOk); |
| |
| global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, predelete_global); |
| |
| EXPECT_EQ( |
| predelete_storage_key, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| |
| EXPECT_EQ(DeleteHostData("foo.com"), QuotaStatusCode::kOk); |
| |
| global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(predelete_global - 1, global_usage_result.usage); |
| |
| EXPECT_EQ( |
| predelete_storage_key - 1, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| } |
| |
| TEST_F(QuotaManagerImplTest, DeleteHostDataMultiple) { |
| static const ClientBucketData kData1[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| {"http://foo.com:1/", kDefaultBucketName, 20}, |
| {"http://bar.com/", kDefaultBucketName, 4000}, |
| }; |
| static const ClientBucketData kData2[] = { |
| {"http://foo.com/", kDefaultBucketName, 50000}, |
| {"http://foo.com:1/", kDefaultBucketName, 6000}, |
| {"https://foo.com/", kDefaultBucketName, 80}, |
| {"http://bar.com/", kDefaultBucketName, 9}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| MockQuotaClient* idb_client = |
| CreateAndRegisterClient(QuotaClientType::kIndexedDatabase); |
| RegisterClientBucketData(fs_client, kData1); |
| RegisterClientBucketData(idb_client, kData2); |
| |
| auto global_usage_result = GetGlobalUsage(); |
| const int64_t predelete_global = global_usage_result.usage; |
| |
| const int64_t predelete_sk_foo = |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage; |
| const int64_t predelete_sk_sfoo = |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("https://foo.com/")).usage; |
| const int64_t predelete_sk_foo1 = |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com:1/")).usage; |
| const int64_t predelete_sk_bar = |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://bar.com/")).usage; |
| |
| EXPECT_EQ(DeleteHostData("foo.com"), QuotaStatusCode::kOk); |
| EXPECT_EQ(DeleteHostData("bar.com"), QuotaStatusCode::kOk); |
| EXPECT_EQ(DeleteHostData("foo.com"), QuotaStatusCode::kOk); |
| |
| const BucketTableEntries& entries = DumpBucketTable(); |
| for (const auto& entry : entries) { |
| std::optional<StorageKey> storage_key = |
| StorageKey::Deserialize(entry->storage_key); |
| ASSERT_TRUE(storage_key.has_value()); |
| |
| EXPECT_NE(std::string("http://foo.com/"), |
| storage_key.value().origin().GetURL().spec()); |
| EXPECT_NE(std::string("http://foo.com:1/"), |
| storage_key.value().origin().GetURL().spec()); |
| EXPECT_NE(std::string("https://foo.com/"), |
| storage_key.value().origin().GetURL().spec()); |
| EXPECT_NE(std::string("http://bar.com/"), |
| std::move(storage_key).value().origin().GetURL().spec()); |
| } |
| |
| global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, |
| predelete_global - (1 + 20 + 4000 + 50000 + 6000 + 80 + 9)); |
| |
| EXPECT_EQ( |
| predelete_sk_foo - (1 + 50000), |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| EXPECT_EQ( |
| predelete_sk_sfoo - (80), |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("https://foo.com/")).usage); |
| EXPECT_EQ( |
| predelete_sk_foo1 - (20 + 6000), |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com:1/")).usage); |
| EXPECT_EQ( |
| predelete_sk_bar - (4000 + 9), |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://bar.com/")).usage); |
| } |
| |
| TEST_F(QuotaManagerImplTest, DeleteBucketNoClients) { |
| ASSERT_OK_AND_ASSIGN(auto bucket, |
| CreateBucketForTesting(ToStorageKey("http://foo.com"), |
| kDefaultBucketName)); |
| EXPECT_EQ(DeleteBucketData(bucket.ToBucketLocator(), AllQuotaClientTypes()), |
| QuotaStatusCode::kOk); |
| } |
| |
| TEST_F(QuotaManagerImplTest, DeleteBucketDataMultiple) { |
| static const ClientBucketData kData1[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| {"http://foo.com:1/", kDefaultBucketName, 20}, |
| {"http://bar.com/", kDefaultBucketName, 4000}, |
| }; |
| static const ClientBucketData kData2[] = { |
| {"http://foo.com/", kDefaultBucketName, 50000}, |
| {"http://foo.com:1/", kDefaultBucketName, 6000}, |
| {"https://foo.com/", kDefaultBucketName, 80}, |
| {"http://bar.com/", kDefaultBucketName, 9}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| MockQuotaClient* idb_client = |
| CreateAndRegisterClient(QuotaClientType::kIndexedDatabase); |
| RegisterClientBucketData(fs_client, kData1); |
| RegisterClientBucketData(idb_client, kData2); |
| |
| ASSERT_OK_AND_ASSIGN( |
| auto foo_bucket, |
| GetBucket(ToStorageKey("http://foo.com"), kDefaultBucketName)); |
| |
| ASSERT_OK_AND_ASSIGN( |
| auto bar_bucket, |
| GetBucket(ToStorageKey("http://bar.com"), kDefaultBucketName)); |
| |
| auto global_usage_result = GetGlobalUsage(); |
| const int64_t predelete_global_tmp = global_usage_result.usage; |
| |
| const int64_t predelete_sk_foo_tmp = |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage; |
| const int64_t predelete_sk_sfoo_tmp = |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("https://foo.com/")).usage; |
| const int64_t predelete_sk_foo1_tmp = |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com:1/")).usage; |
| const int64_t predelete_sk_bar_tmp = |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://bar.com/")).usage; |
| |
| for (const ClientBucketData& data : kData1) { |
| NotifyDefaultBucketAccessed(ToStorageKey(data.origin), base::Time::Now()); |
| } |
| for (const ClientBucketData& data : kData2) { |
| NotifyDefaultBucketAccessed(ToStorageKey(data.origin), base::Time::Now()); |
| } |
| task_environment_.RunUntilIdle(); |
| |
| EXPECT_EQ( |
| DeleteBucketData(foo_bucket.ToBucketLocator(), AllQuotaClientTypes()), |
| QuotaStatusCode::kOk); |
| EXPECT_EQ( |
| DeleteBucketData(bar_bucket.ToBucketLocator(), AllQuotaClientTypes()), |
| QuotaStatusCode::kOk); |
| |
| EXPECT_THAT(GetBucket(foo_bucket.storage_key, foo_bucket.name), |
| base::test::ErrorIs(QuotaError::kNotFound)); |
| |
| EXPECT_THAT(GetBucket(bar_bucket.storage_key, bar_bucket.name), |
| base::test::ErrorIs(QuotaError::kNotFound)); |
| |
| global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, |
| predelete_global_tmp - (1 + 4000 + 50000 + 9)); |
| |
| EXPECT_EQ( |
| predelete_sk_foo_tmp - (1 + 50000), |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| EXPECT_EQ( |
| predelete_sk_sfoo_tmp, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("https://foo.com/")).usage); |
| EXPECT_EQ( |
| predelete_sk_foo1_tmp, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com:1/")).usage); |
| EXPECT_EQ( |
| predelete_sk_bar_tmp - (4000 + 9), |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://bar.com/")).usage); |
| } |
| |
| TEST_F(QuotaManagerImplTest, FindAndDeleteBucketData) { |
| static const ClientBucketData kData1[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| {"http://bar.com/", kDefaultBucketName, 4000}, |
| }; |
| static const ClientBucketData kData2[] = { |
| {"http://foo.com/", kDefaultBucketName, 50000}, |
| {"http://bar.com/", kDefaultBucketName, 9}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| MockQuotaClient* idb_client = |
| CreateAndRegisterClient(QuotaClientType::kIndexedDatabase); |
| RegisterClientBucketData(fs_client, kData1); |
| RegisterClientBucketData(idb_client, kData2); |
| |
| ASSERT_OK_AND_ASSIGN( |
| auto foo_bucket, |
| GetBucket(ToStorageKey("http://foo.com"), kDefaultBucketName)); |
| |
| ASSERT_OK_AND_ASSIGN( |
| auto bar_bucket, |
| GetBucket(ToStorageKey("http://bar.com"), kDefaultBucketName)); |
| |
| // Check usage data before deletion. |
| auto global_usage_result = GetGlobalUsage(); |
| ASSERT_EQ((1 + 9 + 4000 + 50000), global_usage_result.usage); |
| const int64_t predelete_global_tmp = global_usage_result.usage; |
| |
| ASSERT_EQ( |
| (1 + 50000), |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| ASSERT_EQ( |
| (9 + 4000), |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://bar.com/")).usage); |
| |
| // Delete bucket for "http://foo.com/". |
| EXPECT_EQ(FindAndDeleteBucketData(foo_bucket.storage_key, foo_bucket.name), |
| QuotaStatusCode::kOk); |
| |
| EXPECT_THAT(GetBucket(foo_bucket.storage_key, foo_bucket.name), |
| base::test::ErrorIs(QuotaError::kNotFound)); |
| |
| global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, predelete_global_tmp - (1 + 50000)); |
| |
| EXPECT_EQ( |
| 0, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| |
| // Delete bucket for "http://bar.com/". |
| EXPECT_EQ(FindAndDeleteBucketData(bar_bucket.storage_key, bar_bucket.name), |
| QuotaStatusCode::kOk); |
| |
| EXPECT_THAT(GetBucket(bar_bucket.storage_key, bar_bucket.name), |
| base::test::ErrorIs(QuotaError::kNotFound)); |
| |
| global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, 0); |
| |
| EXPECT_EQ( |
| 0, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://bar.com/")).usage); |
| } |
| |
| TEST_F(QuotaManagerImplTest, FindAndDeleteBucketDataWithDBError) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 123}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| |
| RegisterClientBucketData(fs_client, kData); |
| |
| // Check usage data before deletion. |
| ASSERT_EQ( |
| 123, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| |
| // Bucket lookup uses the `buckets_by_storage_key` index. So, we can corrupt |
| // any other index, and SQLite will only detect the corruption when trying to |
| // delete a bucket. |
| QuotaError corruption_error = CorruptDatabaseForTesting( |
| base::BindOnce([](const base::FilePath& db_path) { |
| ASSERT_TRUE(sql::test::CorruptIndexRootPage( |
| db_path, "buckets_by_last_accessed")); |
| })); |
| ASSERT_EQ(QuotaError::kNone, corruption_error); |
| |
| // Deleting the bucket will result in an error. |
| EXPECT_NE(FindAndDeleteBucketData(ToStorageKey("http://foo.com"), |
| kDefaultBucketName), |
| QuotaStatusCode::kOk); |
| |
| auto global_usage_result = GetGlobalUsage(); |
| EXPECT_EQ(global_usage_result.usage, 0); |
| |
| EXPECT_EQ( |
| 0, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetDiskAvailabilityAndTempPoolSize) { |
| ResetQuotaManagerImpl(/*is_incognito=*/false); |
| |
| base::test::TestFuture<int64_t, int64_t, int64_t> quota_internals_future; |
| quota_manager_impl()->GetDiskAvailabilityAndTempPoolSize( |
| quota_internals_future.GetCallback()); |
| std::tuple quota_internals_result = quota_internals_future.Take(); |
| |
| int64_t available_space = |
| static_cast<uint64_t>(GetAvailableDiskSpaceForTest()); |
| int64_t total_space = available_space * 2; |
| |
| EXPECT_EQ(total_space, std::get<0>(quota_internals_result)); |
| EXPECT_EQ(available_space, std::get<1>(quota_internals_result)); |
| EXPECT_EQ(kDefaultPoolSize, std::get<2>(quota_internals_result)); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetDiskAvailabilityAndTempPoolSize_Incognito) { |
| // Test to make sure total_space and available_space are retrieved |
| // as expected, without producing a crash. |
| ResetQuotaManagerImpl(/*is_incognito=*/true); |
| |
| base::test::TestFuture<int64_t, int64_t, int64_t> quota_internals_future; |
| quota_manager_impl()->GetDiskAvailabilityAndTempPoolSize( |
| quota_internals_future.GetCallback()); |
| std::tuple quota_internals_result = quota_internals_future.Take(); |
| |
| EXPECT_EQ(kDefaultPoolSize, std::get<0>(quota_internals_result)); |
| EXPECT_EQ(kDefaultPoolSize, std::get<1>(quota_internals_result)); |
| EXPECT_EQ(kDefaultPoolSize, std::get<2>(quota_internals_result)); |
| } |
| |
| TEST_F(QuotaManagerImplTest, NotifyAndLRUBucket) { |
| static const ClientBucketData kData[] = { |
| {"http://a.com/", kDefaultBucketName, 0}, |
| {"http://a.com:1/", kDefaultBucketName, 0}, |
| {"http://c.com/", kDefaultBucketName, 0}, |
| }; |
| QuotaDatabase::SetClockForTesting(task_environment_.GetMockClock()); |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(fs_client, kData); |
| |
| task_environment_.FastForwardBy(base::Minutes(1)); |
| NotifyDefaultBucketAccessed(ToStorageKey("http://a.com/"), |
| task_environment_.GetMockClock()->Now()); |
| NotifyDefaultBucketAccessed(ToStorageKey("http://c.com/"), |
| task_environment_.GetMockClock()->Now()); |
| |
| std::optional<BucketLocator> eviction_bucket = GetEvictionBucket(); |
| EXPECT_EQ("http://a.com:1/", |
| eviction_bucket->storage_key.origin().GetURL().spec()); |
| |
| DeleteBucketData(*eviction_bucket, AllQuotaClientTypes()); |
| eviction_bucket = GetEvictionBucket(); |
| EXPECT_EQ("http://a.com/", |
| eviction_bucket->storage_key.origin().GetURL().spec()); |
| |
| DeleteBucketData(*eviction_bucket, AllQuotaClientTypes()); |
| eviction_bucket = GetEvictionBucket(); |
| EXPECT_EQ("http://c.com/", |
| eviction_bucket->storage_key.origin().GetURL().spec()); |
| QuotaDatabase::SetClockForTesting(nullptr); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetBucketsForEviction) { |
| static const ClientBucketData kData[] = { |
| {"http://a.com/", kDefaultBucketName, 107}, |
| {"http://b.com/", kDefaultBucketName, 300}, |
| {"http://c.com/", kDefaultBucketName, 713}, |
| }; |
| QuotaDatabase::SetClockForTesting(task_environment_.GetMockClock()); |
| MockQuotaClient* client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(client, kData); |
| GetGlobalUsage(); |
| |
| task_environment_.FastForwardBy(base::Minutes(1)); |
| NotifyDefaultBucketAccessed(ToStorageKey("http://a.com/"), |
| task_environment_.GetMockClock()->Now()); |
| task_environment_.FastForwardBy(base::Minutes(1)); |
| NotifyDefaultBucketAccessed(ToStorageKey("http://b.com/"), |
| task_environment_.GetMockClock()->Now()); |
| task_environment_.FastForwardBy(base::Minutes(1)); |
| NotifyDefaultBucketAccessed(ToStorageKey("http://c.com/"), |
| task_environment_.GetMockClock()->Now()); |
| |
| auto buckets = GetEvictionBuckets(110); |
| EXPECT_THAT(buckets, testing::UnorderedElementsAre( |
| testing::Field(&BucketLocator::storage_key, |
| ToStorageKey("http://a.com")), |
| testing::Field(&BucketLocator::storage_key, |
| ToStorageKey("http://b.com")))); |
| |
| // Notify that the `bucket_a` is accessed. Now b is the LRU (and also happens |
| // to satisfy the desire to evict 110b of data). |
| NotifyDefaultBucketAccessed(ToStorageKey("http://a.com/"), |
| task_environment_.GetMockClock()->Now()); |
| buckets = GetEvictionBuckets(110); |
| EXPECT_THAT(buckets, |
| testing::UnorderedElementsAre(testing::Field( |
| &BucketLocator::storage_key, ToStorageKey("http://b.com")))); |
| QuotaDatabase::SetClockForTesting(nullptr); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetBucketsModifiedBetween) { |
| static const ClientBucketData kData[] = { |
| {"http://a.com/", kDefaultBucketName, 0}, |
| {"http://a.com:1/", kDefaultBucketName, 0}, |
| {"https://a.com/", kDefaultBucketName, 0}, |
| {"http://c.com/", kDefaultBucketName, 0}, |
| }; |
| MockQuotaClient* client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(client, kData); |
| |
| auto buckets = GetBucketsModifiedBetween(base::Time(), base::Time::Max()); |
| EXPECT_EQ(4U, buckets.size()); |
| |
| base::Time time1 = client->IncrementMockTime(); |
| ModifyDefaultBucketAndNotify(client, ToStorageKey("http://a.com/"), 10); |
| ModifyDefaultBucketAndNotify(client, ToStorageKey("http://a.com:1/"), 10); |
| base::Time time2 = client->IncrementMockTime(); |
| ModifyDefaultBucketAndNotify(client, ToStorageKey("https://a.com/"), 10); |
| ModifyDefaultBucketAndNotify(client, ToStorageKey("http://c.com/"), 10); |
| base::Time time3 = client->IncrementMockTime(); |
| |
| // Database call to ensure modification calls have completed. |
| std::ignore = GetBucket(ToStorageKey("http://a.com"), kDefaultBucketName); |
| |
| buckets = GetBucketsModifiedBetween(time1, base::Time::Max()); |
| EXPECT_THAT(buckets, testing::UnorderedElementsAre( |
| testing::Field(&BucketLocator::storage_key, |
| ToStorageKey("http://a.com")), |
| testing::Field(&BucketLocator::storage_key, |
| ToStorageKey("http://a.com:1")), |
| testing::Field(&BucketLocator::storage_key, |
| ToStorageKey("https://a.com")), |
| testing::Field(&BucketLocator::storage_key, |
| ToStorageKey("http://c.com")))); |
| |
| buckets = GetBucketsModifiedBetween(time2, base::Time::Max()); |
| EXPECT_EQ(2U, buckets.size()); |
| |
| buckets = GetBucketsModifiedBetween(time3, base::Time::Max()); |
| EXPECT_TRUE(buckets.empty()); |
| |
| ModifyDefaultBucketAndNotify(client, ToStorageKey("http://a.com/"), 10); |
| |
| // Database call to ensure modification calls have completed. |
| std::ignore = GetBucket(ToStorageKey("http://a.com"), kDefaultBucketName); |
| |
| buckets = GetBucketsModifiedBetween(time3, base::Time::Max()); |
| EXPECT_THAT(buckets, |
| testing::UnorderedElementsAre(testing::Field( |
| &BucketLocator::storage_key, ToStorageKey("http://a.com/")))); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetBucketsModifiedBetweenWithDatabaseError) { |
| disable_database_bootstrap(true); |
| OpenDatabase(); |
| |
| // Disable quota database for database error behavior. |
| DisableQuotaDatabase(); |
| |
| auto buckets = GetBucketsModifiedBetween(base::Time(), base::Time::Max()); |
| |
| // Return empty set when error is encountered. |
| EXPECT_TRUE(buckets.empty()); |
| } |
| |
| TEST_F(QuotaManagerImplTest, DumpBucketTable) { |
| // Dumping an unpopulated bucket table returns an empty vector. |
| const BucketTableEntries& initial_entries = DumpBucketTable(); |
| EXPECT_TRUE(initial_entries.empty()); |
| |
| const StorageKey kStorageKey1 = ToStorageKey("http://example1.com/"); |
| const StorageKey kStorageKey2 = ToStorageKey("http://example2.com/"); |
| std::ignore = CreateBucketForTesting(kStorageKey1, kDefaultBucketName); |
| std::ignore = CreateBucketForTesting(kStorageKey2, kDefaultBucketName); |
| |
| NotifyDefaultBucketAccessed(kStorageKey1, base::Time::Now()); |
| NotifyDefaultBucketAccessed(kStorageKey2, base::Time::Now()); |
| NotifyDefaultBucketAccessed(kStorageKey2, base::Time::Now()); |
| task_environment_.RunUntilIdle(); |
| |
| const BucketTableEntries& entries = DumpBucketTable(); |
| EXPECT_THAT(entries, |
| testing::UnorderedElementsAre( |
| MatchesBucketTableEntry(kStorageKey1.Serialize(), 1), |
| MatchesBucketTableEntry(kStorageKey2.Serialize(), 2))); |
| } |
| |
| TEST_F(QuotaManagerImplTest, RetrieveBucketsTable) { |
| const StorageKey kStorageKey1 = ToStorageKey("http://example1.com/"); |
| const StorageKey kStorageKey2 = ToStorageKey("http://example2.com/"); |
| const base::Time kAccessTime = base::Time::Now(); |
| |
| static const ClientBucketData kData[] = { |
| {"http://example1.com/", kDefaultBucketName, 123}, |
| {"http://example2.com/", kDefaultBucketName, 456}, |
| }; |
| |
| MockQuotaClient* client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(client, kData); |
| |
| NotifyDefaultBucketAccessed(kStorageKey1, kAccessTime); |
| NotifyDefaultBucketAccessed(kStorageKey2, kAccessTime); |
| const base::Time time1 = base::Time::Now(); |
| |
| auto bucket1 = GetBucket(kStorageKey1, kDefaultBucketName); |
| auto bucket2 = GetBucket(kStorageKey2, kDefaultBucketName); |
| |
| const std::vector<storage::mojom::BucketTableEntryPtr> bucket_table_entries = |
| RetrieveBucketsTable(); |
| |
| auto* entry1 = FindBucketTableEntry(bucket_table_entries, bucket1->id); |
| EXPECT_TRUE(entry1); |
| EXPECT_EQ(entry1->storage_key, kStorageKey1.Serialize()); |
| EXPECT_EQ(entry1->name, kDefaultBucketName); |
| EXPECT_EQ(entry1->use_count, 1); |
| EXPECT_EQ(entry1->last_accessed, kAccessTime); |
| EXPECT_GE(entry1->last_modified, kAccessTime); |
| EXPECT_LE(entry1->last_modified, time1); |
| EXPECT_EQ(entry1->usage, 123); |
| |
| auto* entry2 = FindBucketTableEntry(bucket_table_entries, bucket2->id); |
| EXPECT_TRUE(entry2); |
| EXPECT_EQ(entry2->storage_key, kStorageKey2.Serialize()); |
| EXPECT_EQ(entry2->name, kDefaultBucketName); |
| EXPECT_EQ(entry2->use_count, 1); |
| EXPECT_EQ(entry2->last_accessed, kAccessTime); |
| EXPECT_GE(entry1->last_modified, kAccessTime); |
| EXPECT_LE(entry1->last_modified, time1); |
| EXPECT_EQ(entry2->usage, 456); |
| } |
| |
| TEST_F(QuotaManagerImplTest, DeleteSpecificClientTypeSingleBucket) { |
| static const ClientBucketData kData1[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| }; |
| static const ClientBucketData kData2[] = { |
| {"http://foo.com/", kDefaultBucketName, 2}, |
| }; |
| static const ClientBucketData kData3[] = { |
| {"http://foo.com/", kDefaultBucketName, 4}, |
| }; |
| static const ClientBucketData kData4[] = { |
| {"http://foo.com/", kDefaultBucketName, 8}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| MockQuotaClient* cache_client = |
| CreateAndRegisterClient(QuotaClientType::kServiceWorkerCache); |
| MockQuotaClient* sw_client = |
| CreateAndRegisterClient(QuotaClientType::kServiceWorker); |
| MockQuotaClient* idb_client = |
| CreateAndRegisterClient(QuotaClientType::kIndexedDatabase); |
| RegisterClientBucketData(fs_client, kData1); |
| RegisterClientBucketData(cache_client, kData2); |
| RegisterClientBucketData(sw_client, kData3); |
| RegisterClientBucketData(idb_client, kData4); |
| |
| ASSERT_OK_AND_ASSIGN( |
| auto foo_bucket, |
| GetBucket(ToStorageKey("http://foo.com"), kDefaultBucketName)); |
| |
| const int64_t predelete_sk_foo_tmp = |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage; |
| |
| DeleteBucketData(foo_bucket.ToBucketLocator(), |
| {QuotaClientType::kFileSystem}); |
| EXPECT_EQ( |
| predelete_sk_foo_tmp - 1, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| |
| DeleteBucketData(foo_bucket.ToBucketLocator(), |
| {QuotaClientType::kServiceWorkerCache}); |
| EXPECT_EQ( |
| predelete_sk_foo_tmp - 2 - 1, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| |
| DeleteBucketData(foo_bucket.ToBucketLocator(), |
| {QuotaClientType::kServiceWorker}); |
| EXPECT_EQ( |
| predelete_sk_foo_tmp - 4 - 2 - 1, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| |
| DeleteBucketData(foo_bucket.ToBucketLocator(), |
| {QuotaClientType::kIndexedDatabase}); |
| EXPECT_EQ( |
| predelete_sk_foo_tmp - 8 - 4 - 2 - 1, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| } |
| |
| TEST_F(QuotaManagerImplTest, DeleteMultipleClientTypesSingleBucket) { |
| static const ClientBucketData kData1[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| }; |
| static const ClientBucketData kData2[] = { |
| {"http://foo.com/", kDefaultBucketName, 2}, |
| }; |
| static const ClientBucketData kData3[] = { |
| {"http://foo.com/", kDefaultBucketName, 4}, |
| }; |
| static const ClientBucketData kData4[] = { |
| {"http://foo.com/", kDefaultBucketName, 8}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| MockQuotaClient* cache_client = |
| CreateAndRegisterClient(QuotaClientType::kServiceWorkerCache); |
| MockQuotaClient* sw_client = |
| CreateAndRegisterClient(QuotaClientType::kServiceWorker); |
| MockQuotaClient* idb_client = |
| CreateAndRegisterClient(QuotaClientType::kIndexedDatabase); |
| RegisterClientBucketData(fs_client, kData1); |
| RegisterClientBucketData(cache_client, kData2); |
| RegisterClientBucketData(sw_client, kData3); |
| RegisterClientBucketData(idb_client, kData4); |
| |
| ASSERT_OK_AND_ASSIGN( |
| auto foo_bucket, |
| GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName)); |
| |
| const int64_t predelete_sk_foo_tmp = |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage; |
| |
| DeleteBucketData( |
| foo_bucket.ToBucketLocator(), |
| {QuotaClientType::kFileSystem, QuotaClientType::kServiceWorker}); |
| |
| EXPECT_EQ( |
| predelete_sk_foo_tmp - 4 - 1, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| |
| DeleteBucketData(foo_bucket.ToBucketLocator(), |
| {QuotaClientType::kServiceWorkerCache, |
| QuotaClientType::kIndexedDatabase}); |
| |
| EXPECT_EQ( |
| predelete_sk_foo_tmp - 8 - 4 - 2 - 1, |
| GetStorageKeyUsageWithBreakdown(ToStorageKey("http://foo.com/")).usage); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsageAndQuota_Incognito) { |
| ResetQuotaManagerImpl(true); |
| |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 10}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(fs_client, kData); |
| |
| // Query global usage to warmup the usage tracker caching. |
| GetGlobalUsage(); |
| |
| const int kPoolSize = 1000; |
| const int kPerStorageKeyQuota = kPoolSize / 5; |
| SetQuotaSettings(kPoolSize, kPerStorageKeyQuota, INT64_C(0)); |
| |
| auto storage_capacity = GetStorageCapacity(); |
| EXPECT_EQ(storage_capacity.total_space, kPoolSize); |
| EXPECT_EQ(storage_capacity.available_space, kPoolSize - 10); |
| |
| auto result = GetUsageAndQuotaForWebApps(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 10); |
| EXPECT_GE(result.quota, kPerStorageKeyQuota); |
| } |
| |
| TEST_F(QuotaManagerImplTest, GetUsageAndQuota_SessionOnly) { |
| const StorageKey kEpheremalStorageKey = ToStorageKey("http://ephemeral/"); |
| mock_special_storage_policy()->AddSessionOnly( |
| kEpheremalStorageKey.origin().GetURL()); |
| |
| auto result = GetUsageAndQuotaForWebApps(kEpheremalStorageKey); |
| EXPECT_EQ(quota_manager_impl()->settings().session_only_per_storage_key_quota, |
| result.quota); |
| } |
| |
| TEST_F(QuotaManagerImplTest, MaybeRunStoragePressureCallback) { |
| bool callback_ran = false; |
| auto cb = base::BindRepeating( |
| [](bool* callback_ran, const StorageKey& storage_key) { |
| *callback_ran = true; |
| }, |
| &callback_ran); |
| |
| SetStoragePressureCallback(cb); |
| |
| int64_t kGBytes = QuotaManagerImpl::kMBytes * 1024; |
| MaybeRunStoragePressureCallback(StorageKey(), 100 * kGBytes, 2 * kGBytes); |
| task_environment_.RunUntilIdle(); |
| EXPECT_FALSE(callback_ran); |
| |
| MaybeRunStoragePressureCallback(StorageKey(), 100 * kGBytes, kGBytes); |
| task_environment_.RunUntilIdle(); |
| EXPECT_TRUE(callback_ran); |
| } |
| |
| TEST_F(QuotaManagerImplTest, OverrideQuotaForStorageKey) { |
| StorageKey storage_key = ToStorageKey("https://foo.com"); |
| std::unique_ptr<QuotaOverrideHandle> handle = GetQuotaOverrideHandle(); |
| |
| base::RunLoop run_loop; |
| handle->OverrideQuotaForStorageKey( |
| storage_key, 5000, |
| base::BindLambdaForTesting([&]() { run_loop.Quit(); })); |
| run_loop.Run(); |
| |
| auto result = GetUsageAndQuotaForWebApps(storage_key); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 0); |
| EXPECT_EQ(result.quota, 5000); |
| } |
| |
| TEST_F(QuotaManagerImplTest, OverrideQuotaForStorageKey_Disable) { |
| StorageKey storage_key = ToStorageKey("https://foo.com"); |
| std::unique_ptr<QuotaOverrideHandle> handle1 = GetQuotaOverrideHandle(); |
| std::unique_ptr<QuotaOverrideHandle> handle2 = GetQuotaOverrideHandle(); |
| |
| base::RunLoop run_loop1; |
| handle1->OverrideQuotaForStorageKey( |
| storage_key, 5000, |
| base::BindLambdaForTesting([&]() { run_loop1.Quit(); })); |
| run_loop1.Run(); |
| |
| auto result = GetUsageAndQuotaForWebApps(storage_key); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.quota, 5000); |
| |
| base::RunLoop run_loop2; |
| handle2->OverrideQuotaForStorageKey( |
| storage_key, 9000, |
| base::BindLambdaForTesting([&]() { run_loop2.Quit(); })); |
| run_loop2.Run(); |
| |
| result = GetUsageAndQuotaForWebApps(storage_key); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.quota, 9000); |
| |
| base::RunLoop run_loop3; |
| handle2->OverrideQuotaForStorageKey( |
| storage_key, std::nullopt, |
| base::BindLambdaForTesting([&]() { run_loop3.Quit(); })); |
| run_loop3.Run(); |
| |
| result = GetUsageAndQuotaForWebApps(storage_key); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.quota, kDefaultPerStorageKeyQuota); |
| } |
| |
| TEST_F(QuotaManagerImplTest, WithdrawQuotaOverride) { |
| StorageKey storage_key = ToStorageKey("https://foo.com"); |
| std::unique_ptr<QuotaOverrideHandle> handle1 = GetQuotaOverrideHandle(); |
| std::unique_ptr<QuotaOverrideHandle> handle2 = GetQuotaOverrideHandle(); |
| |
| base::RunLoop run_loop1; |
| handle1->OverrideQuotaForStorageKey( |
| storage_key, 5000, |
| base::BindLambdaForTesting([&]() { run_loop1.Quit(); })); |
| run_loop1.Run(); |
| |
| auto result = GetUsageAndQuotaForWebApps(storage_key); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.quota, 5000); |
| |
| base::RunLoop run_loop2; |
| handle1->OverrideQuotaForStorageKey( |
| storage_key, 8000, |
| base::BindLambdaForTesting([&]() { run_loop2.Quit(); })); |
| run_loop2.Run(); |
| |
| result = GetUsageAndQuotaForWebApps(storage_key); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.quota, 8000); |
| |
| // Quota should remain overridden if only one of the two handles withdraws |
| // it's overrides |
| handle2.reset(); |
| result = GetUsageAndQuotaForWebApps(storage_key); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.quota, 8000); |
| |
| handle1.reset(); |
| task_environment_.RunUntilIdle(); |
| result = GetUsageAndQuotaForWebApps(storage_key); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.quota, kDefaultPerStorageKeyQuota); |
| } |
| |
| TEST_F(QuotaManagerImplTest, DeleteBucketData_QuotaManagerDeletedImmediately) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| }; |
| MockQuotaClient* client = |
| CreateAndRegisterClient(QuotaClientType::kIndexedDatabase); |
| RegisterClientBucketData(client, kData); |
| |
| ASSERT_OK_AND_ASSIGN( |
| BucketInfo bucket, |
| GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName)); |
| |
| base::test::TestFuture<QuotaStatusCode> delete_bucket_data_future; |
| quota_manager_impl_->DeleteBucketData( |
| bucket.ToBucketLocator(), {QuotaClientType::kIndexedDatabase}, |
| delete_bucket_data_future.GetCallback()); |
| quota_manager_impl_.reset(); |
| EXPECT_NE(QuotaStatusCode::kOk, delete_bucket_data_future.Get()); |
| } |
| |
| TEST_F(QuotaManagerImplTest, DeleteBucketData_CallbackDeletesQuotaManager) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| }; |
| MockQuotaClient* client = |
| CreateAndRegisterClient(QuotaClientType::kIndexedDatabase); |
| RegisterClientBucketData(client, kData); |
| |
| ASSERT_OK_AND_ASSIGN( |
| BucketInfo bucket, |
| GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName)); |
| |
| base::RunLoop run_loop; |
| QuotaStatusCode delete_bucket_data_result = QuotaStatusCode::kUnknown; |
| quota_manager_impl_->DeleteBucketData( |
| bucket.ToBucketLocator(), {QuotaClientType::kIndexedDatabase}, |
| base::BindLambdaForTesting([&](QuotaStatusCode status_code) { |
| quota_manager_impl_.reset(); |
| delete_bucket_data_result = status_code; |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| |
| EXPECT_EQ(QuotaStatusCode::kOk, delete_bucket_data_result); |
| } |
| |
| TEST_F(QuotaManagerImplTest, DeleteHostData_CallbackDeletesQuotaManager) { |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 1}, |
| }; |
| MockQuotaClient* client = |
| CreateAndRegisterClient(QuotaClientType::kIndexedDatabase); |
| RegisterClientBucketData(client, kData); |
| |
| ASSERT_OK_AND_ASSIGN( |
| BucketInfo bucket, |
| GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName)); |
| |
| auto status = DeleteBucketData(bucket.ToBucketLocator(), |
| {QuotaClientType::kFileSystem}); |
| EXPECT_EQ(status, QuotaStatusCode::kOk); |
| |
| base::RunLoop run_loop; |
| QuotaStatusCode delete_host_data_result = QuotaStatusCode::kUnknown; |
| quota_manager_impl_->DeleteHostData( |
| "foo.com", base::BindLambdaForTesting([&](QuotaStatusCode status_code) { |
| quota_manager_impl_.reset(); |
| delete_host_data_result = status_code; |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| |
| EXPECT_EQ(QuotaStatusCode::kOk, delete_host_data_result); |
| } |
| |
| TEST_F(QuotaManagerImplTest, SimulateStoragePressure_Incognito) { |
| bool callback_ran = false; |
| |
| auto cb = base::BindLambdaForTesting( |
| [&callback_ran](const StorageKey& storage_key) { callback_ran = true; }); |
| |
| SetStoragePressureCallback(cb); |
| |
| ResetQuotaManagerImpl(/*is_incognito=*/true); |
| |
| // This command should return and never execute the callback since it was |
| // setup to be in Incognito. |
| quota_manager_impl_->SimulateStoragePressure( |
| url::Origin::Create(GURL("https://example.com"))); |
| |
| EXPECT_FALSE(callback_ran); |
| } |
| |
| TEST_F(QuotaManagerImplTest, |
| QuotaManagerObserver_NotifiedOnAddedChangedAndDeleted) { |
| auto clock = std::make_unique<base::SimpleTestClock>(); |
| QuotaDatabase::SetClockForTesting(clock.get()); |
| clock->SetNow(base::Time::Now()); |
| |
| SetupQuotaManagerObserver(); |
| |
| BucketInitParams params(ToStorageKey("http://a.com/"), "bucket_a"); |
| |
| // Create bucket. |
| ASSERT_OK_AND_ASSIGN(auto bucket, UpdateOrCreateBucket(params)); |
| RunUntilObserverNotifies(); |
| |
| ASSERT_EQ(observer_notifications_.size(), 1U); |
| ObserverNotification notification = observer_notifications_[0]; |
| ASSERT_EQ(notification.type, kCreateOrUpdate); |
| ASSERT_EQ(notification.bucket_info, bucket); |
| observer_notifications_.clear(); |
| |
| params.persistent = true; |
| params.expiration = clock->Now() + base::Days(1); |
| |
| // Update bucket. |
| ASSERT_OK_AND_ASSIGN(auto updated_bucket, UpdateOrCreateBucket(params)); |
| RunUntilObserverNotifies(); |
| |
| ASSERT_EQ(observer_notifications_.size(), 1U); |
| notification = observer_notifications_[0]; |
| ASSERT_EQ(notification.type, kCreateOrUpdate); |
| EXPECT_EQ(notification.bucket_info, updated_bucket); |
| EXPECT_EQ(notification.bucket_info->persistent, params.persistent); |
| EXPECT_EQ(notification.bucket_info->expiration, params.expiration); |
| observer_notifications_.clear(); |
| |
| // Delete bucket. |
| auto status = |
| DeleteBucketData(bucket.ToBucketLocator(), AllQuotaClientTypes()); |
| RunUntilObserverNotifies(); |
| |
| ASSERT_EQ(status, QuotaStatusCode::kOk); |
| ASSERT_EQ(observer_notifications_.size(), 1U); |
| notification = observer_notifications_[0]; |
| ASSERT_EQ(notification.type, kDelete); |
| EXPECT_EQ(notification.bucket_locator, updated_bucket.ToBucketLocator()); |
| |
| QuotaDatabase::SetClockForTesting(nullptr); |
| } |
| |
| TEST_F(QuotaManagerImplTest, QuotaManagerObserver_NotifiedOnExpired) { |
| auto clock = std::make_unique<base::SimpleTestClock>(); |
| QuotaDatabase::SetClockForTesting(clock.get()); |
| clock->SetNow(base::Time::Now()); |
| |
| SetupQuotaManagerObserver(); |
| |
| BucketInitParams params(ToStorageKey("http://a.com/"), "bucket_a"); |
| params.expiration = clock->Now() + base::Days(5); |
| |
| ASSERT_OK_AND_ASSIGN(auto bucket, UpdateOrCreateBucket(params)); |
| RunUntilObserverNotifies(); |
| |
| ASSERT_EQ(observer_notifications_.size(), 1U); |
| ObserverNotification notification = observer_notifications_[0]; |
| ASSERT_EQ(notification.type, kCreateOrUpdate); |
| ASSERT_EQ(notification.bucket_info, bucket); |
| observer_notifications_.clear(); |
| |
| clock->Advance(base::Days(20)); |
| base::test::TestFuture<QuotaStatusCode> future; |
| quota_manager_impl_->EvictExpiredBuckets(future.GetCallback()); |
| EXPECT_EQ(QuotaStatusCode::kOk, future.Get()); |
| |
| EXPECT_FALSE(GetBucketById(bucket.id).has_value()); |
| ASSERT_EQ(observer_notifications_.size(), 1U); |
| notification = observer_notifications_[0]; |
| ASSERT_EQ(notification.type, kDelete); |
| EXPECT_EQ(notification.bucket_locator, bucket.ToBucketLocator()); |
| |
| QuotaDatabase::SetClockForTesting(nullptr); |
| } |
| |
| TEST_F(QuotaManagerImplTest, StaticReportedQuota_NonBucket) { |
| scoped_feature_list_.InitAndEnableFeature( |
| storage::features::kStaticStorageQuota); |
| |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 80}, |
| {"http://unlimited/", kDefaultBucketName, 10}, |
| }; |
| mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/")); |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(fs_client, kData); |
| |
| const int64_t kPoolSize = GetAvailableDiskSpaceForTest(); |
| const int64_t kPerStorageKeyQuota = kPoolSize / 5; |
| SetQuotaSettings(kPoolSize, kPerStorageKeyQuota, |
| kMustRemainAvailableForSystem); |
| |
| // Static quota is returned for sites without unlimited storage permissions. |
| auto result = GetUsageAndQuotaWithBreakdown(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 80); |
| EXPECT_NE(result.quota, kPerStorageKeyQuota); |
| |
| int64_t initial_reported_quota = result.quota; |
| int64_t additional_usage = initial_reported_quota + 100; |
| ASSERT_OK_AND_ASSIGN( |
| auto foo_bucket, |
| GetBucket(ToStorageKey("http://foo.com/"), kDefaultBucketName)); |
| fs_client->ModifyBucketAndNotify(foo_bucket.ToBucketLocator(), |
| additional_usage); |
| result = GetUsageAndQuotaWithBreakdown(ToStorageKey("http://foo.com/")); |
| // Quota increases with usage. |
| EXPECT_GT(result.quota, initial_reported_quota); |
| EXPECT_GT(result.quota, result.usage); |
| EXPECT_EQ(result.usage, 80 + additional_usage); |
| |
| // Actual quota is returned for sites with unlimited storage permissions. |
| result = GetUsageAndQuotaWithBreakdown(ToStorageKey("http://unlimited/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 10); |
| EXPECT_EQ(result.quota, GetStorageCapacity().available_space + result.usage); |
| } |
| |
| TEST_F(QuotaManagerImplTest, StaticReportedQuota_NonBucket_LowDisk) { |
| scoped_feature_list_.InitAndEnableFeature( |
| storage::features::kStaticStorageQuota); |
| |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", kDefaultBucketName, 80}, |
| }; |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| RegisterClientBucketData(fs_client, kData); |
| |
| const int64_t kPoolSize = QuotaManagerImpl::kGBytes - 1; // Just under 1 GiB. |
| const int64_t kPerStorageKeyQuota = kPoolSize / 5; |
| SetQuotaSettings(kPoolSize, kPerStorageKeyQuota, |
| kMustRemainAvailableForSystem); |
| |
| // Static quota is returned for sites without unlimited storage permissions. |
| auto result = GetUsageAndQuotaWithBreakdown(ToStorageKey("http://foo.com/")); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 80); |
| // Quota == usage + 1 GiB. |
| EXPECT_EQ(result.quota, 80 + QuotaManagerImpl::kGBytes); |
| } |
| |
| TEST_F(QuotaManagerImplTest, StaticReportedQuota_NonBucket_NukeManager) { |
| scoped_feature_list_.InitAndEnableFeature( |
| storage::features::kStaticStorageQuota); |
| |
| base::test::TestFuture<QuotaStatusCode, int64_t, int64_t, |
| blink::mojom::UsageBreakdownPtr> |
| future; |
| quota_manager_impl_->GetUsageAndReportedQuotaWithBreakdown( |
| ToStorageKey("http://foo.com/"), future.GetCallback()); |
| |
| // Nuke before waiting for callback. |
| set_quota_manager_impl(nullptr); |
| |
| auto result = future.Take(); |
| EXPECT_EQ(std::get<0>(result), QuotaStatusCode::kUnknown); |
| EXPECT_EQ(std::get<1>(result), 0); |
| EXPECT_EQ(std::get<2>(result), 0); |
| } |
| |
| TEST_F(QuotaManagerImplTest, StaticReportedQuota_Bucket) { |
| scoped_feature_list_.InitAndEnableFeature( |
| storage::features::kStaticStorageQuota); |
| |
| static const ClientBucketData kData[] = { |
| {"http://foo.com/", "logs", 10}, |
| {"http://foo.com/", "inbox", 60}, |
| {"http://highrequestedquota.com/", "bucket", 0}, |
| {"http://unlimited/", "other", 0}, |
| }; |
| mock_special_storage_policy()->AddUnlimited(GURL("http://unlimited/")); |
| auto storage_capacity = GetStorageCapacity(); |
| |
| MockQuotaClient* fs_client = |
| CreateAndRegisterClient(QuotaClientType::kFileSystem); |
| |
| // Initialize the logs bucket with a non-default quota. |
| BucketInitParams low_quota_params(ToStorageKey("http://foo.com/"), "logs"); |
| low_quota_params.quota = 117; |
| ASSERT_TRUE(UpdateOrCreateBucket(low_quota_params).has_value()); |
| |
| // Initialize a bucket with quota > the max quota. |
| BucketInitParams high_quota_params( |
| ToStorageKey("http://highrequestedquota.com/"), "bucket"); |
| high_quota_params.quota = kDefaultPerStorageKeyQuota + 100; |
| ASSERT_TRUE(UpdateOrCreateBucket(high_quota_params).has_value()); |
| |
| RegisterClientBucketData(fs_client, kData); |
| |
| // Actual bucket quota is returned for bucket with non-default quota. |
| { |
| ASSERT_OK_AND_ASSIGN( |
| BucketInfo bucket, |
| UpdateOrCreateBucket({ToStorageKey("http://foo.com/"), "logs"})); |
| auto result = GetUsageAndQuotaForBucket(bucket); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 10); |
| EXPECT_EQ(result.quota, low_quota_params.quota); |
| } |
| |
| // Static quota is returned for bucket with default quota and limited storage. |
| { |
| ASSERT_OK_AND_ASSIGN( |
| BucketInfo bucket, |
| UpdateOrCreateBucket({ToStorageKey("http://foo.com/"), "inbox"})); |
| auto result = GetUsageAndQuotaForBucket(bucket); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 60); |
| EXPECT_NE(result.quota, kDefaultPerStorageKeyQuota); |
| |
| int64_t initial_reported_quota = result.quota; |
| int64_t additional_usage = initial_reported_quota + 100; |
| fs_client->ModifyBucketAndNotify(bucket.ToBucketLocator(), |
| additional_usage); |
| result = GetUsageAndQuotaForBucket(bucket); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| |
| // Quota increases with usage. |
| EXPECT_GT(result.quota, initial_reported_quota); |
| EXPECT_GT(result.quota, result.usage); |
| EXPECT_EQ(result.usage, 60 + additional_usage); |
| } |
| |
| // Requested quota is returned for bucket with requested quota > the max. |
| { |
| ASSERT_OK_AND_ASSIGN( |
| BucketInfo bucket, |
| UpdateOrCreateBucket( |
| {ToStorageKey("http://highrequestedquota.com/"), "bucket"})); |
| auto result = GetUsageAndQuotaForBucket(bucket); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 0); |
| EXPECT_NE(result.quota, kDefaultPerStorageKeyQuota); |
| EXPECT_EQ(result.quota, high_quota_params.quota); |
| } |
| |
| // Actual quota is returned for bucket with default quota and unlimited |
| // storage. |
| { |
| ASSERT_OK_AND_ASSIGN( |
| BucketInfo bucket, |
| UpdateOrCreateBucket({ToStorageKey("http://unlimited/"), "logs"})); |
| auto result = GetUsageAndQuotaForBucket(bucket); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 0); |
| EXPECT_EQ(result.quota, storage_capacity.available_space); |
| } |
| } |
| |
| TEST_F(QuotaManagerImplTest, StaticReportedQuota_Bucket_LowDisk) { |
| scoped_feature_list_.InitAndEnableFeature( |
| storage::features::kStaticStorageQuota); |
| |
| const int64_t kPoolSize = |
| 3 * QuotaManagerImpl::kGBytes + 1; // Just over 3 GiB. |
| const int64_t kPerStorageKeyQuota = kPoolSize / 5; |
| SetQuotaSettings(kPoolSize, kPerStorageKeyQuota, 0); |
| StorageKey storage_key = ToStorageKey("http://example.com/"); |
| std::string bucket_name = "bucket"; |
| |
| ASSERT_OK_AND_ASSIGN(auto bucket, |
| UpdateOrCreateBucket({storage_key, bucket_name})); |
| |
| auto result = GetUsageAndQuotaForBucket(bucket); |
| EXPECT_EQ(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 0); |
| EXPECT_NE(result.quota, 4 * QuotaManagerImpl::kGBytes); |
| } |
| |
| TEST_F(QuotaManagerImplTest, StaticReportedQuota_Bucket_BucketNotFound) { |
| scoped_feature_list_.InitAndEnableFeature( |
| storage::features::kStaticStorageQuota); |
| |
| StorageKey storage_key = ToStorageKey("http://example.com/"); |
| std::string bucket_name = "bucket"; |
| ASSERT_OK_AND_ASSIGN(auto bucket, |
| UpdateOrCreateBucket({storage_key, bucket_name})); |
| |
| FindAndDeleteBucketData(storage_key, bucket_name); |
| |
| auto result = GetUsageAndQuotaForBucket(bucket); |
| EXPECT_NE(result.status, QuotaStatusCode::kOk); |
| EXPECT_EQ(result.usage, 0); |
| EXPECT_EQ(result.quota, 0); |
| } |
| |
| } // namespace storage |