// Copyright 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 "chrome/browser/component_updater/test/component_updater_service_unittest.h"
#include "base/file_util.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/component_updater/component_updater_utils.h"
#include "chrome/browser/component_updater/test/test_installer.h"
#include "chrome/common/chrome_paths.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/resource_controller.h"
#include "content/public/browser/resource_request_info.h"
#include "content/public/browser/resource_throttle.h"
#include "libxml/globals.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_request_test_util.h"
#include "url/gurl.h"

using content::BrowserThread;

using ::testing::_;
using ::testing::AnyNumber;
using ::testing::InSequence;
using ::testing::Mock;

namespace component_updater {

#define POST_INTERCEPT_SCHEME "https"
#define POST_INTERCEPT_HOSTNAME "localhost2"
#define POST_INTERCEPT_PATH "/update2"

MockServiceObserver::MockServiceObserver() {
}

MockServiceObserver::~MockServiceObserver() {
}

bool PartialMatch::Match(const std::string& actual) const {
  return actual.find(expected_) != std::string::npos;
}

TestConfigurator::TestConfigurator()
    : initial_time_(0),
      times_(1),
      recheck_time_(0),
      ondemand_time_(0),
      cus_(NULL),
      context_(new net::TestURLRequestContextGetter(
          BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO))) {
}

TestConfigurator::~TestConfigurator() {
}

int TestConfigurator::InitialDelay() {
  return initial_time_;
}

int TestConfigurator::NextCheckDelay() {
  // This is called when a new full cycle of checking for updates is going
  // to happen. In test we normally only test one cycle so it is a good
  // time to break from the test messageloop Run() method so the test can
  // finish.
  if (--times_ <= 0) {
    quit_closure_.Run();
    return 0;
  }
  return 1;
}

int TestConfigurator::StepDelay() {
  return 0;
}

int TestConfigurator::StepDelayMedium() {
  return NextCheckDelay();
}

int TestConfigurator::MinimumReCheckWait() {
  return recheck_time_;
}

int TestConfigurator::OnDemandDelay() {
  return ondemand_time_;
}

GURL TestConfigurator::UpdateUrl() {
  return GURL(POST_INTERCEPT_SCHEME
              "://" POST_INTERCEPT_HOSTNAME POST_INTERCEPT_PATH);
}

GURL TestConfigurator::PingUrl() {
  return UpdateUrl();
}

std::string TestConfigurator::ExtraRequestParams() {
  return "extra=\"foo\"";
}

size_t TestConfigurator::UrlSizeLimit() {
  return 256;
}

net::URLRequestContextGetter* TestConfigurator::RequestContext() {
  return context_.get();
}

// Don't use the utility process to run code out-of-process.
bool TestConfigurator::InProcess() {
  return true;
}

bool TestConfigurator::DeltasEnabled() const {
  return true;
}

bool TestConfigurator::UseBackgroundDownloader() const {
  return false;
}

// Set how many update checks are called, the default value is just once.
void TestConfigurator::SetLoopCount(int times) {
  times_ = times;
}

void TestConfigurator::SetRecheckTime(int seconds) {
  recheck_time_ = seconds;
}

void TestConfigurator::SetOnDemandTime(int seconds) {
  ondemand_time_ = seconds;
}

void TestConfigurator::SetComponentUpdateService(ComponentUpdateService* cus) {
  cus_ = cus;
}

void TestConfigurator::SetQuitClosure(const base::Closure& quit_closure) {
  quit_closure_ = quit_closure;
}

void TestConfigurator::SetInitialDelay(int seconds) {
  initial_time_ = seconds;
}

InterceptorFactory::InterceptorFactory()
    : URLRequestPostInterceptorFactory(POST_INTERCEPT_SCHEME,
                                       POST_INTERCEPT_HOSTNAME) {
}

InterceptorFactory::~InterceptorFactory() {
}

URLRequestPostInterceptor* InterceptorFactory::CreateInterceptor() {
  return URLRequestPostInterceptorFactory::CreateInterceptor(
      base::FilePath::FromUTF8Unsafe(POST_INTERCEPT_PATH));
}

ComponentUpdaterTest::ComponentUpdaterTest()
    : test_config_(NULL),
      thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {
  // The component updater instance under test.
  test_config_ = new TestConfigurator;
  component_updater_.reset(ComponentUpdateServiceFactory(test_config_));
  test_config_->SetComponentUpdateService(component_updater_.get());

  // The test directory is chrome/test/data/components.
  PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_);
  test_data_dir_ = test_data_dir_.AppendASCII("components");

  net::URLFetcher::SetEnableInterceptionForTests(true);
}

ComponentUpdaterTest::~ComponentUpdaterTest() {
  net::URLFetcher::SetEnableInterceptionForTests(false);
}

void ComponentUpdaterTest::SetUp() {
  get_interceptor_.reset(new GetInterceptor);
  interceptor_factory_.reset(new InterceptorFactory);
  post_interceptor_ = interceptor_factory_->CreateInterceptor();
  EXPECT_TRUE(post_interceptor_);
}

void ComponentUpdaterTest::TearDown() {
  interceptor_factory_.reset();
  get_interceptor_.reset();
  xmlCleanupGlobals();
}

ComponentUpdateService* ComponentUpdaterTest::component_updater() {
  return component_updater_.get();
}

// Makes the full path to a component updater test file.
const base::FilePath ComponentUpdaterTest::test_file(const char* file) {
  return test_data_dir_.AppendASCII(file);
}

