// 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/history/core/browser/web_history_service.h"

#include "base/location.h"
#include "base/memory/scoped_ptr.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/thread_task_runner_handle.h"
#include "chrome/browser/history/web_history_service_factory.h"
#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/profile_sync_service_factory.h"
#include "chrome/browser/sync/profile_sync_service_mock.h"
#include "chrome/test/base/testing_profile.h"
#include "components/signin/core/browser/profile_oauth2_token_service.h"
#include "components/signin/core/browser/signin_manager.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "net/http/http_status_code.h"
#include "net/url_request/url_request_context_getter.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using history::WebHistoryService;
using ::testing::Return;

namespace {

// A testing web history service that does extra checks and creates a
// TestRequest instead of a normal request.
class TestingWebHistoryService : public WebHistoryService {
 public:
  explicit TestingWebHistoryService(
      ProfileOAuth2TokenService* token_service,
      SigninManagerBase* signin_manager,
      const scoped_refptr<net::URLRequestContextGetter>& request_context)
      : WebHistoryService(token_service, signin_manager, request_context),
        expected_url_(GURL()),
        expected_audio_history_value_(false),
        current_expected_post_data_("") {}
  ~TestingWebHistoryService() override {}

  WebHistoryService::Request* CreateRequest(
      const GURL& url, const CompletionCallback& callback) override;

  // This is sorta an override but override and static don't mix.
  // This function just calls WebHistoryService::ReadResponse.
  static scoped_ptr<base::DictionaryValue> ReadResponse(
      Request* request);

  const std::string& GetExpectedPostData(WebHistoryService::Request* request);

  std::string GetExpectedAudioHistoryValue();

  void SetAudioHistoryCallback(bool success, bool new_enabled_value);

  void GetAudioHistoryCallback(bool success, bool new_enabled_value);

  void MultipleRequestsCallback(bool success, bool new_enabled_value);

  void SetExpectedURL(const GURL& expected_url) {
    expected_url_ = expected_url;
  }

  void SetExpectedAudioHistoryValue(bool expected_value) {
    expected_audio_history_value_ = expected_value;
  }

  void SetExpectedPostData(const std::string& expected_data) {
    current_expected_post_data_ = expected_data;
  }

  void EnsureNoPendingRequestsRemain() {
    EXPECT_EQ(0u, GetNumberOfPendingAudioHistoryRequests());
  }

 private:
  GURL expected_url_;
  bool expected_audio_history_value_;
  std::string current_expected_post_data_;
  std::map<Request*, std::string> expected_post_data_;

  DISALLOW_COPY_AND_ASSIGN(TestingWebHistoryService);
};

// A testing request class that allows expected values to be filled in.
class TestRequest : public WebHistoryService::Request {
 public:
  TestRequest(const GURL& url,
              const WebHistoryService::CompletionCallback& callback,
              int response_code,
              const std::string& response_body)
      : web_history_service_(nullptr),
        url_(url),
        callback_(callback),
        response_code_(response_code),
        response_body_(response_body),
        post_data_(""),
        is_pending_(false) {
  }

  TestRequest(const GURL& url,
              const WebHistoryService::CompletionCallback& callback,
              TestingWebHistoryService* web_history_service)
      : web_history_service_(web_history_service),
        url_(url),
        callback_(callback),
        response_code_(net::HTTP_OK),
        response_body_(""),
        post_data_(""),
        is_pending_(false) {
    response_body_ = std::string("{\"history_recording_enabled\":") +
                     web_history_service->GetExpectedAudioHistoryValue() +
                     ("}");
  }

  ~TestRequest() override {}

  // history::Request overrides
  bool IsPending() override { return is_pending_; }
  int GetResponseCode() override { return response_code_; }
  const std::string& GetResponseBody() override { return response_body_; }
  void SetPostData(const std::string& post_data) override {
    post_data_ = post_data;
  }

  void Start() override {
    is_pending_ = true;
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE,
        base::Bind(&TestRequest::MimicReturnFromFetch, base::Unretained(this)));
  }

