// 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 "content/browser/appcache/appcache.h"
#include "content/browser/appcache/appcache_host.h"
#include "content/browser/appcache/mock_appcache_service.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {

namespace {

class MockAppCacheFrontend : public AppCacheFrontend {
 public:
  void OnCacheSelected(int host_id, const AppCacheInfo& info) override {}
  void OnStatusChanged(const std::vector<int>& host_ids,
                       AppCacheStatus status) override {}
  void OnEventRaised(const std::vector<int>& host_ids,
                     AppCacheEventID event_id) override {}
  void OnProgressEventRaised(const std::vector<int>& host_ids,
                             const GURL& url,
                             int num_total,
                             int num_complete) override {}
  void OnErrorEventRaised(const std::vector<int>& host_ids,
                          const AppCacheErrorDetails& details) override {}
  void OnLogMessage(int host_id,
                    AppCacheLogLevel log_level,
                    const std::string& message) override {}
  void OnContentBlocked(int host_id, const GURL& manifest_url) override {}
};

}  // namespace

class AppCacheTest : public testing::Test {
};

TEST(AppCacheTest, CleanupUnusedCache) {
  MockAppCacheService service;
  MockAppCacheFrontend frontend;
  scoped_refptr<AppCache> cache(new AppCache(service.storage(), 111));
  cache->set_complete(true);
  scoped_refptr<AppCacheGroup> group(
      new AppCacheGroup(service.storage(), GURL("http://blah/manifest"), 111));
  group->AddCache(cache.get());

  AppCacheHost host1(1, &frontend, &service);
  AppCacheHost host2(2, &frontend, &service);

  host1.AssociateCompleteCache(cache.get());
  host2.AssociateCompleteCache(cache.get());

  host1.AssociateNoCache(GURL());
  host2.AssociateNoCache(GURL());
}

TEST(AppCacheTest, AddModifyRemoveEntry) {
  MockAppCacheService service;
  scoped_refptr<AppCache> cache(new AppCache(service.storage(), 111));

  EXPECT_TRUE(cache->entries().empty());
  EXPECT_EQ(0L, cache->cache_size());

  const GURL kFooUrl("http://foo.com");
  const int64 kFooResponseId = 1;
  const int64 kFooSize = 100;
  AppCacheEntry entry1(AppCacheEntry::MASTER, kFooResponseId, kFooSize);
  cache->AddEntry(kFooUrl, entry1);
  EXPECT_EQ(entry1.types(), cache->GetEntry(kFooUrl)->types());
  EXPECT_EQ(1UL, cache->entries().size());
  EXPECT_EQ(kFooSize, cache->cache_size());

  const GURL kBarUrl("http://bar.com");
  const int64 kBarResponseId = 2;
  const int64 kBarSize = 200;
  AppCacheEntry entry2(AppCacheEntry::FALLBACK, kBarResponseId, kBarSize);
  EXPECT_TRUE(cache->AddOrModifyEntry(kBarUrl, entry2));
  EXPECT_EQ(entry2.types(), cache->GetEntry(kBarUrl)->types());
  EXPECT_EQ(2UL, cache->entries().size());
  EXPECT_EQ(kFooSize + kBarSize, cache->cache_size());

  // Expected to return false when an existing entry is modified.
  AppCacheEntry entry3(AppCacheEntry::EXPLICIT);
  EXPECT_FALSE(cache->AddOrModifyEntry(kFooUrl, entry3));
  EXPECT_EQ((AppCacheEntry::MASTER | AppCacheEntry::EXPLICIT),
            cache->GetEntry(kFooUrl)->types());
  // Only the type should be modified.
  EXPECT_EQ(kFooResponseId, cache->GetEntry(kFooUrl)->response_id());
  EXPECT_EQ(kFooSize, cache->GetEntry(kFooUrl)->response_size());
  EXPECT_EQ(kFooSize + kBarSize, cache->cache_size());

  EXPECT_EQ(entry2.types(), cache->GetEntry(kBarUrl)->types());  // unchanged

  cache->RemoveEntry(kBarUrl);
  EXPECT_EQ(kFooSize, cache->cache_size());
  cache->RemoveEntry(kFooUrl);
  EXPECT_EQ(0L, cache->cache_size());
  EXPECT_TRUE(cache->entries().empty());
}

