// 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/request_sender.h"

#include <memory>

#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/update_client/test_configurator.h"
#include "components/update_client/url_request_post_interceptor.h"
#include "net/url_request/url_fetcher.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace update_client {

namespace {

const char kUrl1[] = "https://localhost2/path1";
const char kUrl2[] = "https://localhost2/path2";
const char kUrlPath1[] = "path1";
const char kUrlPath2[] = "path2";

// TODO(sorin): refactor as a utility function for unit tests.
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);
}

}  // namespace

class RequestSenderTest : public testing::Test {
 public:
  RequestSenderTest();
  ~RequestSenderTest() override;

  // Overrides from testing::Test.
  void SetUp() override;
  void TearDown() override;

  void RequestSenderComplete(int error,
                             const std::string& response,
                             int retry_after_sec);

 protected:
  void Quit();
  void RunThreads();
  void RunThreadsUntilIdle();

  scoped_refptr<TestConfigurator> config_;
  std::unique_ptr<RequestSender> request_sender_;
  std::unique_ptr<InterceptorFactory> interceptor_factory_;

  URLRequestPostInterceptor* post_interceptor_1_;  // Owned by the factory.
  URLRequestPostInterceptor* post_interceptor_2_;  // Owned by the factory.

  int error_;
  std::string response_;

 private:
  base::MessageLoopForIO loop_;
  base::Closure quit_closure_;

  DISALLOW_COPY_AND_ASSIGN(RequestSenderTest);
};

RequestSenderTest::RequestSenderTest()
    : post_interceptor_1_(nullptr), post_interceptor_2_(nullptr), error_(0) {}

RequestSenderTest::~RequestSenderTest() {}

void RequestSenderTest::SetUp() {
  config_ = new TestConfigurator(base::ThreadTaskRunnerHandle::Get(),
                                 base::ThreadTaskRunnerHandle::Get());
  interceptor_factory_.reset(
      new InterceptorFactory(base::ThreadTaskRunnerHandle::Get()));
  post_interceptor_1_ =
      interceptor_factory_->CreateInterceptorForPath(kUrlPath1);
  post_interceptor_2_ =
      interceptor_factory_->CreateInterceptorForPath(kUrlPath2);
  EXPECT_TRUE(post_interceptor_1_);
  EXPECT_TRUE(post_interceptor_2_);

  request_sender_.reset();
}

void RequestSenderTest::TearDown() {
  request_sender_.reset();

  post_interceptor_1_ = nullptr;
  post_interceptor_2_ = nullptr;

  interceptor_factory_.reset();

  config_ = nullptr;

  RunThreadsUntilIdle();
}

void RequestSenderTest::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.
  RunThreadsUntilIdle();
}

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

void RequestSenderTest::Quit() {
  if (!quit_closure_.is_null())
    quit_closure_.Run();
}

void RequestSenderTest::RequestSenderComplete(int error,
                                              const std::string& response,
                                              int retry_after_sec) {
  error_ = error;
  response_ = response;

  Quit();
}

// Tests that when a request to the first url succeeds, the subsequent urls are
// not tried.
TEST_F(RequestSenderTest, RequestSendSuccess) {
  EXPECT_TRUE(post_interceptor_1_->ExpectRequest(
      new PartialMatch("test"), test_file("updatecheck_reply_1.xml")));

  std::vector<GURL> urls;
  urls.push_back(GURL(kUrl1));
  urls.push_back(GURL(kUrl2));
  request_sender_.reset(new RequestSender(config_));
  request_sender_->Send(false, "test", urls,
                        base::Bind(&RequestSenderTest::RequestSenderComplete,
                                   base::Unretained(this)));
  RunThreads();

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

  // Sanity check the request.
  EXPECT_STREQ("test", post_interceptor_1_->GetRequests()[0].c_str());

  // Check the response post conditions.
  EXPECT_EQ(0, error_);
  EXPECT_TRUE(base::StartsWith(response_,
                               "<?xml version='1.0' encoding='UTF-8'?>",
                               base::CompareCase::SENSITIVE));
  EXPECT_EQ(443ul, response_.size());
}

// Tests that the request succeeds using the second url after the first url
// has failed.
TEST_F(RequestSenderTest, RequestSendSuccessWithFallback) {
  EXPECT_TRUE(
      post_interceptor_1_->ExpectRequest(new PartialMatch("test"), 403));
  EXPECT_TRUE(post_interceptor_2_->ExpectRequest(new PartialMatch("test")));

  std::vector<GURL> urls;
  urls.push_back(GURL(kUrl1));
  urls.push_back(GURL(kUrl2));
  request_sender_.reset(new RequestSender(config_));
  request_sender_->Send(false, "test", urls,
                        base::Bind(&RequestSenderTest::RequestSenderComplete,
                                   base::Unretained(this)));
  RunThreads();

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

  EXPECT_STREQ("test", post_interceptor_1_->GetRequests()[0].c_str());
  EXPECT_STREQ("test", post_interceptor_2_->GetRequests()[0].c_str());
  EXPECT_EQ(0, error_);
}

// Tests that the request fails when both urls have failed.
TEST_F(RequestSenderTest, RequestSendFailed) {
  EXPECT_TRUE(
      post_interceptor_1_->ExpectRequest(new PartialMatch("test"), 403));
  EXPECT_TRUE(
      post_interceptor_2_->ExpectRequest(new PartialMatch("test"), 403));

  std::vector<GURL> urls;
  urls.push_back(GURL(kUrl1));
  urls.push_back(GURL(kUrl2));
  request_sender_.reset(new RequestSender(config_));
  request_sender_->Send(false, "test", urls,
                        base::Bind(&RequestSenderTest::RequestSenderComplete,
                                   base::Unretained(this)));
  RunThreads();

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

  EXPECT_STREQ("test", post_interceptor_1_->GetRequests()[0].c_str());
  EXPECT_STREQ("test", post_interceptor_2_->GetRequests()[0].c_str());
  EXPECT_EQ(403, error_);
}

// Tests that the request fails when no urls are provided.
TEST_F(RequestSenderTest, RequestSendFailedNoUrls) {
  std::vector<GURL> urls;
  request_sender_.reset(new RequestSender(config_));
  request_sender_->Send(false, "test", urls,
                        base::Bind(&RequestSenderTest::RequestSenderComplete,
                                   base::Unretained(this)));
  RunThreads();

  EXPECT_EQ(-1, error_);
}

// Tests that a CUP request fails if the response is not signed.
TEST_F(RequestSenderTest, RequestSendCupError) {
  EXPECT_TRUE(post_interceptor_1_->ExpectRequest(
      new PartialMatch("test"), test_file("updatecheck_reply_1.xml")));

  std::vector<GURL> urls;
  urls.push_back(GURL(kUrl1));
  request_sender_.reset(new RequestSender(config_));
  request_sender_->Send(true, "test", urls,
                        base::Bind(&RequestSenderTest::RequestSenderComplete,
                                   base::Unretained(this)));
  RunThreads();

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

  EXPECT_STREQ("test", post_interceptor_1_->GetRequests()[0].c_str());
  EXPECT_EQ(RequestSender::kErrorResponseNotTrusted, error_);
  EXPECT_TRUE(response_.empty());
}

}  // namespace update_client
