| Index: chrome/browser/extensions/webstore_bundle_browsertest.cc
|
| diff --git a/chrome/browser/extensions/webstore_bundle_browsertest.cc b/chrome/browser/extensions/webstore_bundle_browsertest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..251e7d2b4ec5fac2b0eb329c22d4fd150cc6e0ae
|
| --- /dev/null
|
| +++ b/chrome/browser/extensions/webstore_bundle_browsertest.cc
|
| @@ -0,0 +1,477 @@
|
| +// Copyright (c) 2011 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 <string>
|
| +#include <vector>
|
| +
|
| +#include "base/command_line.h"
|
| +#include "base/stringprintf.h"
|
| +#include "base/basictypes.h"
|
| +#include "base/logging.h"
|
| +#include "base/memory/ref_counted.h"
|
| +#include "base/memory/scoped_ptr.h"
|
| +#include "base/message_loop.h"
|
| +#include "base/string16.h"
|
| +#include "chrome/browser/extensions/extension_browsertest.h"
|
| +#include "chrome/browser/extensions/extension_service.h"
|
| +#include "chrome/browser/extensions/webstore_bundle.h"
|
| +#include "chrome/browser/tabs/tab_strip_model.h"
|
| +#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
|
| +#include "chrome/browser/ui/browser.h"
|
| +#include "chrome/common/chrome_switches.h"
|
| +#include "chrome/test/base/testing_profile.h"
|
| +#include "chrome/test/base/ui_test_utils.h"
|
| +#include "content/browser/browser_thread.h"
|
| +#include "content/browser/tab_contents/navigation_controller.h"
|
| +#include "googleurl/src/gurl.h"
|
| +#include "net/base/host_port_pair.h"
|
| +#include "net/base/mock_host_resolver.h"
|
| +
|
| +namespace {
|
| +
|
| +// These are just shorter aliases for some of the WebstoreBundle enums.
|
| +WebstoreBundle::ItemMatcher MATCH_ALL = WebstoreBundle::MATCH_ALL;
|
| +WebstoreBundle::ItemMatcher MATCH_APPS = WebstoreBundle::MATCH_APPS;
|
| +WebstoreBundle::ItemMatcher MATCH_EXTENSIONS = WebstoreBundle::MATCH_EXTENSIONS;
|
| +
|
| +WebstoreBundle::Item::State STATE_INITIAL =
|
| + WebstoreBundle::Item::STATE_INITIAL;
|
| +WebstoreBundle::Item::State STATE_APPROVED =
|
| + WebstoreBundle::Item::STATE_APPROVED;
|
| +WebstoreBundle::Item::State STATE_INSTALLED =
|
| + WebstoreBundle::Item::STATE_INSTALLED;
|
| +WebstoreBundle::Item::State STATE_NOT_INSTALLED =
|
| + WebstoreBundle::Item::STATE_NOT_INSTALLED;
|
| +
|
| +const char kGalleryDomain[] = "cws.com";
|
| +
|
| +struct TestItem {
|
| + std::string id;
|
| + std::string localized_name;
|
| + std::string manifest;
|
| +};
|
| +
|
| +const TestItem kBundleItems[] = {
|
| + { "pkapffpjmiilhlhbibjhamlmdhfneidj",
|
| + "Bundle App 1",
|
| + "{"
|
| + " \"name\": \"Bundle App 1\","
|
| + " \"version\": \"1\","
|
| + " \"app\": {"
|
| + " \"urls\": [ \"http://www.testapp.com\" ],"
|
| + " \"launch\": { \"web_url\": \"http://www.testapp.com\" }"
|
| + " },"
|
| + " \"permissions\": [ \"clipboardRead\" ]"
|
| + "}" },
|
| + { "bmfoocgfinpmkmlbjhcbofejhkhlbchk",
|
| + "Extension Bundle 1",
|
| + "{"
|
| + " \"name\": \"Extension Bundle 1\","
|
| + " \"version\": \"1\","
|
| + " \"permissions\": [ \"tabs\" ]"
|
| + "}" },
|
| + { "mpneghmdnmaolkljkipbhaienajcflfe",
|
| + "Extension Bundle 2",
|
| + "{"
|
| + " \"name\": \"Extension Bundle 2\","
|
| + " \"version\": \"1\","
|
| + " \"permissions\": [\"management\", \"http://google.com\" ],"
|
| + " \"content_script\": [{"
|
| + " \"matches\": [ \"http://www.example.com/*\" ],"
|
| + " \"js\": [ \"content_script.js\" ],"
|
| + " \"run_at\": \"document_start\""
|
| + " }]"
|
| + "}" }
|
| +};
|
| +
|
| +const TestItem k404BundleItems[] = {
|
| + { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
| + "Extension A",
|
| + "{"
|
| + " \"name\": \"Extension A\","
|
| + " \"version\": \"1\","
|
| + " \"permissions\": [ \"tabs\" ]"
|
| + "}" }
|
| +};
|
| +
|
| +const TestItem kBadItems[] = {
|
| + { "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
| + "Extension B",
|
| + "{"
|
| + " \"name\": \"Extension B\","
|
| + " \"version\": \"1\","
|
| + " \"permissions\" [ \"http://*/*\" ]" // Syntax error on this line.
|
| + "}" },
|
| +};
|
| +
|
| +const TestItem kBadCrxItems[] = {
|
| + { "cccccccccccccccccccccccccccccccc",
|
| + "Extension C",
|
| + "{"
|
| + " \"name\": \"Extension C\","
|
| + " \"version\": \"1\","
|
| + " \"permissions\": [ \"bookmarks\", \"http://example.org/*\" ]"
|
| + "}" },
|
| +};
|
| +
|
| +// A WebstoreBundle::Delegate that waits for the callbacks and record the
|
| +// results.
|
| +class WebstoreBundleListener : public WebstoreBundle::Delegate {
|
| + public:
|
| + WebstoreBundleListener()
|
| + : approved_(false),
|
| + canceled_(false),
|
| + completed_(false),
|
| + waiting_(false) {}
|
| + virtual ~WebstoreBundleListener() {}
|
| +
|
| + void OnBundleInstallApproved() OVERRIDE;
|
| + void OnBundleInstallCanceled(bool user_initiated) OVERRIDE;
|
| + void OnBundleInstallCompleted() OVERRIDE;
|
| +
|
| + void ResetResults();
|
| + void Wait();
|
| +
|
| + bool approved() { return approved_; }
|
| + bool canceled() { return canceled_; }
|
| + bool completed() { return completed_; }
|
| +
|
| + private:
|
| + void QuitMessageLoopIfWaiting();
|
| +
|
| + bool approved_;
|
| + bool canceled_;
|
| + bool completed_;
|
| + bool waiting_;
|
| +};
|
| +
|
| +void WebstoreBundleListener::OnBundleInstallApproved() {
|
| + approved_ = true;
|
| + QuitMessageLoopIfWaiting();
|
| +}
|
| +
|
| +void WebstoreBundleListener::OnBundleInstallCanceled(bool user_initiated) {
|
| + canceled_ = true;
|
| + QuitMessageLoopIfWaiting();
|
| +}
|
| +
|
| +void WebstoreBundleListener::OnBundleInstallCompleted() {
|
| + completed_ = true;
|
| + QuitMessageLoopIfWaiting();
|
| +}
|
| +
|
| +void WebstoreBundleListener::ResetResults() {
|
| + approved_ = false;
|
| + canceled_ = false;
|
| + completed_ = false;
|
| +}
|
| +
|
| +void WebstoreBundleListener::Wait() {
|
| + if (approved_ || canceled_ || completed_)
|
| + return;
|
| +
|
| + waiting_ = true;
|
| + ui_test_utils::RunMessageLoop();
|
| +}
|
| +
|
| +void WebstoreBundleListener::QuitMessageLoopIfWaiting() {
|
| + if (waiting_) {
|
| + waiting_ = false;
|
| + MessageLoopForUI::current()->Quit();
|
| + }
|
| +}
|
| +
|
| +class WebstoreBundleTest : public ExtensionBrowserTest {
|
| + public:
|
| + virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE;
|
| + virtual void SetUpInProcessBrowserTestFixture() OVERRIDE;
|
| + protected:
|
| + GURL GetTestServerURL(const std::string& domain, const std::string& filename);
|
| +};
|
| +
|
| +void WebstoreBundleTest::SetUpCommandLine(CommandLine* command_line) {
|
| + // We start the test server now instead of in SetUpInProcessTestFixture so
|
| + // we can get its port number.
|
| + ASSERT_TRUE(test_server()->Start());
|
| +
|
| + InProcessBrowserTest::SetUpCommandLine(command_line);
|
| +
|
| + net::HostPortPair host_pair = test_server()->host_port_pair();
|
| + std::string download_url = base::StringPrintf(
|
| + "http://%s:%d/files/extensions/api_test/webstore_private/bundle/%s.crx",
|
| + kGalleryDomain, host_pair.port(), "%s");
|
| + command_line->AppendSwitchASCII(
|
| + switches::kAppsGalleryDownloadURL, download_url);
|
| + command_line->AppendSwitchASCII(
|
| + switches::kAppsGalleryURL,
|
| + base::StringPrintf("http://%s", kGalleryDomain));
|
| +}
|
| +
|
| +void WebstoreBundleTest::SetUpInProcessBrowserTestFixture() {
|
| + host_resolver()->AddRule(kGalleryDomain, "127.0.0.1");
|
| +}
|
| +
|
| +GURL WebstoreBundleTest::GetTestServerURL(const std::string& domain,
|
| + const std::string& filename) {
|
| + GURL page_url = test_server()->GetURL(
|
| + "files/extensions/api_test/webstore_private/" + filename);
|
| +
|
| + GURL::Replacements replace_host;
|
| + replace_host.SetHostStr(domain);
|
| + return page_url.ReplaceComponents(replace_host);
|
| +}
|
| +
|
| +void AddItemsToItemList(WebstoreBundle::ItemList* item_list,
|
| + const TestItem items[],
|
| + size_t item_count) {
|
| + for (size_t i = 0; i < item_count; ++i)
|
| + item_list->push_back(new WebstoreBundle::Item(
|
| + items[i].id, items[i].manifest, items[i].localized_name));
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +// Tests the state transitions and delegate callbacks when the user denies
|
| +// the confirmation prompt.
|
| +IN_PROC_BROWSER_TEST_F(WebstoreBundleTest, ParseAndCancel) {
|
| + WebstoreBundle::SetAutoApproveForTesting(false);
|
| +
|
| + Profile* profile = browser()->profile();
|
| +
|
| + WebstoreBundle::ItemList items;
|
| + AddItemsToItemList(&items, kBundleItems, ARRAYSIZE_UNSAFE(kBundleItems));
|
| + AddItemsToItemList(&items, kBadItems, ARRAYSIZE_UNSAFE(kBadItems));
|
| +
|
| + scoped_refptr<WebstoreBundle> bundle = new WebstoreBundle(profile, &items);
|
| + scoped_ptr<WebstoreBundleListener> listener(new WebstoreBundleListener());
|
| +
|
| + // All should start in STATE_INITIAL;
|
| + EXPECT_EQ(4u, bundle->GetItemsInState(STATE_INITIAL, MATCH_ALL).size());
|
| +
|
| + bundle->PromptForApproval(listener.get());
|
| + listener->Wait();
|
| +
|
| + EXPECT_TRUE(listener->canceled());
|
| + EXPECT_FALSE(listener->approved());
|
| +
|
| + // All the extensions should be in STATE_NOT_INSTALLED now.
|
| + EXPECT_EQ(4u, bundle->GetItemsInState(STATE_NOT_INSTALLED, MATCH_ALL).size());
|
| +}
|
| +
|
| +// Tests the regular flow for approving and installing a bundle.
|
| +IN_PROC_BROWSER_TEST_F(WebstoreBundleTest, InstallSuccess) {
|
| + WebstoreBundle::SetAutoApproveForTesting(true);
|
| +
|
| + Profile* profile = browser()->profile();
|
| + ExtensionService* service = profile->GetExtensionService();
|
| +
|
| + WebstoreBundle::ItemList items;
|
| + AddItemsToItemList(&items, kBundleItems, ARRAYSIZE_UNSAFE(kBundleItems));
|
| +
|
| + scoped_refptr<WebstoreBundle> bundle = new WebstoreBundle(profile, &items);
|
| + scoped_ptr<WebstoreBundleListener> listener(new WebstoreBundleListener());
|
| + bundle->PromptForApproval(listener.get());
|
| + listener->Wait();
|
| +
|
| + EXPECT_TRUE(listener->approved());
|
| + EXPECT_FALSE(listener->canceled());
|
| +
|
| + std::vector<const WebstoreBundle::Item*> approved_items =
|
| + bundle->GetItemsInState(STATE_APPROVED, MATCH_ALL);
|
| +
|
| + // All members should have been parsed successfully.
|
| + EXPECT_EQ(3u, approved_items.size());
|
| +
|
| + // The items should have had there manifests parsed.
|
| + for (size_t i = 0; i < approved_items.size(); ++i)
|
| + EXPECT_TRUE(approved_items[i]->dummy_extension().get());
|
| +
|
| + // No extensions should have failed.
|
| + EXPECT_EQ(0u, bundle->GetItemsInState(STATE_NOT_INSTALLED, MATCH_ALL).size());
|
| +
|
| + // Two extensions and one app were passed in.
|
| + EXPECT_EQ(1u, bundle->GetItemsInState(STATE_APPROVED, MATCH_APPS).size());
|
| + EXPECT_EQ(2u, bundle->GetItemsInState(
|
| + STATE_APPROVED, MATCH_EXTENSIONS).size());
|
| +
|
| + EXPECT_TRUE(bundle->HasExtensionsInState(STATE_APPROVED));
|
| + EXPECT_TRUE(bundle->HasAppsInState(STATE_APPROVED));
|
| +
|
| + listener->ResetResults();
|
| +
|
| + // This page doesn't exist, but it doesn't matter.
|
| + ui_test_utils::NavigateToURL(browser(), GetTestServerURL(kGalleryDomain, ""));
|
| + TabStripModel* tabstrip = browser()->tabstrip_model();
|
| +
|
| + // Install the bundle.
|
| + bundle->CompleteInstall(&tabstrip->GetTabContentsAt(0)->controller(),
|
| + listener.get());
|
| +
|
| + listener->Wait();
|
| +
|
| + ASSERT_TRUE(listener->completed());
|
| + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kBundleItems); ++i)
|
| + EXPECT_TRUE(service->GetExtensionById(kBundleItems[i].id, false));
|
| +
|
| + // 2 apps and 1 extension should have been installed.
|
| + EXPECT_EQ(3u, bundle->GetItemsInState(STATE_INSTALLED, MATCH_ALL).size());
|
| + EXPECT_EQ(1u, bundle->GetItemsInState(STATE_INSTALLED, MATCH_APPS).size());
|
| + EXPECT_EQ(2u, bundle->GetItemsInState(
|
| + STATE_INSTALLED, MATCH_EXTENSIONS).size());
|
| +
|
| + // The failed heading should be empty since all succeeded.
|
| + EXPECT_TRUE(bundle->GetFailedHeadingTextForBubble().empty());
|
| + EXPECT_TRUE(!bundle->GetInstalledHeadingTextForBubble().empty());
|
| +}
|
| +
|
| +// Tests that the WebstoreBundle handles crx files that fail to install.
|
| +IN_PROC_BROWSER_TEST_F(WebstoreBundleTest, InstallSuccessAndFailure) {
|
| + WebstoreBundle::SetAutoApproveForTesting(true);
|
| +
|
| + Profile* profile = browser()->profile();
|
| + ExtensionService* service = profile->GetExtensionService();
|
| +
|
| + WebstoreBundle::ItemList items;
|
| + // Install 2 extensions and 1 app.
|
| + AddItemsToItemList(&items, kBundleItems, ARRAYSIZE_UNSAFE(kBundleItems));
|
| + // Fail to parse 1 extension manifest.
|
| + AddItemsToItemList(&items, kBadItems, ARRAYSIZE_UNSAFE(kBadItems));
|
| + // Fail to install 1 extension.
|
| + AddItemsToItemList(
|
| + &items, kBadCrxItems, ARRAYSIZE_UNSAFE(kBadCrxItems));
|
| +
|
| + scoped_refptr<WebstoreBundle> bundle = new WebstoreBundle(profile, &items);
|
| + scoped_ptr<WebstoreBundleListener> listener(new WebstoreBundleListener());
|
| + bundle->PromptForApproval(listener.get());
|
| + listener->Wait();
|
| +
|
| + EXPECT_TRUE(listener->approved());
|
| + EXPECT_FALSE(listener->canceled());
|
| +
|
| + std::vector<const WebstoreBundle::Item*> approved_items =
|
| + bundle->GetItemsInState(STATE_APPROVED, MATCH_ALL);
|
| +
|
| + // All items except the one with the bad manifest should have been parsed.
|
| + EXPECT_EQ(4u, approved_items.size());
|
| + for (size_t i = 0; i < approved_items.size(); ++i)
|
| + EXPECT_TRUE(approved_items[i]->dummy_extension().get());
|
| +
|
| + // While the one with the syntax error should have gone to
|
| + // STATE_NOT_INSTALLED.
|
| + EXPECT_EQ(1u, bundle->GetItemsInState(STATE_NOT_INSTALLED, MATCH_ALL).size());
|
| +
|
| + // Three extensions and one app should have been parsed successfully.
|
| + EXPECT_EQ(1u, bundle->GetItemsInState(STATE_APPROVED, MATCH_APPS).size());
|
| + EXPECT_EQ(3u, bundle->GetItemsInState(
|
| + STATE_APPROVED, MATCH_EXTENSIONS).size());
|
| +
|
| + EXPECT_TRUE(bundle->HasExtensionsInState(STATE_APPROVED));
|
| + EXPECT_TRUE(bundle->HasAppsInState(STATE_APPROVED));
|
| +
|
| + listener->ResetResults();
|
| +
|
| + // This page doesn't exist, but it doesn't matter.
|
| + ui_test_utils::NavigateToURL(browser(), GetTestServerURL(kGalleryDomain, ""));
|
| + TabStripModel* tabstrip = browser()->tabstrip_model();
|
| +
|
| + // Install the bundle.
|
| + bundle->CompleteInstall(&tabstrip->GetTabContentsAt(0)->controller(),
|
| + listener.get());
|
| +
|
| + listener->Wait();
|
| +
|
| + ASSERT_TRUE(listener->completed());
|
| +
|
| + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kBundleItems); ++i)
|
| + EXPECT_TRUE(service->GetExtensionById(kBundleItems[i].id, false));
|
| +
|
| + // Two extensions and one app should have been installed.
|
| + EXPECT_EQ(3u, bundle->GetItemsInState(STATE_INSTALLED, MATCH_ALL).size());
|
| + EXPECT_EQ(1u, bundle->GetItemsInState(STATE_INSTALLED, MATCH_APPS).size());
|
| + EXPECT_EQ(2u, bundle->GetItemsInState(
|
| + STATE_INSTALLED, MATCH_EXTENSIONS).size());
|
| +
|
| + // Two extensions failed to be installed -- one at the manifest parse state
|
| + // and one while unpacking the crx.
|
| + EXPECT_EQ(2u, bundle->GetItemsInState(STATE_NOT_INSTALLED, MATCH_ALL).size());
|
| +
|
| + EXPECT_TRUE(!bundle->GetFailedHeadingTextForBubble().empty());
|
| + EXPECT_TRUE(!bundle->GetInstalledHeadingTextForBubble().empty());
|
| +}
|
| +
|
| +/* The WebstoreInstaller can't handle 404 errors right now.
|
| +IN_PROC_BROWSER_TEST_F(WebstoreBundleTest, FAILS_InstallSuccessAnd404Failure) {
|
| + WebstoreBundle::SetAutoApproveForTesting(true);
|
| +
|
| + Profile* profile = browser()->profile();
|
| + ExtensionService* service = profile->GetExtensionService();
|
| +
|
| + WebstoreBundle::ItemList items;
|
| + // kBundleItems will install 2 extensions and 1 app.
|
| + AddItemsToItemList(&items, kBundleItems, ARRAYSIZE_UNSAFE(kBundleItems));
|
| +
|
| + // kBadItems will fail parsing of 1 extension.
|
| + AddItemsToItemList(&items, kBadItems, ARRAYSIZE_UNSAFE(kBadItems));
|
| +
|
| + // k404BundleItems will 404 when installing 1 extension.
|
| + AddItemsToItemList(
|
| + &items, k404BundleItems, ARRAYSIZE_UNSAFE(k404BundleItems));
|
| +
|
| + scoped_refptr<WebstoreBundle> bundle = new WebstoreBundle(profile, &items);
|
| + scoped_ptr<WebstoreBundleListener> listener(new WebstoreBundleListener());
|
| + bundle->PromptForApproval(listener.get());
|
| + listener->Wait();
|
| +
|
| + EXPECT_TRUE(listener->approved());
|
| + EXPECT_FALSE(listener->canceled());
|
| +
|
| + std::vector<const WebstoreBundle::Item*> approved_items =
|
| + bundle->GetItemsInState(STATE_APPROVED, MATCH_ALL);
|
| +
|
| + // All members should have been parsed successfully.
|
| + EXPECT_EQ(4u, approved_items.size());
|
| +
|
| + // The items should have had there manifests parsed.
|
| + for (size_t i = 0; i < approved_items.size(); ++i)
|
| + EXPECT_TRUE(approved_items[i]->dummy_extension().get());
|
| +
|
| + // While the one with the syntax error should have gone to
|
| + // STATE_NOT_INSTALLED.
|
| + EXPECT_EQ(1u, bundle->GetItemsInState(STATE_NOT_INSTALLED, MATCH_ALL).size());
|
| +
|
| + // Three extensions and one app should have been parsed successfully.
|
| + EXPECT_EQ(1u, bundle->GetItemsInState(STATE_APPROVED, MATCH_APPS).size());
|
| + EXPECT_EQ(3u, bundle->GetItemsInState(
|
| + STATE_APPROVED, MATCH_EXTENSIONS).size());
|
| +
|
| + EXPECT_TRUE(bundle->HasExtensionsInState(STATE_APPROVED));
|
| + EXPECT_TRUE(bundle->HasAppsInState(STATE_APPROVED));
|
| +
|
| + listener->ResetResults();
|
| +
|
| + // This page doesn't exist, but it doesn't matter.
|
| + ui_test_utils::NavigateToURL(browser(), GetTestServerURL(kGalleryDomain, ""));
|
| + TabStripModel* tabstrip = browser()->tabstrip_model();
|
| +
|
| + // Install the bundle.
|
| + bundle->CompleteInstall(&tabstrip->GetTabContentsAt(0)->controller(),
|
| + listener.get());
|
| +
|
| + listener->Wait();
|
| +
|
| + ASSERT_TRUE(listener->completed());
|
| +
|
| + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kBundleItems); ++i)
|
| + EXPECT_TRUE(service->GetExtensionById(kBundleItems[i].id, false));
|
| +
|
| + EXPECT_EQ(3u, bundle->GetItemsInState(STATE_INSTALLED, MATCH_ALL).size());
|
| + EXPECT_EQ(1u, bundle->GetItemsInState(STATE_INSTALLED, MATCH_APPS).size());
|
| + EXPECT_EQ(2u, bundle->GetItemsInState(
|
| + STATE_INSTALLED, MATCH_EXTENSIONS).size());
|
| + EXPECT_EQ(2u, bundle->GetItemsInState(STATE_NOT_INSTALLED, MATCH_ALL).size());
|
| +
|
| + EXPECT_TRUE(!bundle->GetFailedHeadingTextForBubble().empty());
|
| + EXPECT_TRUE(!bundle->GetInstalledHeadingTextForBubble().empty());
|
| +}
|
| +*/
|
|
|