  void MimicReturnFromFetch() {
    // Mimic a successful fetch and return. We don't actually send out a request
    // in unittests.
    EXPECT_EQ(web_history_service_->GetExpectedPostData(this), post_data_);
    callback_.Run(this, true);
  }

 private:
  TestingWebHistoryService* web_history_service_;
  GURL url_;
  WebHistoryService::CompletionCallback callback_;
  int response_code_;
  std::string response_body_;
  std::string post_data_;
  bool is_pending_;

  DISALLOW_COPY_AND_ASSIGN(TestRequest);
};

WebHistoryService::Request* TestingWebHistoryService::CreateRequest(
    const GURL& url, const CompletionCallback& callback) {
  EXPECT_EQ(expected_url_, url);
  WebHistoryService::Request* request =
      new TestRequest(url, callback, this);
  expected_post_data_[request] = current_expected_post_data_;
  return request;
}

scoped_ptr<base::DictionaryValue> TestingWebHistoryService::ReadResponse(
    Request* request) {
  return WebHistoryService::ReadResponse(request);
}

void TestingWebHistoryService::SetAudioHistoryCallback(
    bool success, bool new_enabled_value) {
  EXPECT_TRUE(success);
  // |new_enabled_value| should be equal to whatever the audio history value
  // was just set to.
  EXPECT_EQ(expected_audio_history_value_, new_enabled_value);
}

void TestingWebHistoryService::GetAudioHistoryCallback(
  bool success, bool new_enabled_value) {
  EXPECT_TRUE(success);
  EXPECT_EQ(expected_audio_history_value_, new_enabled_value);
}

void TestingWebHistoryService::MultipleRequestsCallback(
  bool success, bool new_enabled_value) {
  EXPECT_TRUE(success);
  EXPECT_EQ(expected_audio_history_value_, new_enabled_value);
}

const std::string& TestingWebHistoryService::GetExpectedPostData(
    Request* request) {
  return expected_post_data_[request];
}

std::string TestingWebHistoryService::GetExpectedAudioHistoryValue() {
  if (expected_audio_history_value_)
    return "true";
  return "false";
}

static scoped_ptr<KeyedService> BuildWebHistoryService(
    content::BrowserContext* context) {
  Profile* profile = static_cast<Profile*>(context);
  return make_scoped_ptr(new TestingWebHistoryService(
      ProfileOAuth2TokenServiceFactory::GetForProfile(profile),
      SigninManagerFactory::GetForProfile(profile),
      profile->GetRequestContext()));
}

}  // namespace

// A test class used for testing the WebHistoryService class.
// In order for WebHistoryService to be valid, we must have a valid
// ProfileSyncService. Using the ProfileSyncServiceMock class allows to
// assign specific return values as needed to make sure the web history
// service is available.
class WebHistoryServiceTest : public testing::Test {
 public:
  WebHistoryServiceTest()
      : thread_bundle_(content::TestBrowserThreadBundle::REAL_DB_THREAD) {}
  ~WebHistoryServiceTest() override {}

  void SetUp() override {
    ProfileSyncServiceFactory::GetInstance()->SetTestingFactoryAndUse(
      &profile_, &ProfileSyncServiceMock::BuildMockProfileSyncService);
    // Use SetTestingFactoryAndUse to force creation and initialization.
    WebHistoryServiceFactory::GetInstance()->SetTestingFactoryAndUse(
      &profile_, &BuildWebHistoryService);

    ProfileSyncServiceMock* sync_service = static_cast<ProfileSyncServiceMock*>(
        ProfileSyncServiceFactory::GetInstance()->GetForProfile(&profile_));
    EXPECT_CALL(*sync_service,
                IsSyncActive()).WillRepeatedly(Return(true));
    syncer::ModelTypeSet result;
    result.Put(syncer::HISTORY_DELETE_DIRECTIVES);
    EXPECT_CALL(*sync_service,
                GetActiveDataTypes()).WillRepeatedly(Return(result));
  }
  void TearDown() override {
    base::RunLoop run_loop;
    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
                                                  run_loop.QuitClosure());
    run_loop.Run();
  }
  Profile* profile() { return &profile_; }

  ProfileSyncServiceMock* mock_sync_service() {
    return static_cast<ProfileSyncServiceMock*>(
      ProfileSyncServiceFactory::GetInstance()->GetForProfile(&profile_));
  }

 private:

  content::TestBrowserThreadBundle thread_bundle_;
  TestingProfile profile_;

  DISALLOW_COPY_AND_ASSIGN(WebHistoryServiceTest);
};

