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_uploader_impl.h"
6
7 #include "base/bind.h"
8 #include "base/callback.h"
9 #include "base/memory/ref_counted.h"
10 #include "base/memory/ref_counted_memory.h"
11 #include "base/message_loop/message_loop.h"
12 #include "base/run_loop.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/synchronization/lock.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "base/threading/non_thread_safe.h"
17 #include "base/threading/thread.h"
18 #include "google_apis/gaia/fake_oauth2_token_service.h"
19 #include "google_apis/gaia/gaia_constants.h"
20 #include "google_apis/gaia/oauth2_token_service_request.h"
21 #include "net/test/embedded_test_server/embedded_test_server.h"
22 #include "net/test/embedded_test_server/http_request.h"
23 #include "net/test/embedded_test_server/http_response.h"
24 #include "net/url_request/url_request_test_util.h"
25 #include "sync/api/attachments/attachment.h"
26 #include "sync/protocol/sync.pb.h"
27 #include "testing/gmock/include/gmock/gmock-matchers.h"
28 #include "testing/gtest/include/gtest/gtest.h"
29
30 namespace {
31
32 const char kAttachmentData[] = "some data";
33 const char kAccountId[] = "some-account-id";
34 const char kAccessToken[] = "some-access-token";
35 const char kAuthorization[] = "Authorization";
36
37 } // namespace
38
39 namespace syncer {
40
41 using net::test_server::BasicHttpResponse;
42 using net::test_server::HttpRequest;
43 using net::test_server::HttpResponse;
44
45 class RequestHandler;
46
47 // A mock implementation of an OAuth2TokenService.
48 //
49 // Use |SetResponse| to vary the response to token requests.
50 //
51 // Use |num_invalidate_token| and |last_token_invalidated| to check the number
52 // of invalidate token operations performed and the last token invalidated.
53 class MockOAuth2TokenService : public FakeOAuth2TokenService {
54 public:
55 MockOAuth2TokenService();
56 virtual ~MockOAuth2TokenService();
57
58 void SetResponse(const GoogleServiceAuthError& error,
59 const std::string& access_token,
60 const base::Time& expiration);
61
num_invalidate_token() const62 int num_invalidate_token() const { return num_invalidate_token_; }
63
last_token_invalidated() const64 const std::string& last_token_invalidated() const {
65 return last_token_invalidated_;
66 }
67
68 protected:
69 virtual void FetchOAuth2Token(RequestImpl* request,
70 const std::string& account_id,
71 net::URLRequestContextGetter* getter,
72 const std::string& client_id,
73 const std::string& client_secret,
74 const ScopeSet& scopes) OVERRIDE;
75
76 virtual void InvalidateOAuth2Token(const std::string& account_id,
77 const std::string& client_id,
78 const ScopeSet& scopes,
79 const std::string& access_token) OVERRIDE;
80
81 private:
82 GoogleServiceAuthError response_error_;
83 std::string response_access_token_;
84 base::Time response_expiration_;
85 int num_invalidate_token_;
86 std::string last_token_invalidated_;
87 };
88
MockOAuth2TokenService()89 MockOAuth2TokenService::MockOAuth2TokenService()
90 : response_error_(GoogleServiceAuthError::AuthErrorNone()),
91 response_access_token_(kAccessToken),
92 response_expiration_(base::Time::Max()),
93 num_invalidate_token_(0) {
94 }
95
~MockOAuth2TokenService()96 MockOAuth2TokenService::~MockOAuth2TokenService() {
97 }
98
SetResponse(const GoogleServiceAuthError & error,const std::string & access_token,const base::Time & expiration)99 void MockOAuth2TokenService::SetResponse(const GoogleServiceAuthError& error,
100 const std::string& access_token,
101 const base::Time& expiration) {
102 response_error_ = error;
103 response_access_token_ = access_token;
104 response_expiration_ = expiration;
105 }
106
FetchOAuth2Token(RequestImpl * request,const std::string & account_id,net::URLRequestContextGetter * getter,const std::string & client_id,const std::string & client_secret,const ScopeSet & scopes)107 void MockOAuth2TokenService::FetchOAuth2Token(
108 RequestImpl* request,
109 const std::string& account_id,
110 net::URLRequestContextGetter* getter,
111 const std::string& client_id,
112 const std::string& client_secret,
113 const ScopeSet& scopes) {
114 base::MessageLoop::current()->PostTask(
115 FROM_HERE,
116 base::Bind(&OAuth2TokenService::RequestImpl::InformConsumer,
117 request->AsWeakPtr(),
118 response_error_,
119 response_access_token_,
120 response_expiration_));
121 }
122
InvalidateOAuth2Token(const std::string & account_id,const std::string & client_id,const ScopeSet & scopes,const std::string & access_token)123 void MockOAuth2TokenService::InvalidateOAuth2Token(
124 const std::string& account_id,
125 const std::string& client_id,
126 const ScopeSet& scopes,
127 const std::string& access_token) {
128 ++num_invalidate_token_;
129 last_token_invalidated_ = access_token;
130 }
131
132 class TokenServiceProvider
133 : public OAuth2TokenServiceRequest::TokenServiceProvider,
134 base::NonThreadSafe {
135 public:
136 TokenServiceProvider(OAuth2TokenService* token_service);
137 virtual ~TokenServiceProvider();
138
139 // OAuth2TokenService::TokenServiceProvider implementation.
140 virtual scoped_refptr<base::SingleThreadTaskRunner>
141 GetTokenServiceTaskRunner() OVERRIDE;
142 virtual OAuth2TokenService* GetTokenService() OVERRIDE;
143
144 private:
145 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
146 OAuth2TokenService* token_service_;
147 };
148
TokenServiceProvider(OAuth2TokenService * token_service)149 TokenServiceProvider::TokenServiceProvider(OAuth2TokenService* token_service)
150 : task_runner_(base::ThreadTaskRunnerHandle::Get()),
151 token_service_(token_service) {
152 DCHECK(token_service_);
153 }
154
~TokenServiceProvider()155 TokenServiceProvider::~TokenServiceProvider() {
156 }
157
158 scoped_refptr<base::SingleThreadTaskRunner>
GetTokenServiceTaskRunner()159 TokenServiceProvider::GetTokenServiceTaskRunner() {
160 return task_runner_;
161 }
162
GetTokenService()163 OAuth2TokenService* TokenServiceProvider::GetTokenService() {
164 DCHECK(task_runner_->BelongsToCurrentThread());
165 return token_service_;
166 }
167
168 // Text fixture for AttachmentUploaderImpl test.
169 //
170 // This fixture provides an embedded HTTP server and a mock OAuth2 token service
171 // for interacting with AttachmentUploaderImpl
172 class AttachmentUploaderImplTest : public testing::Test,
173 public base::NonThreadSafe {
174 public:
175 void OnRequestReceived(const HttpRequest& request);
176
177 protected:
178 AttachmentUploaderImplTest();
179 virtual void SetUp();
180 virtual void TearDown();
181
182 // Run the message loop until UploadDone has been invoked |num_uploads| times.
183 void RunAndWaitFor(int num_uploads);
184
185 scoped_ptr<AttachmentUploader>& uploader();
186 const AttachmentUploader::UploadCallback& upload_callback() const;
187 std::vector<HttpRequest>& http_requests_received();
188 std::vector<AttachmentUploader::UploadResult>& upload_results();
189 std::vector<AttachmentId>& updated_attachment_ids();
190 MockOAuth2TokenService& token_service();
191 base::MessageLoopForIO& message_loop();
192 RequestHandler& request_handler();
193
194 private:
195 // An UploadCallback invoked by AttachmentUploaderImpl.
196 void UploadDone(const AttachmentUploader::UploadResult& result,
197 const AttachmentId& updated_attachment_id);
198
199 base::MessageLoopForIO message_loop_;
200 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter_;
201 scoped_ptr<RequestHandler> request_handler_;
202 scoped_ptr<AttachmentUploader> uploader_;
203 AttachmentUploader::UploadCallback upload_callback_;
204 net::test_server::EmbeddedTestServer server_;
205 // A closure that signals an upload has finished.
206 base::Closure signal_upload_done_;
207 std::vector<HttpRequest> http_requests_received_;
208 std::vector<AttachmentUploader::UploadResult> upload_results_;
209 std::vector<AttachmentId> updated_attachment_ids_;
210 scoped_ptr<MockOAuth2TokenService> token_service_;
211
212 // Must be last data member.
213 base::WeakPtrFactory<AttachmentUploaderImplTest> weak_ptr_factory_;
214 };
215
216 // Handles HTTP requests received by the EmbeddedTestServer.
217 //
218 // Responds with HTTP_OK by default. See |SetStatusCode|.
219 class RequestHandler : public base::NonThreadSafe {
220 public:
221 // Construct a RequestHandler that will PostTask to |test| using
222 // |test_task_runner|.
223 RequestHandler(
224 const scoped_refptr<base::SingleThreadTaskRunner>& test_task_runner,
225 const base::WeakPtr<AttachmentUploaderImplTest>& test);
226
227 ~RequestHandler();
228
229 scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request);
230
231 // Set the HTTP status code to respond with.
232 void SetStatusCode(const net::HttpStatusCode& status_code);
233
234 // Returns the HTTP status code that will be used in responses.
235 net::HttpStatusCode GetStatusCode() const;
236
237 private:
238 // Protects status_code_.
239 mutable base::Lock mutex_;
240 net::HttpStatusCode status_code_;
241
242 scoped_refptr<base::SingleThreadTaskRunner> test_task_runner_;
243 base::WeakPtr<AttachmentUploaderImplTest> test_;
244 };
245
AttachmentUploaderImplTest()246 AttachmentUploaderImplTest::AttachmentUploaderImplTest()
247 : weak_ptr_factory_(this) {
248 }
249
OnRequestReceived(const HttpRequest & request)250 void AttachmentUploaderImplTest::OnRequestReceived(const HttpRequest& request) {
251 DCHECK(CalledOnValidThread());
252 http_requests_received_.push_back(request);
253 }
254
SetUp()255 void AttachmentUploaderImplTest::SetUp() {
256 DCHECK(CalledOnValidThread());
257 request_handler_.reset(new RequestHandler(message_loop_.message_loop_proxy(),
258 weak_ptr_factory_.GetWeakPtr()));
259 url_request_context_getter_ =
260 new net::TestURLRequestContextGetter(message_loop_.message_loop_proxy());
261
262 ASSERT_TRUE(server_.InitializeAndWaitUntilReady());
263 server_.RegisterRequestHandler(
264 base::Bind(&RequestHandler::HandleRequest,
265 base::Unretained(request_handler_.get())));
266
267 std::string url_prefix(
268 base::StringPrintf("http://localhost:%d/uploads/", server_.port()));
269
270 token_service_.reset(new MockOAuth2TokenService);
271 scoped_ptr<OAuth2TokenServiceRequest::TokenServiceProvider>
272 token_service_provider(new TokenServiceProvider(token_service_.get()));
273
274 OAuth2TokenService::ScopeSet scopes;
275 scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope);
276 uploader().reset(new AttachmentUploaderImpl(url_prefix,
277 url_request_context_getter_,
278 kAccountId,
279 scopes,
280 token_service_provider.Pass()));
281
282 upload_callback_ = base::Bind(&AttachmentUploaderImplTest::UploadDone,
283 base::Unretained(this));
284 }
285
TearDown()286 void AttachmentUploaderImplTest::TearDown() {
287 base::RunLoop().RunUntilIdle();
288 }
289
RunAndWaitFor(int num_uploads)290 void AttachmentUploaderImplTest::RunAndWaitFor(int num_uploads) {
291 for (int i = 0; i < num_uploads; ++i) {
292 // Run the loop until one upload completes.
293 base::RunLoop run_loop;
294 signal_upload_done_ = run_loop.QuitClosure();
295 run_loop.Run();
296 }
297 }
298
uploader()299 scoped_ptr<AttachmentUploader>& AttachmentUploaderImplTest::uploader() {
300 return uploader_;
301 }
302
303 const AttachmentUploader::UploadCallback&
upload_callback() const304 AttachmentUploaderImplTest::upload_callback() const {
305 return upload_callback_;
306 }
307
http_requests_received()308 std::vector<HttpRequest>& AttachmentUploaderImplTest::http_requests_received() {
309 return http_requests_received_;
310 }
311
312 std::vector<AttachmentUploader::UploadResult>&
upload_results()313 AttachmentUploaderImplTest::upload_results() {
314 return upload_results_;
315 }
316
317 std::vector<AttachmentId>&
updated_attachment_ids()318 AttachmentUploaderImplTest::updated_attachment_ids() {
319 return updated_attachment_ids_;
320 }
321
token_service()322 MockOAuth2TokenService& AttachmentUploaderImplTest::token_service() {
323 return *token_service_;
324 }
325
message_loop()326 base::MessageLoopForIO& AttachmentUploaderImplTest::message_loop() {
327 return message_loop_;
328 }
329
request_handler()330 RequestHandler& AttachmentUploaderImplTest::request_handler() {
331 return *request_handler_;
332 }
333
UploadDone(const AttachmentUploader::UploadResult & result,const AttachmentId & updated_attachment_id)334 void AttachmentUploaderImplTest::UploadDone(
335 const AttachmentUploader::UploadResult& result,
336 const AttachmentId& updated_attachment_id) {
337 DCHECK(CalledOnValidThread());
338 upload_results_.push_back(result);
339 updated_attachment_ids_.push_back(updated_attachment_id);
340 DCHECK(!signal_upload_done_.is_null());
341 signal_upload_done_.Run();
342 }
343
RequestHandler(const scoped_refptr<base::SingleThreadTaskRunner> & test_task_runner,const base::WeakPtr<AttachmentUploaderImplTest> & test)344 RequestHandler::RequestHandler(
345 const scoped_refptr<base::SingleThreadTaskRunner>& test_task_runner,
346 const base::WeakPtr<AttachmentUploaderImplTest>& test)
347 : status_code_(net::HTTP_OK),
348 test_task_runner_(test_task_runner),
349 test_(test) {
350 DetachFromThread();
351 }
352
~RequestHandler()353 RequestHandler::~RequestHandler() {
354 DetachFromThread();
355 }
356
HandleRequest(const HttpRequest & request)357 scoped_ptr<HttpResponse> RequestHandler::HandleRequest(
358 const HttpRequest& request) {
359 DCHECK(CalledOnValidThread());
360 test_task_runner_->PostTask(
361 FROM_HERE,
362 base::Bind(
363 &AttachmentUploaderImplTest::OnRequestReceived, test_, request));
364 scoped_ptr<BasicHttpResponse> response(new BasicHttpResponse);
365 response->set_code(GetStatusCode());
366 response->set_content_type("text/plain");
367 return response.PassAs<HttpResponse>();
368 }
369
SetStatusCode(const net::HttpStatusCode & status_code)370 void RequestHandler::SetStatusCode(const net::HttpStatusCode& status_code) {
371 base::AutoLock lock(mutex_);
372 status_code_ = status_code;
373 }
374
GetStatusCode() const375 net::HttpStatusCode RequestHandler::GetStatusCode() const {
376 base::AutoLock lock(mutex_);
377 return status_code_;
378 }
379
380 // Verify the "happy case" of uploading an attachment.
381 //
382 // Token is requested, token is returned, HTTP request is made, attachment is
383 // received by server.
TEST_F(AttachmentUploaderImplTest,UploadAttachment_HappyCase)384 TEST_F(AttachmentUploaderImplTest, UploadAttachment_HappyCase) {
385 token_service().AddAccount(kAccountId);
386 request_handler().SetStatusCode(net::HTTP_OK);
387
388 scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString);
389 some_data->data() = kAttachmentData;
390 Attachment attachment = Attachment::Create(some_data);
391 uploader()->UploadAttachment(attachment, upload_callback());
392
393 // Run until the done callback is invoked.
394 RunAndWaitFor(1);
395
396 // See that the done callback was invoked with the right arguments.
397 ASSERT_EQ(1U, upload_results().size());
398 EXPECT_EQ(AttachmentUploader::UPLOAD_SUCCESS, upload_results()[0]);
399 ASSERT_EQ(1U, updated_attachment_ids().size());
400 EXPECT_EQ(attachment.GetId(), updated_attachment_ids()[0]);
401
402 // See that the HTTP server received one request.
403 ASSERT_EQ(1U, http_requests_received().size());
404 const HttpRequest& http_request = http_requests_received().front();
405 EXPECT_EQ(net::test_server::METHOD_POST, http_request.method);
406 std::string expected_relative_url("/uploads/" +
407 attachment.GetId().GetProto().unique_id());
408 EXPECT_EQ(expected_relative_url, http_request.relative_url);
409 EXPECT_TRUE(http_request.has_content);
410 EXPECT_EQ(kAttachmentData, http_request.content);
411 const std::string header_name(kAuthorization);
412 const std::string header_value(std::string("Bearer ") + kAccessToken);
413 EXPECT_THAT(http_request.headers,
414 testing::Contains(testing::Pair(header_name, header_value)));
415
416 // TODO(maniscalco): Once AttachmentUploaderImpl is capable of updating the
417 // AttachmentId with server address information about the attachment, add some
418 // checks here to verify it works properly (bug 371522).
419 }
420
421 // Verify two overlapping calls to upload the same attachment result in only one
422 // HTTP request.
TEST_F(AttachmentUploaderImplTest,UploadAttachment_Collapse)423 TEST_F(AttachmentUploaderImplTest, UploadAttachment_Collapse) {
424 token_service().AddAccount(kAccountId);
425 request_handler().SetStatusCode(net::HTTP_OK);
426
427 scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString);
428 some_data->data() = kAttachmentData;
429 Attachment attachment1 = Attachment::Create(some_data);
430 Attachment attachment2 = attachment1;
431 uploader()->UploadAttachment(attachment1, upload_callback());
432 uploader()->UploadAttachment(attachment2, upload_callback());
433
434 // Wait for upload_callback() to be invoked twice.
435 RunAndWaitFor(2);
436 // See there was only one request.
437 EXPECT_EQ(1U, http_requests_received().size());
438 }
439
440 // Verify that the internal state associated with an upload is removed when the
441 // uplaod finishes. We do this by issuing two non-overlapping uploads for the
442 // same attachment and see that it results in two HTTP requests.
TEST_F(AttachmentUploaderImplTest,UploadAttachment_CleanUpAfterUpload)443 TEST_F(AttachmentUploaderImplTest, UploadAttachment_CleanUpAfterUpload) {
444 token_service().AddAccount(kAccountId);
445 request_handler().SetStatusCode(net::HTTP_OK);
446
447 scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString);
448 some_data->data() = kAttachmentData;
449 Attachment attachment1 = Attachment::Create(some_data);
450 Attachment attachment2 = attachment1;
451 uploader()->UploadAttachment(attachment1, upload_callback());
452
453 // Wait for upload_callback() to be invoked before starting the second upload.
454 RunAndWaitFor(1);
455 uploader()->UploadAttachment(attachment2, upload_callback());
456
457 // Wait for upload_callback() to be invoked a second time.
458 RunAndWaitFor(1);
459 // See there were two requests.
460 ASSERT_EQ(2U, http_requests_received().size());
461 }
462
463 // Verify that we do not issue an HTTP request when we fail to receive an access
464 // token.
465 //
466 // Token is requested, no token is returned, no HTTP request is made
TEST_F(AttachmentUploaderImplTest,UploadAttachment_FailToGetToken)467 TEST_F(AttachmentUploaderImplTest, UploadAttachment_FailToGetToken) {
468 // Note, we won't receive a token because we did not add kAccountId to the
469 // token service.
470 scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString);
471 some_data->data() = kAttachmentData;
472 Attachment attachment = Attachment::Create(some_data);
473 uploader()->UploadAttachment(attachment, upload_callback());
474
475 RunAndWaitFor(1);
476
477 // See that the done callback was invoked.
478 ASSERT_EQ(1U, upload_results().size());
479 EXPECT_EQ(AttachmentUploader::UPLOAD_UNSPECIFIED_ERROR, upload_results()[0]);
480 ASSERT_EQ(1U, updated_attachment_ids().size());
481 EXPECT_EQ(attachment.GetId(), updated_attachment_ids()[0]);
482
483 // See that no HTTP request was received.
484 ASSERT_EQ(0U, http_requests_received().size());
485 }
486
487 // Verify behavior when the server returns "503 Service Unavailable".
TEST_F(AttachmentUploaderImplTest,UploadAttachment_ServiceUnavilable)488 TEST_F(AttachmentUploaderImplTest, UploadAttachment_ServiceUnavilable) {
489 token_service().AddAccount(kAccountId);
490 request_handler().SetStatusCode(net::HTTP_SERVICE_UNAVAILABLE);
491
492 scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString);
493 some_data->data() = kAttachmentData;
494 Attachment attachment = Attachment::Create(some_data);
495 uploader()->UploadAttachment(attachment, upload_callback());
496
497 RunAndWaitFor(1);
498
499 // See that the done callback was invoked.
500 ASSERT_EQ(1U, upload_results().size());
501 EXPECT_EQ(AttachmentUploader::UPLOAD_UNSPECIFIED_ERROR, upload_results()[0]);
502 ASSERT_EQ(1U, updated_attachment_ids().size());
503 EXPECT_EQ(attachment.GetId(), updated_attachment_ids()[0]);
504
505 // See that the HTTP server received one request.
506 ASSERT_EQ(1U, http_requests_received().size());
507 const HttpRequest& http_request = http_requests_received().front();
508 EXPECT_EQ(net::test_server::METHOD_POST, http_request.method);
509 std::string expected_relative_url("/uploads/" +
510 attachment.GetId().GetProto().unique_id());
511 EXPECT_EQ(expected_relative_url, http_request.relative_url);
512 EXPECT_TRUE(http_request.has_content);
513 EXPECT_EQ(kAttachmentData, http_request.content);
514 std::string expected_header(kAuthorization);
515 const std::string header_name(kAuthorization);
516 const std::string header_value(std::string("Bearer ") + kAccessToken);
517 EXPECT_THAT(http_request.headers,
518 testing::Contains(testing::Pair(header_name, header_value)));
519
520 // See that we did not invalidate the token.
521 ASSERT_EQ(0, token_service().num_invalidate_token());
522 }
523
524 // Verify that when we receive an "401 Unauthorized" we invalidate the access
525 // token.
TEST_F(AttachmentUploaderImplTest,UploadAttachment_BadToken)526 TEST_F(AttachmentUploaderImplTest, UploadAttachment_BadToken) {
527 token_service().AddAccount(kAccountId);
528 request_handler().SetStatusCode(net::HTTP_UNAUTHORIZED);
529
530 scoped_refptr<base::RefCountedString> some_data(new base::RefCountedString);
531 some_data->data() = kAttachmentData;
532 Attachment attachment = Attachment::Create(some_data);
533 uploader()->UploadAttachment(attachment, upload_callback());
534
535 RunAndWaitFor(1);
536
537 // See that the done callback was invoked.
538 ASSERT_EQ(1U, upload_results().size());
539 EXPECT_EQ(AttachmentUploader::UPLOAD_UNSPECIFIED_ERROR, upload_results()[0]);
540 ASSERT_EQ(1U, updated_attachment_ids().size());
541 EXPECT_EQ(attachment.GetId(), updated_attachment_ids()[0]);
542
543 // See that the HTTP server received one request.
544 ASSERT_EQ(1U, http_requests_received().size());
545 const HttpRequest& http_request = http_requests_received().front();
546 EXPECT_EQ(net::test_server::METHOD_POST, http_request.method);
547 std::string expected_relative_url("/uploads/" +
548 attachment.GetId().GetProto().unique_id());
549 EXPECT_EQ(expected_relative_url, http_request.relative_url);
550 EXPECT_TRUE(http_request.has_content);
551 EXPECT_EQ(kAttachmentData, http_request.content);
552 std::string expected_header(kAuthorization);
553 const std::string header_name(kAuthorization);
554 const std::string header_value(std::string("Bearer ") + kAccessToken);
555 EXPECT_THAT(http_request.headers,
556 testing::Contains(testing::Pair(header_name, header_value)));
557
558 // See that we invalidated the token.
559 ASSERT_EQ(1, token_service().num_invalidate_token());
560 }
561
562 // TODO(maniscalco): Add test case for when we are uploading an attachment that
563 // already exists. 409 Conflict? (bug 379825)
564
565 } // namespace syncer
566