root/chrome/common/extensions/api/extension_api_unittest.cc

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. CreateAPIFeature
  2. TEST
  3. TEST
  4. TEST
  5. TEST
  6. TEST
  7. TEST
  8. TEST
  9. CreateExtensionWithPermissions
  10. CreateExtensionWithPermission
  11. TEST
  12. CreateHostedApp
  13. CreatePackagedAppWithPermissions
  14. TEST
  15. TEST
  16. TEST
  17. MatchesURL
  18. TEST
  19. TEST
  20. TEST
  21. TEST
  22. GetDictionaryFromList
  23. TEST
  24. TEST
  25. TEST

// Copyright (c) 2012 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 "extensions/common/extension_api.h"

#include <string>
#include <vector>

#include "base/file_util.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/common/chrome_paths.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/features/api_feature.h"
#include "extensions/common/features/base_feature_provider.h"
#include "extensions/common/features/simple_feature.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/test_util.h"
#include "extensions/common/value_builder.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace extensions {

using test_util::BuildExtension;

SimpleFeature* CreateAPIFeature() {
  return new APIFeature();
}

TEST(ExtensionAPITest, Creation) {
  ExtensionAPI* shared_instance = ExtensionAPI::GetSharedInstance();
  EXPECT_EQ(shared_instance, ExtensionAPI::GetSharedInstance());

  scoped_ptr<ExtensionAPI> new_instance(
      ExtensionAPI::CreateWithDefaultConfiguration());
  EXPECT_NE(new_instance.get(),
            scoped_ptr<ExtensionAPI>(
                ExtensionAPI::CreateWithDefaultConfiguration()).get());

  ExtensionAPI empty_instance;

  struct {
    ExtensionAPI* api;
    bool expect_populated;
  } test_data[] = {
    { shared_instance, true },
    { new_instance.get(), true },
    { &empty_instance, false }
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
    EXPECT_EQ(test_data[i].expect_populated,
              test_data[i].api->GetSchema("bookmarks.create") != NULL);
  }
}

TEST(ExtensionAPITest, SplitDependencyName) {
  struct {
    std::string input;
    std::string expected_feature_type;
    std::string expected_feature_name;
  } test_data[] = {
    { "", "api", "" },  // assumes "api" when no type is present
    { "foo", "api", "foo" },
    { "foo:", "foo", "" },
    { ":foo", "", "foo" },
    { "foo:bar", "foo", "bar" },
    { "foo:bar.baz", "foo", "bar.baz" }
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
    std::string feature_type;
    std::string feature_name;
    ExtensionAPI::SplitDependencyName(test_data[i].input, &feature_type,
                                      &feature_name);
    EXPECT_EQ(test_data[i].expected_feature_type, feature_type) << i;
    EXPECT_EQ(test_data[i].expected_feature_name, feature_name) << i;
  }
}

TEST(ExtensionAPITest, IsPrivileged) {
  scoped_ptr<ExtensionAPI> extension_api(
      ExtensionAPI::CreateWithDefaultConfiguration());

  EXPECT_FALSE(extension_api->IsPrivileged("runtime.connect"));
  EXPECT_FALSE(extension_api->IsPrivileged("runtime.onConnect"));
  EXPECT_FALSE(extension_api->IsPrivileged("runtime.lastError"));

  // Exists, but privileged.
  EXPECT_TRUE(extension_api->IsPrivileged("extension.getViews"));
  EXPECT_TRUE(extension_api->IsPrivileged("history.search"));

  // Whole APIs that are unprivileged.
  EXPECT_FALSE(extension_api->IsPrivileged("app.getDetails"));
  EXPECT_FALSE(extension_api->IsPrivileged("app.isInstalled"));
  EXPECT_FALSE(extension_api->IsPrivileged("storage.local"));
  EXPECT_FALSE(extension_api->IsPrivileged("storage.local.onChanged"));
  EXPECT_FALSE(extension_api->IsPrivileged("storage.local.set"));
  EXPECT_FALSE(extension_api->IsPrivileged("storage.local.MAX_ITEMS"));
  EXPECT_FALSE(extension_api->IsPrivileged("storage.set"));
}

