PageRenderTime 254ms CodeModel.GetById 48ms app.highlight 195ms RepoModel.GetById 1ms app.codeStats 0ms

/core/externals/update-engine/externals/gdata-objectivec-client/Source/Tests/GDataServiceTest.m

http://macfuse.googlecode.com/
Objective C | 1565 lines | 949 code | 384 blank | 232 comment | 34 complexity | d45d4c1810094d3bbb106172c70b825c MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/* Copyright (c) 2007 Google Inc.
   2 *
   3 * Licensed under the Apache License, Version 2.0 (the "License");
   4 * you may not use this file except in compliance with the License.
   5 * You may obtain a copy of the License at
   6 *
   7 *     http://www.apache.org/licenses/LICENSE-2.0
   8 *
   9 * Unless required by applicable law or agreed to in writing, software
  10 * distributed under the License is distributed on an "AS IS" BASIS,
  11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12 * See the License for the specific language governing permissions and
  13 * limitations under the License.
  14 */
  15
  16//
  17//  GDataServiceTest.m
  18//
  19
  20// service tests are slow, so we might skip them when developing other
  21// unit tests
  22#if !GDATA_SKIPSERVICETEST
  23
  24#import <SenTestingKit/SenTestingKit.h>
  25
  26#import "GData.h"
  27
  28
  29@interface GDataServiceTest : SenTestCase {
  30  NSTask *server_;
  31  BOOL isServerRunning_;
  32
  33  GDataServiceGoogle *service_;
  34
  35  GDataServiceTicket *ticket_;
  36  GDataObject *fetchedObject_;
  37  NSError *fetcherError_;
  38  int fetchStartedNotificationCount_;
  39  int fetchStoppedNotificationCount_;
  40  unsigned long long lastProgressDeliveredCount_;
  41  unsigned long long lastProgressTotalCount_;
  42  int parseStartedCount_;
  43  int parseStoppedCount_;
  44  int retryCounter_;
  45  int retryDelayStartedNotificationCount_;
  46  int retryDelayStoppedNotificationCount_;
  47
  48  NSString *authToken_;
  49  NSError *authError_;
  50}
  51@end
  52
  53#define typeof __typeof__ // fixes http://www.brethorsting.com/blog/2006/02/stupid-issue-with-ocunit.html
  54
  55// Denote that a function/method retains the nsobject that it returns.
  56// This fixes warnings generated by clang when we return a non-autoreleased
  57// object from a function.
  58// http://clang-analyzer.llvm.org/annotations.html#cocoa_mem
  59#ifndef NS_RETURNS_RETAINED
  60  #ifndef __has_feature  // Optional.
  61    #define __has_feature(x) 0  // Compatibility with non-clang compilers.
  62  #endif  // __has_feature
  63
  64  #if __has_feature(attribute_ns_returns_retained)
  65    #define NS_RETURNS_RETAINED __attribute__((ns_returns_retained))
  66  #else
  67    #define NS_RETURNS_RETAINED
  68  #endif  // __has_feature(attribute_ns_returns_retained)
  69#endif
  70
  71@interface MyGDataFeedSpreadsheetSurrogate: GDataFeedSpreadsheet
  72- (NSString *)mySurrogateFeedName;
  73@end
  74
  75@interface MyGDataEntrySpreadsheetSurrogate: GDataEntrySpreadsheet
  76- (NSString *)mySurrogateEntryName;
  77@end
  78
  79@interface MyGDataLinkSurrogate: GDataLink
  80- (NSString *)mySurrogateLinkName;
  81@end
  82
  83NSTask *StartHTTPServerTask(int portNumber) NS_RETURNS_RETAINED;
  84
  85// StartHTTPServerTask is used below and in GTMHTTPFetcherTest
  86NSTask *StartHTTPServerTask(int portNumber) {
  87  // run the python http server, located in the Tests directory
  88  NSString *currentDir = [[NSFileManager defaultManager] currentDirectoryPath];
  89  NSString *serverPath = [currentDir stringByAppendingPathComponent:@"Tests/GDataTestHTTPServer.py"];
  90
  91  // The framework builds as garbage collection-compatible, so unit tests run
  92  // with GC both enabled and disabled.  But launching the python http server
  93  // with GC disabled causes it to return an error.  To avoid that, we'll
  94  // change its launch environment to allow the python server run with GC.
  95  // We also remove the Malloc debugging variables, since they interfere with
  96  // the program output.
  97  NSDictionary *env = [[NSProcessInfo processInfo] environment];
  98  NSMutableDictionary *mutableEnv = [NSMutableDictionary dictionaryWithDictionary:env];
  99  [mutableEnv removeObjectForKey:@"OBJC_DISABLE_GC"];
 100  [mutableEnv removeObjectForKey:@"MallocGuardEdges"];
 101  [mutableEnv removeObjectForKey:@"MallocPreScribble"];
 102  [mutableEnv removeObjectForKey:@"MallocScribble"];
 103
 104  NSArray *argArray = [NSArray arrayWithObjects:serverPath,
 105                       @"-p", [NSString stringWithFormat:@"%d", portNumber],
 106                       @"-r", [serverPath stringByDeletingLastPathComponent],
 107                       nil];
 108
 109  NSTask *server = [[NSTask alloc] init];
 110  [server setArguments:argArray];
 111  [server setLaunchPath:@"/usr/bin/python"];
 112  [server setEnvironment:mutableEnv];
 113
 114  // pipe will be cleaned up when server is torn down.
 115  NSPipe *pipe = [NSPipe pipe];
 116  [server setStandardOutput:pipe];
 117  [server setStandardError:pipe];
 118  [server launch];
 119
 120  NSData *launchMessageData = [[pipe fileHandleForReading] availableData];
 121  NSString *launchStr = [[[NSString alloc] initWithData:launchMessageData
 122                                               encoding:NSUTF8StringEncoding] autorelease];
 123
 124  // our server sends out a string to confirm that it launched;
 125  // launchStr either has the confirmation, or the error message.
 126
 127  NSString *expectedLaunchStr = @"started GDataTestServer.py...";
 128  BOOL isServerRunning = [launchStr isEqual:expectedLaunchStr];
 129  return isServerRunning ? server : nil;
 130}
 131
 132@implementation GDataServiceTest
 133
 134static int kServerPortNumber = 54579;
 135
 136- (void)setUp {
 137  server_ = StartHTTPServerTask(kServerPortNumber);
 138  isServerRunning_ = (server_ != nil);
 139
 140  STAssertTrue(isServerRunning_,
 141               @">>> Python http test server failed to launch; skipping service tests\n");
 142
 143  // create the GData service object, and set it to authenticate
 144  // from our own python http server
 145  service_ = [[GDataServiceGoogleSpreadsheet alloc] init];
 146
 147  NSString *authDomain = [NSString stringWithFormat:@"localhost:%d", kServerPortNumber];
 148  [service_ setSignInDomain:authDomain];
 149
 150  // install observers for fetcher notifications
 151  NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
 152  [nc addObserver:self selector:@selector(fetchStateChanged:) name:kGTMHTTPFetcherStartedNotification object:nil];
 153  [nc addObserver:self selector:@selector(fetchStateChanged:) name:kGTMHTTPFetcherStoppedNotification object:nil];
 154  [nc addObserver:self selector:@selector(parseStateChanged:) name:kGDataServiceTicketParsingStartedNotification object:nil];
 155  [nc addObserver:self selector:@selector(parseStateChanged:) name:kGDataServiceTicketParsingStoppedNotification object:nil];
 156  [nc addObserver:self selector:@selector(retryDelayStateChanged:) name:kGTMHTTPFetcherRetryDelayStartedNotification object:nil];
 157  [nc addObserver:self selector:@selector(retryDelayStateChanged:) name:kGTMHTTPFetcherRetryDelayStoppedNotification object:nil];
 158}
 159
 160- (void)fetchStateChanged:(NSNotification *)note {
 161  GTMHTTPFetcher *fetcher = [note object];
 162  GDataServiceTicketBase *ticket = [fetcher GDataTicket];
 163
 164  STAssertNotNil(ticket, @"cannot get ticket from fetch notification");
 165
 166  if ([[note name] isEqual:kGTMHTTPFetcherStartedNotification]) {
 167    ++fetchStartedNotificationCount_;
 168  } else {
 169    ++fetchStoppedNotificationCount_;
 170  }
 171
 172  STAssertTrue(retryDelayStartedNotificationCount_ <= fetchStartedNotificationCount_,
 173               @"fetch notification imbalance: starts=%d stops=%d",
 174               fetchStartedNotificationCount_, retryDelayStartedNotificationCount_);
 175}
 176
 177- (void)parseStateChanged:(NSNotification *)note {
 178  GDataServiceTicketBase *ticket = [note object];
 179
 180  STAssertTrue([ticket isKindOfClass:[GDataServiceTicketBase class]],
 181               @"cannot get ticket from parse notification");
 182
 183  if ([[note name] isEqual:kGDataServiceTicketParsingStartedNotification]) {
 184    ++parseStartedCount_;
 185  } else {
 186    ++parseStoppedCount_;
 187  }
 188
 189  STAssertTrue(parseStoppedCount_ <= parseStartedCount_,
 190               @"parse notification imbalance: starts=%d stops=%d",
 191               parseStartedCount_,
 192               parseStoppedCount_);
 193}
 194
 195- (void)retryDelayStateChanged:(NSNotification *)note {
 196  GTMHTTPFetcher *fetcher = [note object];
 197  GDataServiceTicketBase *ticket = [fetcher GDataTicket];
 198
 199  STAssertNotNil(ticket, @"cannot get ticket from retry delay notification");
 200
 201  if ([[note name] isEqual:kGTMHTTPFetcherRetryDelayStartedNotification]) {
 202    ++retryDelayStartedNotificationCount_;
 203  } else {
 204    ++retryDelayStoppedNotificationCount_;
 205  }
 206
 207  STAssertTrue(retryDelayStoppedNotificationCount_ <= retryDelayStartedNotificationCount_,
 208               @"retry delay notification imbalance: starts=%d stops=%d",
 209               retryDelayStartedNotificationCount_,
 210               retryDelayStoppedNotificationCount_);
 211}
 212
 213- (void)resetFetchResponse {
 214  [fetchedObject_ release];
 215  fetchedObject_ = nil;
 216
 217  [fetcherError_ release];
 218  fetcherError_ = nil;
 219
 220  [ticket_ release];
 221  ticket_ = nil;
 222
 223  retryCounter_ = 0;
 224
 225  lastProgressDeliveredCount_ = 0;
 226  lastProgressTotalCount_ = 0;
 227
 228  // Set the UA to avoid log warnings during tests, except the first test,
 229  // which will use an auto-generated user agent
 230  if ([service_ userAgent] == nil) {
 231    [service_ setUserAgent:@"GData-UnitTests-99.99"];
 232  }
 233
 234  if (![service_ shouldCacheResponseData]) {
 235    // we don't want to see 304s in our service response tests now,
 236    // though the tests below will check for them in the underlying
 237    // fetchers when we get a cached response
 238    [service_ clearResponseDataCache];
 239  }
 240
 241  fetchStartedNotificationCount_ = 0;
 242  fetchStoppedNotificationCount_ = 0;
 243  parseStartedCount_ = 0;
 244  parseStoppedCount_ = 0;
 245  retryDelayStartedNotificationCount_ = 0;
 246  retryDelayStoppedNotificationCount_ = 0;
 247}
 248
 249- (void)tearDown {
 250
 251  [server_ terminate];
 252  [server_ waitUntilExit];
 253  [server_ release];
 254  server_ = nil;
 255
 256  isServerRunning_ = NO;
 257
 258  [service_ release];
 259  service_ = nil;
 260
 261  [self resetFetchResponse];
 262}
 263
 264- (NSURL *)fileURLToTestFileName:(NSString *)name {
 265
 266  // we need to create http URLs referring to the desired
 267  // resource for the python http server running locally
 268
 269  // return a localhost:port URL for the test file
 270  NSString *urlString = [NSString stringWithFormat:@"http://localhost:%d/%@",
 271                         kServerPortNumber, name];
 272
 273  NSURL *url = [NSURL URLWithString:urlString];
 274
 275  // just for sanity, let's make sure we see the file locally, so
 276  // we can expect the Python http server to find it too
 277  NSString *filePath = [NSString stringWithFormat:@"Tests/%@", name];
 278
 279
 280  // we exclude the "?status=" that would indicate that the URL
 281  // should cause a retryable error
 282  NSRange range = [filePath rangeOfString:@"?status="];
 283  if (range.location == NSNotFound) {
 284    range = [filePath rangeOfString:@"?statusxml="];
 285  }
 286  if (range.length > 0) {
 287    filePath = [filePath substringToIndex:range.location];
 288  }
 289
 290  // we remove the extensions that would indicate special ways of
 291  // testing the URL
 292  if ([[filePath pathExtension] isEqual:@"auth"] ||
 293      [[filePath pathExtension] isEqual:@"authsub"] ||
 294      [[filePath pathExtension] isEqual:@"location"]) {
 295    filePath = [filePath stringByDeletingPathExtension];
 296  }
 297
 298  BOOL doesExist = [[NSFileManager defaultManager] fileExistsAtPath:filePath];
 299  STAssertTrue(doesExist, @"Missing test file %@", filePath);
 300
 301  return url;
 302}
 303
 304
 305// deleteResource calls don't return data or an error, so we'll use
 306// a global int for the callbacks to increment to say they're done
 307// (because NSURLConnection is performing the fetches, all this
 308// will be safely executed on the same thread)
 309static int gFetchCounter = 0;
 310
 311- (void)waitForFetch {
 312
 313  int fetchCounter = gFetchCounter;
 314
 315  // Give time for the fetch to happen, but give up if
 316  // 10 seconds elapse with no response
 317  NSDate* giveUpDate = [NSDate dateWithTimeIntervalSinceNow:10.0];
 318
 319  while ((!fetchedObject_ && !fetcherError_)
 320         && fetchCounter == gFetchCounter
 321         && [giveUpDate timeIntervalSinceNow] > 0) {
 322
 323    NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:0.001];
 324    [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
 325  }
 326
 327}
 328
 329- (void)testFetch {
 330
 331  if (!isServerRunning_) return;
 332
 333  // values & keys for testing that userdata is passed from
 334  // service to ticket
 335  NSDate *defaultUserData = [NSDate date];
 336  NSString *customUserData = @"my special ticket user data";
 337
 338  NSString *testPropertyKey = @"testPropertyKey";
 339  NSDate *defaultPropertyValue = [NSDate dateWithTimeIntervalSinceNow:999];
 340  NSString *customPropertyValue = @"ticket properties override service props";
 341
 342  [service_ setServiceUserData:defaultUserData];
 343  [service_ setServiceProperty:defaultPropertyValue forKey:testPropertyKey];
 344
 345  // an ".auth" extension tells the server to require the success auth token,
 346  // but the server will ignore the .auth extension when looking for the file
 347  NSURL *feedURL = [self fileURLToTestFileName:@"FeedSpreadsheetTest1.xml"];
 348  NSURL *feedErrorURL = [self fileURLToTestFileName:@"FeedSpreadsheetTest1.xml?statusxml=499"];
 349  NSURL *authFeedURL = [self fileURLToTestFileName:@"FeedSpreadsheetTest1.xml.auth"];
 350  NSURL *authSubFeedURL = [self fileURLToTestFileName:@"FeedSpreadsheetTest1.xml.authsub"];
 351
 352  //
 353  // test:  download feed only, no auth, caching on
 354  //
 355  [service_ setShouldCacheResponseData:YES];
 356
 357  ticket_ = (GDataServiceTicket *)
 358    [service_ fetchPublicFeedWithURL:feedURL
 359                           feedClass:kGDataUseRegisteredClass
 360                            delegate:self
 361                   didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 362  [ticket_ retain];
 363
 364  [self waitForFetch];
 365
 366  // we'll call into the GDataObject to get its ID to confirm it's good
 367  NSString *sheetID = @"http://spreadsheets.google.com/feeds/spreadsheets/private/full";
 368
 369  STAssertEqualObjects([(GDataFeedSpreadsheet *)fetchedObject_ identifier],
 370                       sheetID, @"fetching %@ error %@", feedURL, fetcherError_);
 371  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
 372  STAssertEqualObjects([ticket_ userData], defaultUserData, @"userdata error");
 373  STAssertEqualObjects([ticket_ propertyForKey:testPropertyKey],
 374                       defaultPropertyValue, @"default property missing");
 375
 376  // no cookies should be sent with our first request
 377  NSURLRequest *request = [[ticket_ objectFetcher] mutableRequest];
 378
 379  NSString *cookiesSent = [[request allHTTPHeaderFields] objectForKey:@"Cookie"];
 380  STAssertNil(cookiesSent, @"Cookies sent unexpectedly: %@", cookiesSent);
 381
 382  // cookies should have been set with the response; specifically, TestCookie
 383  // should be set to the name of the file requested
 384  NSURLResponse *response = [[ticket_ objectFetcher] response];
 385
 386  NSDictionary *responseHeaderFields = [(NSHTTPURLResponse *)response allHeaderFields];
 387  NSString *cookiesSetString = [responseHeaderFields objectForKey:@"Set-Cookie"];
 388  NSString *cookieExpected = [NSString stringWithFormat:@"TestCookie=%@",
 389                              [[feedURL path] lastPathComponent]];
 390
 391  STAssertEqualObjects(cookiesSetString, cookieExpected, @"Unexpected cookie");
 392
 393  // check that the expected notifications happened
 394  STAssertEquals(fetchStartedNotificationCount_, 1, @"fetch start note missing");
 395  STAssertEquals(fetchStoppedNotificationCount_, 1, @"fetch stopped note missing");
 396  STAssertEquals(parseStartedCount_, 1, @"parse start note missing");
 397  STAssertEquals(parseStoppedCount_, 1, @"parse stopped note missing");
 398  STAssertEquals(retryDelayStartedNotificationCount_, 0, @"retry delay note unexpected");
 399  STAssertEquals(retryDelayStoppedNotificationCount_, 0, @"retry delay note unexpected");
 400
 401  // save a copy of the retrieved object to compare with our cache responses
 402  // later
 403  GDataObject *objectCopy = [[fetchedObject_ copy] autorelease];
 404
 405
 406  //
 407  // test: download feed only, unmodified so fetching a cached copy
 408  //
 409
 410  [self resetFetchResponse];
 411
 412  ticket_ = (GDataServiceTicket *)
 413    [service_ fetchPublicFeedWithURL:feedURL
 414                           feedClass:kGDataUseRegisteredClass
 415                            delegate:self
 416                   didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 417
 418  [ticket_ retain];
 419
 420  [self waitForFetch];
 421
 422  // the TestCookie set previously should be sent with this request
 423  request = [[ticket_ objectFetcher] mutableRequest];
 424  cookiesSent = [[request allHTTPHeaderFields] objectForKey:@"Cookie"];
 425  STAssertEqualObjects(cookiesSent, cookieExpected,
 426                       @"Cookie not sent");
 427
 428  // verify the object is unchanged from the uncached fetch
 429  STAssertEqualObjects(fetchedObject_, objectCopy,
 430                       @"fetching from cache for %@", feedURL);
 431  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
 432
 433  // verify the underlying fetcher got a 304 (not modified) status
 434#if 0
 435  // TODO: The python test server doesn't provide 304s for ETag matches
 436  
 437  STAssertEquals([[ticket_ objectFetcher] statusCode],
 438                 (NSInteger)kGTMHTTPFetcherStatusNotModified,
 439                 @"fetching cached copy of %@", feedURL);
 440#endif
 441
 442  //
 443  // test: download feed only, caching turned off so we get an
 444  //       actual response again
 445  //
 446
 447  [self resetFetchResponse];
 448
 449  [service_ setShouldCacheResponseData:NO];
 450
 451  ticket_ = (GDataServiceTicket *)
 452    [service_ fetchPublicFeedWithURL:feedURL
 453                           feedClass:kGDataUseRegisteredClass
 454                            delegate:self
 455                   didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 456  [ticket_ retain];
 457
 458  [self waitForFetch];
 459
 460  // verify the object is unchanged from the original fetch
 461  STAssertEqualObjects(fetchedObject_, objectCopy,
 462                       @"fetching from cache for %@", feedURL);
 463  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
 464
 465  // verify the underlying fetcher got a 200 (good) status
 466  STAssertEquals([[ticket_ objectFetcher] statusCode], (NSInteger)200,
 467                 @"fetching uncached copy of %@", feedURL);
 468
 469  //
 470  // test: download feed only, no auth, forcing a structured xml error
 471  //
 472  [self resetFetchResponse];
 473
 474  ticket_ = (GDataServiceTicket *)
 475    [service_ fetchPublicFeedWithURL:feedErrorURL
 476                           feedClass:kGDataUseRegisteredClass
 477                            delegate:self
 478                   didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 479  [ticket_ retain];
 480
 481  [self waitForFetch];
 482
 483  STAssertNil(fetchedObject_, @"fetching %@", feedURL);
 484  STAssertEquals([fetcherError_ code], (NSInteger)499,
 485                 @"fetcherError_=%@", fetcherError_);
 486
 487  // get the error group from the error's userInfo and test the main
 488  // error's fields
 489  GDataServerErrorGroup *errorGroup =
 490  [[fetcherError_ userInfo] objectForKey:kGDataStructuredErrorsKey];
 491  STAssertNotNil(errorGroup, @"lacks error group");
 492
 493  GDataServerError *serverError = [errorGroup mainError];
 494
 495  STAssertEqualObjects([serverError domain], kGDataErrorDomainCore, @"domain");
 496  STAssertEqualObjects([serverError code], @"code_499", @"code");
 497  STAssertTrue([[serverError internalReason] hasPrefix:@"forced status error"],
 498               @"internalReason: %@", [serverError internalReason]);
 499  STAssertEqualObjects([serverError extendedHelpURI], @"http://help.com",
 500                       @"help");
 501  STAssertEqualObjects([serverError sendReportURI], @"http://report.com",
 502                       @"sendReport");
 503
 504
 505  //
 506  // test: download feed only, no auth, allocating a surrogate feed class
 507  //
 508  [self resetFetchResponse];
 509
 510  NSDictionary *surrogates = [NSDictionary dictionaryWithObjectsAndKeys:
 511                              [MyGDataFeedSpreadsheetSurrogate class], [GDataFeedSpreadsheet class],
 512                              [MyGDataEntrySpreadsheetSurrogate class], [GDataEntrySpreadsheet class],
 513                              [MyGDataLinkSurrogate class], [GDataLink class],
 514                              nil];
 515  [service_ setServiceSurrogates:surrogates];
 516
 517  ticket_ = (GDataServiceTicket *)
 518    [service_ fetchPublicFeedWithURL:feedURL
 519                           feedClass:kGDataUseRegisteredClass
 520                            delegate:self
 521                   didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 522  [ticket_ retain];
 523
 524  [self waitForFetch];
 525
 526  // we'll call into the GDataObject to get its ID to confirm it's good
 527
 528  STAssertEqualObjects([(GDataFeedSpreadsheet *)fetchedObject_ identifier],
 529                       sheetID, @"fetching %@", feedURL);
 530  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
 531  STAssertEqualObjects([ticket_ userData], defaultUserData, @"userdata error");
 532
 533  // be sure we really got an instance of the surrogate feed, entry, and
 534  // link classes
 535  MyGDataFeedSpreadsheetSurrogate *feed = (MyGDataFeedSpreadsheetSurrogate *)fetchedObject_;
 536  STAssertEqualObjects([feed mySurrogateFeedName],
 537                       @"mySurrogateFeedNameBar", @"fetching %@ with surrogate", feedURL);
 538
 539  MyGDataEntrySpreadsheetSurrogate *entry = [[feed entries] objectAtIndex:0];
 540  STAssertEqualObjects([entry mySurrogateEntryName],
 541                       @"mySurrogateEntryNameFoo", @"fetching %@ with surrogate", feedURL);
 542
 543  MyGDataLinkSurrogate *link = [[entry links] objectAtIndex:0];
 544  STAssertEqualObjects([link mySurrogateLinkName],
 545                       @"mySurrogateLinkNameBaz", @"fetching %@ with surrogate", feedURL);
 546
 547  [service_ setServiceSurrogates:nil];
 548
 549  //
 550  // test:  download feed only, successful auth, with custom ticket userdata
 551  //
 552  [self resetFetchResponse];
 553
 554  // any username & password are considered valid unless the password
 555  // begins with the string "bad"
 556  [service_ setUserCredentialsWithUsername:@"myaccount@mydomain.com"
 557                                  password:@"mypassword"];
 558
 559  ticket_ = [service_ fetchFeedWithURL:authFeedURL
 560                             feedClass:kGDataUseRegisteredClass
 561                              delegate:self
 562                     didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 563  [ticket_ retain];
 564
 565  [ticket_ setUserData:customUserData];
 566  [ticket_ setProperty:customPropertyValue forKey:testPropertyKey];
 567
 568  [self waitForFetch];
 569
 570  STAssertEqualObjects([(GDataFeedSpreadsheet *)fetchedObject_ identifier],
 571                       sheetID, @"fetching %@", authFeedURL);
 572  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
 573  STAssertEqualObjects([ticket_ userData], customUserData, @"userdata error");
 574  STAssertEqualObjects([ticket_ propertyForKey:testPropertyKey],
 575                       customPropertyValue, @"custom property error");
 576
 577  // check that the expected notifications happened for the authentication
 578  // fetch and the object fetch
 579  STAssertEquals(fetchStartedNotificationCount_, 2, @"start note missing");
 580  STAssertEquals(fetchStoppedNotificationCount_, 2, @"stopped note missing");
 581  STAssertEquals(retryDelayStartedNotificationCount_, 0, @"retry delay note unexpected");
 582  STAssertEquals(retryDelayStoppedNotificationCount_, 0, @"retry delay note unexpected");
 583
 584  //
 585  // test: repeat last authenticated download so we're reusing the auth token
 586  //
 587
 588  [self resetFetchResponse];
 589
 590  ticket_ = [service_ fetchFeedWithURL:authFeedURL
 591                             feedClass:kGDataUseRegisteredClass
 592                              delegate:self
 593                     didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 594  [ticket_ retain];
 595
 596  [self waitForFetch];
 597
 598  STAssertEqualObjects([(GDataFeedSpreadsheet *)fetchedObject_ identifier],
 599                       sheetID, @"fetching %@", authFeedURL);
 600  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
 601
 602  //
 603  // test: repeat last authenticated download so we're reusing the auth token,
 604  // but make the auth token invalid to force a re-auth
 605  //
 606
 607  [self resetFetchResponse];
 608
 609  [service_ setAuthToken:@"bogus"];
 610
 611  ticket_ = [service_ fetchFeedWithURL:authFeedURL
 612                             feedClass:kGDataUseRegisteredClass
 613                              delegate:self
 614                     didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 615  [ticket_ retain];
 616
 617  [self waitForFetch];
 618
 619  STAssertEqualObjects([(GDataFeedSpreadsheet *)fetchedObject_ identifier],
 620                       sheetID, @"fetching %@", authFeedURL);
 621  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
 622
 623  //
 624  // test: repeat last authenticated download (which ended with a valid token),
 625  // but make the auth token invalid and disallow a reauth by changing the
 626  // credential date after the fetch starts
 627  //
 628
 629  [self resetFetchResponse];
 630
 631  [service_ setAuthToken:@"bogus"];
 632
 633  ticket_ = [service_ fetchFeedWithURL:authFeedURL
 634                             feedClass:kGDataUseRegisteredClass
 635                              delegate:self
 636                     didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 637  [ticket_ retain];
 638  [service_ setCredentialDate:[NSDate date]];
 639
 640  [self waitForFetch];
 641
 642  STAssertNil(fetchedObject_, @"fetchedObject_=%@", fetchedObject_);
 643  STAssertEquals([fetcherError_ code], (NSInteger)401,
 644                 @"fetcherError_=%@", fetcherError_);
 645
 646  //
 647  // test: do a valid fetch, but change the credential after the fetch begins
 648  //
 649
 650  [self resetFetchResponse];
 651
 652  [service_ setUserCredentialsWithUsername:@"myaccount@mydomain.com"
 653                                  password:@"mypasword"];
 654
 655  ticket_ = [service_ fetchFeedWithURL:authFeedURL
 656                             feedClass:kGDataUseRegisteredClass
 657                              delegate:self
 658                     didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 659  [ticket_ retain];
 660
 661  [service_ setUserCredentialsWithUsername:@"myaccount@mydomain.com"
 662                                  password:@"bad"];
 663
 664  [self waitForFetch];
 665
 666  STAssertEqualObjects([(GDataFeedSpreadsheet *)fetchedObject_ identifier],
 667                       sheetID, @"fetching %@", authFeedURL);
 668  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
 669
 670  //
 671  // test:  download feed only, unsuccessful auth
 672  //
 673  [self resetFetchResponse];
 674
 675  [service_ setUserCredentialsWithUsername:@"myaccount@mydomain.com"
 676                                  password:@"bad"];
 677
 678  ticket_ = [service_ fetchFeedWithURL:authFeedURL
 679                             feedClass:kGDataUseRegisteredClass
 680                              delegate:self
 681                     didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 682  [ticket_ retain];
 683
 684  [self waitForFetch];
 685
 686  STAssertNil(fetchedObject_, @"fetchedObject_=%@", fetchedObject_);
 687  STAssertEquals([fetcherError_ code], (NSInteger)403,
 688                 @"fetcherError_=%@", fetcherError_);
 689  STAssertEqualObjects([ticket_ userData], defaultUserData, @"userdata error");
 690  STAssertEqualObjects([ticket_ propertyForKey:testPropertyKey],
 691                       defaultPropertyValue, @"default property error");
 692
 693
 694  //
 695  // test:  download feed only, unsuccessful auth - captcha required
 696  //
 697  [self resetFetchResponse];
 698
 699  [service_ setUserCredentialsWithUsername:@"myaccount@mydomain.com"
 700                                  password:@"captcha"];
 701
 702  ticket_ = [service_ fetchFeedWithURL:authFeedURL
 703                             feedClass:kGDataUseRegisteredClass
 704                              delegate:self
 705                     didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 706  [ticket_ retain];
 707
 708  [self waitForFetch];
 709
 710  STAssertNil(fetchedObject_, @"fetchedObject_=%@", fetchedObject_);
 711  STAssertEquals([fetcherError_ code], (NSInteger)403,
 712                 @"fetcherError_=%@", fetcherError_);
 713
 714  // get back the captcha token and partial and full URLs from the error
 715  NSDictionary *userInfo = [fetcherError_ userInfo];
 716  NSString *captchaToken = [userInfo objectForKey:@"CaptchaToken"];
 717  NSString *captchaUrl = [userInfo objectForKey:@"CaptchaUrl"];
 718  NSString *captchaFullUrl = [userInfo objectForKey:@"CaptchaFullUrl"];
 719  STAssertEqualObjects(captchaToken, @"CapToken", @"bad captcha token");
 720  STAssertEqualObjects(captchaUrl, @"CapUrl", @"bad captcha relative url");
 721  STAssertTrue([captchaFullUrl hasSuffix:@"/accounts/CapUrl"], @"bad captcha full:%@", captchaFullUrl);
 722
 723  STAssertEqualObjects([ticket_ userData], defaultUserData, @"userdata error");
 724
 725  //
 726  // test:  download feed only, good captcha provided
 727  //
 728  [self resetFetchResponse];
 729
 730  [service_ setUserCredentialsWithUsername:@"myaccount2@mydomain.com"
 731                                  password:@"captcha"];
 732  [service_ setCaptchaToken:@"CapToken" captchaAnswer:@"good"];
 733
 734  ticket_ = [service_ fetchFeedWithURL:authFeedURL
 735                             feedClass:kGDataUseRegisteredClass
 736                              delegate:self
 737                     didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 738  [ticket_ retain];
 739
 740  [self waitForFetch];
 741
 742  // get back the captcha token and partial and full URLs from the error
 743  STAssertEqualObjects([(GDataFeedSpreadsheet *)fetchedObject_ identifier],
 744                       sheetID, @"fetching %@", feedURL);
 745  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
 746  STAssertEqualObjects([ticket_ userData], defaultUserData, @"userdata error");
 747
 748  //
 749  // test:  download feed only, bad captcha provided
 750  //
 751  [self resetFetchResponse];
 752
 753  [service_ setUserCredentialsWithUsername:@"myaccount3@mydomain.com"
 754                                  password:@"captcha"];
 755  [service_ setCaptchaToken:@"CapToken" captchaAnswer:@"bad"];
 756
 757  ticket_ = [service_ fetchFeedWithURL:authFeedURL
 758                             feedClass:kGDataUseRegisteredClass
 759                              delegate:self
 760                     didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 761  [ticket_ retain];
 762
 763  [self waitForFetch];
 764
 765  STAssertNil(fetchedObject_, @"fetchedObject_=%@", fetchedObject_);
 766  STAssertEquals([fetcherError_ code], (NSInteger)403,
 767                 @"fetcherError_=%@", fetcherError_);
 768
 769  // get back the captcha token and partial and full URLs from the error
 770  userInfo = [fetcherError_ userInfo];
 771  captchaToken = [userInfo objectForKey:@"CaptchaToken"];
 772  captchaUrl = [userInfo objectForKey:@"CaptchaUrl"];
 773  captchaFullUrl = [userInfo objectForKey:@"CaptchaFullUrl"];
 774  STAssertEqualObjects(captchaToken, @"CapToken", @"bad captcha token");
 775  STAssertEqualObjects(captchaUrl, @"CapUrl", @"bad captcha relative url");
 776  STAssertTrue([captchaFullUrl hasSuffix:@"/accounts/CapUrl"], @"bad captcha full:%@", captchaFullUrl);
 777
 778  STAssertEqualObjects([ticket_ userData], defaultUserData, @"userdata error");
 779
 780
 781  //
 782  // test:  insert/download entry, successful auth
 783  //
 784  [self resetFetchResponse];
 785
 786  [service_ setUserCredentialsWithUsername:@"myaccount@mydomain.com"
 787                                  password:@"good"];
 788
 789  NSURL *authEntryURL = [self fileURLToTestFileName:@"EntrySpreadsheetCellTest1.xml.auth"];
 790
 791  ticket_ = [service_ fetchEntryByInsertingEntry:[GDataEntrySpreadsheetCell entry]
 792                                      forFeedURL:authEntryURL
 793                                        delegate:self
 794                               didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 795  [ticket_ retain];
 796
 797  [self waitForFetch];
 798
 799  NSString *entryID = @"http://spreadsheets.google.com/feeds/cells/o04181601172097104111.497668944883620000/od6/private/full/R1C1";
 800
 801  STAssertEqualObjects([(GDataEntrySpreadsheetCell *)fetchedObject_ identifier],
 802                       entryID, @"updating %@", authEntryURL);
 803  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
 804  STAssertEqualObjects([ticket_ userData], defaultUserData, @"userdata error");
 805
 806  //
 807  // test:  update/download entry, successful auth
 808  //
 809  [self resetFetchResponse];
 810
 811  ticket_ = [service_ fetchEntryByUpdatingEntry:[GDataEntrySpreadsheetCell entry]
 812                                    forEntryURL:authEntryURL
 813                                       delegate:self
 814                              didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 815  [ticket_ retain];
 816
 817  [self waitForFetch];
 818
 819  STAssertEqualObjects([(GDataEntrySpreadsheetCell *)fetchedObject_ identifier],
 820                       entryID, @"fetching %@", authFeedURL);
 821  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
 822  STAssertEqualObjects([ticket_ userData], defaultUserData, @"userdata error");
 823
 824  //
 825  // test:  update/download streamed entry data with progress monitoring
 826  //        and logging, successful auth, logging on
 827  //
 828  [self resetFetchResponse];
 829
 830  [GTMHTTPFetcher setLoggingEnabled:YES];
 831  [GTMHTTPFetcher setLoggingDirectory:NSTemporaryDirectory()];
 832
 833  // report the logging directory (the log file names depend on the process
 834  // launch time, but this at least lets us manually inspect the logs)
 835  NSLog(@"GDataServiceTest http logging set to %@",
 836        [GTMHTTPFetcher loggingDirectory]);
 837
 838  GDataEntryPhoto *photoEntry = [GDataEntryPhoto photoEntry];
 839  NSImage *image = [NSImage imageNamed:@"NSApplicationIcon"];
 840  NSData *tiffData = [image TIFFRepresentation];
 841  STAssertTrue([tiffData length] > 0, @"failed to make tiff image");
 842
 843  [photoEntry setPhotoData:tiffData];
 844  [photoEntry setPhotoMIMEType:@"image/tiff"];
 845  [photoEntry setUploadSlug:@"unit test photo.tif"];
 846  [photoEntry setTitleWithString:@"Unit Test Photo"];
 847
 848  SEL progressSel = @selector(ticket:hasDeliveredByteCount:ofTotalByteCount:);
 849  [service_ setServiceUploadProgressSelector:progressSel];
 850
 851  // note that the authEntryURL still points to a spreadsheet entry, so
 852  // spreadsheet XML is what will be returned, but we don't really care
 853
 854  ticket_ = [service_ fetchEntryByUpdatingEntry:photoEntry
 855                                    forEntryURL:authEntryURL
 856                                       delegate:self
 857                              didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 858  [ticket_ retain];
 859
 860  [self waitForFetch];
 861
 862  STAssertEqualObjects([(GDataEntrySpreadsheetCell *)fetchedObject_ identifier],
 863                       entryID, @"fetching %@", authFeedURL);
 864  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
 865  STAssertEqualObjects([ticket_ userData], defaultUserData, @"userdata error");
 866
 867  STAssertTrue(lastProgressDeliveredCount_ > 0, @"no byte delivery reported");
 868  STAssertEquals(lastProgressDeliveredCount_, lastProgressTotalCount_,
 869                 @"unexpected byte delivery count");
 870
 871  [GTMHTTPFetcher setLoggingEnabled:NO];
 872  [GTMHTTPFetcher setLoggingDirectory:nil];
 873
 874  [service_ setServiceUploadProgressSelector:nil];
 875
 876
 877  //
 878  // test:  delete entry with ETag, successful auth
 879  //
 880  [self resetFetchResponse];
 881
 882  GDataEntrySpreadsheetCell *entryToDelete = [GDataEntrySpreadsheetCell entry];
 883  [entryToDelete addLink:[GDataLink linkWithRel:@"edit"
 884                                           type:nil
 885                                           href:[authFeedURL absoluteString]]];
 886  [entryToDelete setETag:@"A0MCQHs-fyp7ImA9WxVVF0Q."];
 887
 888  ticket_ = [service_ deleteEntry:entryToDelete
 889                         delegate:self
 890                didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 891  [ticket_ retain];
 892
 893  [self waitForFetch];
 894
 895  STAssertNil(fetchedObject_, @"deleting %@ returned \n%@", authEntryURL, fetchedObject_);
 896  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
 897  STAssertEqualObjects([ticket_ userData], defaultUserData, @"userdata error");
 898
 899  //
 900  // test:  delete resource, successful auth, using method override header
 901  //
 902  [self resetFetchResponse];
 903
 904  [service_ setShouldUseMethodOverrideHeader:YES];
 905
 906  ticket_ = [service_ deleteResourceURL:authEntryURL
 907                                   ETag:nil
 908                               delegate:self
 909                      didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 910  [ticket_ retain];
 911
 912  [self waitForFetch];
 913
 914  STAssertNil(fetchedObject_, @"deleting %@ returned \n%@", authEntryURL, fetchedObject_);
 915  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
 916  STAssertEqualObjects([ticket_ userData], defaultUserData, @"userdata error");
 917
 918  //
 919  // test: fetch feed with authsub, successful
 920  //
 921
 922  [self resetFetchResponse];
 923
 924  [service_ setUserCredentialsWithUsername:nil
 925                                  password:nil];
 926  [service_ setAuthSubToken:@"GoodAuthSubToken"];
 927
 928  ticket_ = [service_ fetchFeedWithURL:authSubFeedURL
 929                             feedClass:kGDataUseRegisteredClass
 930                              delegate:self
 931                     didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 932  [ticket_ retain];
 933
 934  [self waitForFetch];
 935
 936  STAssertEqualObjects([(GDataFeedSpreadsheet *)fetchedObject_ identifier],
 937                       sheetID, @"fetching %@", authSubFeedURL);
 938  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
 939
 940  //
 941  // test: fetch feed with authsub, bad token
 942  //
 943
 944  [self resetFetchResponse];
 945
 946  [service_ setAuthSubToken:@"bogus"];
 947
 948  ticket_ = [service_ fetchFeedWithURL:authSubFeedURL
 949                             feedClass:kGDataUseRegisteredClass
 950                              delegate:self
 951                     didFinishSelector:@selector(ticket:finishedWithObject:error:)];
 952  [ticket_ retain];
 953
 954  [self waitForFetch];
 955
 956  STAssertNil(fetchedObject_, @"fetchedObject_=%@", fetchedObject_);
 957  STAssertEquals([fetcherError_ code], (NSInteger)401,
 958                 @"fetcherError_=%@", fetcherError_);
 959  STAssertEqualObjects([ticket_ userData], defaultUserData, @"userdata error");
 960  STAssertEqualObjects([ticket_ propertyForKey:testPropertyKey],
 961                       defaultPropertyValue, @"default property error");
 962
 963}
 964
 965// fetch callbacks
 966
 967- (void)ticket:(GDataServiceTicket *)ticket finishedWithObject:(GDataObject *)object error:(NSError *)error {
 968
 969  STAssertEquals(ticket, ticket_, @"Got unexpected ticket");
 970
 971  if (error == nil) {
 972    fetchedObject_ = [object retain]; // save the fetched object, if any
 973  } else {
 974    fetcherError_ = [error retain]; // save the error
 975    STAssertNil(object, @"Unexpected object in callback");
 976  }
 977  ++gFetchCounter;
 978}
 979
 980- (void)ticket:(GDataServiceTicket *)ticket
 981hasDeliveredByteCount:(unsigned long long)numberOfBytesRead
 982ofTotalByteCount:(unsigned long long)dataLength {
 983
 984  lastProgressDeliveredCount_ = numberOfBytesRead;
 985  lastProgressTotalCount_ = dataLength;
 986}
 987
 988#pragma mark Retry fetch tests
 989
 990- (void)testRetryFetches {
 991
 992  if (!isServerRunning_) return;
 993
 994  // an ".auth" extension tells the server to require the success auth token,
 995  // but the server will ignore the .auth extension when looking for the file
 996  NSURL *feedStatusURL = [self fileURLToTestFileName:@"FeedSpreadsheetTest1.xml?status=503"];
 997
 998  [service_ setIsServiceRetryEnabled:YES];
 999
1000  //
1001  // test: retry until timeout, then expect failure to be passed to the callback
1002  //
1003
1004  [service_ setServiceMaxRetryInterval:5.]; // retry intervals of 1, 2, 4
1005  [service_ setServiceRetrySelector:@selector(stopRetryTicket:willRetry:forError:)];
1006
1007  ticket_ = (GDataServiceTicket *)
1008    [service_ fetchPublicFeedWithURL:feedStatusURL
1009                           feedClass:kGDataUseRegisteredClass
1010                            delegate:self
1011                   didFinishSelector:@selector(ticket:finishedWithObject:error:)];
1012  [ticket_ retain];
1013
1014  [ticket_ setUserData:[NSNumber numberWithInt:1000]]; // lots of retries
1015
1016  [self waitForFetch];
1017
1018  STAssertNil(fetchedObject_, @"obtained object unexpectedly");
1019  STAssertEquals([fetcherError_ code], (NSInteger)503,
1020                 @"fetcherError_ should be 503, was %@", fetcherError_);
1021  STAssertEquals([[ticket_ objectFetcher] retryCount], (NSUInteger) 3,
1022                 @"retry count should be 3, was %lu",
1023                 (unsigned long) [[ticket_ objectFetcher] retryCount]);
1024
1025  // check that the expected notifications happened for the object
1026  // fetches and the retries
1027  STAssertEquals(fetchStartedNotificationCount_, 4, @"start note missing");
1028  STAssertEquals(fetchStoppedNotificationCount_, 4, @"stopped note missing");
1029  STAssertEquals(retryDelayStartedNotificationCount_, 3, @"retry delay note missing");
1030  STAssertEquals(retryDelayStoppedNotificationCount_, 3, @"retry delay note missing");
1031
1032  //
1033  // test:  retry twice, then give up
1034  //
1035  [self resetFetchResponse];
1036
1037  [service_ setServiceMaxRetryInterval:10.]; // retry intervals of 1, 2, 4, 8
1038  [service_ setServiceRetrySelector:@selector(stopRetryTicket:willRetry:forError:)];
1039
1040  ticket_ = (GDataServiceTicket *)
1041    [service_ fetchPublicFeedWithURL:feedStatusURL
1042                           feedClass:kGDataUseRegisteredClass
1043                            delegate:self
1044                   didFinishSelector:@selector(ticket:finishedWithObject:error:)];
1045  [ticket_ retain];
1046
1047  // set userData to the number of retries allowed
1048  [ticket_ setUserData:[NSNumber numberWithInt:2]];
1049
1050  [self waitForFetch];
1051
1052  STAssertNil(fetchedObject_, @"obtained object unexpectedly");
1053  STAssertEquals([fetcherError_ code], (NSInteger)503,
1054                 @"fetcherError_ should be 503, was %@", fetcherError_);
1055  STAssertEquals([[ticket_ objectFetcher] retryCount], (NSUInteger) 2,
1056                 @"retry count should be 2, was %lu",
1057                 (unsigned long) [[ticket_ objectFetcher] retryCount]);
1058
1059  //
1060  // test:  retry, making the request succeed on the first retry
1061  // by fixing the URL
1062  //
1063  [self resetFetchResponse];
1064
1065  [service_ setServiceMaxRetryInterval:100.];
1066  [service_ setServiceRetrySelector:@selector(fixRequestRetryTicket:willRetry:forError:)];
1067
1068  ticket_ = (GDataServiceTicket *)
1069    [service_ fetchPublicFeedWithURL:feedStatusURL
1070                           feedClass:kGDataUseRegisteredClass
1071                            delegate:self
1072                   didFinishSelector:@selector(ticket:finishedWithObject:error:)];
1073  [ticket_ retain];
1074
1075  [self waitForFetch];
1076
1077  STAssertNotNil(fetchedObject_, @"should have obtained fetched object");
1078  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
1079  STAssertEquals([[ticket_ objectFetcher] retryCount], (NSUInteger) 1,
1080                 @"retry count should be 1, was %lu",
1081                 (unsigned long) [[ticket_ objectFetcher] retryCount]);
1082
1083  //
1084  // test:  retry, making the request succeed on the first retry
1085  // by fixing the URL, and changing the credential before the retry,
1086  // which should not affect the retry
1087  //
1088  [self resetFetchResponse];
1089
1090  [service_ setUserCredentialsWithUsername:@"myaccount@mydomain.com"
1091                                  password:@"mypassword"];
1092
1093  NSURL *authStatusURL = [self fileURLToTestFileName:@"FeedSpreadsheetTest1.xml.auth?status=503"];
1094
1095  ticket_ = [service_ fetchFeedWithURL:authStatusURL
1096                             feedClass:kGDataUseRegisteredClass
1097                              delegate:self
1098                     didFinishSelector:@selector(ticket:finishedWithObject:error:)];
1099  [ticket_ retain];
1100
1101  [service_ setUserCredentialsWithUsername:@"myaccount@mydomain.com"
1102                                  password:@"bad"];
1103  [self waitForFetch];
1104
1105  STAssertNotNil(fetchedObject_, @"should have obtained fetched object");
1106  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
1107  STAssertEquals([[ticket_ objectFetcher] retryCount], (NSUInteger) 1,
1108                 @"retry count should be 1, was %lu",
1109                 (unsigned long) [[ticket_ objectFetcher] retryCount]);
1110
1111  [self resetFetchResponse];
1112}
1113
1114-(BOOL)stopRetryTicket:(GDataServiceTicket *)ticket willRetry:(BOOL)suggestedWillRetry forError:(NSError *)error {
1115
1116  GTMHTTPFetcher *fetcher = [ticket currentFetcher];
1117  [fetcher setMinRetryInterval:1.0]; // force exact starting interval of 1.0 sec
1118
1119  NSUInteger count = [fetcher retryCount];
1120  NSInteger allowedRetryCount = [[ticket userData] intValue];
1121
1122  BOOL shouldRetry = ((NSInteger)count < allowedRetryCount);
1123
1124  STAssertEquals([fetcher nextRetryInterval], pow(2.0, [fetcher retryCount]),
1125                 @"unexpected next retry interval (expected %f, was %f)",
1126                 pow(2.0, [fetcher retryCount]),
1127                 [fetcher nextRetryInterval]);
1128
1129  return shouldRetry;
1130}
1131
1132-(BOOL)fixRequestRetryTicket:(GDataServiceTicket *)ticket willRetry:(BOOL)suggestedWillRetry forError:(NSError *)error {
1133
1134  GTMHTTPFetcher *fetcher = [ticket currentFetcher];
1135  [fetcher setMinRetryInterval:1.0]; // force exact starting interval of 1.0 sec
1136
1137  STAssertEquals([fetcher nextRetryInterval], pow(2.0, [fetcher retryCount]),
1138                 @"unexpected next retry interval (expected %f, was %f)",
1139                 pow(2.0, [fetcher retryCount]),
1140                 [fetcher nextRetryInterval]);
1141
1142  // fix it - change the request to a URL which does not have a status value
1143  NSURL *authFeedStatusURL = [self fileURLToTestFileName:@"FeedSpreadsheetTest1.xml"];
1144  [fetcher setMutableRequest:[NSMutableURLRequest requestWithURL:authFeedStatusURL]];
1145
1146  return YES; // do the retry fetch; it should succeed now
1147}
1148
1149#pragma mark Upload tests
1150
1151- (NSData *)generatedUploadDataWithLength:(NSUInteger)length {
1152  // fill a data block with data
1153  NSMutableData *data = [NSMutableData dataWithLength:length];
1154
1155  unsigned char *bytes = [data mutableBytes];
1156  for (NSUInteger idx = 0; idx < length; idx++) {
1157    bytes[idx] = (unsigned char)((idx + 1) % 256);
1158  }
1159
1160  return data;
1161}
1162
1163static NSString* const kPauseAtKey = @"pauseAt";
1164static NSString* const kRetryAtKey = @"retryAt";
1165static NSString* const kOriginalURLKey = @"origURL";
1166
1167- (void)testChunkedUpload {
1168
1169  if (!isServerRunning_) return;
1170
1171  NSData *bigData = [self generatedUploadDataWithLength:199000];
1172  NSData *smallData = [self generatedUploadDataWithLength:13];
1173
1174  // write the big data into a temp file
1175  NSString *tempDir = NSTemporaryDirectory();
1176  NSString *bigFileName = @"GDataServiceTest_BigFile";
1177  NSString *bigFilePath = [tempDir stringByAppendingPathComponent:bigFileName];
1178  [bigData writeToFile:bigFilePath atomically:NO];
1179
1180  NSFileHandle *bigFileHandle = [NSFileHandle fileHandleForReadingAtPath:bigFilePath];
1181
1182  SEL progressSel = @selector(uploadTicket:hasDeliveredByteCount:ofTotalByteCount:);
1183  [service_ setServiceUploadProgressSelector:progressSel];
1184
1185  SEL retrySel = @selector(uploadRetryTicket:willRetry:forError:);
1186  [service_ setServiceRetrySelector:retrySel];
1187  [service_ setIsServiceRetryEnabled:YES];
1188
1189  [self resetFetchResponse];
1190
1191  [service_ setServiceUploadChunkSize:75000];
1192
1193  //
1194  // test a big upload using an NSFileHandle
1195  //
1196
1197  // with chunk size 75000, for a data block of 199000 bytes, we expect to send
1198  // two 75000-byte chunks and then a 49000-byte final chunk
1199
1200  // a ".location" tells the server to return a "Location" header with the
1201  // same path but an ".upload" suffix replacing the ".location" suffix
1202  NSURL *uploadURL = [self fileURLToTestFileName:@"EntrySpreadsheetCellTest1.xml.location"];
1203  [service_ setUserCredentialsWithUsername:@"myaccount@mydomain.com"
1204                                  password:@"good"];
1205
1206  GDataEntrySpreadsheetCell *newEntry = [GDataEntrySpreadsheetCell entry];
1207  [newEntry setUploadFileHandle:bigFileHandle];
1208  [newEntry setUploadMIMEType:@"foo/bar"];
1209
1210  ticket_ = [service_ fetchEntryByInsertingEntry:newEntry
1211                                      forFeedURL:uploadURL
1212                                        delegate:self
1213                               didFinishSelector:@selector(ticket:finishedWithObject:error:)];
1214  [ticket_ retain];
1215
1216  [self waitForFetch];
1217
1218  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
1219
1220  // check that we got back the expected entry
1221  NSString *entryID = @"http://spreadsheets.google.com/feeds/cells/o04181601172097104111.497668944883620000/od6/private/full/R1C1";
1222  STAssertEqualObjects([(GDataEntrySpreadsheetCell *)fetchedObject_ identifier],
1223                       entryID, @"uploading %@", uploadURL);
1224
1225  // check the request of the final object fetcher to be sure we were uploading
1226  // chunks as expected
1227  GTMHTTPUploadFetcher *uploadFetcher = (GTMHTTPUploadFetcher *) [ticket_ objectFetcher];
1228  GTMHTTPFetcher *fetcher = [uploadFetcher activeFetcher];
1229  NSURLRequest *request = [fetcher mutableRequest];
1230  NSDictionary *reqHdrs = [request allHTTPHeaderFields];
1231
1232  NSString *uploadReqURLStr = @"http://localhost:54579/EntrySpreadsheetCellTest1.xml.upload";
1233  NSString *contentLength = [reqHdrs objectForKey:@"Content-Length"];
1234  NSString *contentRange = [reqHdrs objectForKey:@"Content-Range"];
1235
1236  STAssertEqualObjects([[request URL] absoluteString], uploadReqURLStr,
1237                       @"upload request wrong");
1238  STAssertEqualObjects(contentLength, @"49000", @"content length");
1239  STAssertEqualObjects(contentRange, @"bytes 150000-198999/199000", @"range");
1240
1241  [self resetFetchResponse];
1242
1243  //
1244  // repeat the previous upload, using NSData
1245  //
1246  [newEntry setUploadData:bigData];
1247  [newEntry setUploadFileHandle:nil];
1248
1249  ticket_ = [service_ fetchEntryByInsertingEntry:newEntry
1250                                      forFeedURL:uploadURL
1251                                        delegate:self
1252                               didFinishSelector:@selector(ticket:finishedWithObject:error:)];
1253  [ticket_ retain];
1254
1255  [self waitForFetch];
1256
1257  STAssertNil(fetcherError_, @"fetcherError_=%@", fetcherError_);
1258
1259  // check that we got back the expected entry
1260  entryID = @"http://spreadsheets.google.com/feeds/cells/o04181601172097104111.497668944883620000/od6/private/full/R1C1";
1261  STAssertEqualObjects([(GDataEntrySpreadsheetCell *)fetchedObject_ identifier],
1262                       entryID, @"uploading %@", uploadURL);
1263
1264  // check the request of the final object fetcher to be sure we were uploading
1265  // chunks as expected
1266  uploadFetcher = (GTMHTTPUploadFetcher *) [ticket_ objectFetcher];
1267  fetcher = [uploadFetcher activeFetcher];
1268  request = [fetcher mutableRequest];
1269  reqHdrs = [request allHTTPHeaderFields];
1270
1271  uploadReqURLStr = @"http://localhost:54579/EntrySpreadsheetCellTest1.xml.upload";
1272  contentLength = [reqHdrs objectForKey:@"Content-Length"];
1273  contentRange = [reqHdrs objectForKey:@"Content-Range"];
1274
1275  STAssertEqualObjects([[request URL] absoluteString], uploadReqURLStr,
1276                       @"upload request wrong");
1277  STAssertEqualObjects(contentLength, @"49000", @"content length");
1278  STAssertEqualObjects(contentRange, @"bytes 150000-198999/199000", @"range");
1279
1280  [self resetFetchResponse];
1281
1282  //
1283  // repeat the first upload, pausing after 20000 bytes
1284  //
1285
1286  ticket_ = [service_ fetchEntryByInsertingEntry:newEntry
1287                                      forFeedURL:uploadURL
1288                                        delegate:self
1289                               didFinishSelector:@selector(ticket:finishedWithObject:error:)];
1290  // add a pro

Large files files are truncated, but you can click here to view the full file