TestConfigurator* ComponentUpdaterTest::test_configurator() {
  return test_config_;
}

ComponentUpdateService::Status ComponentUpdaterTest::RegisterComponent(
    CrxComponent* com,
    TestComponents component,
    const Version& version,
    TestInstaller* installer) {
  if (component == kTestComponent_abag) {
    com->name = "test_abag";
    com->pk_hash.assign(abag_hash, abag_hash + arraysize(abag_hash));
  } else if (component == kTestComponent_jebg) {
    com->name = "test_jebg";
    com->pk_hash.assign(jebg_hash, jebg_hash + arraysize(jebg_hash));
  } else {
    com->name = "test_ihfo";
    com->pk_hash.assign(ihfo_hash, ihfo_hash + arraysize(ihfo_hash));
  }
  com->version = version;
  com->installer = installer;
  return component_updater_->RegisterComponent(*com);
}

void ComponentUpdaterTest::RunThreads() {
  base::RunLoop runloop;
  test_configurator()->SetQuitClosure(runloop.QuitClosure());
  runloop.Run();

  // Since some tests need to drain currently enqueued tasks such as network
  // intercepts on the IO thread, run the threads until they are
  // idle. The component updater service won't loop again until the loop count
  // is set and the service is started.
  RunThreadsUntilIdle();
}

void ComponentUpdaterTest::RunThreadsUntilIdle() {
  base::RunLoop().RunUntilIdle();
}

ComponentUpdateService::Status OnDemandTester::OnDemand(
    ComponentUpdateService* cus,
    const std::string& component_id) {
  return cus->GetOnDemandUpdater().OnDemandUpdate(component_id);
}

// Verify that our test fixture work and the component updater can
// be created and destroyed with no side effects.
TEST_F(ComponentUpdaterTest, VerifyFixture) {
  EXPECT_TRUE(component_updater() != NULL);
}

// Verify that the component updater can be caught in a quick
// start-shutdown situation. Failure of this test will be a crash.
TEST_F(ComponentUpdaterTest, StartStop) {
  component_updater()->Start();
  RunThreadsUntilIdle();
  component_updater()->Stop();
}