TEST(ExtensionAPITest, IsPrivilegedFeatures) {
  struct {
    std::string api_full_name;
    bool expect_is_privilged;
  } test_data[] = {
    { "test1", false },
    { "test1.foo", true },
    { "test2", true },
    { "test2.foo", false },
    { "test2.bar", false },
    { "test2.baz", true },
    { "test3", false },
    { "test3.foo", true },
    { "test4", false }
  };

  base::FilePath api_features_path;
  PathService::Get(chrome::DIR_TEST_DATA, &api_features_path);
  api_features_path = api_features_path.AppendASCII("extensions")
      .AppendASCII("extension_api_unittest")
      .AppendASCII("privileged_api_features.json");

  std::string api_features_str;
  ASSERT_TRUE(base::ReadFileToString(
      api_features_path, &api_features_str)) << "privileged_api_features.json";

  scoped_ptr<base::DictionaryValue> value(static_cast<base::DictionaryValue*>(
      base::JSONReader::Read(api_features_str)));
  BaseFeatureProvider api_feature_provider(*value, CreateAPIFeature);

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
    ExtensionAPI api;
    api.RegisterDependencyProvider("api", &api_feature_provider);
    EXPECT_EQ(test_data[i].expect_is_privilged,
              api.IsPrivileged(test_data[i].api_full_name)) << i;
  }
}

