|  | // Copyright 2014 The Chromium Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include <stdint.h> | 
|  |  | 
|  | #include <map> | 
|  | #include <set> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/test/scoped_task_environment.h" | 
|  | #include "content/browser/appcache/appcache_quota_client.h" | 
|  | #include "content/browser/appcache/mock_appcache_service.h" | 
|  | #include "net/base/net_errors.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | using blink::mojom::StorageType; | 
|  |  | 
|  | // Declared to shorten the line lengths. | 
|  | static const StorageType kTemp = StorageType::kTemporary; | 
|  | static const StorageType kPerm = StorageType::kPersistent; | 
|  |  | 
|  | // Base class for our test fixtures. | 
|  | class AppCacheQuotaClientTest : public testing::Test { | 
|  | public: | 
|  | const url::Origin kOriginA; | 
|  | const url::Origin kOriginB; | 
|  | const url::Origin kOriginOther; | 
|  |  | 
|  | AppCacheQuotaClientTest() | 
|  | : kOriginA(url::Origin::Create(GURL("http://host"))), | 
|  | kOriginB(url::Origin::Create(GURL("http://host:8000"))), | 
|  | kOriginOther(url::Origin::Create(GURL("http://other"))), | 
|  | usage_(0), | 
|  | delete_status_(blink::mojom::QuotaStatusCode::kUnknown), | 
|  | num_get_origin_usage_completions_(0), | 
|  | num_get_origins_completions_(0), | 
|  | num_delete_origins_completions_(0), | 
|  | weak_factory_(this) {} | 
|  |  | 
|  | int64_t GetOriginUsage(storage::QuotaClient* client, | 
|  | const url::Origin& origin, | 
|  | StorageType type) { | 
|  | usage_ = -1; | 
|  | AsyncGetOriginUsage(client, origin, type); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | return usage_; | 
|  | } | 
|  |  | 
|  | const std::set<url::Origin>& GetOriginsForType(storage::QuotaClient* client, | 
|  | StorageType type) { | 
|  | origins_.clear(); | 
|  | AsyncGetOriginsForType(client, type); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | return origins_; | 
|  | } | 
|  |  | 
|  | const std::set<url::Origin>& GetOriginsForHost(storage::QuotaClient* client, | 
|  | StorageType type, | 
|  | const std::string& host) { | 
|  | origins_.clear(); | 
|  | AsyncGetOriginsForHost(client, type, host); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | return origins_; | 
|  | } | 
|  |  | 
|  | blink::mojom::QuotaStatusCode DeleteOriginData(storage::QuotaClient* client, | 
|  | StorageType type, | 
|  | const url::Origin& origin) { | 
|  | delete_status_ = blink::mojom::QuotaStatusCode::kUnknown; | 
|  | AsyncDeleteOriginData(client, type, origin); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | return delete_status_; | 
|  | } | 
|  |  | 
|  | void AsyncGetOriginUsage(storage::QuotaClient* client, | 
|  | const url::Origin& origin, | 
|  | StorageType type) { | 
|  | client->GetOriginUsage( | 
|  | origin, type, | 
|  | base::BindOnce(&AppCacheQuotaClientTest::OnGetOriginUsageComplete, | 
|  | weak_factory_.GetWeakPtr())); | 
|  | } | 
|  |  | 
|  | void AsyncGetOriginsForType(storage::QuotaClient* client, StorageType type) { | 
|  | client->GetOriginsForType( | 
|  | type, base::BindOnce(&AppCacheQuotaClientTest::OnGetOriginsComplete, | 
|  | weak_factory_.GetWeakPtr())); | 
|  | } | 
|  |  | 
|  | void AsyncGetOriginsForHost(storage::QuotaClient* client, | 
|  | StorageType type, | 
|  | const std::string& host) { | 
|  | client->GetOriginsForHost( | 
|  | type, host, | 
|  | base::BindOnce(&AppCacheQuotaClientTest::OnGetOriginsComplete, | 
|  | weak_factory_.GetWeakPtr())); | 
|  | } | 
|  |  | 
|  | void AsyncDeleteOriginData(storage::QuotaClient* client, | 
|  | StorageType type, | 
|  | const url::Origin& origin) { | 
|  | client->DeleteOriginData( | 
|  | origin, type, | 
|  | base::BindOnce(&AppCacheQuotaClientTest::OnDeleteOriginDataComplete, | 
|  | weak_factory_.GetWeakPtr())); | 
|  | } | 
|  |  | 
|  | void SetUsageMapEntry(const url::Origin& origin, int64_t usage) { | 
|  | mock_service_.storage()->usage_map_[origin] = usage; | 
|  | } | 
|  |  | 
|  | AppCacheQuotaClient* CreateClient() { | 
|  | return new AppCacheQuotaClient(&mock_service_); | 
|  | } | 
|  |  | 
|  | void Call_NotifyAppCacheReady(AppCacheQuotaClient* client) { | 
|  | client->NotifyAppCacheReady(); | 
|  | } | 
|  |  | 
|  | void Call_NotifyAppCacheDestroyed(AppCacheQuotaClient* client) { | 
|  | client->NotifyAppCacheDestroyed(); | 
|  | } | 
|  |  | 
|  | void Call_OnQuotaManagerDestroyed(AppCacheQuotaClient* client) { | 
|  | client->OnQuotaManagerDestroyed(); | 
|  | } | 
|  |  | 
|  | protected: | 
|  | void OnGetOriginUsageComplete(int64_t usage) { | 
|  | ++num_get_origin_usage_completions_; | 
|  | usage_ = usage; | 
|  | } | 
|  |  | 
|  | void OnGetOriginsComplete(const std::set<url::Origin>& origins) { | 
|  | ++num_get_origins_completions_; | 
|  | origins_ = origins; | 
|  | } | 
|  |  | 
|  | void OnDeleteOriginDataComplete(blink::mojom::QuotaStatusCode status) { | 
|  | ++num_delete_origins_completions_; | 
|  | delete_status_ = status; | 
|  | } | 
|  |  | 
|  | base::test::ScopedTaskEnvironment scoped_task_environment_; | 
|  | int64_t usage_; | 
|  | std::set<url::Origin> origins_; | 
|  | blink::mojom::QuotaStatusCode delete_status_; | 
|  | int num_get_origin_usage_completions_; | 
|  | int num_get_origins_completions_; | 
|  | int num_delete_origins_completions_; | 
|  | MockAppCacheService mock_service_; | 
|  | base::WeakPtrFactory<AppCacheQuotaClientTest> weak_factory_; | 
|  | }; | 
|  |  | 
|  |  | 
|  | TEST_F(AppCacheQuotaClientTest, BasicCreateDestroy) { | 
|  | AppCacheQuotaClient* client = CreateClient(); | 
|  | Call_NotifyAppCacheReady(client); | 
|  | Call_OnQuotaManagerDestroyed(client); | 
|  | Call_NotifyAppCacheDestroyed(client); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheQuotaClientTest, EmptyService) { | 
|  | AppCacheQuotaClient* client = CreateClient(); | 
|  | Call_NotifyAppCacheReady(client); | 
|  |  | 
|  | EXPECT_EQ(0, GetOriginUsage(client, kOriginA, kTemp)); | 
|  | EXPECT_EQ(0, GetOriginUsage(client, kOriginA, kPerm)); | 
|  | EXPECT_TRUE(GetOriginsForType(client, kTemp).empty()); | 
|  | EXPECT_TRUE(GetOriginsForType(client, kPerm).empty()); | 
|  | EXPECT_TRUE(GetOriginsForHost(client, kTemp, kOriginA.host()).empty()); | 
|  | EXPECT_TRUE(GetOriginsForHost(client, kPerm, kOriginA.host()).empty()); | 
|  | EXPECT_EQ(blink::mojom::QuotaStatusCode::kOk, | 
|  | DeleteOriginData(client, kTemp, kOriginA)); | 
|  | EXPECT_EQ(blink::mojom::QuotaStatusCode::kOk, | 
|  | DeleteOriginData(client, kPerm, kOriginA)); | 
|  |  | 
|  | Call_NotifyAppCacheDestroyed(client); | 
|  | Call_OnQuotaManagerDestroyed(client); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheQuotaClientTest, NoService) { | 
|  | AppCacheQuotaClient* client = CreateClient(); | 
|  | Call_NotifyAppCacheReady(client); | 
|  | Call_NotifyAppCacheDestroyed(client); | 
|  |  | 
|  | EXPECT_EQ(0, GetOriginUsage(client, kOriginA, kTemp)); | 
|  | EXPECT_EQ(0, GetOriginUsage(client, kOriginA, kPerm)); | 
|  | EXPECT_TRUE(GetOriginsForType(client, kTemp).empty()); | 
|  | EXPECT_TRUE(GetOriginsForType(client, kPerm).empty()); | 
|  | EXPECT_TRUE(GetOriginsForHost(client, kTemp, kOriginA.host()).empty()); | 
|  | EXPECT_TRUE(GetOriginsForHost(client, kPerm, kOriginA.host()).empty()); | 
|  | EXPECT_EQ(blink::mojom::QuotaStatusCode::kErrorAbort, | 
|  | DeleteOriginData(client, kTemp, kOriginA)); | 
|  | EXPECT_EQ(blink::mojom::QuotaStatusCode::kErrorAbort, | 
|  | DeleteOriginData(client, kPerm, kOriginA)); | 
|  |  | 
|  | Call_OnQuotaManagerDestroyed(client); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheQuotaClientTest, GetOriginUsage) { | 
|  | AppCacheQuotaClient* client = CreateClient(); | 
|  | Call_NotifyAppCacheReady(client); | 
|  |  | 
|  | SetUsageMapEntry(kOriginA, 1000); | 
|  | EXPECT_EQ(1000, GetOriginUsage(client, kOriginA, kTemp)); | 
|  | EXPECT_EQ(0, GetOriginUsage(client, kOriginA, kPerm)); | 
|  |  | 
|  | Call_NotifyAppCacheDestroyed(client); | 
|  | Call_OnQuotaManagerDestroyed(client); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheQuotaClientTest, GetOriginsForHost) { | 
|  | AppCacheQuotaClient* client = CreateClient(); | 
|  | Call_NotifyAppCacheReady(client); | 
|  |  | 
|  | EXPECT_EQ(kOriginA.host(), kOriginB.host()); | 
|  | EXPECT_NE(kOriginA.host(), kOriginOther.host()); | 
|  |  | 
|  | std::set<url::Origin> origins = | 
|  | GetOriginsForHost(client, kTemp, kOriginA.host()); | 
|  | EXPECT_TRUE(origins.empty()); | 
|  |  | 
|  | SetUsageMapEntry(kOriginA, 1000); | 
|  | SetUsageMapEntry(kOriginB, 10); | 
|  | SetUsageMapEntry(kOriginOther, 500); | 
|  |  | 
|  | origins = GetOriginsForHost(client, kTemp, kOriginA.host()); | 
|  | EXPECT_EQ(2ul, origins.size()); | 
|  | EXPECT_TRUE(origins.find(kOriginA) != origins.end()); | 
|  | EXPECT_TRUE(origins.find(kOriginB) != origins.end()); | 
|  |  | 
|  | origins = GetOriginsForHost(client, kTemp, kOriginOther.host()); | 
|  | EXPECT_EQ(1ul, origins.size()); | 
|  | EXPECT_TRUE(origins.find(kOriginOther) != origins.end()); | 
|  |  | 
|  | origins = GetOriginsForHost(client, kPerm, kOriginA.host()); | 
|  | EXPECT_TRUE(origins.empty()); | 
|  |  | 
|  | Call_NotifyAppCacheDestroyed(client); | 
|  | Call_OnQuotaManagerDestroyed(client); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheQuotaClientTest, GetOriginsForType) { | 
|  | AppCacheQuotaClient* client = CreateClient(); | 
|  | Call_NotifyAppCacheReady(client); | 
|  |  | 
|  | EXPECT_TRUE(GetOriginsForType(client, kTemp).empty()); | 
|  | EXPECT_TRUE(GetOriginsForType(client, kPerm).empty()); | 
|  |  | 
|  | SetUsageMapEntry(kOriginA, 1000); | 
|  | SetUsageMapEntry(kOriginB, 10); | 
|  |  | 
|  | std::set<url::Origin> origins = GetOriginsForType(client, kTemp); | 
|  | EXPECT_EQ(2ul, origins.size()); | 
|  | EXPECT_TRUE(origins.find(kOriginA) != origins.end()); | 
|  | EXPECT_TRUE(origins.find(kOriginB) != origins.end()); | 
|  |  | 
|  | EXPECT_TRUE(GetOriginsForType(client, kPerm).empty()); | 
|  |  | 
|  | Call_NotifyAppCacheDestroyed(client); | 
|  | Call_OnQuotaManagerDestroyed(client); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheQuotaClientTest, DeleteOriginData) { | 
|  | AppCacheQuotaClient* client = CreateClient(); | 
|  | Call_NotifyAppCacheReady(client); | 
|  |  | 
|  | // Perm deletions are short circuited in the Client and | 
|  | // should not reach the AppCacheServiceImpl. | 
|  | EXPECT_EQ(blink::mojom::QuotaStatusCode::kOk, | 
|  | DeleteOriginData(client, kPerm, kOriginA)); | 
|  | EXPECT_EQ(0, mock_service_.delete_called_count()); | 
|  |  | 
|  | EXPECT_EQ(blink::mojom::QuotaStatusCode::kOk, | 
|  | DeleteOriginData(client, kTemp, kOriginA)); | 
|  | EXPECT_EQ(1, mock_service_.delete_called_count()); | 
|  |  | 
|  | mock_service_.set_mock_delete_appcaches_for_origin_result( | 
|  | net::ERR_ABORTED); | 
|  | EXPECT_EQ(blink::mojom::QuotaStatusCode::kErrorAbort, | 
|  | DeleteOriginData(client, kTemp, kOriginA)); | 
|  | EXPECT_EQ(2, mock_service_.delete_called_count()); | 
|  |  | 
|  | Call_OnQuotaManagerDestroyed(client); | 
|  | Call_NotifyAppCacheDestroyed(client); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheQuotaClientTest, PendingRequests) { | 
|  | AppCacheQuotaClient* client = CreateClient(); | 
|  |  | 
|  | SetUsageMapEntry(kOriginA, 1000); | 
|  | SetUsageMapEntry(kOriginB, 10); | 
|  | SetUsageMapEntry(kOriginOther, 500); | 
|  |  | 
|  | // Queue up some reqeusts. | 
|  | AsyncGetOriginUsage(client, kOriginA, kPerm); | 
|  | AsyncGetOriginUsage(client, kOriginB, kTemp); | 
|  | AsyncGetOriginsForType(client, kPerm); | 
|  | AsyncGetOriginsForType(client, kTemp); | 
|  | AsyncGetOriginsForHost(client, kTemp, kOriginA.host()); | 
|  | AsyncGetOriginsForHost(client, kTemp, kOriginOther.host()); | 
|  | AsyncDeleteOriginData(client, kTemp, kOriginA); | 
|  | AsyncDeleteOriginData(client, kPerm, kOriginA); | 
|  | AsyncDeleteOriginData(client, kTemp, kOriginB); | 
|  |  | 
|  | EXPECT_EQ(0, num_get_origin_usage_completions_); | 
|  | EXPECT_EQ(0, num_get_origins_completions_); | 
|  | EXPECT_EQ(0, num_delete_origins_completions_); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(0, num_get_origin_usage_completions_); | 
|  | EXPECT_EQ(0, num_get_origins_completions_); | 
|  | EXPECT_EQ(0, num_delete_origins_completions_); | 
|  |  | 
|  | // Pending requests should get serviced when the appcache is ready. | 
|  | Call_NotifyAppCacheReady(client); | 
|  | EXPECT_EQ(2, num_get_origin_usage_completions_); | 
|  | EXPECT_EQ(4, num_get_origins_completions_); | 
|  | EXPECT_EQ(0, num_delete_origins_completions_); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(3, num_delete_origins_completions_);  // deletes are really async | 
|  |  | 
|  | // They should be serviced in order requested. | 
|  | EXPECT_EQ(10, usage_); | 
|  | EXPECT_EQ(1ul, origins_.size()); | 
|  | EXPECT_TRUE(origins_.find(kOriginOther) != origins_.end()); | 
|  |  | 
|  | Call_NotifyAppCacheDestroyed(client); | 
|  | Call_OnQuotaManagerDestroyed(client); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheQuotaClientTest, DestroyServiceWithPending) { | 
|  | AppCacheQuotaClient* client = CreateClient(); | 
|  |  | 
|  | SetUsageMapEntry(kOriginA, 1000); | 
|  | SetUsageMapEntry(kOriginB, 10); | 
|  | SetUsageMapEntry(kOriginOther, 500); | 
|  |  | 
|  | // Queue up some reqeusts prior to being ready. | 
|  | AsyncGetOriginUsage(client, kOriginA, kPerm); | 
|  | AsyncGetOriginUsage(client, kOriginB, kTemp); | 
|  | AsyncGetOriginsForType(client, kPerm); | 
|  | AsyncGetOriginsForType(client, kTemp); | 
|  | AsyncGetOriginsForHost(client, kTemp, kOriginA.host()); | 
|  | AsyncGetOriginsForHost(client, kTemp, kOriginOther.host()); | 
|  | AsyncDeleteOriginData(client, kTemp, kOriginA); | 
|  | AsyncDeleteOriginData(client, kPerm, kOriginA); | 
|  | AsyncDeleteOriginData(client, kTemp, kOriginB); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(0, num_get_origin_usage_completions_); | 
|  | EXPECT_EQ(0, num_get_origins_completions_); | 
|  | EXPECT_EQ(0, num_delete_origins_completions_); | 
|  |  | 
|  | // Kill the service. | 
|  | Call_NotifyAppCacheDestroyed(client); | 
|  |  | 
|  | // All should have been aborted and called completion. | 
|  | EXPECT_EQ(2, num_get_origin_usage_completions_); | 
|  | EXPECT_EQ(4, num_get_origins_completions_); | 
|  | EXPECT_EQ(3, num_delete_origins_completions_); | 
|  | EXPECT_EQ(0, usage_); | 
|  | EXPECT_TRUE(origins_.empty()); | 
|  | EXPECT_EQ(blink::mojom::QuotaStatusCode::kErrorAbort, delete_status_); | 
|  |  | 
|  | Call_OnQuotaManagerDestroyed(client); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheQuotaClientTest, DestroyQuotaManagerWithPending) { | 
|  | AppCacheQuotaClient* client = CreateClient(); | 
|  |  | 
|  | SetUsageMapEntry(kOriginA, 1000); | 
|  | SetUsageMapEntry(kOriginB, 10); | 
|  | SetUsageMapEntry(kOriginOther, 500); | 
|  |  | 
|  | // Queue up some reqeusts prior to being ready. | 
|  | AsyncGetOriginUsage(client, kOriginA, kPerm); | 
|  | AsyncGetOriginUsage(client, kOriginB, kTemp); | 
|  | AsyncGetOriginsForType(client, kPerm); | 
|  | AsyncGetOriginsForType(client, kTemp); | 
|  | AsyncGetOriginsForHost(client, kTemp, kOriginA.host()); | 
|  | AsyncGetOriginsForHost(client, kTemp, kOriginOther.host()); | 
|  | AsyncDeleteOriginData(client, kTemp, kOriginA); | 
|  | AsyncDeleteOriginData(client, kPerm, kOriginA); | 
|  | AsyncDeleteOriginData(client, kTemp, kOriginB); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(0, num_get_origin_usage_completions_); | 
|  | EXPECT_EQ(0, num_get_origins_completions_); | 
|  | EXPECT_EQ(0, num_delete_origins_completions_); | 
|  |  | 
|  | // Kill the quota manager. | 
|  | Call_OnQuotaManagerDestroyed(client); | 
|  | Call_NotifyAppCacheReady(client); | 
|  |  | 
|  | // Callbacks should be deleted and not called. | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(0, num_get_origin_usage_completions_); | 
|  | EXPECT_EQ(0, num_get_origins_completions_); | 
|  | EXPECT_EQ(0, num_delete_origins_completions_); | 
|  |  | 
|  | Call_NotifyAppCacheDestroyed(client); | 
|  | } | 
|  |  | 
|  | TEST_F(AppCacheQuotaClientTest, DestroyWithDeleteInProgress) { | 
|  | AppCacheQuotaClient* client = CreateClient(); | 
|  | Call_NotifyAppCacheReady(client); | 
|  |  | 
|  | // Start an async delete. | 
|  | AsyncDeleteOriginData(client, kTemp, kOriginB); | 
|  | EXPECT_EQ(0, num_delete_origins_completions_); | 
|  |  | 
|  | // Kill the service. | 
|  | Call_NotifyAppCacheDestroyed(client); | 
|  |  | 
|  | // Should have been aborted. | 
|  | EXPECT_EQ(1, num_delete_origins_completions_); | 
|  | EXPECT_EQ(blink::mojom::QuotaStatusCode::kErrorAbort, delete_status_); | 
|  |  | 
|  | // A real completion callback from the service should | 
|  | // be dropped if it comes in after NotifyAppCacheDestroyed. | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_EQ(1, num_delete_origins_completions_); | 
|  | EXPECT_EQ(blink::mojom::QuotaStatusCode::kErrorAbort, delete_status_); | 
|  |  | 
|  | Call_OnQuotaManagerDestroyed(client); | 
|  | } | 
|  |  | 
|  | }  // namespace content |