blob: 70c667fc901fd16f5f7a89cb88c259832849eb5f [file] [log] [blame]
// Copyright 2014 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 "components/update_client/update_checker.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/version.h"
#include "build/build_config.h"
#include "components/prefs/testing_pref_service.h"
#include "components/update_client/component.h"
#include "components/update_client/persisted_data.h"
#include "components/update_client/test_configurator.h"
#include "components/update_client/update_engine.h"
#include "components/update_client/url_request_post_interceptor.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
using std::string;
namespace update_client {
namespace {
base::FilePath test_file(const char* file) {
base::FilePath path;
PathService::Get(base::DIR_SOURCE_ROOT, &path);
return path.AppendASCII("components")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("update_client")
.AppendASCII(file);
}
const char kUpdateItemId[] = "jebgalgnebhfojomionfpkfelancnnkf";
} // namespace
class UpdateCheckerTest : public testing::Test {
public:
UpdateCheckerTest();
~UpdateCheckerTest() override;
// Overrides from testing::Test.
void SetUp() override;
void TearDown() override;
void UpdateCheckComplete(int error, int retry_after_sec);
protected:
void Quit();
void RunThreads();
std::unique_ptr<Component> MakeComponent() const;
scoped_refptr<TestConfigurator> config_;
std::unique_ptr<TestingPrefServiceSimple> pref_;
std::unique_ptr<PersistedData> metadata_;
std::unique_ptr<UpdateChecker> update_checker_;
std::unique_ptr<InterceptorFactory> interceptor_factory_;
URLRequestPostInterceptor* post_interceptor_ =
nullptr; // Owned by the factory.
int error_ = 0;
int retry_after_sec_ = 0;
std::unique_ptr<UpdateContext> update_context_;
private:
std::unique_ptr<UpdateContext> MakeFakeUpdateContext() const;
base::test::ScopedTaskEnvironment scoped_task_environment_;
base::Closure quit_closure_;
DISALLOW_COPY_AND_ASSIGN(UpdateCheckerTest);
};
UpdateCheckerTest::UpdateCheckerTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::IO) {}
UpdateCheckerTest::~UpdateCheckerTest() {
}
void UpdateCheckerTest::SetUp() {
config_ = base::MakeRefCounted<TestConfigurator>();
pref_ = base::MakeUnique<TestingPrefServiceSimple>();
PersistedData::RegisterPrefs(pref_->registry());
metadata_ = base::MakeUnique<PersistedData>(pref_.get());
interceptor_factory_ =
base::MakeUnique<InterceptorFactory>(base::ThreadTaskRunnerHandle::Get());
post_interceptor_ = interceptor_factory_->CreateInterceptor();
EXPECT_TRUE(post_interceptor_);
update_checker_ = nullptr;
error_ = 0;
retry_after_sec_ = 0;
update_context_ = MakeFakeUpdateContext();
}
void UpdateCheckerTest::TearDown() {
update_checker_ = nullptr;
post_interceptor_ = nullptr;
interceptor_factory_ = nullptr;
config_ = nullptr;
// The PostInterceptor requires the message loop to run to destruct correctly.
// TODO(sorin): This is fragile and should be fixed.
scoped_task_environment_.RunUntilIdle();
}
void UpdateCheckerTest::RunThreads() {
base::RunLoop runloop;
quit_closure_ = 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.
scoped_task_environment_.RunUntilIdle();
}
void UpdateCheckerTest::Quit() {
if (!quit_closure_.is_null())
quit_closure_.Run();
}
void UpdateCheckerTest::UpdateCheckComplete(int error, int retry_after_sec) {
error_ = error;
retry_after_sec_ = retry_after_sec;
Quit();
}
std::unique_ptr<UpdateContext> UpdateCheckerTest::MakeFakeUpdateContext()
const {
return base::MakeUnique<UpdateContext>(
config_, false, std::vector<std::string>(),
UpdateClient::CrxDataCallback(), UpdateEngine::NotifyObserversCallback(),
UpdateEngine::Callback(), nullptr);
}
std::unique_ptr<Component> UpdateCheckerTest::MakeComponent() const {
CrxComponent crx_component;
crx_component.name = "test_jebg";
crx_component.pk_hash.assign(jebg_hash, jebg_hash + arraysize(jebg_hash));
crx_component.installer = NULL;
crx_component.version = base::Version("0.9");
crx_component.fingerprint = "fp1";
auto component = base::MakeUnique<Component>(*update_context_, kUpdateItemId);
component->state_ = base::MakeUnique<Component::StateNew>(component.get());
component->crx_component_ = crx_component;
return component;
}
TEST_F(UpdateCheckerTest, UpdateCheckSuccess) {
EXPECT_TRUE(post_interceptor_->ExpectRequest(
new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));
update_checker_ = UpdateChecker::Create(config_, metadata_.get());
IdToComponentPtrMap components;
components[kUpdateItemId] = MakeComponent();
auto& component = components[kUpdateItemId];
component->crx_component_.installer_attributes["ap"] = "some_ap";
update_checker_->CheckForUpdates(
std::vector<std::string>{kUpdateItemId}, components, "extra=\"params\"",
true,
base::Bind(&UpdateCheckerTest::UpdateCheckComplete,
base::Unretained(this)));
RunThreads();
EXPECT_EQ(1, post_interceptor_->GetHitCount())
<< post_interceptor_->GetRequestsAsString();
ASSERT_EQ(1, post_interceptor_->GetCount())
<< post_interceptor_->GetRequestsAsString();
// Sanity check the request.
const auto request = post_interceptor_->GetRequests()[0];
EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
"request protocol=\"3.1\" extra=\"params\""));
// The request must not contain any "dlpref" in the default case.
EXPECT_EQ(string::npos, request.find(" dlpref=\""));
EXPECT_NE(
string::npos,
request.find(
std::string("<app appid=\"") + kUpdateItemId +
"\" version=\"0.9\" "
"brand=\"TEST\" ap=\"some_ap\"><updatecheck/><ping rd=\"-2\" "));
EXPECT_NE(string::npos,
request.find("<packages><package fp=\"fp1\"/></packages></app>"));
EXPECT_NE(string::npos, request.find("<hw physmemory="));
// Tests that the progid is injected correctly from the configurator.
EXPECT_NE(
string::npos,
request.find(" version=\"fake_prodid-30.0\" prodversion=\"30.0\" "));
// Sanity check the arguments of the callback after parsing.
EXPECT_EQ(0, error_);
EXPECT_EQ(base::Version("1.0"), component->next_version_);
EXPECT_EQ(1u, component->crx_urls_.size());
EXPECT_EQ(
GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx"),
component->crx_urls_.front());
EXPECT_STREQ("this", component->action_run_.c_str());
#if (OS_WIN)
EXPECT_NE(string::npos, request.find(" domainjoined="));
#if defined(GOOGLE_CHROME_BUILD)
// Check the Omaha updater state data in the request.
EXPECT_NE(string::npos, request.find("<updater "));
EXPECT_NE(string::npos, request.find(" name=\"Omaha\" "));
#endif // GOOGLE_CHROME_BUILD
#endif // OS_WINDOWS
}
// Tests that an invalid "ap" is not serialized.
TEST_F(UpdateCheckerTest, UpdateCheckInvalidAp) {
EXPECT_TRUE(post_interceptor_->ExpectRequest(
new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));
update_checker_ = UpdateChecker::Create(config_, metadata_.get());
IdToComponentPtrMap components;
components[kUpdateItemId] = MakeComponent();
// Make "ap" too long.
auto& component = components[kUpdateItemId];
component->crx_component_.installer_attributes["ap"] = std::string(257, 'a');
update_checker_->CheckForUpdates(
std::vector<std::string>{kUpdateItemId}, components, "", true,
base::Bind(&UpdateCheckerTest::UpdateCheckComplete,
base::Unretained(this)));
RunThreads();
EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
std::string("app appid=\"") + kUpdateItemId +
"\" version=\"0.9\" "
"brand=\"TEST\"><updatecheck/><ping rd=\"-2\" "));
EXPECT_NE(string::npos,
post_interceptor_->GetRequests()[0].find(
"<packages><package fp=\"fp1\"/></packages></app>"));
}
TEST_F(UpdateCheckerTest, UpdateCheckSuccessNoBrand) {
EXPECT_TRUE(post_interceptor_->ExpectRequest(
new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));
config_->SetBrand("TOOLONG"); // Sets an invalid brand code.
update_checker_ = UpdateChecker::Create(config_, metadata_.get());
IdToComponentPtrMap components;
components[kUpdateItemId] = MakeComponent();
update_checker_->CheckForUpdates(
std::vector<std::string>{kUpdateItemId}, components, "", true,
base::Bind(&UpdateCheckerTest::UpdateCheckComplete,
base::Unretained(this)));
RunThreads();
EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
std::string("<app appid=\"") + kUpdateItemId +
"\" version=\"0.9\">"
"<updatecheck/><ping rd=\"-2\" "));
EXPECT_NE(string::npos,
post_interceptor_->GetRequests()[0].find(
"<packages><package fp=\"fp1\"/></packages></app>"));
}
// Simulates a 403 server response error.
TEST_F(UpdateCheckerTest, UpdateCheckError) {
EXPECT_TRUE(
post_interceptor_->ExpectRequest(new PartialMatch("updatecheck"), 403));
update_checker_ = UpdateChecker::Create(config_, metadata_.get());
IdToComponentPtrMap components;
components[kUpdateItemId] = MakeComponent();
auto& component = components[kUpdateItemId];
update_checker_->CheckForUpdates(
std::vector<std::string>{kUpdateItemId}, components, "", true,
base::Bind(&UpdateCheckerTest::UpdateCheckComplete,
base::Unretained(this)));
RunThreads();
EXPECT_EQ(1, post_interceptor_->GetHitCount())
<< post_interceptor_->GetRequestsAsString();
EXPECT_EQ(1, post_interceptor_->GetCount())
<< post_interceptor_->GetRequestsAsString();
EXPECT_EQ(403, error_);
EXPECT_FALSE(component->next_version_.IsValid());
}
TEST_F(UpdateCheckerTest, UpdateCheckDownloadPreference) {
EXPECT_TRUE(post_interceptor_->ExpectRequest(
new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));
config_->SetDownloadPreference(string("cacheable"));
update_checker_ = UpdateChecker::Create(config_, metadata_.get());
IdToComponentPtrMap components;
components[kUpdateItemId] = MakeComponent();
update_checker_->CheckForUpdates(
std::vector<std::string>{kUpdateItemId}, components, "extra=\"params\"",
true,
base::Bind(&UpdateCheckerTest::UpdateCheckComplete,
base::Unretained(this)));
RunThreads();
// The request must contain dlpref="cacheable".
EXPECT_NE(string::npos,
post_interceptor_->GetRequests()[0].find(" dlpref=\"cacheable\""));
}
// This test is checking that an update check signed with CUP fails, since there
// is currently no entity that can respond with a valid signed response.
// A proper CUP test requires network mocks, which are not available now.
TEST_F(UpdateCheckerTest, UpdateCheckCupError) {
EXPECT_TRUE(post_interceptor_->ExpectRequest(
new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));
config_->SetEnabledCupSigning(true);
update_checker_ = UpdateChecker::Create(config_, metadata_.get());
IdToComponentPtrMap components;
components[kUpdateItemId] = MakeComponent();
const auto& component = components[kUpdateItemId];
update_checker_->CheckForUpdates(
std::vector<std::string>{kUpdateItemId}, components, "", true,
base::Bind(&UpdateCheckerTest::UpdateCheckComplete,
base::Unretained(this)));
RunThreads();
EXPECT_EQ(1, post_interceptor_->GetHitCount())
<< post_interceptor_->GetRequestsAsString();
ASSERT_EQ(1, post_interceptor_->GetCount())
<< post_interceptor_->GetRequestsAsString();
// Sanity check the request.
EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
std::string("<app appid=\"") + kUpdateItemId +
"\" version=\"0.9\" "
"brand=\"TEST\"><updatecheck/><ping rd=\"-2\" "));
EXPECT_NE(string::npos,
post_interceptor_->GetRequests()[0].find(
"<packages><package fp=\"fp1\"/></packages></app>"));
// Expect an error since the response is not trusted.
EXPECT_EQ(-10000, error_);
EXPECT_FALSE(component->next_version_.IsValid());
}
// Tests that the UpdateCheckers will not make an update check for a
// component that requires encryption when the update check URL is unsecure.
TEST_F(UpdateCheckerTest, UpdateCheckRequiresEncryptionError) {
config_->SetUpdateCheckUrl(GURL("http:\\foo\bar"));
update_checker_ = UpdateChecker::Create(config_, metadata_.get());
IdToComponentPtrMap components;
components[kUpdateItemId] = MakeComponent();
auto& component = components[kUpdateItemId];
component->crx_component_.requires_network_encryption = true;
update_checker_->CheckForUpdates(
std::vector<std::string>{kUpdateItemId}, components, "", true,
base::Bind(&UpdateCheckerTest::UpdateCheckComplete,
base::Unretained(this)));
RunThreads();
EXPECT_EQ(-1, error_);
EXPECT_FALSE(component->next_version_.IsValid());
}
// Tests that the PersistedData will get correctly update and reserialize
// the elapsed_days value.
TEST_F(UpdateCheckerTest, UpdateCheckDateLastRollCall) {
EXPECT_TRUE(post_interceptor_->ExpectRequest(
new PartialMatch("updatecheck"), test_file("updatecheck_reply_4.xml")));
EXPECT_TRUE(post_interceptor_->ExpectRequest(
new PartialMatch("updatecheck"), test_file("updatecheck_reply_4.xml")));
update_checker_ = UpdateChecker::Create(config_, metadata_.get());
IdToComponentPtrMap components;
components[kUpdateItemId] = MakeComponent();
// Do two update-checks.
update_checker_->CheckForUpdates(
std::vector<std::string>{kUpdateItemId}, components, "extra=\"params\"",
true,
base::Bind(&UpdateCheckerTest::UpdateCheckComplete,
base::Unretained(this)));
RunThreads();
update_checker_ = UpdateChecker::Create(config_, metadata_.get());
update_checker_->CheckForUpdates(
std::vector<std::string>{kUpdateItemId}, components, "extra=\"params\"",
true,
base::Bind(&UpdateCheckerTest::UpdateCheckComplete,
base::Unretained(this)));
RunThreads();
EXPECT_EQ(2, post_interceptor_->GetHitCount())
<< post_interceptor_->GetRequestsAsString();
ASSERT_EQ(2, post_interceptor_->GetCount())
<< post_interceptor_->GetRequestsAsString();
EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
"<ping rd=\"-2\" ping_freshness="));
EXPECT_NE(string::npos, post_interceptor_->GetRequests()[1].find(
"<ping rd=\"3383\" ping_freshness="));
}
TEST_F(UpdateCheckerTest, UpdateCheckUpdateDisabled) {
EXPECT_TRUE(post_interceptor_->ExpectRequest(
new PartialMatch("updatecheck"), test_file("updatecheck_reply_1.xml")));
config_->SetBrand("");
update_checker_ = UpdateChecker::Create(config_, metadata_.get());
IdToComponentPtrMap components;
components[kUpdateItemId] = MakeComponent();
auto& component = components[kUpdateItemId];
// Tests the scenario where:
// * the component does not support group policies.
// * the component updates are disabled.
// Expects the group policy to be ignored and the update check to not
// include the "updatedisabled" attribute.
EXPECT_FALSE(
component->crx_component_.supports_group_policy_enable_component_updates);
update_checker_->CheckForUpdates(
std::vector<std::string>{kUpdateItemId}, components, "", false,
base::Bind(&UpdateCheckerTest::UpdateCheckComplete,
base::Unretained(this)));
RunThreads();
EXPECT_NE(string::npos, post_interceptor_->GetRequests()[0].find(
std::string("<app appid=\"") + kUpdateItemId +
"\" version=\"0.9\">"
"<updatecheck/>"));
// Tests the scenario where:
// * the component supports group policies.
// * the component updates are disabled.
// Expects the update check to include the "updatedisabled" attribute.
component->crx_component_.supports_group_policy_enable_component_updates =
true;
update_checker_ = UpdateChecker::Create(config_, metadata_.get());
update_checker_->CheckForUpdates(
std::vector<std::string>{kUpdateItemId}, components, "", false,
base::Bind(&UpdateCheckerTest::UpdateCheckComplete,
base::Unretained(this)));
RunThreads();
EXPECT_NE(string::npos, post_interceptor_->GetRequests()[1].find(
std::string("<app appid=\"") + kUpdateItemId +
"\" version=\"0.9\">"
"<updatecheck updatedisabled=\"true\"/>"));
// Tests the scenario where:
// * the component does not support group policies.
// * the component updates are enabled.
// Expects the update check to not include the "updatedisabled" attribute.
component->crx_component_.supports_group_policy_enable_component_updates =
false;
update_checker_ = UpdateChecker::Create(config_, metadata_.get());
update_checker_->CheckForUpdates(
std::vector<std::string>{kUpdateItemId}, components, "", true,
base::Bind(&UpdateCheckerTest::UpdateCheckComplete,
base::Unretained(this)));
RunThreads();
EXPECT_NE(string::npos, post_interceptor_->GetRequests()[2].find(
std::string("<app appid=\"") + kUpdateItemId +
"\" version=\"0.9\">"
"<updatecheck/>"));
// Tests the scenario where:
// * the component supports group policies.
// * the component updates are enabled.
// Expects the update check to not include the "updatedisabled" attribute.
component->crx_component_.supports_group_policy_enable_component_updates =
true;
update_checker_ = UpdateChecker::Create(config_, metadata_.get());
update_checker_->CheckForUpdates(
std::vector<std::string>{kUpdateItemId}, components, "", true,
base::Bind(&UpdateCheckerTest::UpdateCheckComplete,
base::Unretained(this)));
RunThreads();
EXPECT_NE(string::npos, post_interceptor_->GetRequests()[3].find(
std::string("<app appid=\"") + kUpdateItemId +
"\" version=\"0.9\">"
"<updatecheck/>"));
}
TEST_F(UpdateCheckerTest, NoUpdateActionRun) {
EXPECT_TRUE(post_interceptor_->ExpectRequest(
new PartialMatch("updatecheck"),
test_file("updatecheck_reply_noupdate.xml")));
update_checker_ = UpdateChecker::Create(config_, metadata_.get());
IdToComponentPtrMap components;
components[kUpdateItemId] = MakeComponent();
auto& component = components[kUpdateItemId];
update_checker_->CheckForUpdates(
std::vector<std::string>{kUpdateItemId}, components, "", true,
base::Bind(&UpdateCheckerTest::UpdateCheckComplete,
base::Unretained(this)));
RunThreads();
EXPECT_EQ(1, post_interceptor_->GetHitCount())
<< post_interceptor_->GetRequestsAsString();
ASSERT_EQ(1, post_interceptor_->GetCount())
<< post_interceptor_->GetRequestsAsString();
EXPECT_EQ(0, error_);
EXPECT_STREQ("this", component->action_run_.c_str());
}
} // namespace update_client