TEST(ExtensionAPITest, APIFeatures) {
  struct {
    std::string api_full_name;
    bool expect_is_available;
    Feature::Context context;
    GURL url;
  } test_data[] = {
    { "test1", false, Feature::WEB_PAGE_CONTEXT, GURL() },
    { "test1", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() },
    { "test1", true, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() },
    { "test1", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
    { "test2", true, Feature::WEB_PAGE_CONTEXT, GURL("http://google.com") },
    { "test2", false, Feature::BLESSED_EXTENSION_CONTEXT,
        GURL("http://google.com") },
    { "test2.foo", false, Feature::WEB_PAGE_CONTEXT,
        GURL("http://google.com") },
    { "test2.foo", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
    { "test3", false, Feature::WEB_PAGE_CONTEXT, GURL("http://google.com") },
    { "test3.foo", true, Feature::WEB_PAGE_CONTEXT, GURL("http://google.com") },
    { "test3.foo", true, Feature::BLESSED_EXTENSION_CONTEXT,
        GURL("http://bad.com") },
    { "test4", true, Feature::BLESSED_EXTENSION_CONTEXT,
        GURL("http://bad.com") },
    { "test4.foo", false, Feature::BLESSED_EXTENSION_CONTEXT,
        GURL("http://bad.com") },
    { "test4.foo", false, Feature::UNBLESSED_EXTENSION_CONTEXT,
        GURL("http://bad.com") },
    { "test4.foo.foo", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
    { "test5", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") },
    { "test5", false, Feature::WEB_PAGE_CONTEXT, GURL("http://bar.com") },
    { "test5.blah", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") },
    { "test5.blah", false, Feature::WEB_PAGE_CONTEXT, GURL("http://bar.com") },
    { "test6", false, Feature::BLESSED_EXTENSION_CONTEXT, GURL() },
    { "test6.foo", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() },
    { "test7", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") },
    { "test7.foo", false, Feature::WEB_PAGE_CONTEXT, GURL("http://bar.com") },
    { "test7.foo", true, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") },
    { "test7.bar", false, Feature::WEB_PAGE_CONTEXT, GURL("http://bar.com") },
    { "test7.bar", false, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") },

    // Test parent/child.
    { "parent1", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
    { "parent1", false, Feature::WEB_PAGE_CONTEXT, GURL("http://foo.com") },
    { "parent1.child1", false, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
    { "parent1.child1", true, Feature::WEB_PAGE_CONTEXT,
        GURL("http://foo.com") },
    { "parent1.child2", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
    { "parent1.child2", false, Feature::WEB_PAGE_CONTEXT,
        GURL("http://foo.com") },
    { "parent2", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
    { "parent2", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() },
    { "parent2", true, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() },
    { "parent2.child3", false, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
    { "parent2.child3", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() },
    { "parent2.child3", false, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() },
    { "parent2.child3.child.child", true, Feature::CONTENT_SCRIPT_CONTEXT,
        GURL() },
    { "parent2.child3.child.child", false, Feature::BLESSED_EXTENSION_CONTEXT,
        GURL() },
    { "parent2.child3.child.child", true, Feature::UNBLESSED_EXTENSION_CONTEXT,
        GURL() },
    { "parent3", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
    { "parent3", false, Feature::BLESSED_EXTENSION_CONTEXT, GURL() },
    { "parent3", false, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() },
    { "parent3.noparent", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
    { "parent3.noparent", true, Feature::BLESSED_EXTENSION_CONTEXT, GURL() },
    { "parent3.noparent", true, Feature::UNBLESSED_EXTENSION_CONTEXT, GURL() },
    { "parent3.noparent.child", true, Feature::CONTENT_SCRIPT_CONTEXT, GURL() },
    { "parent3.noparent.child", true, Feature::BLESSED_EXTENSION_CONTEXT,
        GURL() },
    { "parent3.noparent.child", true, Feature::UNBLESSED_EXTENSION_CONTEXT,
        GURL() }
  };

  base::FilePath api_features_path;
  PathService::Get(chrome::DIR_TEST_DATA, &api_features_path);
  api_features_path = api_features_path.AppendASCII("extensions")
      .AppendASCII("extension_api_unittest")
      .AppendASCII("api_features.json");

  std::string api_features_str;
  ASSERT_TRUE(base::ReadFileToString(
      api_features_path, &api_features_str)) << "api_features.json";

  scoped_ptr<base::DictionaryValue> value(static_cast<base::DictionaryValue*>(
      base::JSONReader::Read(api_features_str)));
  BaseFeatureProvider api_feature_provider(*value, CreateAPIFeature);

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
    ExtensionAPI api;
    api.RegisterDependencyProvider("api", &api_feature_provider);
    for (base::DictionaryValue::Iterator iter(*value); !iter.IsAtEnd();
         iter.Advance()) {
      if (iter.key().find(".") == std::string::npos)
        api.RegisterSchemaResource(iter.key(), 0);
    }

    EXPECT_EQ(test_data[i].expect_is_available,
              api.IsAvailable(test_data[i].api_full_name,
                              NULL,
                              test_data[i].context,
                              test_data[i].url).is_available()) << i;
  }
}

TEST(ExtensionAPITest, IsAnyFeatureAvailableToContext) {
  scoped_refptr<const Extension> app = ExtensionBuilder()
    .SetManifest(DictionaryBuilder()
      .Set("name", "app")
      .Set("app", DictionaryBuilder()
        .Set("background", DictionaryBuilder()
          .Set("scripts", ListBuilder().Append("background.js"))))
      .Set("version", "1")
      .Set("manifest_version", 2)).Build();
  scoped_refptr<const Extension> extension = ExtensionBuilder()
    .SetManifest(DictionaryBuilder()
      .Set("name", "extension")
      .Set("version", "1")
      .Set("manifest_version", 2)).Build();

  struct {
    std::string api_full_name;
    bool expect_is_available;
    Feature::Context context;
    const Extension* extension;
    GURL url;
  } test_data[] = {
    { "test1", false, Feature::WEB_PAGE_CONTEXT, NULL, GURL() },
    { "test1", true, Feature::UNBLESSED_EXTENSION_CONTEXT, NULL, GURL() },
    { "test1", false, Feature::UNBLESSED_EXTENSION_CONTEXT, app.get(), GURL() },
    { "test1", true, Feature::UNBLESSED_EXTENSION_CONTEXT, extension.get(),
        GURL() },
    { "test2", true, Feature::CONTENT_SCRIPT_CONTEXT, NULL, GURL() },
    { "test2", true, Feature::WEB_PAGE_CONTEXT, NULL,
        GURL("http://google.com") },
    { "test2.foo", false, Feature::WEB_PAGE_CONTEXT, NULL,
        GURL("http://google.com") },
    { "test3", true, Feature::CONTENT_SCRIPT_CONTEXT, NULL, GURL() },
    { "test3", true, Feature::WEB_PAGE_CONTEXT, NULL, GURL("http://foo.com") },
    { "test4.foo", true, Feature::CONTENT_SCRIPT_CONTEXT, NULL, GURL() },
    { "test7", false, Feature::WEB_PAGE_CONTEXT, NULL,
        GURL("http://google.com") },
    { "test7", true, Feature::WEB_PAGE_CONTEXT, NULL, GURL("http://foo.com") },
    { "test7", false, Feature::WEB_PAGE_CONTEXT, NULL, GURL("http://bar.com") }
  };

  base::FilePath api_features_path;
  PathService::Get(chrome::DIR_TEST_DATA, &api_features_path);
  api_features_path = api_features_path.AppendASCII("extensions")
      .AppendASCII("extension_api_unittest")
      .AppendASCII("api_features.json");

  std::string api_features_str;
  ASSERT_TRUE(base::ReadFileToString(
      api_features_path, &api_features_str)) << "api_features.json";

  scoped_ptr<base::DictionaryValue> value(static_cast<base::DictionaryValue*>(
      base::JSONReader::Read(api_features_str)));
  BaseFeatureProvider api_feature_provider(*value, CreateAPIFeature);

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
    ExtensionAPI api;
    api.RegisterDependencyProvider("api", &api_feature_provider);
    for (base::DictionaryValue::Iterator iter(*value); !iter.IsAtEnd();
         iter.Advance()) {
      if (iter.key().find(".") == std::string::npos)
        api.RegisterSchemaResource(iter.key(), 0);
    }

    Feature* test_feature =
        api_feature_provider.GetFeature(test_data[i].api_full_name);
    ASSERT_TRUE(test_feature);
    EXPECT_EQ(test_data[i].expect_is_available,
              api.IsAnyFeatureAvailableToContext(*test_feature,
                                                 test_data[i].extension,
                                                 test_data[i].context,
                                                 test_data[i].url))
        << i;
  }
}

TEST(ExtensionAPITest, LazyGetSchema) {
  scoped_ptr<ExtensionAPI> apis(ExtensionAPI::CreateWithDefaultConfiguration());

  EXPECT_EQ(NULL, apis->GetSchema(std::string()));
  EXPECT_EQ(NULL, apis->GetSchema(std::string()));
  EXPECT_EQ(NULL, apis->GetSchema("experimental"));
  EXPECT_EQ(NULL, apis->GetSchema("experimental"));
  EXPECT_EQ(NULL, apis->GetSchema("foo"));
  EXPECT_EQ(NULL, apis->GetSchema("foo"));

  EXPECT_TRUE(apis->GetSchema("dns"));
  EXPECT_TRUE(apis->GetSchema("dns"));
  EXPECT_TRUE(apis->GetSchema("extension"));
  EXPECT_TRUE(apis->GetSchema("extension"));
  EXPECT_TRUE(apis->GetSchema("infobars"));
  EXPECT_TRUE(apis->GetSchema("infobars"));
  EXPECT_TRUE(apis->GetSchema("omnibox"));
  EXPECT_TRUE(apis->GetSchema("omnibox"));
  EXPECT_TRUE(apis->GetSchema("storage"));
  EXPECT_TRUE(apis->GetSchema("storage"));
}

scoped_refptr<Extension> CreateExtensionWithPermissions(
    const std::set<std::string>& permissions) {
  base::DictionaryValue manifest;
  manifest.SetString("name", "extension");
  manifest.SetString("version", "1.0");
  manifest.SetInteger("manifest_version", 2);
  {
    scoped_ptr<base::ListValue> permissions_list(new base::ListValue());
    for (std::set<std::string>::const_iterator i = permissions.begin();
        i != permissions.end(); ++i) {
      permissions_list->Append(new base::StringValue(*i));
    }
    manifest.Set("permissions", permissions_list.release());
  }

  std::string error;
  scoped_refptr<Extension> extension(Extension::Create(
      base::FilePath(), Manifest::UNPACKED,
      manifest, Extension::NO_FLAGS, &error));
  CHECK(extension.get());
  CHECK(error.empty());

  return extension;
}

scoped_refptr<Extension> CreateExtensionWithPermission(
    const std::string& permission) {
  std::set<std::string> permissions;
  permissions.insert(permission);
  return CreateExtensionWithPermissions(permissions);
}

TEST(ExtensionAPITest, ExtensionWithUnprivilegedAPIs) {
  scoped_refptr<Extension> extension;
  {
    std::set<std::string> permissions;
    permissions.insert("storage");
    permissions.insert("history");
    extension = CreateExtensionWithPermissions(permissions);
  }

  scoped_ptr<ExtensionAPI> extension_api(
      ExtensionAPI::CreateWithDefaultConfiguration());

  const FeatureProvider& api_features = *FeatureProvider::GetAPIFeatures();

  // "storage" is completely unprivileged.
  EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
      *api_features.GetFeature("storage"),
      NULL,
      Feature::BLESSED_EXTENSION_CONTEXT,
      GURL()));
  EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
      *api_features.GetFeature("storage"),
      NULL,
      Feature::UNBLESSED_EXTENSION_CONTEXT,
      GURL()));
  EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
      *api_features.GetFeature("storage"),
      NULL,
      Feature::CONTENT_SCRIPT_CONTEXT,
      GURL()));

  // "extension" is partially unprivileged.
  EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
      *api_features.GetFeature("extension"),
      NULL,
      Feature::BLESSED_EXTENSION_CONTEXT,
      GURL()));
  EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
      *api_features.GetFeature("extension"),
      NULL,
      Feature::UNBLESSED_EXTENSION_CONTEXT,
      GURL()));
  EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
      *api_features.GetFeature("extension"),
      NULL,
      Feature::CONTENT_SCRIPT_CONTEXT,
      GURL()));
  EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
      *api_features.GetFeature("extension.getURL"),
      NULL,
      Feature::CONTENT_SCRIPT_CONTEXT,
      GURL()));

  // "history" is entirely privileged.
  EXPECT_TRUE(extension_api->IsAnyFeatureAvailableToContext(
      *api_features.GetFeature("history"),
      NULL,
      Feature::BLESSED_EXTENSION_CONTEXT,
      GURL()));
  EXPECT_FALSE(extension_api->IsAnyFeatureAvailableToContext(
      *api_features.GetFeature("history"),
      NULL,
      Feature::UNBLESSED_EXTENSION_CONTEXT,
      GURL()));
  EXPECT_FALSE(extension_api->IsAnyFeatureAvailableToContext(
      *api_features.GetFeature("history"),
      NULL,
      Feature::CONTENT_SCRIPT_CONTEXT,
      GURL()));
}

