// Copyright 2015 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/interests/interests_fetcher.h"

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/thread_task_runner_handle.h"
#include "chrome/common/chrome_switches.h"
#include "components/signin/core/browser/fake_profile_oauth2_token_service.h"
#include "net/base/net_errors.h"
#include "net/http/http_status_code.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "net/url_request/url_request_status.h"
#include "net/url_request/url_request_test_util.h"
#include "testing/gmock/include/gmock/gmock-generated-function-mockers.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gmock/include/gmock/gmock-more-matchers.h"
#include "testing/gmock/include/gmock/gmock-spec-builders.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::IsEmpty;
using testing::Not;

namespace {

const int kInterestsFetcherURLFetcherID = 0;
const char kInterestsURL[] = "http://www.fake.com";
const char kEmptyResponse[] =
    "{\n"
    "  \"interests\": []\n"
    "}\n";
const char kSuccessfulResponse[] =
    "{\n"
    "  \"interests\": [\n"
    "    {\n"
    "      \"name\": \"Google\",\n"
    "      \"imageUrl\": \"https://fake.com/fake.png\",\n"
    "      \"relevance\": 0.9\n"
    "    },\n"
    "    {\n"
    "      \"name\": \"Google Chrome\",\n"
    "      \"imageUrl\": \"https://fake.com/fake.png\",\n"
    "      \"relevance\": 0.98\n"
    "    }\n"
    "  ]\n"
    "}\n";
const char kAccountId[] = "account@gmail.com";

std::vector<InterestsFetcher::Interest> GetExpectedEmptyResponse() {
  return std::vector<InterestsFetcher::Interest>();
}

std::vector<InterestsFetcher::Interest> GetExpectedSuccessfulResponse() {
  std::vector<InterestsFetcher::Interest> res;
  res.push_back(InterestsFetcher::Interest{
      "Google", GURL("https://fake.com/fake.png"), 0.9});
  res.push_back(InterestsFetcher::Interest{
      "Google Chrome", GURL("https://fake.com/fake.png"), 0.98});
  return res;
}
}  // namespace

class InterestsFetcherTest : public testing::Test {
 public:
  InterestsFetcherTest()
      : request_context_(new net::TestURLRequestContextGetter(
            base::ThreadTaskRunnerHandle::Get())),
        url_fetcher_factory_(new net::TestURLFetcherFactory()) {
    token_service_.UpdateCredentials(kAccountId, "refresh_token");

    base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();

    command_line->AppendSwitchASCII(switches::kInterestsURL, kInterestsURL);
  }

  MOCK_METHOD0(OnSuccessfulResponse, void());
  MOCK_METHOD0(OnEmptyResponse, void());
  MOCK_METHOD0(OnFailedResponse, void());

  void OnReceivedInterests(
      scoped_ptr<std::vector<InterestsFetcher::Interest>> interests) {
    if (!interests) {
      OnFailedResponse();
      return;
    }

    if (*interests == GetExpectedEmptyResponse())
      OnEmptyResponse();
    else if (*interests == GetExpectedSuccessfulResponse())
      OnSuccessfulResponse();
    else
      FAIL() << "Unexpected interests response.";
  }

 protected:
  void RequestInterests() {
    request_.reset(new InterestsFetcher(&token_service_,
                                        kAccountId,
                                        request_context_.get()));

    request_->FetchInterests(base::Bind(
        &InterestsFetcherTest::OnReceivedInterests, base::Unretained(this)));
  }

  net::TestURLFetcher* GetURLFetcher() {
    net::TestURLFetcher* url_fetcher =
        url_fetcher_factory_->GetFetcherByID(kInterestsFetcherURLFetcherID);
    EXPECT_TRUE(url_fetcher);
    return url_fetcher;
  }

  void IssueAccessTokenErrors() {
    token_service_.IssueErrorForAllPendingRequestsForAccount(
        kAccountId, GoogleServiceAuthError::FromServiceError("Error!"));
  }

  void IssueAccessTokens() {
    token_service_.IssueAllTokensForAccount(
        kAccountId, "access_token",
        base::Time::Now() + base::TimeDelta::FromHours(1));
  }

  void SendResponse(net::Error error,
                    int response_code,
                    const std::string& response) {
    net::TestURLFetcher* url_fetcher = GetURLFetcher();
    url_fetcher->set_status(net::URLRequestStatus::FromError(error));
    url_fetcher->set_response_code(response_code);
    url_fetcher->SetResponseString(response);
    url_fetcher->delegate()->OnURLFetchComplete(url_fetcher);
  }

  void SendValidResponse(const std::string& response) {
    SendResponse(net::OK, net::HTTP_OK, response);
  }

  void SendFailedResponse() {
    SendResponse(net::ERR_ABORTED, 0, std::string());
  }

  void SendAuthorizationError() {
    SendResponse(net::OK, net::HTTP_UNAUTHORIZED, std::string());
  }

  base::MessageLoop message_loop_;
  FakeProfileOAuth2TokenService token_service_;
  scoped_refptr<net::TestURLRequestContextGetter> request_context_;
  scoped_ptr<net::TestURLFetcherFactory> url_fetcher_factory_;
  scoped_ptr<InterestsFetcher> request_;
};

TEST_F(InterestsFetcherTest, EmptyResponse) {
  RequestInterests();
  EXPECT_CALL(*this, OnEmptyResponse());
  IssueAccessTokens();
  SendValidResponse(kEmptyResponse);
}

TEST_F(InterestsFetcherTest, SuccessfullResponse) {
  RequestInterests();
  EXPECT_CALL(*this, OnSuccessfulResponse());
  IssueAccessTokens();
  SendValidResponse(kSuccessfulResponse);
}

TEST_F(InterestsFetcherTest, FailedResponse) {
  RequestInterests();
  EXPECT_CALL(*this, OnFailedResponse());
  IssueAccessTokens();
  SendFailedResponse();
}

TEST_F(InterestsFetcherTest, FailedOAuthRequest) {
  RequestInterests();
  EXPECT_CALL(*this, OnFailedResponse());
  IssueAccessTokenErrors();
}

TEST_F(InterestsFetcherTest, RetryOnAuthorizationError) {
  RequestInterests();

  EXPECT_CALL(*this, OnEmptyResponse()).Times(0);
  IssueAccessTokens();
  SendAuthorizationError();

  EXPECT_CALL(*this, OnEmptyResponse());
  IssueAccessTokens();
  SendValidResponse(kEmptyResponse);
}

TEST_F(InterestsFetcherTest, RetryOnlyOnceOnAuthorizationError) {
  RequestInterests();

  EXPECT_CALL(*this, OnEmptyResponse()).Times(0);
  IssueAccessTokens();
  SendAuthorizationError();

  EXPECT_CALL(*this, OnFailedResponse());
  IssueAccessTokens();
  SendAuthorizationError();
}