summaryrefslogtreecommitdiffstats
path: root/chrome/browser/extensions/extension_updater_unittest.cc
diff options
context:
space:
mode:
authorasargent@chromium.org <asargent@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-10 16:43:17 +0000
committerasargent@chromium.org <asargent@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-07-10 16:43:17 +0000
commit93fd78f484b75a75ab346d03cb2d8cb6e5ba4037 (patch)
tree253bcbc69e67d7b5da0cc40789f3156909011ef0 /chrome/browser/extensions/extension_updater_unittest.cc
parent5c4266928220b850dc96474953f0b3a29739e0d3 (diff)
downloadchromium_src-93fd78f484b75a75ab346d03cb2d8cb6e5ba4037.zip
chromium_src-93fd78f484b75a75ab346d03cb2d8cb6e5ba4037.tar.gz
chromium_src-93fd78f484b75a75ab346d03cb2d8cb6e5ba4037.tar.bz2
Implementation of Extension Updater.
This also contains changes to the ExtensionsService to hook up the ExtensionUpdater. BUG=http://crbug.com/12117 TEST=Extensions still work Review URL: http://codereview.chromium.org/149213 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@20379 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/extensions/extension_updater_unittest.cc')
-rw-r--r--chrome/browser/extensions/extension_updater_unittest.cc448
1 files changed, 448 insertions, 0 deletions
diff --git a/chrome/browser/extensions/extension_updater_unittest.cc b/chrome/browser/extensions/extension_updater_unittest.cc
new file mode 100644
index 0000000..6b22617
--- /dev/null
+++ b/chrome/browser/extensions/extension_updater_unittest.cc
@@ -0,0 +1,448 @@
+// Copyright (c) 2009 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 "base/file_util.h"
+#include "base/string_util.h"
+#include "chrome/browser/extensions/extension_updater.h"
+#include "chrome/browser/extensions/extensions_service.h"
+#include "chrome/browser/net/test_url_fetcher_factory.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_error_reporter.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+const char *valid_xml =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </app>"
+"</gupdate>";
+
+const char* missing_appid =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' />"
+" </app>"
+"</gupdate>";
+
+const char* invalid_codebase =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345' status='ok'>"
+" <updatecheck codebase='example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' />"
+" </app>"
+"</gupdate>";
+
+const char* missing_version =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345' status='ok'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' />"
+" </app>"
+"</gupdate>";
+
+const char* invalid_version =
+"<?xml version='1.0'?>"
+"<gupdate xmlns='http://www.google.com/update2/response' protocol='2.0'>"
+" <app appid='12345' status='ok'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx' "
+" version='1.2.3.a'/>"
+" </app>"
+"</gupdate>";
+
+const char *uses_namespace_prefix =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<g:gupdate xmlns:g='http://www.google.com/update2/response' protocol='2.0'>"
+" <g:app appid='12345'>"
+" <g:updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </g:app>"
+"</g:gupdate>";
+
+// Includes unrelated <app> tags from other xml namespaces - this should
+// not cause problems.
+const char *similar_tagnames =
+"<?xml version='1.0' encoding='UTF-8'?>"
+"<gupdate xmlns='http://www.google.com/update2/response'"
+" xmlns:a='http://a' protocol='2.0'>"
+" <a:app/>"
+" <b:app xmlns:b='http://b' />"
+" <app appid='12345'>"
+" <updatecheck codebase='http://example.com/extension_1.2.3.4.crx'"
+" version='1.2.3.4' prodversionmin='2.0.143.0' />"
+" </app>"
+"</gupdate>";
+
+
+// Do-nothing base class for further specialized test classes.
+class MockService : public ExtensionUpdateService {
+ public:
+ MockService() {}
+ virtual ~MockService() {}
+
+ virtual const ExtensionList* extensions() const {
+ EXPECT_TRUE(false);
+ return NULL;
+ }
+
+ virtual void UpdateExtension(const std::string& id,
+ const FilePath& extension_path,
+ bool alert_on_error,
+ ExtensionInstallCallback* callback) {
+ EXPECT_TRUE(false);
+ }
+
+ virtual Extension* GetExtensionById(const std::string& id) {
+ EXPECT_TRUE(false);
+ return NULL;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockService);
+};
+
+
+const char* kIdPrefix = "000000000000000000000000000000000000000";
+
+// Creates test extensions and inserts them into list. The name and
+// version are all based on their index.
+void CreateTestExtensions(int count, ExtensionList *list) {
+ for (int i = 1; i <= count; i++) {
+ DictionaryValue input;
+ Extension* e = new Extension();
+ input.SetString(Extension::kVersionKey, StringPrintf("%d.0.0.0", i));
+ input.SetString(Extension::kNameKey, StringPrintf("Extension %d", i));
+ std::string error;
+ EXPECT_TRUE(e->InitFromValue(input, false, &error));
+ list->push_back(e);
+ }
+}
+
+class ServiceForManifestTests : public MockService {
+ public:
+ ServiceForManifestTests() {}
+
+ virtual ~ServiceForManifestTests() {}
+
+ virtual Extension* GetExtensionById(const std::string& id) {
+ for (ExtensionList::iterator iter = extensions_.begin();
+ iter != extensions_.end(); ++iter) {
+ if ((*iter)->id() == id) {
+ return *iter;
+ }
+ }
+ return NULL;
+ }
+
+ virtual const ExtensionList* extensions() const { return &extensions_; }
+
+ void set_extensions(ExtensionList extensions) {
+ extensions_ = extensions;
+ }
+
+ private:
+ ExtensionList extensions_;
+};
+
+class ServiceForDownloadTests : public MockService {
+ public:
+ explicit ServiceForDownloadTests() : callback_(NULL) {}
+ virtual ~ServiceForDownloadTests() {
+ // expect that cleanup happened via FireInstallCallback
+ EXPECT_EQ(NULL, callback_);
+ EXPECT_TRUE(install_path_.empty());
+ }
+
+ virtual void UpdateExtension(const std::string& id,
+ const FilePath& extension_path,
+ bool alert_on_error,
+ ExtensionInstallCallback* callback) {
+ // Since this mock only has support for one oustanding update, ensure
+ // that the callback doesn't need to be run.
+ EXPECT_TRUE(install_path_.empty());
+ EXPECT_EQ(NULL, callback_);
+
+ extension_id_ = id;
+ install_path_ = extension_path;
+ callback_ = callback;
+ }
+
+ void FireInstallCallback() {
+ EXPECT_TRUE(callback_ != NULL);
+ callback_->Run(install_path_, static_cast<Extension*>(NULL));
+ delete callback_;
+ callback_ = NULL;
+ install_path_ = FilePath();
+ }
+
+ const std::string& extension_id() { return extension_id_; }
+ const FilePath& install_path() { return install_path_; }
+
+ private:
+ std::string extension_id_;
+ FilePath install_path_;
+ ExtensionInstallCallback* callback_;
+};
+
+static const int kUpdateFrequencySecs = 15;
+
+// All of our tests that need to use private APIs of ExtensionUpdater live
+// inside this class (which is a friend to ExtensionUpdater).
+class ExtensionUpdaterTest : public testing::Test {
+ public:
+ static void expectParseFailure(const char *xml) {
+ ScopedVector<ExtensionUpdater::ParseResult> result;
+ EXPECT_FALSE(ExtensionUpdater::Parse(xml, &result.get()));
+ }
+
+ // Make a test ParseResult
+ static ExtensionUpdater::ParseResult* MakeParseResult(std::string id,
+ std::string version,
+ std::string url) {
+ ExtensionUpdater::ParseResult *result = new ExtensionUpdater::ParseResult;
+ result->extension_id = id;
+ result->version.reset(Version::GetVersionFromString(version));
+ result->crx_url = GURL(url);
+ return result;
+ }
+
+ static void TestXmlParsing() {
+ ExtensionErrorReporter::Init(false);
+
+ // Test parsing of a number of invalid xml cases
+ expectParseFailure("");
+ expectParseFailure(missing_appid);
+ expectParseFailure(invalid_codebase);
+ expectParseFailure(missing_version);
+ expectParseFailure(invalid_version);
+
+ // Parse some valid XML, and check that all params came out as expected
+ ScopedVector<ExtensionUpdater::ParseResult> results;
+ EXPECT_TRUE(ExtensionUpdater::Parse(valid_xml, &results.get()));
+ EXPECT_FALSE(results->empty());
+ ExtensionUpdater::ParseResult* firstResult = results->at(0);
+ EXPECT_EQ(GURL("http://example.com/extension_1.2.3.4.crx"),
+ firstResult->crx_url);
+ scoped_ptr<Version> version(Version::GetVersionFromString("1.2.3.4"));
+ EXPECT_EQ(0, version->CompareTo(*firstResult->version.get()));
+ version.reset(Version::GetVersionFromString("2.0.143.0"));
+ EXPECT_EQ(0, version->CompareTo(*firstResult->browser_min_version.get()));
+
+ // Parse some xml that uses namespace prefixes.
+ results.reset();
+ EXPECT_TRUE(ExtensionUpdater::Parse(uses_namespace_prefix, &results.get()));
+ EXPECT_TRUE(ExtensionUpdater::Parse(similar_tagnames, &results.get()));
+ }
+
+ static void TestDetermineUpdates() {
+ // Create a set of test extensions
+ ServiceForManifestTests service;
+ ExtensionList tmp;
+ CreateTestExtensions(3, &tmp);
+ service.set_extensions(tmp);
+
+ MessageLoop message_loop;
+ scoped_refptr<ExtensionUpdater> updater =
+ new ExtensionUpdater(&service, kUpdateFrequencySecs, &message_loop);
+
+ // Check passing an empty list of parse results to DetermineUpdates
+ ExtensionUpdater::ParseResultList updates;
+ std::vector<int> updateable = updater->DetermineUpdates(updates);
+ EXPECT_TRUE(updateable.empty());
+
+ // Create two updates - expect that DetermineUpdates will return the first
+ // one (v1.0 installed, v1.1 available) but not the second one (both
+ // installed and available at v2.0).
+ scoped_ptr<Version> one(Version::GetVersionFromString("1.0"));
+ EXPECT_TRUE(tmp[0]->version()->Equals(*one));
+ updates.push_back(MakeParseResult(tmp[0]->id(),
+ "1.1", "http://localhost/e1_1.1.crx"));
+ updates.push_back(MakeParseResult(tmp[1]->id(),
+ tmp[1]->VersionString(), "http://localhost/e2_2.0.crx"));
+ updateable = updater->DetermineUpdates(updates);
+ EXPECT_EQ(1u, updateable.size());
+ EXPECT_EQ(0, updateable[0]);
+ STLDeleteElements(&updates);
+ }
+
+ static void TestMultipleManifestDownloading() {
+ TestURLFetcherFactory factory;
+ TestURLFetcher* fetcher = NULL;
+ URLFetcher::set_factory(&factory);
+ ServiceForDownloadTests service;
+ MessageLoop message_loop;
+ scoped_refptr<ExtensionUpdater> updater =
+ new ExtensionUpdater(&service, kUpdateFrequencySecs, &message_loop);
+
+ GURL url1("http://localhost/manifest1");
+ GURL url2("http://localhost/manifest2");
+
+ // Request 2 update checks - the first should begin immediately and the
+ // second one should be queued up.
+ updater->StartUpdateCheck(url1);
+ updater->StartUpdateCheck(url2);
+
+ std::string manifest_data("invalid xml");
+ fetcher = factory.GetFetcherByID(ExtensionUpdater::kManifestFetcherId);
+ EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
+ fetcher->delegate()->OnURLFetchComplete(
+ fetcher, url1, URLRequestStatus(), 200, ResponseCookies(),
+ manifest_data);
+
+ // Now that the first request is complete, make sure the second one has
+ // been started.
+ fetcher = factory.GetFetcherByID(ExtensionUpdater::kManifestFetcherId);
+ EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
+ fetcher->delegate()->OnURLFetchComplete(
+ fetcher, url2, URLRequestStatus(), 200, ResponseCookies(),
+ manifest_data);
+ }
+
+ static void TestSingleExtensionDownloading() {
+ MessageLoop message_loop;
+ TestURLFetcherFactory factory;
+ TestURLFetcher* fetcher = NULL;
+ URLFetcher::set_factory(&factory);
+ ServiceForDownloadTests service;
+ scoped_refptr<ExtensionUpdater> updater =
+ new ExtensionUpdater(&service, kUpdateFrequencySecs, &message_loop);
+
+ GURL test_url("http://localhost/extension.crx");
+
+ std::string id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+
+ updater->FetchUpdatedExtension(id, test_url);
+
+ // Call back the ExtensionUpdater with a 200 response and some test data
+ std::string extension_data("whatever");
+ fetcher = factory.GetFetcherByID(ExtensionUpdater::kExtensionFetcherId);
+ EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
+ fetcher->delegate()->OnURLFetchComplete(
+ fetcher, test_url, URLRequestStatus(), 200, ResponseCookies(),
+ extension_data);
+
+ message_loop.RunAllPending();
+
+ // Expect that ExtensionUpdater asked the mock extensions service to install
+ // a file with the test data for the right id.
+ EXPECT_EQ(id, service.extension_id());
+ FilePath tmpfile_path = service.install_path();
+ EXPECT_FALSE(tmpfile_path.empty());
+ std::string file_contents;
+ EXPECT_TRUE(file_util::ReadFileToString(tmpfile_path, &file_contents));
+ EXPECT_TRUE(extension_data == file_contents);
+
+ service.FireInstallCallback();
+
+ message_loop.RunAllPending();
+
+ // Make sure the temp file is cleaned up
+ EXPECT_FALSE(file_util::PathExists(tmpfile_path));
+
+ URLFetcher::set_factory(NULL);
+ }
+
+ static void TestMultipleExtensionDownloading() {
+ MessageLoopForUI message_loop;
+ TestURLFetcherFactory factory;
+ TestURLFetcher* fetcher = NULL;
+ URLFetcher::set_factory(&factory);
+ ServiceForDownloadTests service;
+ scoped_refptr<ExtensionUpdater> updater =
+ new ExtensionUpdater(&service, kUpdateFrequencySecs, &message_loop);
+
+ GURL url1("http://localhost/extension1.crx");
+ GURL url2("http://localhost/extension2.crx");
+
+ std::string id1 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
+ std::string id2 = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
+
+ // Start two fetches
+ updater->FetchUpdatedExtension(id1, url1);
+ updater->FetchUpdatedExtension(id2, url2);
+
+ // Make the first fetch complete.
+ std::string extension_data1("whatever");
+ fetcher = factory.GetFetcherByID(ExtensionUpdater::kExtensionFetcherId);
+ EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
+ fetcher->delegate()->OnURLFetchComplete(
+ fetcher, url1, URLRequestStatus(), 200, ResponseCookies(),
+ extension_data1);
+ message_loop.RunAllPending();
+
+ // Expect that the service was asked to do an install with the right data,
+ // and fire the callback indicating the install finished.
+ FilePath tmpfile_path = service.install_path();
+ EXPECT_FALSE(tmpfile_path.empty());
+ EXPECT_EQ(id1, service.extension_id());
+ service.FireInstallCallback();
+
+ // Make sure the tempfile got cleaned up.
+ message_loop.RunAllPending();
+ EXPECT_FALSE(tmpfile_path.empty());
+ EXPECT_FALSE(file_util::PathExists(tmpfile_path));
+
+ // Make sure the second fetch finished and asked the service to do an
+ // update.
+ std::string extension_data2("whatever2");
+ fetcher = factory.GetFetcherByID(ExtensionUpdater::kExtensionFetcherId);
+ EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
+ fetcher->delegate()->OnURLFetchComplete(
+ fetcher, url2, URLRequestStatus(), 200, ResponseCookies(),
+ extension_data2);
+ message_loop.RunAllPending();
+ EXPECT_EQ(id2, service.extension_id());
+ EXPECT_FALSE(service.install_path().empty());
+
+ // Make sure the correct crx contents were passed for the update call.
+ std::string file_contents;
+ EXPECT_TRUE(file_util::ReadFileToString(service.install_path(),
+ &file_contents));
+ EXPECT_TRUE(extension_data2 == file_contents);
+ service.FireInstallCallback();
+ message_loop.RunAllPending();
+ }
+};
+
+// Because we test some private methods of ExtensionUpdater, it's easer for the
+// actual test code to live in ExtenionUpdaterTest methods instead of TEST_F
+// subclasses where friendship with ExtenionUpdater is not inherited.
+
+TEST(ExtensionUpdaterTest, TestXmlParsing) {
+ ExtensionUpdaterTest::TestXmlParsing();
+}
+
+TEST(ExtensionUpdaterTest, TestDetermineUpdates) {
+ ExtensionUpdaterTest::TestDetermineUpdates();
+}
+
+TEST(ExtensionUpdaterTest, TestMultipleManifestDownloading) {
+ ExtensionUpdaterTest::TestMultipleManifestDownloading();
+}
+
+TEST(ExtensionUpdaterTest, TestSingleExtensionDownloading) {
+ ExtensionUpdaterTest::TestSingleExtensionDownloading();
+}
+
+TEST(ExtensionUpdaterTest, TestMultipleExtensionDownloading) {
+ ExtensionUpdaterTest::TestMultipleExtensionDownloading();
+}
+
+
+// TODO(asargent) - (http://crbug.com/12780) add tests for:
+// -prodversionmin (shouldn't update if browser version too old)
+// -manifests & updates arriving out of order / interleaved
+// -Profile::GetDefaultRequestContext() returning null
+// (should not crash, but just do check later)
+// -malformed update url (empty, file://, has query, has a # fragment, etc.)
+// -An extension gets uninstalled while updates are in progress (so it doesn't
+// "come back from the dead")
+// -An extension gets manually updated to v3 while we're downloading v2 (ie
+// you don't get downgraded accidentally)
+// -An update manifest mentions multiple updates