scoped_refptr<Extension> CreateHostedApp() {
  base::DictionaryValue values;
  values.SetString(manifest_keys::kName, "test");
  values.SetString(manifest_keys::kVersion, "0.1");
  values.Set(manifest_keys::kWebURLs, new base::ListValue());
  values.SetString(manifest_keys::kLaunchWebURL,
                   "http://www.example.com");
  std::string error;
  scoped_refptr<Extension> extension(Extension::Create(
      base::FilePath(), Manifest::INTERNAL, values, Extension::NO_FLAGS,
      &error));
  CHECK(extension.get());
  return extension;
}

scoped_refptr<Extension> CreatePackagedAppWithPermissions(
    const std::set<std::string>& permissions) {
  base::DictionaryValue values;
  values.SetString(manifest_keys::kName, "test");
  values.SetString(manifest_keys::kVersion, "0.1");
  values.SetString(manifest_keys::kPlatformAppBackground,
      "http://www.example.com");

  base::DictionaryValue* app = new base::DictionaryValue();
  base::DictionaryValue* background = new base::DictionaryValue();
  base::ListValue* scripts = new base::ListValue();
  scripts->Append(new base::StringValue("test.js"));
  background->Set("scripts", scripts);
  app->Set("background", background);
  values.Set(manifest_keys::kApp, app);
  {
    scoped_ptr<base::ListValue> permissions_list(new base::ListValue());
    for (std::set<std::string>::const_iterator i = permissions.begin();
        i != permissions.end(); ++i) {
      permissions_list->Append(new base::StringValue(*i));
    }
    values.Set("permissions", permissions_list.release());
  }

  std::string error;
  scoped_refptr<Extension> extension(Extension::Create(
      base::FilePath(), Manifest::INTERNAL, values, Extension::NO_FLAGS,
      &error));
  CHECK(extension.get()) << error;
  return extension;
}