// Verify that when the server has no updates, we go back to sleep and
// the COMPONENT_UPDATER_STARTED and COMPONENT_UPDATER_SLEEPING notifications
// are generated. No pings are sent.
TEST_F(ComponentUpdaterTest, CheckCrxSleep) {
  MockServiceObserver observer;

  EXPECT_CALL(observer,
              OnEvent(ServiceObserver::COMPONENT_UPDATER_STARTED, ""))
      .Times(1);
  EXPECT_CALL(observer,
              OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
      .Times(2);
  EXPECT_CALL(observer,
              OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                      "abagagagagagagagagagagagagagagag"))
      .Times(2);

  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));

  TestInstaller installer;
  CrxComponent com;
  component_updater()->AddObserver(&observer);
  EXPECT_EQ(
      ComponentUpdateService::kOk,
      RegisterComponent(&com, kTestComponent_abag, Version("1.1"), &installer));

  // We loop twice, but there are no updates so we expect two sleep messages.
  test_configurator()->SetLoopCount(2);
  component_updater()->Start();
  RunThreads();

  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->install_count());

  // Expect to see the two update check requests and no other requests,
  // including pings.
  EXPECT_EQ(2, post_interceptor_->GetHitCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(2, post_interceptor_->GetCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[0].find(
          "<app appid=\"abagagagagagagagagagagagagagagag\" version=\"1.1\">"
          "<updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[1].find(
          "<app appid=\"abagagagagagagagagagagagagagagag\" version=\"1.1\">"
          "<updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();

  component_updater()->Stop();

  // Loop twice again but this case we simulate a server error by returning
  // an empty file. Expect the behavior of the service to be the same as before.
  EXPECT_CALL(observer, OnEvent(ServiceObserver::COMPONENT_UPDATER_STARTED, ""))
      .Times(1);
  EXPECT_CALL(observer,
              OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
      .Times(2);
  EXPECT_CALL(observer,
              OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                      "abagagagagagagagagagagagagagagag"))
      .Times(2);

  post_interceptor_->Reset();
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_empty")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_empty")));

  test_configurator()->SetLoopCount(2);
  component_updater()->Start();
  RunThreads();

  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->install_count());

  EXPECT_EQ(2, post_interceptor_->GetHitCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(2, post_interceptor_->GetCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[0].find(
          "<app appid=\"abagagagagagagagagagagagagagagag\" version=\"1.1\">"
          "<updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[1].find(
          "<app appid=\"abagagagagagagagagagagagagagagag\" version=\"1.1\">"
          "<updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();

  component_updater()->Stop();
}

// Verify that we can check for updates and install one component. Besides
// the notifications above COMPONENT_UPDATE_FOUND and COMPONENT_UPDATE_READY
// should have been fired. We do two loops so the second time around there
// should be nothing left to do.
// We also check that the following network requests are issued:
// 1- update check
// 2- download crx
// 3- ping
// 4- second update check.
TEST_F(ComponentUpdaterTest, InstallCrx) {
  MockServiceObserver observer;
  {
    InSequence seq;
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_STARTED, ""))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATE_FOUND,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "abagagagagagagagagagagagagagagag"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATE_DOWNLOADING,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
                .Times(AnyNumber());
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATE_READY,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATED,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "abagagagagagagagagagagagagagagag"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
        .Times(1);
  }

  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));

  get_interceptor_->SetResponse(
      GURL(expected_crx_url),
      test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));

  component_updater()->AddObserver(&observer);

  TestInstaller installer1;
  CrxComponent com1;
  RegisterComponent(&com1, kTestComponent_jebg, Version("0.9"), &installer1);
  TestInstaller installer2;
  CrxComponent com2;
  RegisterComponent(&com2, kTestComponent_abag, Version("2.2"), &installer2);

  test_configurator()->SetLoopCount(2);
  component_updater()->Start();
  RunThreads();

  EXPECT_EQ(0, static_cast<TestInstaller*>(com1.installer)->error());
  EXPECT_EQ(1, static_cast<TestInstaller*>(com1.installer)->install_count());
  EXPECT_EQ(0, static_cast<TestInstaller*>(com2.installer)->error());
  EXPECT_EQ(0, static_cast<TestInstaller*>(com2.installer)->install_count());

  // Expect three request in total: two update checks and one ping.
  EXPECT_EQ(3, post_interceptor_->GetHitCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(3, post_interceptor_->GetCount())
      << post_interceptor_->GetRequestsAsString();

  // Expect one component download.
  EXPECT_EQ(1, get_interceptor_->GetHitCount());

  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[0].find(
          "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"0.9\">"
          "<updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[0].find(
          "<app appid=\"abagagagagagagagagagagagagagagag\" version=\"2.2\">"
          "<updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();

  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[1].find(
          "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" "
          "version=\"0.9\" nextversion=\"1.0\">"
          "<event eventtype=\"3\" eventresult=\"1\"/>"))
      << post_interceptor_->GetRequestsAsString();

  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[2].find(
          "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"1.0\">"
          "<updatecheck /></app>"));
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[2].find(
          "<app appid=\"abagagagagagagagagagagagagagagag\" version=\"2.2\">"
          "<updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();

  // Test the protocol version is correct and the extra request attributes
  // are included in the request.
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[0].find(
          "request protocol=\"3.0\" extra=\"foo\""))
      << post_interceptor_->GetRequestsAsString();

  // Tokenize the request string to look for specific attributes, which
  // are important for backward compatibility with the version v2 of the update
  // protocol. In this case, inspect the <request>, which is the first element
  // after the xml declaration of the update request body.
  // Expect to find the |os|, |arch|, |prodchannel|, and |prodversion|
  // attributes:
  // <?xml version="1.0" encoding="UTF-8"?>
  // <request... os=... arch=... prodchannel=... prodversion=...>
  // ...
  // </request>
  const std::string update_request(post_interceptor_->GetRequests()[0]);
  std::vector<base::StringPiece> elements;
  Tokenize(update_request, "<>", &elements);
  EXPECT_NE(string::npos, elements[1].find(" os="));
  EXPECT_NE(string::npos, elements[1].find(" arch="));
  EXPECT_NE(string::npos, elements[1].find(" prodchannel="));
  EXPECT_NE(string::npos, elements[1].find(" prodversion="));

  // Look for additional attributes of the request, such as |version|,
  // |requestid|, |lang|, and |nacl_arch|.
  EXPECT_NE(string::npos, elements[1].find(" version="));
  EXPECT_NE(string::npos, elements[1].find(" requestid="));
  EXPECT_NE(string::npos, elements[1].find(" lang="));
  EXPECT_NE(string::npos, elements[1].find(" nacl_arch="));

  component_updater()->Stop();
}

// This test checks that the "prodversionmin" value is handled correctly. In
// particular there should not be an install because the minimum product
// version is much higher than of chrome.
TEST_F(ComponentUpdaterTest, ProdVersionCheck) {
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_2.xml")));

  get_interceptor_->SetResponse(
      GURL(expected_crx_url),
      test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));

  TestInstaller installer;
  CrxComponent com;
  RegisterComponent(&com, kTestComponent_jebg, Version("0.9"), &installer);

  test_configurator()->SetLoopCount(1);
  component_updater()->Start();
  RunThreads();

  // Expect one update check and no ping.
  EXPECT_EQ(1, post_interceptor_->GetHitCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(1, post_interceptor_->GetCount())
      << post_interceptor_->GetRequestsAsString();

  // Expect no download to occur.
  EXPECT_EQ(0, get_interceptor_->GetHitCount());

  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->install_count());

  component_updater()->Stop();
}

