blob: 77ba581069d46e08a18cce3530ac9e2a76610388 [file] [log] [blame]
// Copyright 2017 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/ntp_snippets/contextual/contextual_suggestions_fetcher_impl.h"
#include <utility>
#include <vector>
#include "base/json/json_reader.h"
#include "base/optional.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "components/ntp_snippets/category.h"
#include "components/ntp_snippets/remote/test_utils.h"
#include "components/prefs/testing_pref_service.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "net/url_request/url_request_test_util.h"
#include "services/identity/public/cpp/identity_test_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace ntp_snippets {
namespace {
using testing::_;
using testing::AllOf;
using testing::Eq;
using testing::Field;
using testing::IsEmpty;
using testing::Property;
using testing::StartsWith;
const char kTestEmail[] = "foo@bar.com";
const char kValidURL[] = "http://valid-url.test";
MATCHER(IsEmptySuggestionsList, "is an empty list of suggestions") {
ContextualSuggestionsFetcher::OptionalSuggestions& optional_suggestions =
*arg;
if (!optional_suggestions) {
*result_listener << "expected a suggestion list.";
return false;
}
if (optional_suggestions->size() != 0) {
*result_listener << "expected empty suggestions, got: "
<< optional_suggestions->size();
return false;
}
return true;
}
MATCHER_P(IsSingleSuggestion,
url,
"is a list with the single suggestion %(url)s") {
ContextualSuggestionsFetcher::OptionalSuggestions& optional_suggestions =
*arg;
if (!optional_suggestions) {
*result_listener << "expected a suggestion list.";
return false;
}
if (optional_suggestions->size() != 1) {
*result_listener << "expected single suggestion, got: "
<< optional_suggestions->size();
return false;
}
if (optional_suggestions.value()[0]->url().spec() != url) {
*result_listener << "unexpected url, got: "
<< optional_suggestions.value()[0]->url().spec();
return false;
}
return true;
}
class MockSuggestionsAvailableCallback {
public:
// Workaround for gMock's lack of support for movable arguments.
void WrappedRun(
Status status,
ContextualSuggestionsFetcher::OptionalSuggestions optional_suggestions) {
Run(status, &optional_suggestions);
}
MOCK_METHOD2(Run,
void(Status status,
ContextualSuggestionsFetcher::OptionalSuggestions*
optional_suggestions));
};
void ParseJson(const std::string& json,
const SuccessCallback& success_callback,
const ErrorCallback& error_callback) {
base::JSONReader json_reader;
std::unique_ptr<base::Value> value = json_reader.ReadToValue(json);
if (value) {
success_callback.Run(std::move(value));
} else {
error_callback.Run(json_reader.GetErrorMessage());
}
}
} // namespace
class ContextualSuggestionsFetcherTest : public testing::Test {
public:
ContextualSuggestionsFetcherTest()
: fake_url_fetcher_factory_(new net::FakeURLFetcherFactory(nullptr)),
mock_task_runner_(new base::TestMockTimeTaskRunner(
base::TestMockTimeTaskRunner::Type::kBoundToThread)) {
scoped_refptr<net::TestURLRequestContextGetter> request_context_getter =
new net::TestURLRequestContextGetter(mock_task_runner_.get());
fetcher_ = std::make_unique<ContextualSuggestionsFetcherImpl>(
identity_test_env_.identity_manager(),
std::move(request_context_getter), test_utils_.pref_service(),
base::BindRepeating(&ParseJson));
}
~ContextualSuggestionsFetcherTest() override {
}
void FastForwardUntilNoTasksRemain() {
mock_task_runner_->FastForwardUntilNoTasksRemain();
}
void InitializeFakeCredentials() {
identity_test_env()->MakePrimaryAccountAvailable(kTestEmail);
}
void SetFakeResponse(const std::string& response_data,
net::HttpStatusCode response_code,
net::URLRequestStatus::Status status) {
fake_url_fetcher_factory_->SetFakeResponse(
fetcher_->GetFetchUrlForTesting(), response_data, response_code,
status);
}
ContextualSuggestionsFetcher::SuggestionsAvailableCallback
ToSuggestionsAvailableCallback(MockSuggestionsAvailableCallback* callback) {
return base::BindOnce(&MockSuggestionsAvailableCallback::WrappedRun,
base::Unretained(callback));
}
ContextualSuggestionsFetcher& fetcher() { return *fetcher_; }
MockSuggestionsAvailableCallback& mock_suggestions_available_callback() {
return mock_suggestions_available_callback_;
}
identity::IdentityTestEnvironment* identity_test_env() {
return &identity_test_env_;
}
private:
identity::IdentityTestEnvironment identity_test_env_;
std::unique_ptr<net::FakeURLFetcherFactory> fake_url_fetcher_factory_;
std::unique_ptr<ContextualSuggestionsFetcherImpl> fetcher_;
MockSuggestionsAvailableCallback mock_suggestions_available_callback_;
scoped_refptr<base::TestMockTimeTaskRunner> mock_task_runner_;
test::RemoteSuggestionsTestUtils test_utils_;
DISALLOW_COPY_AND_ASSIGN(ContextualSuggestionsFetcherTest);
};
TEST_F(ContextualSuggestionsFetcherTest, ShouldCreateFetcher) {
FastForwardUntilNoTasksRemain();
EXPECT_THAT(fetcher().GetLastStatusForTesting(), IsEmpty());
}
TEST_F(ContextualSuggestionsFetcherTest, ShouldFetchSuggestion) {
InitializeFakeCredentials();
const std::string kValidResponseData =
"{\"categories\" : [{"
" \"id\": 0,"
" \"suggestions\" : [{"
" \"title\" : \"Title\","
" \"summary\" : \"...\","
" \"url\" : \"http://localhost/foobar\","
" \"imageUrl\" : \"http://localhost/foobar.jpg\""
" }]"
"}]}";
SetFakeResponse(/*response_data=*/kValidResponseData, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
EXPECT_CALL(mock_suggestions_available_callback(),
Run(Property(&Status::IsSuccess, true),
IsSingleSuggestion("http://localhost/foobar")));
fetcher().FetchContextualSuggestions(
GURL(kValidURL),
ToSuggestionsAvailableCallback(&mock_suggestions_available_callback()));
identity_test_env()->WaitForAccessTokenRequestAndRespondWithToken(
"access_token", base::Time::Max());
FastForwardUntilNoTasksRemain();
EXPECT_THAT(fetcher().GetLastStatusForTesting(), Eq("OK"));
EXPECT_THAT(fetcher().GetLastJsonForTesting(), Eq(kValidResponseData));
}
TEST_F(ContextualSuggestionsFetcherTest, ShouldFetchEmptySuggestionsList) {
InitializeFakeCredentials();
const std::string kValidEmptyCategoryResponseData =
"{\"categories\" : [{"
" \"id\": 0,"
" \"suggestions\": []"
"}]}";
SetFakeResponse(/*response_data=*/kValidEmptyCategoryResponseData,
net::HTTP_OK, net::URLRequestStatus::SUCCESS);
EXPECT_CALL(mock_suggestions_available_callback(),
Run(Property(&Status::IsSuccess, true),
/*fetched_categories=*/IsEmptySuggestionsList()));
fetcher().FetchContextualSuggestions(
GURL(kValidURL),
ToSuggestionsAvailableCallback(&mock_suggestions_available_callback()));
identity_test_env()->WaitForAccessTokenRequestAndRespondWithToken(
"access_token", base::Time::Max());
FastForwardUntilNoTasksRemain();
EXPECT_THAT(fetcher().GetLastStatusForTesting(), Eq("OK"));
EXPECT_THAT(fetcher().GetLastJsonForTesting(),
Eq(kValidEmptyCategoryResponseData));
}
TEST_F(ContextualSuggestionsFetcherTest,
ShouldReportErrorForEmptyResponseData) {
InitializeFakeCredentials();
SetFakeResponse(/*response_data=*/std::string(), net::HTTP_NOT_FOUND,
net::URLRequestStatus::SUCCESS);
EXPECT_CALL(mock_suggestions_available_callback(),
Run(Property(&Status::IsSuccess, false), _));
fetcher().FetchContextualSuggestions(
GURL(kValidURL),
ToSuggestionsAvailableCallback(&mock_suggestions_available_callback()));
identity_test_env()->WaitForAccessTokenRequestAndRespondWithToken(
"access_token", base::Time::Max());
FastForwardUntilNoTasksRemain();
}
TEST_F(ContextualSuggestionsFetcherTest,
ShouldReportErrorForInvalidResponseData) {
InitializeFakeCredentials();
const std::string kInvalidResponseData = "{ \"recos\": []";
SetFakeResponse(/*response_data=*/kInvalidResponseData, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
EXPECT_CALL(
mock_suggestions_available_callback(),
Run(Field(&Status::code, StatusCode::TEMPORARY_ERROR),
/*fetched_categories=*/Property(
&ContextualSuggestionsFetcher::OptionalSuggestions::has_value,
false)));
fetcher().FetchContextualSuggestions(
GURL(kValidURL),
ToSuggestionsAvailableCallback(&mock_suggestions_available_callback()));
identity_test_env()->WaitForAccessTokenRequestAndRespondWithToken(
"access_token", base::Time::Max());
FastForwardUntilNoTasksRemain();
EXPECT_THAT(fetcher().GetLastStatusForTesting(),
StartsWith("Received invalid JSON (error "));
EXPECT_THAT(fetcher().GetLastJsonForTesting(), Eq(kInvalidResponseData));
}
} // namespace ntp_snippets