TEST(AppCacheTest, InitializeWithManifest) {
  MockAppCacheService service;

  scoped_refptr<AppCache> cache(new AppCache(service.storage(), 1234));
  EXPECT_TRUE(cache->fallback_namespaces_.empty());
  EXPECT_TRUE(cache->online_whitelist_namespaces_.empty());
  EXPECT_FALSE(cache->online_whitelist_all_);

  AppCacheManifest manifest;
  manifest.explicit_urls.insert("http://one.com");
  manifest.explicit_urls.insert("http://two.com");
  manifest.fallback_namespaces.push_back(
      AppCacheNamespace(APPCACHE_FALLBACK_NAMESPACE, GURL("http://fb1.com"),
                GURL("http://fbone.com"), true));
  manifest.online_whitelist_namespaces.push_back(
      AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, GURL("http://w1.com"),
          GURL(), false));
  manifest.online_whitelist_namespaces.push_back(
      AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, GURL("http://w2.com"),
          GURL(), false));
  manifest.online_whitelist_all = true;

  cache->InitializeWithManifest(&manifest);
  const std::vector<AppCacheNamespace>& fallbacks =
      cache->fallback_namespaces_;
  size_t expected = 1;
  EXPECT_EQ(expected, fallbacks.size());
  EXPECT_EQ(GURL("http://fb1.com"), fallbacks[0].namespace_url);
  EXPECT_EQ(GURL("http://fbone.com"), fallbacks[0].target_url);
  EXPECT_TRUE(fallbacks[0].is_pattern);
  const AppCacheNamespaceVector& whitelist =
      cache->online_whitelist_namespaces_;
  expected = 2;
  EXPECT_EQ(expected, whitelist.size());
  EXPECT_EQ(GURL("http://w1.com"), whitelist[0].namespace_url);
  EXPECT_EQ(GURL("http://w2.com"), whitelist[1].namespace_url);
  EXPECT_TRUE(cache->online_whitelist_all_);

  // Ensure collections in manifest were taken over by the cache rather than
  // copied.
  EXPECT_TRUE(manifest.fallback_namespaces.empty());
  EXPECT_TRUE(manifest.online_whitelist_namespaces.empty());
}