// Test that a update check due to an on demand call can cause installs.
// Here is the timeline:
//  - First loop: we return a reply that indicates no update, so
//    nothing happens.
//  - We make an on demand call.
//  - This triggers a second loop, which has a reply that triggers an install.
TEST_F(ComponentUpdaterTest, OnDemandUpdate) {
  MockServiceObserver observer;
  {
    InSequence seq;
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_STARTED, ""))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "abagagagagagagagagagagagagagagag"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_STARTED, ""))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATE_FOUND,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "abagagagagagagagagagagagagagagag"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATE_DOWNLOADING,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
                .Times(AnyNumber());
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATE_READY,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATED,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
        .Times(1);
  }

  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_empty")));

  get_interceptor_->SetResponse(
      GURL(expected_crx_url),
      test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));

  component_updater()->AddObserver(&observer);

  TestInstaller installer1;
  CrxComponent com1;
  RegisterComponent(&com1, kTestComponent_abag, Version("2.2"), &installer1);
  TestInstaller installer2;
  CrxComponent com2;
  RegisterComponent(&com2, kTestComponent_jebg, Version("0.9"), &installer2);

  // No update normally.
  test_configurator()->SetLoopCount(1);
  component_updater()->Start();
  RunThreads();
  component_updater()->Stop();

  EXPECT_EQ(1, post_interceptor_->GetHitCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(1, post_interceptor_->GetCount())
      << post_interceptor_->GetRequestsAsString();

  EXPECT_EQ(0, get_interceptor_->GetHitCount());

  // Update after an on-demand check is issued.
  post_interceptor_->Reset();
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));

  EXPECT_EQ(
      ComponentUpdateService::kOk,
      OnDemandTester::OnDemand(component_updater(), GetCrxComponentID(com2)));
  test_configurator()->SetLoopCount(1);
  component_updater()->Start();
  RunThreads();

  EXPECT_EQ(0, static_cast<TestInstaller*>(com1.installer)->error());
  EXPECT_EQ(0, static_cast<TestInstaller*>(com1.installer)->install_count());
  EXPECT_EQ(0, static_cast<TestInstaller*>(com2.installer)->error());
  EXPECT_EQ(1, static_cast<TestInstaller*>(com2.installer)->install_count());

  EXPECT_EQ(2, post_interceptor_->GetHitCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(2, post_interceptor_->GetCount())
      << post_interceptor_->GetRequestsAsString();

  EXPECT_EQ(1, get_interceptor_->GetHitCount());

  // Expect the update check to contain an "ondemand" request for the
  // second component (com2) and a normal request for the other component.
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[0].find(
          "<app appid=\"abagagagagagagagagagagagagagagag\" "
          "version=\"2.2\"><updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[0].find(
          "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" "
          "version=\"0.9\" installsource=\"ondemand\"><updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[1].find(
          "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" "
          "version=\"0.9\" nextversion=\"1.0\">"
          "<event eventtype=\"3\" eventresult=\"1\"/>"))
      << post_interceptor_->GetRequestsAsString();

  // Also check what happens if previous check too soon. It works, since this
  // direct OnDemand call does not implement a cooldown.
  test_configurator()->SetOnDemandTime(60 * 60);
  EXPECT_EQ(
      ComponentUpdateService::kOk,
      OnDemandTester::OnDemand(component_updater(), GetCrxComponentID(com2)));
  // Okay, now reset to 0 for the other tests.
  test_configurator()->SetOnDemandTime(0);
  component_updater()->Stop();

  // Test a few error cases. NOTE: We don't have callbacks for
  // when the updates failed yet.
  EXPECT_TRUE(Mock::VerifyAndClearExpectations(&observer));
  {
    InSequence seq;
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_STARTED, ""))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "abagagagagagagagagagagagagagagag"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
        .Times(1);
  }

  // No update: error from no server response
  post_interceptor_->Reset();
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_empty")));

  test_configurator()->SetLoopCount(1);
  component_updater()->Start();
  EXPECT_EQ(
      ComponentUpdateService::kOk,
      OnDemandTester::OnDemand(component_updater(), GetCrxComponentID(com2)));
  RunThreads();
  component_updater()->Stop();

  EXPECT_EQ(1, post_interceptor_->GetHitCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(1, post_interceptor_->GetCount())
      << post_interceptor_->GetRequestsAsString();

  // No update: already updated to 1.0 so nothing new
  EXPECT_TRUE(Mock::VerifyAndClearExpectations(&observer));
  {
    InSequence seq;
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_STARTED, ""))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "abagagagagagagagagagagagagagagag"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
        .Times(1);
  }

  post_interceptor_->Reset();
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));

  test_configurator()->SetLoopCount(1);
  component_updater()->Start();
  EXPECT_EQ(
      ComponentUpdateService::kOk,
      OnDemandTester::OnDemand(component_updater(), GetCrxComponentID(com2)));
  RunThreads();

  EXPECT_EQ(1, post_interceptor_->GetHitCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(1, post_interceptor_->GetCount())
      << post_interceptor_->GetRequestsAsString();

  component_updater()->Stop();
}

