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()); |
+} |
+*/ |