TEST_F(WebHistoryServiceTest, GetAudioHistoryEnabled) {
  TestingWebHistoryService* web_history_service =
    static_cast<TestingWebHistoryService*>(
    WebHistoryServiceFactory::GetForProfile(profile()));
  EXPECT_TRUE(web_history_service);

  web_history_service->SetExpectedURL(
    GURL("https://history.google.com/history/api/lookup?client=audio"));
  web_history_service->SetExpectedAudioHistoryValue(true);
  web_history_service->GetAudioHistoryEnabled(
    base::Bind(&TestingWebHistoryService::GetAudioHistoryCallback,
    base::Unretained(web_history_service)));
  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE,
      base::Bind(&TestingWebHistoryService::EnsureNoPendingRequestsRemain,
                 base::Unretained(web_history_service)));
}

TEST_F(WebHistoryServiceTest, SetAudioHistoryEnabledTrue) {
  TestingWebHistoryService* web_history_service =
      static_cast<TestingWebHistoryService*>(
          WebHistoryServiceFactory::GetForProfile(profile()));
  EXPECT_TRUE(web_history_service);

  web_history_service->SetExpectedURL(
      GURL("https://history.google.com/history/api/change"));
  web_history_service->SetExpectedAudioHistoryValue(true);
  web_history_service->SetExpectedPostData(
    "{\"client\":\"audio\",\"enable_history_recording\":true}");
  web_history_service->SetAudioHistoryEnabled(
      true,
      base::Bind(&TestingWebHistoryService::SetAudioHistoryCallback,
                 base::Unretained(web_history_service)));
  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE,
      base::Bind(&TestingWebHistoryService::EnsureNoPendingRequestsRemain,
                 base::Unretained(web_history_service)));
}

TEST_F(WebHistoryServiceTest, SetAudioHistoryEnabledFalse) {
  TestingWebHistoryService* web_history_service =
    static_cast<TestingWebHistoryService*>(
    WebHistoryServiceFactory::GetForProfile(profile()));
  EXPECT_TRUE(web_history_service);

  web_history_service->SetExpectedURL(
    GURL("https://history.google.com/history/api/change"));
  web_history_service->SetExpectedAudioHistoryValue(false);
  web_history_service->SetExpectedPostData(
    "{\"client\":\"audio\",\"enable_history_recording\":false}");
  web_history_service->SetAudioHistoryEnabled(
    false,
    base::Bind(&TestingWebHistoryService::SetAudioHistoryCallback,
    base::Unretained(web_history_service)));
  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE,
      base::Bind(&TestingWebHistoryService::EnsureNoPendingRequestsRemain,
                 base::Unretained(web_history_service)));
}