// Verify that a previously registered component can get re-registered
// with a different version.
TEST_F(ComponentUpdaterTest, CheckReRegistration) {
  MockServiceObserver observer;
  {
    InSequence seq;
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_STARTED, ""))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATE_FOUND,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "abagagagagagagagagagagagagagagag"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATE_DOWNLOADING,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
                .Times(AnyNumber());
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATE_READY,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATED,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "abagagagagagagagagagagagagagagag"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
        .Times(1);
  }

  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));

  get_interceptor_->SetResponse(
      GURL(expected_crx_url),
      test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));

  component_updater()->AddObserver(&observer);

  TestInstaller installer1;
  CrxComponent com1;
  RegisterComponent(&com1, kTestComponent_jebg, Version("0.9"), &installer1);
  TestInstaller installer2;
  CrxComponent com2;
  RegisterComponent(&com2, kTestComponent_abag, Version("2.2"), &installer2);

  // Loop twice to issue two checks: (1) with original 0.9 version, update to
  // 1.0, and do the second check (2) with the updated 1.0 version.
  test_configurator()->SetLoopCount(2);
  component_updater()->Start();
  RunThreads();

  EXPECT_EQ(0, static_cast<TestInstaller*>(com1.installer)->error());
  EXPECT_EQ(1, static_cast<TestInstaller*>(com1.installer)->install_count());
  EXPECT_EQ(0, static_cast<TestInstaller*>(com2.installer)->error());
  EXPECT_EQ(0, static_cast<TestInstaller*>(com2.installer)->install_count());

  EXPECT_EQ(3, post_interceptor_->GetHitCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(1, get_interceptor_->GetHitCount());

  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[0].find(
          "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"0.9\">"
          "<updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[1].find(
          "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" "
          "version=\"0.9\" nextversion=\"1.0\">"
          "<event eventtype=\"3\" eventresult=\"1\"/>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[2].find(
          "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"1.0\">"
          "<updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();

  component_updater()->Stop();

  // Now re-register, pretending to be an even newer version (2.2)
  EXPECT_TRUE(Mock::VerifyAndClearExpectations(&observer));
  {
    InSequence seq;
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_STARTED, ""))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "jebgalgnebhfojomionfpkfelancnnkf"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "abagagagagagagagagagagagagagagag"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
        .Times(1);
  }

  post_interceptor_->Reset();
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));

  TestInstaller installer3;
  EXPECT_EQ(ComponentUpdateService::kReplaced,
            RegisterComponent(
                &com1, kTestComponent_jebg, Version("2.2"), &installer3));

  // Loop once just to notice the check happening with the re-register version.
  test_configurator()->SetLoopCount(1);
  component_updater()->Start();
  RunThreads();

  // We created a new installer, so the counts go back to 0.
  EXPECT_EQ(0, static_cast<TestInstaller*>(com1.installer)->error());
  EXPECT_EQ(0, static_cast<TestInstaller*>(com1.installer)->install_count());
  EXPECT_EQ(0, static_cast<TestInstaller*>(com2.installer)->error());
  EXPECT_EQ(0, static_cast<TestInstaller*>(com2.installer)->install_count());

  // One update check and no additional pings are expected.
  EXPECT_EQ(1, post_interceptor_->GetHitCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(1, post_interceptor_->GetCount())
      << post_interceptor_->GetRequestsAsString();

  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[0].find(
          "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"2.2\">"
          "<updatecheck /></app>"));

  component_updater()->Stop();
}