TEST(ExtensionAPITest, HostedAppPermissions) {
  scoped_refptr<Extension> extension = CreateHostedApp();

  scoped_ptr<ExtensionAPI> extension_api(
      ExtensionAPI::CreateWithDefaultConfiguration());

  // "runtime" and "tabs" should not be available in hosted apps.
  EXPECT_FALSE(extension_api->IsAvailable("runtime",
                                          extension.get(),
                                          Feature::BLESSED_EXTENSION_CONTEXT,
                                          GURL()).is_available());
  EXPECT_FALSE(extension_api->IsAvailable("runtime.id",
                                          extension.get(),
                                          Feature::BLESSED_EXTENSION_CONTEXT,
                                          GURL()).is_available());
  EXPECT_FALSE(extension_api->IsAvailable("runtime.sendMessage",
                                          extension.get(),
                                          Feature::BLESSED_EXTENSION_CONTEXT,
                                          GURL()).is_available());
  EXPECT_FALSE(extension_api->IsAvailable("runtime.sendNativeMessage",
                                          extension.get(),
                                          Feature::BLESSED_EXTENSION_CONTEXT,
                                          GURL()).is_available());
  EXPECT_FALSE(extension_api->IsAvailable("tabs.create",
                                          extension.get(),
                                          Feature::BLESSED_EXTENSION_CONTEXT,
                                          GURL()).is_available());
}

TEST(ExtensionAPITest, AppAndFriendsAvailability) {

  scoped_ptr<ExtensionAPI> extension_api(
      ExtensionAPI::CreateWithDefaultConfiguration());

  // Make sure chrome.app.runtime and chrome.app.window are available to apps,
  // and chrome.app is not.
  {
    std::set<std::string> permissions;
    permissions.insert("app.runtime");
    permissions.insert("app.window");
    scoped_refptr<Extension> extension =
        CreatePackagedAppWithPermissions(permissions);
    EXPECT_FALSE(extension_api->IsAvailable(
        "app",
        extension.get(),
        Feature::BLESSED_EXTENSION_CONTEXT,
        GURL("http://foo.com")).is_available());
    EXPECT_TRUE(extension_api->IsAvailable(
        "app.runtime",
        extension.get(),
        Feature::BLESSED_EXTENSION_CONTEXT,
        GURL("http://foo.com")).is_available());
    EXPECT_TRUE(extension_api->IsAvailable(
        "app.window",
        extension.get(),
        Feature::BLESSED_EXTENSION_CONTEXT,
        GURL("http://foo.com")).is_available());
  }
  // Make sure chrome.app.runtime and chrome.app.window are not available to
  // extensions, and chrome.app is.
  {
    std::set<std::string> permissions;
    scoped_refptr<Extension> extension =
        CreateExtensionWithPermissions(permissions);
    EXPECT_TRUE(extension_api->IsAvailable(
        "app",
        extension.get(),
        Feature::BLESSED_EXTENSION_CONTEXT,
        GURL("http://foo.com")).is_available());
    EXPECT_FALSE(extension_api->IsAvailable(
        "app.runtime",
        extension.get(),
        Feature::BLESSED_EXTENSION_CONTEXT,
        GURL("http://foo.com")).is_available());
    EXPECT_FALSE(extension_api->IsAvailable(
        "app.window",
        extension.get(),
        Feature::BLESSED_EXTENSION_CONTEXT,
        GURL("http://foo.com")).is_available());
  }
}

