blob: 6b53167d725383562e0da42451b8033a04cc3f40 [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 "chrome/browser/supervised_user/child_accounts/family_info_fetcher.h"
#include <stddef.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/json/json_writer.h"
#include "base/message_loop/message_loop.h"
#include "base/test/mock_callback.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/values.h"
#include "net/base/net_errors.h"
#include "net/http/http_status_code.h"
#include "net/url_request/url_request_test_util.h"
#include "services/identity/public/cpp/identity_test_environment.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
const char kAccountId[] = "user@gmail.com";
const char kDifferentAccountId[] = "some_other_user@gmail.com";
bool operator==(const FamilyInfoFetcher::FamilyProfile& family1,
const FamilyInfoFetcher::FamilyProfile& family2) {
return family1.id == family2.id &&
family1.name == family2.name;
}
bool operator==(const FamilyInfoFetcher::FamilyMember& account1,
const FamilyInfoFetcher::FamilyMember& account2) {
return account1.obfuscated_gaia_id == account2.obfuscated_gaia_id &&
account1.role == account2.role &&
account1.display_name == account2.display_name &&
account1.email == account2.email &&
account1.profile_url == account2.profile_url &&
account1.profile_image_url == account2.profile_image_url;
}
namespace {
std::string BuildGetFamilyProfileResponse(
const FamilyInfoFetcher::FamilyProfile& family) {
base::DictionaryValue dict;
auto family_dict = std::make_unique<base::DictionaryValue>();
family_dict->SetKey("familyId", base::Value(family.id));
std::unique_ptr<base::DictionaryValue> profile_dict =
std::make_unique<base::DictionaryValue>();
profile_dict->SetKey("name", base::Value(family.name));
family_dict->SetWithoutPathExpansion("profile", std::move(profile_dict));
dict.SetWithoutPathExpansion("family", std::move(family_dict));
std::string result;
base::JSONWriter::Write(dict, &result);
return result;
}
std::string BuildEmptyGetFamilyProfileResponse() {
base::DictionaryValue dict;
dict.SetWithoutPathExpansion("family",
std::make_unique<base::DictionaryValue>());
std::string result;
base::JSONWriter::Write(dict, &result);
return result;
}
std::string BuildGetFamilyMembersResponse(
const std::vector<FamilyInfoFetcher::FamilyMember>& members) {
base::DictionaryValue dict;
auto list = std::make_unique<base::ListValue>();
for (size_t i = 0; i < members.size(); i++) {
const FamilyInfoFetcher::FamilyMember& member = members[i];
std::unique_ptr<base::DictionaryValue> member_dict(
new base::DictionaryValue);
member_dict->SetKey("userId", base::Value(member.obfuscated_gaia_id));
member_dict->SetKey(
"role", base::Value(FamilyInfoFetcher::RoleToString(member.role)));
if (!member.display_name.empty() ||
!member.email.empty() ||
!member.profile_url.empty() ||
!member.profile_image_url.empty()) {
auto profile_dict = std::make_unique<base::DictionaryValue>();
if (!member.display_name.empty())
profile_dict->SetKey("displayName", base::Value(member.display_name));
if (!member.email.empty())
profile_dict->SetKey("email", base::Value(member.email));
if (!member.profile_url.empty())
profile_dict->SetKey("profileUrl", base::Value(member.profile_url));
if (!member.profile_image_url.empty())
profile_dict->SetKey("profileImageUrl",
base::Value(member.profile_image_url));
member_dict->SetWithoutPathExpansion("profile", std::move(profile_dict));
}
list->Append(std::move(member_dict));
}
dict.SetWithoutPathExpansion("members", std::move(list));
std::string result;
base::JSONWriter::Write(dict, &result);
return result;
}
} // namespace
class FamilyInfoFetcherTest
: public testing::Test,
public FamilyInfoFetcher::Consumer {
public:
MOCK_METHOD1(OnGetFamilyProfileSuccess,
void(const FamilyInfoFetcher::FamilyProfile& family));
MOCK_METHOD1(OnGetFamilyMembersSuccess,
void(const std::vector<FamilyInfoFetcher::FamilyMember>&
members));
MOCK_METHOD1(OnFailure, void(FamilyInfoFetcher::ErrorCode error));
private:
void EnsureFamilyInfoFetcher() {
DCHECK(!fetcher_);
fetcher_.reset(new FamilyInfoFetcher(
this, identity_test_env_.identity_manager(),
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_)));
}
protected:
void StartGetFamilyProfile() {
EnsureFamilyInfoFetcher();
fetcher_->StartGetFamilyProfile();
}
void StartGetFamilyMembers() {
EnsureFamilyInfoFetcher();
fetcher_->StartGetFamilyMembers();
}
void IssueRefreshToken() {
identity_test_env_.MakePrimaryAccountAvailable(kAccountId);
}
void IssueRefreshTokenForDifferentAccount() {
identity_test_env_.MakeAccountAvailable(kDifferentAccountId);
}
void WaitForAccessTokenRequestAndIssueToken() {
identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithToken(
identity_test_env_.identity_manager()->GetPrimaryAccountId(),
"access_token", base::Time::Now() + base::TimeDelta::FromHours(1));
}
void SendResponse(net::Error error, const std::string& response) {
fetcher_->OnSimpleLoaderCompleteInternal(error, net::HTTP_OK, response);
}
void SendValidGetFamilyProfileResponse(
const FamilyInfoFetcher::FamilyProfile& family) {
SendResponse(net::OK, BuildGetFamilyProfileResponse(family));
}
void SendValidGetFamilyMembersResponse(
const std::vector<FamilyInfoFetcher::FamilyMember>& members) {
SendResponse(net::OK, BuildGetFamilyMembersResponse(members));
}
void SendInvalidGetFamilyProfileResponse() {
SendResponse(net::OK, BuildEmptyGetFamilyProfileResponse());
}
void SendFailedResponse() {
SendResponse(net::ERR_ABORTED, std::string());
}
base::MessageLoop message_loop_;
identity::IdentityTestEnvironment identity_test_env_;
network::TestURLLoaderFactory test_url_loader_factory_;
std::unique_ptr<FamilyInfoFetcher> fetcher_;
};
TEST_F(FamilyInfoFetcherTest, GetFamilyProfileSuccess) {
IssueRefreshToken();
StartGetFamilyProfile();
WaitForAccessTokenRequestAndIssueToken();
FamilyInfoFetcher::FamilyProfile family("test", "My Test Family");
EXPECT_CALL(*this, OnGetFamilyProfileSuccess(family));
SendValidGetFamilyProfileResponse(family);
}
TEST_F(FamilyInfoFetcherTest, GetFamilyMembersSuccess) {
IssueRefreshToken();
StartGetFamilyMembers();
WaitForAccessTokenRequestAndIssueToken();
std::vector<FamilyInfoFetcher::FamilyMember> members;
members.push_back(
FamilyInfoFetcher::FamilyMember("someObfuscatedGaiaId",
FamilyInfoFetcher::HEAD_OF_HOUSEHOLD,
"Homer Simpson",
"homer@simpson.com",
"http://profile.url/homer",
"http://profile.url/homer/image"));
members.push_back(
FamilyInfoFetcher::FamilyMember("anotherObfuscatedGaiaId",
FamilyInfoFetcher::PARENT,
"Marge Simpson",
std::string(),
"http://profile.url/marge",
std::string()));
members.push_back(
FamilyInfoFetcher::FamilyMember("obfuscatedGaiaId3",
FamilyInfoFetcher::CHILD,
"Lisa Simpson",
"lisa@gmail.com",
std::string(),
"http://profile.url/lisa/image"));
members.push_back(
FamilyInfoFetcher::FamilyMember("obfuscatedGaiaId4",
FamilyInfoFetcher::CHILD,
"Bart Simpson",
"bart@bart.bart",
std::string(),
std::string()));
members.push_back(
FamilyInfoFetcher::FamilyMember("obfuscatedGaiaId5",
FamilyInfoFetcher::MEMBER,
std::string(),
std::string(),
std::string(),
std::string()));
EXPECT_CALL(*this, OnGetFamilyMembersSuccess(members));
SendValidGetFamilyMembersResponse(members);
}
TEST_F(FamilyInfoFetcherTest, SuccessAfterWaitingForRefreshToken) {
// Early set the primary account so that the fetcher is created with a proper
// account_id. We don't use IssueRefreshToken() as it also sets a refresh
// token for the primary account and that's something we don't want for this
// test.
identity_test_env_.SetPrimaryAccount(kAccountId);
StartGetFamilyProfile();
// Since there is no refresh token yet, we should not get a request for an
// access token at this point.
base::MockCallback<base::OnceClosure> access_token_requested;
EXPECT_CALL(access_token_requested, Run()).Times(0);
identity_test_env_.SetCallbackForNextAccessTokenRequest(
access_token_requested.Get());
// In this case we don't directly call IssueRefreshToken() as it calls
// MakePrimaryAccountAvailable(). Since we already have a primary account set
// we cannot set another one without clearing it before.
identity_test_env_.SetRefreshTokenForPrimaryAccount();
// Do reset the callback for access token request before using the Wait* APIs.
identity_test_env_.SetCallbackForNextAccessTokenRequest(base::OnceClosure());
WaitForAccessTokenRequestAndIssueToken();
FamilyInfoFetcher::FamilyProfile family("test", "My Test Family");
EXPECT_CALL(*this, OnGetFamilyProfileSuccess(family));
SendValidGetFamilyProfileResponse(family);
}
TEST_F(FamilyInfoFetcherTest, NoRefreshToken) {
// Set the primary account before creating the fetcher to allow it to properly
// retrieve the primary account_id from IdentityManager. We don't call
// IssueRefreshToken because we don't want it to precisely issue a refresh
// token for the primary account, just set it.
identity_test_env_.SetPrimaryAccount(kAccountId);
StartGetFamilyProfile();
IssueRefreshTokenForDifferentAccount();
// Credentials for a different user should be ignored, i.e. not result in a
// request for an access token.
base::MockCallback<base::OnceClosure> access_token_requested;
EXPECT_CALL(access_token_requested, Run()).Times(0);
identity_test_env_.SetCallbackForNextAccessTokenRequest(
access_token_requested.Get());
// After all refresh tokens have been loaded, there is still no token for our
// user, so we expect a token error.
EXPECT_CALL(*this, OnFailure(FamilyInfoFetcher::TOKEN_ERROR));
identity_test_env_.ReloadAccountsFromDisk();
}
TEST_F(FamilyInfoFetcherTest, GetTokenFailure) {
IssueRefreshToken();
StartGetFamilyProfile();
// On failure to get an access token we expect a token error.
EXPECT_CALL(*this, OnFailure(FamilyInfoFetcher::TOKEN_ERROR));
identity_test_env_.WaitForAccessTokenRequestIfNecessaryAndRespondWithError(
identity_test_env_.identity_manager()->GetPrimaryAccountId(),
GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
}
TEST_F(FamilyInfoFetcherTest, InvalidResponse) {
IssueRefreshToken();
StartGetFamilyProfile();
WaitForAccessTokenRequestAndIssueToken();
// Invalid response data should result in a service error.
EXPECT_CALL(*this, OnFailure(FamilyInfoFetcher::SERVICE_ERROR));
SendInvalidGetFamilyProfileResponse();
}
TEST_F(FamilyInfoFetcherTest, FailedResponse) {
IssueRefreshToken();
StartGetFamilyProfile();
WaitForAccessTokenRequestAndIssueToken();
// Failed API call should result in a network error.
EXPECT_CALL(*this, OnFailure(FamilyInfoFetcher::NETWORK_ERROR));
SendFailedResponse();
}