// Verify that we can download and install a component and a differential
// update to that component. We do three loops; the final loop should do
// nothing.
// We also check that exactly 5 non-ping network requests are issued:
// 1- update check (response: v1 available)
// 2- download crx (v1)
// 3- update check (response: v2 available)
// 4- download differential crx (v1 to v2)
// 5- update check (response: no further update available)
// There should be two pings, one for each update. The second will bear a
// diffresult=1, while the first will not.
TEST_F(ComponentUpdaterTest, DifferentialUpdate) {
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"),
      test_file("updatecheck_diff_reply_1.xml")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"),
      test_file("updatecheck_diff_reply_2.xml")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"),
      test_file("updatecheck_diff_reply_3.xml")));

  get_interceptor_->SetResponse(
      GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"),
      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"));
  get_interceptor_->SetResponse(
      GURL("http://localhost/download/"
           "ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"),
      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"));

  VersionedTestInstaller installer;
  CrxComponent com;
  RegisterComponent(&com, kTestComponent_ihfo, Version("0.0"), &installer);

  test_configurator()->SetLoopCount(3);
  component_updater()->Start();
  RunThreads();

  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
  EXPECT_EQ(2, static_cast<TestInstaller*>(com.installer)->install_count());

  EXPECT_EQ(5, post_interceptor_->GetHitCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(5, post_interceptor_->GetCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(2, get_interceptor_->GetHitCount());

  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[0].find(
          "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"0.0\">"
          "<updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[1].find(
          "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" "
          "version=\"0.0\" nextversion=\"1.0\">"
          "<event eventtype=\"3\" eventresult=\"1\" nextfp=\"1\"/>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[2].find(
          "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"1.0\">"
          "<updatecheck /><packages><package fp=\"1\"/></packages></app>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[3].find(
          "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" "
          "version=\"1.0\" nextversion=\"2.0\">"
          "<event eventtype=\"3\" eventresult=\"1\" diffresult=\"1\" "
          "previousfp=\"1\" nextfp=\"22\"/>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[4].find(
          "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"2.0\">"
          "<updatecheck /><packages><package fp=\"22\"/></packages></app>"))
      << post_interceptor_->GetRequestsAsString();
  component_updater()->Stop();
}

// Verify that component installation falls back to downloading and installing
// a full update if the differential update fails (in this case, because the
// installer does not know about the existing files). We do two loops; the final
// loop should do nothing.
// We also check that exactly 4 non-ping network requests are issued:
// 1- update check (loop 1)
// 2- download differential crx
// 3- download full crx
// 4- update check (loop 2 - no update available)
// There should be one ping for the first attempted update.
// This test is flaky on Android. crbug.com/329883
#if defined(OS_ANDROID)
#define MAYBE_DifferentialUpdateFails DISABLED_DifferentialUpdateFails
#else
#define MAYBE_DifferentialUpdateFails DifferentialUpdateFails
#endif
TEST_F(ComponentUpdaterTest, MAYBE_DifferentialUpdateFails) {
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"),
      test_file("updatecheck_diff_reply_2.xml")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"),
      test_file("updatecheck_diff_reply_3.xml")));

  get_interceptor_->SetResponse(
      GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"),
      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"));
  get_interceptor_->SetResponse(
      GURL("http://localhost/download/"
          "ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"),
      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"));
  get_interceptor_->SetResponse(
      GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"),
      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"));

  TestInstaller installer;
  CrxComponent com;
  RegisterComponent(&com, kTestComponent_ihfo, Version("1.0"), &installer);

  test_configurator()->SetLoopCount(2);
  component_updater()->Start();
  RunThreads();

  // A failed differential update does not count as a failed install.
  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
  EXPECT_EQ(1, static_cast<TestInstaller*>(com.installer)->install_count());

  EXPECT_EQ(3, post_interceptor_->GetHitCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(3, post_interceptor_->GetCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(2, get_interceptor_->GetHitCount());

  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[0].find(
          "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"1.0\">"
          "<updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[1].find(
          "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" "
          "version=\"1.0\" nextversion=\"2.0\">"
          "<event eventtype=\"3\" eventresult=\"1\" diffresult=\"0\" "
          "differrorcat=\"2\" differrorcode=\"16\" nextfp=\"22\"/>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[2].find(
          "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"2.0\">"
          "<updatecheck /><packages><package fp=\"22\"/></packages></app>"))
      << post_interceptor_->GetRequestsAsString();

  component_updater()->Stop();
}

// Test is flakey on Android bots. See crbug.com/331420.
#if defined(OS_ANDROID)
#define MAYBE_CheckFailedInstallPing DISABLED_CheckFailedInstallPing
#else
#define MAYBE_CheckFailedInstallPing CheckFailedInstallPing
#endif
// Verify that a failed installation causes an install failure ping.
TEST_F(ComponentUpdaterTest, MAYBE_CheckFailedInstallPing) {
  // This test installer reports installation failure.
  class : public TestInstaller {
    virtual bool Install(const base::DictionaryValue& manifest,
                         const base::FilePath& unpack_path) OVERRIDE {
      ++install_count_;
      base::DeleteFile(unpack_path, true);
      return false;
    }
  } installer;

  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
  get_interceptor_->SetResponse(
      GURL(expected_crx_url),
      test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"));

  // Start with 0.9, and attempt update to 1.0.
  // Loop twice to issue two checks: (1) with original 0.9 version
  // and (2), which should retry with 0.9.
  CrxComponent com;
  RegisterComponent(&com, kTestComponent_jebg, Version("0.9"), &installer);

  test_configurator()->SetLoopCount(2);
  component_updater()->Start();
  RunThreads();

  EXPECT_EQ(4, post_interceptor_->GetHitCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(2, get_interceptor_->GetHitCount());

  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[0].find(
          "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"0.9\">"
          "<updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[1].find(
          "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" "
          "version=\"0.9\" nextversion=\"1.0\">"
          "<event eventtype=\"3\" eventresult=\"0\" "
          "errorcat=\"3\" errorcode=\"9\"/>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[2].find(
          "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"0.9\">"
          "<updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[3].find(
          "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" "
          "version=\"0.9\" nextversion=\"1.0\">"
          "<event eventtype=\"3\" eventresult=\"0\" "
          "errorcat=\"3\" errorcode=\"9\"/>"))
      << post_interceptor_->GetRequestsAsString();

  // Loop once more, but expect no ping because a noupdate response is issued.
  // This is necessary to clear out the fire-and-forget ping from the previous
  // iteration.
  post_interceptor_->Reset();
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"),
      test_file("updatecheck_reply_noupdate.xml")));

  test_configurator()->SetLoopCount(1);
  component_updater()->Start();
  RunThreads();

  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
  EXPECT_EQ(2, static_cast<TestInstaller*>(com.installer)->install_count());

  EXPECT_EQ(1, post_interceptor_->GetHitCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(1, post_interceptor_->GetCount())
      << post_interceptor_->GetRequestsAsString();

  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[0].find(
          "<app appid=\"jebgalgnebhfojomionfpkfelancnnkf\" version=\"0.9\">"
          "<updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();

  component_updater()->Stop();
}

// Verify that we successfully propagate a patcher error.
// ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad.crx contains an incorrect
// patching instruction that should fail.
TEST_F(ComponentUpdaterTest, DifferentialUpdateFailErrorcode) {
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"),
      test_file("updatecheck_diff_reply_1.xml")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"),
      test_file("updatecheck_diff_reply_2.xml")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(new PartialMatch("event")));
  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"),
      test_file("updatecheck_diff_reply_3.xml")));

  get_interceptor_->SetResponse(
      GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"),
      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"));
  // This intercept returns a different file than what is specified in the
  // update check response and requested in the download. The file that is
  // actually dowloaded contains a patching error, an therefore, an error
  // is injected at the time of patching.
  get_interceptor_->SetResponse(
      GURL("http://localhost/download/"
           "ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"),
      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_1to2_bad.crx"));
  get_interceptor_->SetResponse(
      GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"),
      test_file("ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"));

  VersionedTestInstaller installer;
  CrxComponent com;
  RegisterComponent(&com, kTestComponent_ihfo, Version("0.0"), &installer);

  test_configurator()->SetLoopCount(3);
  component_updater()->Start();
  RunThreads();
  component_updater()->Stop();

  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
  EXPECT_EQ(2, static_cast<TestInstaller*>(com.installer)->install_count());

  EXPECT_EQ(5, post_interceptor_->GetHitCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(5, post_interceptor_->GetCount())
      << post_interceptor_->GetRequestsAsString();
  EXPECT_EQ(3, get_interceptor_->GetHitCount());

  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[0].find(
          "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"0.0\">"
          "<updatecheck /></app>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[1].find(
          "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" "
          "version=\"0.0\" nextversion=\"1.0\">"
          "<event eventtype=\"3\" eventresult=\"1\" nextfp=\"1\"/>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[2].find(
          "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"1.0\">"
          "<updatecheck /><packages><package fp=\"1\"/></packages></app>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[3].find(
          "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" "
          "version=\"1.0\" nextversion=\"2.0\">"
          "<event eventtype=\"3\" eventresult=\"1\" "
          "diffresult=\"0\" differrorcat=\"2\" "
          "differrorcode=\"14\" diffextracode1=\"305\" "
          "previousfp=\"1\" nextfp=\"22\"/>"))
      << post_interceptor_->GetRequestsAsString();
  EXPECT_NE(
      string::npos,
      post_interceptor_->GetRequests()[4].find(
          "<app appid=\"ihfokbkgjpifnbbojhneepfflplebdkc\" version=\"2.0\">"
          "<updatecheck /><packages><package fp=\"22\"/></packages></app>"))
      << post_interceptor_->GetRequestsAsString();
}

class TestResourceController : public content::ResourceController {
 public:
  virtual void SetThrottle(content::ResourceThrottle* throttle) {}
};

content::ResourceThrottle* RequestTestResourceThrottle(
    ComponentUpdateService* cus,
    TestResourceController* controller,
    const char* crx_id) {
  net::TestURLRequestContext context;
  net::TestURLRequest url_request(GURL("http://foo.example.com/thing.bin"),
                                  net::DEFAULT_PRIORITY,
                                  NULL,
                                  &context);

  content::ResourceThrottle* rt =
      cus->GetOnDemandUpdater().GetOnDemandResourceThrottle(&url_request,
                                                            crx_id);
  rt->set_controller_for_testing(controller);
  controller->SetThrottle(rt);
  return rt;
}

void RequestAndDeleteResourceThrottle(ComponentUpdateService* cus,
                                      const char* crx_id) {
  // By requesting a throttle and deleting it immediately we ensure that we
  // hit the case where the component updater tries to use the weak
  // pointer to a dead Resource throttle.
  class NoCallResourceController : public TestResourceController {
   public:
    virtual ~NoCallResourceController() {}
    virtual void Cancel() OVERRIDE { CHECK(false); }
    virtual void CancelAndIgnore() OVERRIDE { CHECK(false); }
    virtual void CancelWithError(int error_code) OVERRIDE { CHECK(false); }
    virtual void Resume() OVERRIDE { CHECK(false); }
  } controller;

  delete RequestTestResourceThrottle(cus, &controller, crx_id);
}

TEST_F(ComponentUpdaterTest, ResourceThrottleDeletedNoUpdate) {
  MockServiceObserver observer;
  {
    InSequence seq;
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_STARTED, ""))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "abagagagagagagagagagagagagagagag"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
        .Times(1);
  }

  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));

  TestInstaller installer;
  CrxComponent com;
  component_updater()->AddObserver(&observer);
  EXPECT_EQ(
      ComponentUpdateService::kOk,
      RegisterComponent(&com, kTestComponent_abag, Version("1.1"), &installer));
  // The following two calls ensure that we don't do an update check via the
  // timer, so the only update check should be the on-demand one.
  test_configurator()->SetInitialDelay(1000000);
  test_configurator()->SetRecheckTime(1000000);
  test_configurator()->SetLoopCount(1);
  component_updater()->Start();

  RunThreadsUntilIdle();

  EXPECT_EQ(0, post_interceptor_->GetHitCount());

  BrowserThread::PostTask(BrowserThread::IO,
                          FROM_HERE,
                          base::Bind(&RequestAndDeleteResourceThrottle,
                                     component_updater(),
                                     "abagagagagagagagagagagagagagagag"));

  RunThreads();

  EXPECT_EQ(1, post_interceptor_->GetHitCount());
  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
  EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->install_count());

  component_updater()->Stop();
}