TEST(AppCacheTest, FindResponseForRequest) {
  MockAppCacheService service;

  const GURL kOnlineNamespaceUrl("http://blah/online_namespace");
  const GURL kFallbackEntryUrl1("http://blah/fallback_entry1");
  const GURL kFallbackNamespaceUrl1("http://blah/fallback_namespace/");
  const GURL kFallbackEntryUrl2("http://blah/fallback_entry2");
  const GURL kFallbackNamespaceUrl2("http://blah/fallback_namespace/longer");
  const GURL kManifestUrl("http://blah/manifest");
  const GURL kForeignExplicitEntryUrl("http://blah/foreign");
  const GURL kInOnlineNamespaceUrl(
      "http://blah/online_namespace/network");
  const GURL kExplicitInOnlineNamespaceUrl(
      "http://blah/online_namespace/explicit");
  const GURL kFallbackTestUrl1("http://blah/fallback_namespace/1");
  const GURL kFallbackTestUrl2("http://blah/fallback_namespace/longer2");
  const GURL kInterceptNamespace("http://blah/intercept_namespace/");
  const GURL kInterceptNamespaceWithinFallback(
      "http://blah/fallback_namespace/intercept_namespace/");
  const GURL kInterceptNamespaceEntry("http://blah/intercept_entry");
  const GURL kOnlineNamespaceWithinOtherNamespaces(
      "http://blah/fallback_namespace/intercept_namespace/1/online");

  const int64 kFallbackResponseId1 = 1;
  const int64 kFallbackResponseId2 = 2;
  const int64 kManifestResponseId = 3;
  const int64 kForeignExplicitResponseId = 4;
  const int64 kExplicitInOnlineNamespaceResponseId = 5;
  const int64 kInterceptResponseId = 6;

  AppCacheManifest manifest;
  manifest.online_whitelist_namespaces.push_back(
      AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, kOnlineNamespaceUrl,
          GURL(), false));
  manifest.online_whitelist_namespaces.push_back(
      AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE,
          kOnlineNamespaceWithinOtherNamespaces, GURL(), false));
  manifest.fallback_namespaces.push_back(
      AppCacheNamespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackNamespaceUrl1,
          kFallbackEntryUrl1, false));
  manifest.fallback_namespaces.push_back(
      AppCacheNamespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackNamespaceUrl2,
          kFallbackEntryUrl2, false));
  manifest.intercept_namespaces.push_back(
      AppCacheNamespace(APPCACHE_INTERCEPT_NAMESPACE, kInterceptNamespace,
          kInterceptNamespaceEntry, false));
  manifest.intercept_namespaces.push_back(
      AppCacheNamespace(APPCACHE_INTERCEPT_NAMESPACE,
          kInterceptNamespaceWithinFallback, kInterceptNamespaceEntry, false));

  // Create a cache with some namespaces and entries.
  scoped_refptr<AppCache> cache(new AppCache(service.storage(), 1234));
  cache->InitializeWithManifest(&manifest);
  cache->AddEntry(
      kFallbackEntryUrl1,
      AppCacheEntry(AppCacheEntry::FALLBACK, kFallbackResponseId1));
  cache->AddEntry(
      kFallbackEntryUrl2,
      AppCacheEntry(AppCacheEntry::FALLBACK, kFallbackResponseId2));
  cache->AddEntry(
      kManifestUrl,
      AppCacheEntry(AppCacheEntry::MANIFEST, kManifestResponseId));
  cache->AddEntry(
      kForeignExplicitEntryUrl,
      AppCacheEntry(AppCacheEntry::EXPLICIT | AppCacheEntry::FOREIGN,
                    kForeignExplicitResponseId));
  cache->AddEntry(
      kExplicitInOnlineNamespaceUrl,
      AppCacheEntry(AppCacheEntry::EXPLICIT,
                    kExplicitInOnlineNamespaceResponseId));
  cache->AddEntry(
      kInterceptNamespaceEntry,
      AppCacheEntry(AppCacheEntry::INTERCEPT, kInterceptResponseId));
  cache->set_complete(true);

  // See that we get expected results from FindResponseForRequest

  bool found = false;
  AppCacheEntry entry;
  AppCacheEntry fallback_entry;
  GURL intercept_namespace;
  GURL fallback_namespace;
  bool network_namespace = false;

  found = cache->FindResponseForRequest(GURL("http://blah/miss"),
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_FALSE(found);

  found = cache->FindResponseForRequest(kForeignExplicitEntryUrl,
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_TRUE(found);
  EXPECT_EQ(kForeignExplicitResponseId, entry.response_id());
  EXPECT_FALSE(fallback_entry.has_response_id());
  EXPECT_FALSE(network_namespace);

  entry = AppCacheEntry();  // reset

  found = cache->FindResponseForRequest(kManifestUrl,
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_TRUE(found);
  EXPECT_EQ(kManifestResponseId, entry.response_id());
  EXPECT_FALSE(fallback_entry.has_response_id());
  EXPECT_FALSE(network_namespace);

  entry = AppCacheEntry();  // reset

  found = cache->FindResponseForRequest(kInOnlineNamespaceUrl,
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_TRUE(found);
  EXPECT_FALSE(entry.has_response_id());
  EXPECT_FALSE(fallback_entry.has_response_id());
  EXPECT_TRUE(network_namespace);

  network_namespace = false;  // reset

  found = cache->FindResponseForRequest(kExplicitInOnlineNamespaceUrl,
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_TRUE(found);
  EXPECT_EQ(kExplicitInOnlineNamespaceResponseId, entry.response_id());
  EXPECT_FALSE(fallback_entry.has_response_id());
  EXPECT_FALSE(network_namespace);

  entry = AppCacheEntry();  // reset

  found = cache->FindResponseForRequest(kFallbackTestUrl1,
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_TRUE(found);
  EXPECT_FALSE(entry.has_response_id());
  EXPECT_EQ(kFallbackResponseId1, fallback_entry.response_id());
  EXPECT_EQ(kFallbackEntryUrl1,
            cache->GetFallbackEntryUrl(fallback_namespace));
  EXPECT_FALSE(network_namespace);

  fallback_entry = AppCacheEntry();  // reset

  found = cache->FindResponseForRequest(kFallbackTestUrl2,
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_TRUE(found);
  EXPECT_FALSE(entry.has_response_id());
  EXPECT_EQ(kFallbackResponseId2, fallback_entry.response_id());
  EXPECT_EQ(kFallbackEntryUrl2,
            cache->GetFallbackEntryUrl(fallback_namespace));
  EXPECT_FALSE(network_namespace);

  fallback_entry = AppCacheEntry();  // reset

  found = cache->FindResponseForRequest(kOnlineNamespaceWithinOtherNamespaces,
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_TRUE(found);
  EXPECT_FALSE(entry.has_response_id());
  EXPECT_FALSE(fallback_entry.has_response_id());
  EXPECT_TRUE(network_namespace);

  fallback_entry = AppCacheEntry();  // reset

  found = cache->FindResponseForRequest(
      kOnlineNamespaceWithinOtherNamespaces.Resolve("online_resource"),
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_TRUE(found);
  EXPECT_FALSE(entry.has_response_id());
  EXPECT_FALSE(fallback_entry.has_response_id());
  EXPECT_TRUE(network_namespace);

  fallback_namespace = GURL();

  found = cache->FindResponseForRequest(
      kInterceptNamespace.Resolve("intercept_me"),
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_TRUE(found);
  EXPECT_EQ(kInterceptResponseId, entry.response_id());
  EXPECT_EQ(kInterceptNamespaceEntry,
            cache->GetInterceptEntryUrl(intercept_namespace));
  EXPECT_FALSE(fallback_entry.has_response_id());
  EXPECT_TRUE(fallback_namespace.is_empty());
  EXPECT_FALSE(network_namespace);

  entry = AppCacheEntry();  // reset

  found = cache->FindResponseForRequest(
      kInterceptNamespaceWithinFallback.Resolve("intercept_me"),
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_TRUE(found);
  EXPECT_EQ(kInterceptResponseId, entry.response_id());
  EXPECT_EQ(kInterceptNamespaceEntry,
            cache->GetInterceptEntryUrl(intercept_namespace));
  EXPECT_FALSE(fallback_entry.has_response_id());
  EXPECT_TRUE(fallback_namespace.is_empty());
  EXPECT_FALSE(network_namespace);
}

TEST(AppCacheTest, FindInterceptPatternResponseForRequest) {
  MockAppCacheService service;

  // Setup an appcache with an intercept namespace that uses pattern matching.
  const GURL kInterceptNamespaceBase("http://blah/intercept_namespace/");
  const GURL kInterceptPatternNamespace(
      kInterceptNamespaceBase.Resolve("*.hit*"));
  const GURL kInterceptNamespaceEntry("http://blah/intercept_resource");
  const int64 kInterceptResponseId = 1;
  AppCacheManifest manifest;
  manifest.intercept_namespaces.push_back(
      AppCacheNamespace(APPCACHE_INTERCEPT_NAMESPACE,
          kInterceptPatternNamespace, kInterceptNamespaceEntry, true));
  scoped_refptr<AppCache> cache(new AppCache(service.storage(), 1234));
  cache->InitializeWithManifest(&manifest);
  cache->AddEntry(
      kInterceptNamespaceEntry,
      AppCacheEntry(AppCacheEntry::INTERCEPT, kInterceptResponseId));
  cache->set_complete(true);

  // See that the pattern match works.
  bool found = false;
  AppCacheEntry entry;
  AppCacheEntry fallback_entry;
  GURL intercept_namespace;
  GURL fallback_namespace;
  bool network_namespace = false;

  found = cache->FindResponseForRequest(
      GURL("http://blah/miss"),
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_FALSE(found);

  found = cache->FindResponseForRequest(
      GURL("http://blah/intercept_namespace/another_miss"),
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_FALSE(found);

  found = cache->FindResponseForRequest(
      GURL("http://blah/intercept_namespace/path.hit"),
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_TRUE(found);
  EXPECT_EQ(kInterceptResponseId, entry.response_id());
  EXPECT_EQ(kInterceptNamespaceEntry,
            cache->GetInterceptEntryUrl(intercept_namespace));
  EXPECT_FALSE(fallback_entry.has_response_id());
  EXPECT_TRUE(fallback_namespace.is_empty());
  EXPECT_FALSE(network_namespace);

  entry = AppCacheEntry();  // reset

  found = cache->FindResponseForRequest(
      GURL("http://blah/intercept_namespace/longer/path.hit?arg=ok"),
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_TRUE(found);
  EXPECT_EQ(kInterceptResponseId, entry.response_id());
  EXPECT_EQ(kInterceptNamespaceEntry,
            cache->GetInterceptEntryUrl(intercept_namespace));
  EXPECT_FALSE(fallback_entry.has_response_id());
  EXPECT_TRUE(fallback_namespace.is_empty());
  EXPECT_FALSE(network_namespace);
}

TEST(AppCacheTest, FindFallbackPatternResponseForRequest) {
  MockAppCacheService service;

  // Setup an appcache with a fallback namespace that uses pattern matching.
  const GURL kFallbackNamespaceBase("http://blah/fallback_namespace/");
  const GURL kFallbackPatternNamespace(
      kFallbackNamespaceBase.Resolve("*.hit*"));
  const GURL kFallbackNamespaceEntry("http://blah/fallback_resource");
  const int64 kFallbackResponseId = 1;
  AppCacheManifest manifest;
  manifest.fallback_namespaces.push_back(
      AppCacheNamespace(APPCACHE_FALLBACK_NAMESPACE, kFallbackPatternNamespace,
                kFallbackNamespaceEntry, true));
  scoped_refptr<AppCache> cache(new AppCache(service.storage(), 1234));
  cache->InitializeWithManifest(&manifest);
  cache->AddEntry(
      kFallbackNamespaceEntry,
      AppCacheEntry(AppCacheEntry::FALLBACK, kFallbackResponseId));
  cache->set_complete(true);

  // See that the pattern match works.
  bool found = false;
  AppCacheEntry entry;
  AppCacheEntry fallback_entry;
  GURL intercept_namespace;
  GURL fallback_namespace;
  bool network_namespace = false;

  found = cache->FindResponseForRequest(
      GURL("http://blah/miss"),
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_FALSE(found);

  found = cache->FindResponseForRequest(
      GURL("http://blah/fallback_namespace/another_miss"),
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_FALSE(found);

  found = cache->FindResponseForRequest(
      GURL("http://blah/fallback_namespace/path.hit"),
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_TRUE(found);
  EXPECT_FALSE(entry.has_response_id());
  EXPECT_EQ(kFallbackResponseId, fallback_entry.response_id());
  EXPECT_EQ(kFallbackNamespaceEntry,
            cache->GetFallbackEntryUrl(fallback_namespace));
  EXPECT_FALSE(network_namespace);

  fallback_entry = AppCacheEntry();
  fallback_namespace = GURL();

  found = cache->FindResponseForRequest(
      GURL("http://blah/fallback_namespace/longer/path.hit?arg=ok"),
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_TRUE(found);
  EXPECT_FALSE(entry.has_response_id());
  EXPECT_EQ(kFallbackResponseId, fallback_entry.response_id());
  EXPECT_EQ(kFallbackNamespaceEntry,
            cache->GetFallbackEntryUrl(fallback_namespace));
  EXPECT_TRUE(intercept_namespace.is_empty());
  EXPECT_FALSE(network_namespace);
}


TEST(AppCacheTest, FindNetworkNamespacePatternResponseForRequest) {
  MockAppCacheService service;

  // Setup an appcache with a network namespace that uses pattern matching.
  const GURL kNetworkNamespaceBase("http://blah/network_namespace/");
  const GURL kNetworkPatternNamespace(
      kNetworkNamespaceBase.Resolve("*.hit*"));
  AppCacheManifest manifest;
  manifest.online_whitelist_namespaces.push_back(
      AppCacheNamespace(APPCACHE_NETWORK_NAMESPACE, kNetworkPatternNamespace,
                GURL(), true));
  manifest.online_whitelist_all = false;
  scoped_refptr<AppCache> cache(new AppCache(service.storage(), 1234));
  cache->InitializeWithManifest(&manifest);
  cache->set_complete(true);

  // See that the pattern match works.
  bool found = false;
  AppCacheEntry entry;
  AppCacheEntry fallback_entry;
  GURL intercept_namespace;
  GURL fallback_namespace;
  bool network_namespace = false;

  found = cache->FindResponseForRequest(
      GURL("http://blah/miss"),
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_FALSE(found);

  found = cache->FindResponseForRequest(
      GURL("http://blah/network_namespace/path.hit"),
      &entry, &intercept_namespace,
      &fallback_entry, &fallback_namespace,
      &network_namespace);
  EXPECT_TRUE(found);
  EXPECT_TRUE(network_namespace);
  EXPECT_FALSE(entry.has_response_id());
  EXPECT_FALSE(fallback_entry.has_response_id());
}

TEST(AppCacheTest, ToFromDatabaseRecords) {
  // Setup a cache with some entries.
  const int64 kCacheId = 1234;
  const int64 kGroupId = 4321;
  const GURL kManifestUrl("http://foo.com/manifest");
  const GURL kInterceptUrl("http://foo.com/intercept.html");
  const GURL kFallbackUrl("http://foo.com/fallback.html");
  const GURL kWhitelistUrl("http://foo.com/whitelist*");
  const std::string kData(
    "CACHE MANIFEST\r"
    "CHROMIUM-INTERCEPT:\r"
    "/intercept return /intercept.html\r"
    "FALLBACK:\r"
    "/ /fallback.html\r"
    "NETWORK:\r"
    "/whitelist* isPattern\r"
    "*\r");
  MockAppCacheService service;
  scoped_refptr<AppCacheGroup> group =
      new AppCacheGroup(service.storage(), kManifestUrl, kGroupId);
  scoped_refptr<AppCache> cache(new AppCache(service.storage(), kCacheId));
  AppCacheManifest manifest;
  EXPECT_TRUE(ParseManifest(kManifestUrl, kData.c_str(), kData.length(),
                            PARSE_MANIFEST_ALLOWING_INTERCEPTS, manifest));
  cache->InitializeWithManifest(&manifest);
  EXPECT_EQ(APPCACHE_NETWORK_NAMESPACE,
            cache->online_whitelist_namespaces_[0].type);
  EXPECT_TRUE(cache->online_whitelist_namespaces_[0].is_pattern);
  EXPECT_EQ(kWhitelistUrl,
            cache->online_whitelist_namespaces_[0].namespace_url);
  cache->AddEntry(
      kManifestUrl,
      AppCacheEntry(AppCacheEntry::MANIFEST, 1, 1));
  cache->AddEntry(
      kInterceptUrl,
      AppCacheEntry(AppCacheEntry::INTERCEPT, 3, 3));
  cache->AddEntry(
      kFallbackUrl,
      AppCacheEntry(AppCacheEntry::FALLBACK, 2, 2));

  // Get it to produce database records and verify them.
  AppCacheDatabase::CacheRecord cache_record;
  std::vector<AppCacheDatabase::EntryRecord> entries;
  std::vector<AppCacheDatabase::NamespaceRecord> intercepts;
  std::vector<AppCacheDatabase::NamespaceRecord> fallbacks;
  std::vector<AppCacheDatabase::OnlineWhiteListRecord> whitelists;
  cache->ToDatabaseRecords(group.get(),
                           &cache_record,
                           &entries,
                           &intercepts,
                           &fallbacks,
                           &whitelists);
  EXPECT_EQ(kCacheId, cache_record.cache_id);
  EXPECT_EQ(kGroupId, cache_record.group_id);
  EXPECT_TRUE(cache_record.online_wildcard);
  EXPECT_EQ(1 + 2 + 3, cache_record.cache_size);
  EXPECT_EQ(3u, entries.size());
  EXPECT_EQ(1u, intercepts.size());
  EXPECT_EQ(1u, fallbacks.size());
  EXPECT_EQ(1u, whitelists.size());
  cache = NULL;

  // Create a new AppCache and populate it with those records and verify.
  cache = new AppCache(service.storage(), kCacheId);
  cache->InitializeWithDatabaseRecords(
      cache_record, entries, intercepts,
      fallbacks, whitelists);
  EXPECT_TRUE(cache->online_whitelist_all_);
  EXPECT_EQ(3u, cache->entries().size());
  EXPECT_TRUE(cache->GetEntry(kManifestUrl));
  EXPECT_TRUE(cache->GetEntry(kInterceptUrl));
  EXPECT_TRUE(cache->GetEntry(kFallbackUrl));
  EXPECT_EQ(kInterceptUrl,
            cache->GetInterceptEntryUrl(GURL("http://foo.com/intercept")));
  EXPECT_EQ(kFallbackUrl,
            cache->GetFallbackEntryUrl(GURL("http://foo.com/")));
  EXPECT_EQ(1 + 2 + 3, cache->cache_size());
  EXPECT_EQ(APPCACHE_NETWORK_NAMESPACE,
            cache->online_whitelist_namespaces_[0].type);
  EXPECT_TRUE(cache->online_whitelist_namespaces_[0].is_pattern);
  EXPECT_EQ(kWhitelistUrl,
            cache->online_whitelist_namespaces_[0].namespace_url);
}

TEST(AppCacheTest, IsNamespaceMatch) {
  AppCacheNamespace prefix;
  prefix.namespace_url = GURL("http://foo.com/prefix");
  prefix.is_pattern = false;
  EXPECT_TRUE(prefix.IsMatch(
      GURL("http://foo.com/prefix_and_anothing_goes")));
  EXPECT_FALSE(prefix.IsMatch(
      GURL("http://foo.com/nope")));

  AppCacheNamespace bar_no_star;
  bar_no_star.namespace_url = GURL("http://foo.com/bar");
  bar_no_star.is_pattern = true;
  EXPECT_TRUE(bar_no_star.IsMatch(
      GURL("http://foo.com/bar")));
  EXPECT_FALSE(bar_no_star.IsMatch(
      GURL("http://foo.com/bar/nope")));

  AppCacheNamespace bar_star;
  bar_star.namespace_url = GURL("http://foo.com/bar/*");
  bar_star.is_pattern = true;
  EXPECT_TRUE(bar_star.IsMatch(
      GURL("http://foo.com/bar/")));
  EXPECT_TRUE(bar_star.IsMatch(
      GURL("http://foo.com/bar/should_match")));
  EXPECT_FALSE(bar_star.IsMatch(
      GURL("http://foo.com/not_bar/should_not_match")));

  AppCacheNamespace star_bar_star;
  star_bar_star.namespace_url = GURL("http://foo.com/*/bar/*");
  star_bar_star.is_pattern = true;
  EXPECT_TRUE(star_bar_star.IsMatch(
      GURL("http://foo.com/any/bar/should_match")));
  EXPECT_TRUE(star_bar_star.IsMatch(
      GURL("http://foo.com/any/bar/")));
  EXPECT_FALSE(star_bar_star.IsMatch(
      GURL("http://foo.com/any/not_bar/no_match")));

  AppCacheNamespace query_star_edit;
  query_star_edit.namespace_url = GURL("http://foo.com/query?id=*&verb=edit*");
  query_star_edit.is_pattern = true;
  EXPECT_TRUE(query_star_edit.IsMatch(
      GURL("http://foo.com/query?id=1234&verb=edit&option=blue")));
  EXPECT_TRUE(query_star_edit.IsMatch(
      GURL("http://foo.com/query?id=12345&option=blue&verb=edit")));
  EXPECT_FALSE(query_star_edit.IsMatch(
      GURL("http://foo.com/query?id=12345&option=blue&verb=print")));
  EXPECT_TRUE(query_star_edit.IsMatch(
      GURL("http://foo.com/query?id=123&verb=print&verb=edit")));

  AppCacheNamespace star_greediness;
  star_greediness.namespace_url = GURL("http://foo.com/*/b");
  star_greediness.is_pattern = true;
  EXPECT_TRUE(star_greediness.IsMatch(
      GURL("http://foo.com/a/b")));
  EXPECT_TRUE(star_greediness.IsMatch(
      GURL("http://foo.com/a/wxy/z/b")));
  EXPECT_TRUE(star_greediness.IsMatch(
      GURL("http://foo.com/a/b/b")));
  EXPECT_TRUE(star_greediness.IsMatch(
      GURL("http://foo.com/b/b")));
  EXPECT_TRUE(star_greediness.IsMatch(
      GURL("http://foo.com/a/b/b/b/b/b")));
  EXPECT_TRUE(star_greediness.IsMatch(
      GURL("http://foo.com/a/b/b/b/a/b")));
  EXPECT_TRUE(star_greediness.IsMatch(
      GURL("http://foo.com/a/b/01234567890abcdef/b")));
  EXPECT_TRUE(star_greediness.IsMatch(
      GURL("http://foo.com/a/b/01234567890abcdef/b01234567890abcdef/b")));
  EXPECT_TRUE(star_greediness.IsMatch(
      GURL("http://foo.com/a/b/01234567890abcdef_eat_some_more_characters_"
           "/and_even_more_for_the_heck_of_it/01234567890abcdef/b")));
}

}  // namespace content