TEST(ExtensionAPITest, ExtensionWithDependencies) {
  // Extension with the "ttsEngine" permission but not the "tts" permission; it
  // should not automatically get "tts" permission.
  {
    scoped_refptr<Extension> extension =
        CreateExtensionWithPermission("ttsEngine");
    scoped_ptr<ExtensionAPI> api(
        ExtensionAPI::CreateWithDefaultConfiguration());
    EXPECT_TRUE(api->IsAvailable("ttsEngine",
                                 extension.get(),
                                 Feature::BLESSED_EXTENSION_CONTEXT,
                                 GURL()).is_available());
    EXPECT_FALSE(api->IsAvailable("tts",
                                  extension.get(),
                                  Feature::BLESSED_EXTENSION_CONTEXT,
                                  GURL()).is_available());
  }

  // Conversely, extension with the "tts" permission but not the "ttsEngine"
  // permission shouldn't get the "ttsEngine" permission.
  {
    scoped_refptr<Extension> extension =
        CreateExtensionWithPermission("tts");
    scoped_ptr<ExtensionAPI> api(
        ExtensionAPI::CreateWithDefaultConfiguration());
    EXPECT_FALSE(api->IsAvailable("ttsEngine",
                                  extension.get(),
                                  Feature::BLESSED_EXTENSION_CONTEXT,
                                  GURL()).is_available());
    EXPECT_TRUE(api->IsAvailable("tts",
                                 extension.get(),
                                 Feature::BLESSED_EXTENSION_CONTEXT,
                                 GURL()).is_available());
  }
}

bool MatchesURL(
    ExtensionAPI* api, const std::string& api_name, const std::string& url) {
  return api->IsAvailable(
      api_name, NULL, Feature::WEB_PAGE_CONTEXT, GURL(url)).is_available();
}

TEST(ExtensionAPITest, URLMatching) {
  scoped_ptr<ExtensionAPI> api(ExtensionAPI::CreateWithDefaultConfiguration());

  // "app" API is available to all URLs that content scripts can be injected.
  EXPECT_TRUE(MatchesURL(api.get(), "app", "http://example.com/example.html"));
  EXPECT_TRUE(MatchesURL(api.get(), "app", "https://blah.net"));
  EXPECT_TRUE(MatchesURL(api.get(), "app", "file://somefile.html"));

  // But not internal URLs.
  EXPECT_FALSE(MatchesURL(api.get(), "app", "about:flags"));
  EXPECT_FALSE(MatchesURL(api.get(), "app", "chrome://flags"));

  // "app" should be available to chrome-extension URLs.
  EXPECT_TRUE(MatchesURL(api.get(), "app",
                          "chrome-extension://fakeextension"));

  // "storage" API (for example) isn't available to any URLs.
  EXPECT_FALSE(MatchesURL(api.get(), "storage",
                          "http://example.com/example.html"));
  EXPECT_FALSE(MatchesURL(api.get(), "storage", "https://blah.net"));
  EXPECT_FALSE(MatchesURL(api.get(), "storage", "file://somefile.html"));
  EXPECT_FALSE(MatchesURL(api.get(), "storage", "about:flags"));
  EXPECT_FALSE(MatchesURL(api.get(), "storage", "chrome://flags"));
  EXPECT_FALSE(MatchesURL(api.get(), "storage",
                          "chrome-extension://fakeextension"));
}

TEST(ExtensionAPITest, GetAPINameFromFullName) {
  struct {
    std::string input;
    std::string api_name;
    std::string child_name;
  } test_data[] = {
    { "", "", "" },
    { "unknown", "", "" },
    { "bookmarks", "bookmarks", "" },
    { "bookmarks.", "bookmarks", "" },
    { ".bookmarks", "", "" },
    { "bookmarks.create", "bookmarks", "create" },
    { "bookmarks.create.", "bookmarks", "create." },
    { "bookmarks.create.monkey", "bookmarks", "create.monkey" },
    { "bookmarkManagerPrivate", "bookmarkManagerPrivate", "" },
    { "bookmarkManagerPrivate.copy", "bookmarkManagerPrivate", "copy" }
  };

  scoped_ptr<ExtensionAPI> api(ExtensionAPI::CreateWithDefaultConfiguration());
  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
    std::string child_name;
    std::string api_name = api->GetAPINameFromFullName(test_data[i].input,
                                                       &child_name);
    EXPECT_EQ(test_data[i].api_name, api_name) << test_data[i].input;
    EXPECT_EQ(test_data[i].child_name, child_name) << test_data[i].input;
  }
}