class CancelResourceController : public TestResourceController {
 public:
  CancelResourceController() : throttle_(NULL), resume_called_(0) {}
  virtual ~CancelResourceController() {
    // Check that the throttle has been resumed by the time we
    // exit the test.
    CHECK_EQ(1, resume_called_);
    delete throttle_;
  }
  virtual void Cancel() OVERRIDE { CHECK(false); }
  virtual void CancelAndIgnore() OVERRIDE { CHECK(false); }
  virtual void CancelWithError(int error_code) OVERRIDE { CHECK(false); }
  virtual void Resume() OVERRIDE {
    BrowserThread::PostTask(BrowserThread::IO,
                            FROM_HERE,
                            base::Bind(&CancelResourceController::ResumeCalled,
                                       base::Unretained(this)));
  }
  virtual void SetThrottle(content::ResourceThrottle* throttle) OVERRIDE {
    throttle_ = throttle;
    bool defer = false;
    // Initially the throttle is blocked. The CUS needs to run a
    // task on the UI thread to  decide if it should unblock.
    throttle_->WillStartRequest(&defer);
    CHECK(defer);
  }

 private:
  void ResumeCalled() { ++resume_called_; }

  content::ResourceThrottle* throttle_;
  int resume_called_;
};

// Tests the on-demand update with resource throttle, including the
// cooldown interval between calls.
TEST_F(ComponentUpdaterTest, ResourceThrottleLiveNoUpdate) {
  MockServiceObserver observer;
  {
    InSequence seq;
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_STARTED, ""))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "abagagagagagagagagagagagagagagag"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_STARTED, ""))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "abagagagagagagagagagagagagagagag"))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
        .Times(1);
    EXPECT_CALL(observer,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_STARTED, ""))
        .Times(1);
  }

  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));

  TestInstaller installer;
  CrxComponent com;
  component_updater()->AddObserver(&observer);
  EXPECT_EQ(
      ComponentUpdateService::kOk,
      RegisterComponent(&com, kTestComponent_abag, Version("1.1"), &installer));
  // The following two calls ensure that we don't do an update check via the
  // timer, so the only update check should be the on-demand one.
  test_configurator()->SetInitialDelay(1000000);
  test_configurator()->SetRecheckTime(1000000);
  test_configurator()->SetLoopCount(1);
  component_updater()->Start();

  RunThreadsUntilIdle();

  EXPECT_EQ(0, post_interceptor_->GetHitCount());

  {
    // First on-demand update check is expected to succeeded.
    CancelResourceController controller;

    BrowserThread::PostTask(
        BrowserThread::IO,
        FROM_HERE,
        base::Bind(base::IgnoreResult(&RequestTestResourceThrottle),
                   component_updater(),
                   &controller,
                   "abagagagagagagagagagagagagagagag"));

    RunThreads();

    EXPECT_EQ(1, post_interceptor_->GetHitCount());
    EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
    EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->install_count());

    component_updater()->Stop();
  }

  {
    // Second on-demand update check is expected to succeed as well, since there
    // is no cooldown interval between calls, due to calling SetOnDemandTime.
    test_configurator()->SetOnDemandTime(0);
    test_configurator()->SetLoopCount(1);
    component_updater()->Start();

    CancelResourceController controller;

    BrowserThread::PostTask(
        BrowserThread::IO,
        FROM_HERE,
        base::Bind(base::IgnoreResult(&RequestTestResourceThrottle),
                   component_updater(),
                   &controller,
                   "abagagagagagagagagagagagagagagag"));

    RunThreads();

    EXPECT_EQ(1, post_interceptor_->GetHitCount());
    EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->error());
    EXPECT_EQ(0, static_cast<TestInstaller*>(com.installer)->install_count());

    component_updater()->Stop();
  }

  {
    // This on-demand call is expected not to trigger a component update check.
    test_configurator()->SetOnDemandTime(1000000);
    component_updater()->Start();

    CancelResourceController controller;

    BrowserThread::PostTask(
        BrowserThread::IO,
        FROM_HERE,
        base::Bind(base::IgnoreResult(&RequestTestResourceThrottle),
                   component_updater(),
                   &controller,
                   "abagagagagagagagagagagagagagagag"));
    RunThreadsUntilIdle();
  }
}