TEST_F(WebHistoryServiceTest, MultipleRequests) {
  TestingWebHistoryService* web_history_service =
    static_cast<TestingWebHistoryService*>(
    WebHistoryServiceFactory::GetForProfile(profile()));
  EXPECT_TRUE(web_history_service);

  web_history_service->SetExpectedURL(
    GURL("https://history.google.com/history/api/change"));
  web_history_service->SetExpectedAudioHistoryValue(false);
  web_history_service->SetExpectedPostData(
    "{\"client\":\"audio\",\"enable_history_recording\":false}");
  web_history_service->SetAudioHistoryEnabled(
    false,
    base::Bind(&TestingWebHistoryService::MultipleRequestsCallback,
    base::Unretained(web_history_service)));

  web_history_service->SetExpectedURL(
    GURL("https://history.google.com/history/api/lookup?client=audio"));
  web_history_service->SetExpectedPostData("");
  web_history_service->GetAudioHistoryEnabled(
    base::Bind(&TestingWebHistoryService::MultipleRequestsCallback,
    base::Unretained(web_history_service)));

  // Check that both requests are no longer pending.
  base::ThreadTaskRunnerHandle::Get()->PostTask(
      FROM_HERE,
      base::Bind(&TestingWebHistoryService::EnsureNoPendingRequestsRemain,
                 base::Unretained(web_history_service)));
}

TEST_F(WebHistoryServiceTest, VerifyReadResponse) {
  // Test that properly formatted response with good response code returns true
  // as expected.
  WebHistoryService::CompletionCallback completion_callback;
  scoped_ptr<WebHistoryService::Request> request(
      new TestRequest(
          GURL("http://history.google.com/"),
          completion_callback,
          net::HTTP_OK, /* response code */
          "{\n" /* response body */
          "  \"history_recording_enabled\": true\n"
          "}"));
  scoped_ptr<base::DictionaryValue> response_value;
  // ReadResponse deletes the request
  response_value = TestingWebHistoryService::ReadResponse(request.get());
  bool enabled_value = false;
  response_value->GetBoolean("history_recording_enabled", &enabled_value);
  EXPECT_TRUE(enabled_value);

  // Test that properly formatted response with good response code returns false
  // as expected.
  scoped_ptr<WebHistoryService::Request> request2(
      new TestRequest(
          GURL("http://history.google.com/"),
          completion_callback,
          net::HTTP_OK,
          "{\n"
          "  \"history_recording_enabled\": false\n"
          "}"));
  scoped_ptr<base::DictionaryValue> response_value2;
  // ReadResponse deletes the request
  response_value2 = TestingWebHistoryService::ReadResponse(request2.get());
  enabled_value = true;
  response_value2->GetBoolean("history_recording_enabled", &enabled_value);
  EXPECT_FALSE(enabled_value);

  // Test that a bad response code returns false.
  scoped_ptr<WebHistoryService::Request> request3(
      new TestRequest(
          GURL("http://history.google.com/"),
          completion_callback,
          net::HTTP_UNAUTHORIZED,
          "{\n"
          "  \"history_recording_enabled\": true\n"
          "}"));
  scoped_ptr<base::DictionaryValue> response_value3;
  // ReadResponse deletes the request
  response_value3 = TestingWebHistoryService::ReadResponse(request3.get());
  EXPECT_FALSE(response_value3);

  // Test that improperly formatted response returns false.
  // Note: we expect to see a warning when running this test similar to
  //   "Non-JSON response received from history server".
  // This test tests how that situation is handled.
  scoped_ptr<WebHistoryService::Request> request4(
      new TestRequest(
          GURL("http://history.google.com/"),
          completion_callback,
          net::HTTP_OK,
          "{\n"
          "  \"history_recording_enabled\": not true\n"
          "}"));
  scoped_ptr<base::DictionaryValue> response_value4;
  // ReadResponse deletes the request
  response_value4 = TestingWebHistoryService::ReadResponse(request4.get());
  EXPECT_FALSE(response_value4);

  // Test that improperly formatted response returns false.
  scoped_ptr<WebHistoryService::Request> request5(
      new TestRequest(
          GURL("http://history.google.com/"),
          completion_callback,
          net::HTTP_OK,
          "{\n"
          "  \"history_recording\": true\n"
          "}"));
  scoped_ptr<base::DictionaryValue> response_value5;
  // ReadResponse deletes the request
  response_value5 = TestingWebHistoryService::ReadResponse(request5.get());
  enabled_value = true;
  EXPECT_FALSE(response_value5->GetBoolean("history_recording_enabled",
                                           &enabled_value));
  EXPECT_TRUE(enabled_value);
}