TEST(ExtensionAPITest, DefaultConfigurationFeatures) {
  scoped_ptr<ExtensionAPI> api(ExtensionAPI::CreateWithDefaultConfiguration());

  SimpleFeature* bookmarks = static_cast<SimpleFeature*>(
      api->GetFeatureDependency("api:bookmarks"));
  SimpleFeature* bookmarks_create = static_cast<SimpleFeature*>(
      api->GetFeatureDependency("api:bookmarks.create"));

  struct {
    SimpleFeature* feature;
    // TODO(aa): More stuff to test over time.
  } test_data[] = {
    { bookmarks },
    { bookmarks_create }
  };

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
    SimpleFeature* feature = test_data[i].feature;
    ASSERT_TRUE(feature) << i;

    EXPECT_TRUE(feature->whitelist()->empty());
    EXPECT_TRUE(feature->extension_types()->empty());

    EXPECT_EQ(1u, feature->GetContexts()->size());
    EXPECT_TRUE(feature->GetContexts()->count(
        Feature::BLESSED_EXTENSION_CONTEXT));

    EXPECT_EQ(Feature::UNSPECIFIED_LOCATION, feature->location());
    EXPECT_TRUE(feature->platforms()->empty());
    EXPECT_EQ(0, feature->min_manifest_version());
    EXPECT_EQ(0, feature->max_manifest_version());
  }
}

TEST(ExtensionAPITest, FeaturesRequireContexts) {
  // TODO(cduvall): Make this check API featues.
  scoped_ptr<base::DictionaryValue> api_features1(new base::DictionaryValue());
  scoped_ptr<base::DictionaryValue> api_features2(new base::DictionaryValue());
  base::DictionaryValue* test1 = new base::DictionaryValue();
  base::DictionaryValue* test2 = new base::DictionaryValue();
  base::ListValue* contexts = new base::ListValue();
  contexts->Append(new base::StringValue("content_script"));
  test1->Set("contexts", contexts);
  test1->SetString("channel", "stable");
  test2->SetString("channel", "stable");
  api_features1->Set("test", test1);
  api_features2->Set("test", test2);

  struct {
    base::DictionaryValue* api_features;
    bool expect_success;
  } test_data[] = {
    { api_features1.get(), true },
    { api_features2.get(), false }
  };


  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_data); ++i) {
    BaseFeatureProvider api_feature_provider(*test_data[i].api_features,
                                             CreateAPIFeature);
    Feature* feature = api_feature_provider.GetFeature("test");
    EXPECT_EQ(test_data[i].expect_success, feature != NULL) << i;
  }
}

static void GetDictionaryFromList(const base::DictionaryValue* schema,
                                  const std::string& list_name,
                                  const int list_index,
                                  const base::DictionaryValue** out) {
  const base::ListValue* list;
  EXPECT_TRUE(schema->GetList(list_name, &list));
  EXPECT_TRUE(list->GetDictionary(list_index, out));
}