// Tests adding and removing observers.
TEST_F(ComponentUpdaterTest, Observer) {
  MockServiceObserver observer1, observer2;

  // Expect that two observers see the events.
  {
    InSequence seq;
    EXPECT_CALL(observer1,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_STARTED, ""))
        .Times(1);
    EXPECT_CALL(observer2,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_STARTED, ""))
        .Times(1);
    EXPECT_CALL(observer1,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "abagagagagagagagagagagagagagagag"))
        .Times(1);
    EXPECT_CALL(observer2,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "abagagagagagagagagagagagagagagag"))
        .Times(1);
    EXPECT_CALL(observer1,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
        .Times(1);
    EXPECT_CALL(observer2,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
        .Times(1);
  }

  EXPECT_TRUE(post_interceptor_->ExpectRequest(
      new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));

  component_updater()->AddObserver(&observer1);
  component_updater()->AddObserver(&observer2);

  TestInstaller installer;
  CrxComponent com;
  EXPECT_EQ(
      ComponentUpdateService::kOk,
      RegisterComponent(&com, kTestComponent_abag, Version("1.1"), &installer));
  test_configurator()->SetLoopCount(1);
  component_updater()->Start();
  RunThreads();

  // After removing the first observer, it's only the second observer that
  // gets the events.
  EXPECT_TRUE(Mock::VerifyAndClearExpectations(&observer1));
  EXPECT_TRUE(Mock::VerifyAndClearExpectations(&observer2));
  {
    InSequence seq;
    EXPECT_CALL(observer2,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_STARTED, ""))
        .Times(1);
    EXPECT_CALL(observer2,
                OnEvent(ServiceObserver::COMPONENT_NOT_UPDATED,
                        "abagagagagagagagagagagagagagagag"))
        .Times(1);
    EXPECT_CALL(observer2,
                OnEvent(ServiceObserver::COMPONENT_UPDATER_SLEEPING, ""))
        .Times(1);
  }

  component_updater()->RemoveObserver(&observer1);

  test_configurator()->SetLoopCount(1);
  component_updater()->Start();
  RunThreads();

  // Both observers are removed and no one gets the events.
  EXPECT_TRUE(Mock::VerifyAndClearExpectations(&observer1));
  EXPECT_TRUE(Mock::VerifyAndClearExpectations(&observer2));
  component_updater()->RemoveObserver(&observer2);

  test_configurator()->SetLoopCount(1);
  component_updater()->Start();
  RunThreads();

  component_updater()->Stop();
}

}  // namespace component_updater