1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "sync/internal_api/public/attachments/attachment_downloader.h" 6 7#include "base/bind.h" 8#include "base/memory/weak_ptr.h" 9#include "base/message_loop/message_loop.h" 10#include "base/run_loop.h" 11#include "base/thread_task_runner_handle.h" 12#include "google_apis/gaia/fake_oauth2_token_service.h" 13#include "google_apis/gaia/gaia_constants.h" 14#include "net/url_request/test_url_fetcher_factory.h" 15#include "net/url_request/url_request_test_util.h" 16#include "sync/api/attachments/attachment.h" 17#include "testing/gtest/include/gtest/gtest.h" 18 19namespace syncer { 20 21namespace { 22 23const char kAccountId[] = "attachments@gmail.com"; 24const char kAccessToken[] = "access.token"; 25const char kAttachmentServerUrl[] = "http://attachments.com/"; 26const char kAttachmentContent[] = "attachment.content"; 27 28// MockOAuth2TokenService remembers last request for access token and verifies 29// that only one request is active at a time. 30// Call RespondToAccessTokenRequest to respond to it. 31class MockOAuth2TokenService : public FakeOAuth2TokenService { 32 public: 33 MockOAuth2TokenService() : num_invalidate_token_(0) {} 34 35 virtual ~MockOAuth2TokenService() {} 36 37 void RespondToAccessTokenRequest(GoogleServiceAuthError error); 38 39 int num_invalidate_token() const { return num_invalidate_token_; } 40 41 protected: 42 virtual void FetchOAuth2Token(RequestImpl* request, 43 const std::string& account_id, 44 net::URLRequestContextGetter* getter, 45 const std::string& client_id, 46 const std::string& client_secret, 47 const ScopeSet& scopes) OVERRIDE; 48 49 virtual void InvalidateOAuth2Token(const std::string& account_id, 50 const std::string& client_id, 51 const ScopeSet& scopes, 52 const std::string& access_token) OVERRIDE; 53 54 private: 55 base::WeakPtr<RequestImpl> last_request_; 56 int num_invalidate_token_; 57}; 58 59void MockOAuth2TokenService::RespondToAccessTokenRequest( 60 GoogleServiceAuthError error) { 61 EXPECT_TRUE(last_request_ != NULL); 62 std::string access_token; 63 base::Time expiration_time; 64 if (error == GoogleServiceAuthError::AuthErrorNone()) { 65 access_token = kAccessToken; 66 expiration_time = base::Time::Max(); 67 } 68 base::MessageLoop::current()->PostTask( 69 FROM_HERE, 70 base::Bind(&OAuth2TokenService::RequestImpl::InformConsumer, 71 last_request_, 72 error, 73 access_token, 74 expiration_time)); 75} 76 77void MockOAuth2TokenService::FetchOAuth2Token( 78 RequestImpl* request, 79 const std::string& account_id, 80 net::URLRequestContextGetter* getter, 81 const std::string& client_id, 82 const std::string& client_secret, 83 const ScopeSet& scopes) { 84 // Only one request at a time is allowed. 85 EXPECT_TRUE(last_request_ == NULL); 86 last_request_ = request->AsWeakPtr(); 87} 88 89void MockOAuth2TokenService::InvalidateOAuth2Token( 90 const std::string& account_id, 91 const std::string& client_id, 92 const ScopeSet& scopes, 93 const std::string& access_token) { 94 ++num_invalidate_token_; 95} 96 97class TokenServiceProvider 98 : public OAuth2TokenServiceRequest::TokenServiceProvider, 99 base::NonThreadSafe { 100 public: 101 TokenServiceProvider(OAuth2TokenService* token_service); 102 103 // OAuth2TokenService::TokenServiceProvider implementation. 104 virtual scoped_refptr<base::SingleThreadTaskRunner> 105 GetTokenServiceTaskRunner() OVERRIDE; 106 virtual OAuth2TokenService* GetTokenService() OVERRIDE; 107 108 private: 109 virtual ~TokenServiceProvider(); 110 111 scoped_refptr<base::SingleThreadTaskRunner> task_runner_; 112 OAuth2TokenService* token_service_; 113}; 114 115TokenServiceProvider::TokenServiceProvider(OAuth2TokenService* token_service) 116 : task_runner_(base::ThreadTaskRunnerHandle::Get()), 117 token_service_(token_service) { 118 DCHECK(token_service_); 119} 120 121TokenServiceProvider::~TokenServiceProvider() { 122} 123 124scoped_refptr<base::SingleThreadTaskRunner> 125TokenServiceProvider::GetTokenServiceTaskRunner() { 126 return task_runner_; 127} 128 129OAuth2TokenService* TokenServiceProvider::GetTokenService() { 130 DCHECK(task_runner_->BelongsToCurrentThread()); 131 return token_service_; 132} 133 134} // namespace 135 136class AttachmentDownloaderImplTest : public testing::Test { 137 protected: 138 typedef std::map<AttachmentId, AttachmentDownloader::DownloadResult> 139 ResultsMap; 140 141 AttachmentDownloaderImplTest() : num_completed_downloads_(0) {} 142 143 virtual void SetUp() OVERRIDE; 144 virtual void TearDown() OVERRIDE; 145 146 AttachmentDownloader* downloader() { return attachment_downloader_.get(); } 147 148 MockOAuth2TokenService* token_service() { return token_service_.get(); } 149 150 int num_completed_downloads() { return num_completed_downloads_; } 151 152 AttachmentDownloader::DownloadCallback download_callback( 153 const AttachmentId& id) { 154 return base::Bind(&AttachmentDownloaderImplTest::DownloadDone, 155 base::Unretained(this), 156 id); 157 } 158 159 void CompleteDownload(int response_code); 160 161 void DownloadDone(const AttachmentId& attachment_id, 162 const AttachmentDownloader::DownloadResult& result, 163 scoped_ptr<Attachment> attachment); 164 165 void VerifyDownloadResult(const AttachmentId& attachment_id, 166 const AttachmentDownloader::DownloadResult& result); 167 168 void RunMessageLoop(); 169 170 private: 171 base::MessageLoopForIO message_loop_; 172 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_; 173 net::TestURLFetcherFactory url_fetcher_factory_; 174 scoped_ptr<MockOAuth2TokenService> token_service_; 175 scoped_ptr<AttachmentDownloader> attachment_downloader_; 176 ResultsMap download_results_; 177 int num_completed_downloads_; 178}; 179 180void AttachmentDownloaderImplTest::SetUp() { 181 url_request_context_getter_ = 182 new net::TestURLRequestContextGetter(message_loop_.message_loop_proxy()); 183 url_fetcher_factory_.set_remove_fetcher_on_delete(true); 184 token_service_.reset(new MockOAuth2TokenService()); 185 token_service_->AddAccount(kAccountId); 186 scoped_refptr<OAuth2TokenServiceRequest::TokenServiceProvider> 187 token_service_provider(new TokenServiceProvider(token_service_.get())); 188 189 OAuth2TokenService::ScopeSet scopes; 190 scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope); 191 attachment_downloader_ = 192 AttachmentDownloader::Create(GURL(kAttachmentServerUrl), 193 url_request_context_getter_, 194 kAccountId, 195 scopes, 196 token_service_provider); 197} 198 199void AttachmentDownloaderImplTest::TearDown() { 200 RunMessageLoop(); 201} 202 203void AttachmentDownloaderImplTest::CompleteDownload(int response_code) { 204 // TestURLFetcherFactory remembers last active URLFetcher. 205 net::TestURLFetcher* fetcher = url_fetcher_factory_.GetFetcherByID(0); 206 // There should be outstanding url fetch request. 207 EXPECT_TRUE(fetcher != NULL); 208 fetcher->set_status(net::URLRequestStatus()); 209 fetcher->set_response_code(response_code); 210 if (response_code == net::HTTP_OK) { 211 fetcher->SetResponseString(kAttachmentContent); 212 } 213 // Call URLFetcherDelegate. 214 net::URLFetcherDelegate* delegate = fetcher->delegate(); 215 delegate->OnURLFetchComplete(fetcher); 216 RunMessageLoop(); 217 // Once result is processed URLFetcher should be deleted. 218 fetcher = url_fetcher_factory_.GetFetcherByID(0); 219 EXPECT_TRUE(fetcher == NULL); 220} 221 222void AttachmentDownloaderImplTest::DownloadDone( 223 const AttachmentId& attachment_id, 224 const AttachmentDownloader::DownloadResult& result, 225 scoped_ptr<Attachment> attachment) { 226 download_results_.insert(std::make_pair(attachment_id, result)); 227 if (result == AttachmentDownloader::DOWNLOAD_SUCCESS) { 228 // Successful download should be accompanied by valid attachment with 229 // matching id and valid data. 230 EXPECT_TRUE(attachment != NULL); 231 EXPECT_EQ(attachment_id, attachment->GetId()); 232 233 scoped_refptr<base::RefCountedMemory> data = attachment->GetData(); 234 std::string data_as_string(data->front_as<char>(), data->size()); 235 EXPECT_EQ(data_as_string, kAttachmentContent); 236 } else { 237 EXPECT_TRUE(attachment == NULL); 238 } 239 ++num_completed_downloads_; 240} 241 242void AttachmentDownloaderImplTest::VerifyDownloadResult( 243 const AttachmentId& attachment_id, 244 const AttachmentDownloader::DownloadResult& result) { 245 ResultsMap::const_iterator iter = download_results_.find(attachment_id); 246 EXPECT_TRUE(iter != download_results_.end()); 247 EXPECT_EQ(iter->second, result); 248} 249 250void AttachmentDownloaderImplTest::RunMessageLoop() { 251 base::RunLoop run_loop; 252 run_loop.RunUntilIdle(); 253} 254 255TEST_F(AttachmentDownloaderImplTest, HappyCase) { 256 AttachmentId id1 = AttachmentId::Create(); 257 // DownloadAttachment should trigger RequestAccessToken. 258 downloader()->DownloadAttachment(id1, download_callback(id1)); 259 RunMessageLoop(); 260 // Return valid access token. 261 token_service()->RespondToAccessTokenRequest( 262 GoogleServiceAuthError::AuthErrorNone()); 263 RunMessageLoop(); 264 // Check that there is outstanding URLFetcher request and complete it. 265 CompleteDownload(net::HTTP_OK); 266 // Verify that callback was called for the right id with the right result. 267 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS); 268} 269 270TEST_F(AttachmentDownloaderImplTest, SameIdMultipleDownloads) { 271 AttachmentId id1 = AttachmentId::Create(); 272 // Call DownloadAttachment two times for the same id. 273 downloader()->DownloadAttachment(id1, download_callback(id1)); 274 downloader()->DownloadAttachment(id1, download_callback(id1)); 275 RunMessageLoop(); 276 // Return valid access token. 277 token_service()->RespondToAccessTokenRequest( 278 GoogleServiceAuthError::AuthErrorNone()); 279 RunMessageLoop(); 280 // Start one more download after access token is received. 281 downloader()->DownloadAttachment(id1, download_callback(id1)); 282 // Complete URLFetcher request. 283 CompleteDownload(net::HTTP_OK); 284 // Verify that all download requests completed. 285 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS); 286 EXPECT_EQ(3, num_completed_downloads()); 287 288 // Let's download the same attachment again. 289 downloader()->DownloadAttachment(id1, download_callback(id1)); 290 RunMessageLoop(); 291 // Verify that it didn't finish prematurely. 292 EXPECT_EQ(3, num_completed_downloads()); 293 // Return valid access token. 294 token_service()->RespondToAccessTokenRequest( 295 GoogleServiceAuthError::AuthErrorNone()); 296 RunMessageLoop(); 297 // Complete URLFetcher request. 298 CompleteDownload(net::HTTP_OK); 299 // Verify that all download requests completed. 300 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS); 301 EXPECT_EQ(4, num_completed_downloads()); 302} 303 304TEST_F(AttachmentDownloaderImplTest, RequestAccessTokenFails) { 305 AttachmentId id1 = AttachmentId::Create(); 306 AttachmentId id2 = AttachmentId::Create(); 307 // Trigger first RequestAccessToken. 308 downloader()->DownloadAttachment(id1, download_callback(id1)); 309 RunMessageLoop(); 310 // Return valid access token. 311 token_service()->RespondToAccessTokenRequest( 312 GoogleServiceAuthError::AuthErrorNone()); 313 RunMessageLoop(); 314 // Trigger second RequestAccessToken. 315 downloader()->DownloadAttachment(id2, download_callback(id2)); 316 RunMessageLoop(); 317 // Fail RequestAccessToken. 318 token_service()->RespondToAccessTokenRequest( 319 GoogleServiceAuthError(GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS)); 320 RunMessageLoop(); 321 // Only id2 should fail. 322 VerifyDownloadResult(id2, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR); 323 // Complete request for id1. 324 CompleteDownload(net::HTTP_OK); 325 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_SUCCESS); 326} 327 328TEST_F(AttachmentDownloaderImplTest, URLFetcher_BadToken) { 329 AttachmentId id1 = AttachmentId::Create(); 330 downloader()->DownloadAttachment(id1, download_callback(id1)); 331 RunMessageLoop(); 332 // Return valid access token. 333 token_service()->RespondToAccessTokenRequest( 334 GoogleServiceAuthError::AuthErrorNone()); 335 RunMessageLoop(); 336 // Fail URLFetcher. This should trigger download failure and access token 337 // invalidation. 338 CompleteDownload(net::HTTP_UNAUTHORIZED); 339 EXPECT_EQ(1, token_service()->num_invalidate_token()); 340 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR); 341} 342 343TEST_F(AttachmentDownloaderImplTest, URLFetcher_ServiceUnavailable) { 344 AttachmentId id1 = AttachmentId::Create(); 345 downloader()->DownloadAttachment(id1, download_callback(id1)); 346 RunMessageLoop(); 347 // Return valid access token. 348 token_service()->RespondToAccessTokenRequest( 349 GoogleServiceAuthError::AuthErrorNone()); 350 RunMessageLoop(); 351 // Fail URLFetcher. This should trigger download failure. Access token 352 // shouldn't be invalidated. 353 CompleteDownload(net::HTTP_SERVICE_UNAVAILABLE); 354 EXPECT_EQ(0, token_service()->num_invalidate_token()); 355 VerifyDownloadResult(id1, AttachmentDownloader::DOWNLOAD_TRANSIENT_ERROR); 356} 357 358} // namespace syncer 359