TEST(ExtensionAPITest, TypesHaveNamespace) {
  base::FilePath manifest_path;
  PathService::Get(chrome::DIR_TEST_DATA, &manifest_path);
  manifest_path = manifest_path.AppendASCII("extensions")
      .AppendASCII("extension_api_unittest")
      .AppendASCII("types_have_namespace.json");

  std::string manifest_str;
  ASSERT_TRUE(base::ReadFileToString(manifest_path, &manifest_str))
      << "Failed to load: " << manifest_path.value();

  ExtensionAPI api;
  api.RegisterSchemaResource("test.foo", 0);
  api.LoadSchema("test.foo", manifest_str);

  const base::DictionaryValue* schema = api.GetSchema("test.foo");

  const base::DictionaryValue* dict;
  const base::DictionaryValue* sub_dict;
  std::string type;

  GetDictionaryFromList(schema, "types", 0, &dict);
  EXPECT_TRUE(dict->GetString("id", &type));
  EXPECT_EQ("test.foo.TestType", type);
  EXPECT_TRUE(dict->GetString("customBindings", &type));
  EXPECT_EQ("test.foo.TestType", type);
  EXPECT_TRUE(dict->GetDictionary("properties", &sub_dict));
  const base::DictionaryValue* property;
  EXPECT_TRUE(sub_dict->GetDictionary("foo", &property));
  EXPECT_TRUE(property->GetString("$ref", &type));
  EXPECT_EQ("test.foo.OtherType", type);
  EXPECT_TRUE(sub_dict->GetDictionary("bar", &property));
  EXPECT_TRUE(property->GetString("$ref", &type));
  EXPECT_EQ("fully.qualified.Type", type);

  GetDictionaryFromList(schema, "functions", 0, &dict);
  GetDictionaryFromList(dict, "parameters", 0, &sub_dict);
  EXPECT_TRUE(sub_dict->GetString("$ref", &type));
  EXPECT_EQ("test.foo.TestType", type);
  EXPECT_TRUE(dict->GetDictionary("returns", &sub_dict));
  EXPECT_TRUE(sub_dict->GetString("$ref", &type));
  EXPECT_EQ("fully.qualified.Type", type);

  GetDictionaryFromList(schema, "functions", 1, &dict);
  GetDictionaryFromList(dict, "parameters", 0, &sub_dict);
  EXPECT_TRUE(sub_dict->GetString("$ref", &type));
  EXPECT_EQ("fully.qualified.Type", type);
  EXPECT_TRUE(dict->GetDictionary("returns", &sub_dict));
  EXPECT_TRUE(sub_dict->GetString("$ref", &type));
  EXPECT_EQ("test.foo.TestType", type);

  GetDictionaryFromList(schema, "events", 0, &dict);
  GetDictionaryFromList(dict, "parameters", 0, &sub_dict);
  EXPECT_TRUE(sub_dict->GetString("$ref", &type));
  EXPECT_EQ("test.foo.TestType", type);
  GetDictionaryFromList(dict, "parameters", 1, &sub_dict);
  EXPECT_TRUE(sub_dict->GetString("$ref", &type));
  EXPECT_EQ("fully.qualified.Type", type);
}

// Tests API availability with an empty manifest.
TEST(ExtensionAPITest, NoPermissions) {
  const struct {
    const char* permission_name;
    bool expect_success;
  } kTests[] = {
    // Test default module/package permission.
    { "extension",      true },
    { "i18n",           true },
    { "permissions",    true },
    { "runtime",        true },
    { "test",           true },
    // These require manifest keys.
    { "browserAction",  false },
    { "pageAction",     false },
    { "pageActions",    false },
    // Some negative tests.
    { "bookmarks",      false },
    { "cookies",        false },
    { "history",        false },
    // Make sure we find the module name after stripping '.'
    { "runtime.abcd.onStartup",  true },
    // Test Tabs/Windows functions.
    { "tabs.create",      true },
    { "tabs.duplicate",   true },
    { "tabs.onRemoved",   true },
    { "tabs.remove",      true },
    { "tabs.update",      true },
    { "tabs.getSelected", true },
    { "tabs.onUpdated",   true },
    { "windows.get",      true },
    { "windows.create",   true },
    { "windows.remove",   true },
    { "windows.update",   true },
    // Test some whitelisted functions. These require no permissions.
    { "app.getDetails",           true },
    { "app.getDetailsForFrame",   true },
    { "app.getIsInstalled",       true },
    { "app.installState",         true },
    { "app.runningState",         true },
    { "management.getPermissionWarningsByManifest", true },
    { "management.uninstallSelf", true },
    // But other functions in those modules do.
    { "management.getPermissionWarningsById", false },
  };

  scoped_ptr<ExtensionAPI> extension_api(
      ExtensionAPI::CreateWithDefaultConfiguration());
  scoped_refptr<Extension> extension =
      BuildExtension(ExtensionBuilder().Pass()).Build();

  for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTests); ++i) {
    EXPECT_EQ(kTests[i].expect_success,
              extension_api->IsAvailable(kTests[i].permission_name,
                                         extension.get(),
                                         Feature::BLESSED_EXTENSION_CONTEXT,
                                         GURL()).is_available())
        << "Permission being tested: " << kTests[i].permission_name;
  }
}

// Tests that permissions that require manifest keys are available when those
// keys are present.
TEST(ExtensionAPITest, ManifestKeys) {
  scoped_ptr<ExtensionAPI> extension_api(
      ExtensionAPI::CreateWithDefaultConfiguration());

  scoped_refptr<Extension> extension =
      BuildExtension(ExtensionBuilder().Pass())
      .MergeManifest(DictionaryBuilder().Set("browser_action",
                                             DictionaryBuilder().Pass()))
      .Build();

  EXPECT_TRUE(extension_api->IsAvailable("browserAction",
                                         extension.get(),
                                         Feature::BLESSED_EXTENSION_CONTEXT,
                                         GURL()).is_available());
  EXPECT_FALSE(extension_api->IsAvailable("pageAction",
                                          extension.get(),
                                          Feature::BLESSED_EXTENSION_CONTEXT,
                                          GURL()).is_available());
}

}  // namespace extensions

/* [<][>][^][v][top][bottom][index][help] */