GBA003/External/Harmony/Backends/Drive/Google/GoogleAPI/Source/Tests/GTLRServiceTest.m
2024-05-30 10:22:15 +08:00

3547 lines
146 KiB
Objective-C

/* Copyright (c) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <XCTest/XCTest.h>
#import <objc/runtime.h>
#import "GTLRService.h"
#import "GTLRUtilities.h"
#import "GTMMIMEDocument.h"
#import "GTLRTestingSvc.h"
#import "GTMSessionFetcherService.h"
@interface GTLRServiceTest : XCTestCase
@end
// Surrogates for testing.
@interface GTLRTestingSvc_File_Surrogate : GTLRTestingSvc_File
@end
@interface GTLRTestingSvc_FileList_Surrogate : GTLRTestingSvc_FileList
@end
@interface GTLRTestingSvc_FileList_Surrogate2 : GTLRTestingSvc_FileList
@end
@implementation GTLRTestingSvc_File_Surrogate
@end
@implementation GTLRTestingSvc_FileList_Surrogate
@end
@implementation GTLRTestingSvc_FileList_Surrogate2
@end
// Internal methods redeclared for testing.
@interface GTLRBatchResponsePart : NSObject
@property(nonatomic, strong) NSDictionary *headers;
@end
@interface GTLRService (InternalMethods)
+ (NSURL *)URLWithString:(NSString *)urlString
queryParameters:(NSDictionary *)queryParameters;
- (GTLRBatchResponsePart *)responsePartWithMIMEPart:(GTMMIMEDocumentPart *)mimePart;
@end
//
// Simple authorizer class for testing
//
@interface GTLRTestAuthorizer : NSObject <GTMFetcherAuthorizationProtocol>
// The value string will be added to the authorization header, like
// Authorization: Bearer value
+ (GTLRTestAuthorizer *)authorizerWithValue:(NSString *)value;
@property(atomic, copy) NSString *value;
@property(atomic, retain) NSError *error;
@end
// GTLRTestLifetimeObject fulfills the expectation when the provided object deallocates.
//
// This allows dealloc to occur asynchronously and the test code to just wait for it.
@interface GTLRTestLifetimeObject : NSObject
+ (instancetype)trackLifetimeOfObject:(id)object expectation:(XCTestExpectation *)expectation;
@end
#if GTM_BACKGROUND_TASK_FETCHING
// An implementation of a substitute UIApplication to track invocations of begin
// and end. It doesn't attempt to match them up.
@interface CountingUIApplication : NSObject<GTMUIApplicationProtocol>
@property(atomic, readonly) NSCountedSet *beginTaskIDs;
@property(atomic, readonly) NSCountedSet *endTaskIDs;
// Optionally, expire tasks immediately (on the next main thread cycle.)
@property(atomic, assign) BOOL shouldExpireTasks;
@property(atomic, readonly) NSUInteger numberOfExpiredTasks;
@end
#endif // GTM_BACKGROUND_TASK_FETCHING
@implementation GTLRServiceTest {
NSURL *_tempFileURLToDelete;
}
- (void)tearDown {
if (_tempFileURLToDelete) {
NSError *deleteError;
XCTAssert([[NSFileManager defaultManager] removeItemAtURL:_tempFileURLToDelete
error:&deleteError],
@"%@", deleteError);
}
[self clearCountingUIApp];
[super tearDown];
}
#pragma mark -
+ (NSData *)dataForTestFileName:(NSString *)fileName {
// Make an URL for the named file in the unit test bundle.
NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
NSURL *fileURL = [testBundle URLForResource:fileName
withExtension:nil];
if (!fileURL) {
NSLog(@"Cannot find \"%@\" in test runtime resources at %@",
fileName, testBundle.resourcePath);
return nil;
}
NSError *error;
NSData *data = [NSData dataWithContentsOfURL:fileURL
options:0
error:&error];
if (!data) {
NSLog(@"Cannot read data for %@: %@", fileName, error);
}
return data;
}
static NSHTTPURLResponse *QueryResponseWithURL(NSURL *url,
NSInteger status,
NSString *contentType) {
// Make a succesful API response to feed to the fetcher testBlock.
return [[NSHTTPURLResponse alloc] initWithURL:url
statusCode:status
HTTPVersion:@"HTTP/1.1"
headerFields:@{ @"Content-Type" : contentType }];
}
- (GTMSessionFetcherTestBlock)fetcherTestBlockWithResponseForFileName:(NSString *)fileName
status:(NSInteger)status {
return [self fetcherTestBlockWithResponseForFileName:fileName
status:status
numberOfStatusErrors:0
multipartBoundary:nil];
}
- (GTMSessionFetcherTestBlock)fetcherTestBlockWithResponseForFileName:(NSString *)fileName
status:(NSInteger)initialStatus
numberOfStatusErrors:(NSInteger)numberOfStatusErrors
multipartBoundary:(NSString *)multipartBoundary {
GTMSessionFetcherTestBlock testBlock = ^(GTMSessionFetcher *fetcherToTest,
GTMSessionFetcherTestResponse testResponse) {
NSString *topContentType = @"application/json; charset=UTF-8";
if (multipartBoundary) {
topContentType = [NSString stringWithFormat:@"multipart/mixed; boundary=%@",
multipartBoundary];
}
NSInteger status = initialStatus;
if (numberOfStatusErrors > 0 && (NSInteger)fetcherToTest.retryCount >= numberOfStatusErrors) {
status = 200;
}
NSHTTPURLResponse *response = QueryResponseWithURL(fetcherToTest.request.URL,
status,
topContentType);
NSData *responseData;
if (status != 204) {
if (fileName) {
responseData = [[self class] dataForTestFileName:fileName];
XCTAssertNotNil(responseData, @"%@", fileName);
}
} else {
// We expect 204 "No content" on delete, for example.
}
NSError *responseError = nil;
if (status >= 400) {
responseError = [NSError errorWithDomain:kGTMSessionFetcherStatusDomain
code:status
userInfo:nil];
}
testResponse(response, responseData, responseError);
};
return testBlock;
}
static NSString *QueryValueForURLItem(NSURL *url, NSString *itemName) {
NSURLComponents *components = [NSURLComponents componentsWithURL:url
resolvingAgainstBaseURL:YES];
NSArray *queryItems = components.queryItems;
for (NSURLQueryItem *thisItem in queryItems) {
if ([thisItem.name isEqual:itemName]) {
return thisItem.value;
}
}
return nil;
}
// This matches GTLRURITemplate's version.
static NSString *EscapeString(NSString *str, BOOL allowReserved) {
NSMutableCharacterSet *cs = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
NSString * const kReservedChars = @":/?#[]@!$&'()*+,;=";
if (allowReserved) {
[cs addCharactersInString:kReservedChars];
} else {
[cs removeCharactersInString:kReservedChars];
}
NSString *resultStr = [str stringByAddingPercentEncodingWithAllowedCharacters:cs];
return resultStr;
}
static BOOL WWWFormDataHasValue(NSData *wwwFormData,
NSString *itemName, NSString *itemValue) {
NSString *escapedItemValue = EscapeString(itemValue, NO);
NSRange fullRange = NSMakeRange(0, wwwFormData.length);
NSString *needle = [NSString stringWithFormat:@"&%@=%@&", itemName, escapedItemValue];
NSData *needleData = [needle dataUsingEncoding:NSUTF8StringEncoding];
NSRange range = [wwwFormData rangeOfData:needleData
options:0
range:fullRange];
if (range.location != NSNotFound) {
return YES;
}
// It could be the first or last, so check those cases.
needle = [NSString stringWithFormat:@"%@=%@&", itemName, escapedItemValue];
needleData = [needle dataUsingEncoding:NSUTF8StringEncoding];
range = [wwwFormData rangeOfData:needleData
options:NSDataSearchAnchored
range:fullRange];
if (range.location == 0) {
return YES;
}
needle = [NSString stringWithFormat:@"&%@=%@", itemName, escapedItemValue];
needleData = [needle dataUsingEncoding:NSUTF8StringEncoding];
range = [wwwFormData rangeOfData:needleData
options:(NSDataSearchAnchored | NSDataSearchBackwards)
range:fullRange];
if (range.location != NSNotFound) {
return YES;
}
// Last case, the data was just the one pair.
needle = [NSString stringWithFormat:@"%@=%@", itemName, escapedItemValue];
needleData = [needle dataUsingEncoding:NSUTF8StringEncoding];
if ([wwwFormData isEqual:needleData]) {
return YES;
}
return NO;
}
static BOOL IsCurrentQueue(dispatch_queue_t targetQueue) {
const char *targetQueueLabel = dispatch_queue_get_label(targetQueue);
const char *currentQueueLabel = dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL);
int result = strcmp(currentQueueLabel, targetQueueLabel);
return result == 0;
}
- (GTLRService *)driveServiceForTest {
// Has valid authorization.
GTLRService *service = [[GTLRTestingSvcService alloc] init];
service.authorizer = [GTLRTestAuthorizer authorizerWithValue:@"catpaws"];
return service;
}
- (BOOL)service:(GTLRService *)service waitForTicket:(GTLRServiceTicket *)ticket {
BOOL finishedInTime = [service waitForTicket:ticket timeout:10.0];
return finishedInTime;
}
- (void)expectTicketNotifications {
[self expectationForNotification:kGTLRServiceTicketStartedNotification
object:nil
handler:nil];
[self expectationForNotification:kGTLRServiceTicketStoppedNotification
object:nil
handler:nil];
}
- (void)expectTicketAndParsingNotifications {
[self expectTicketNotifications];
// Parsing notifications are expected whenever the fetcher returns a nil error
// and non-empty data.
[self expectationForNotification:kGTLRServiceTicketParsingStartedNotification
object:nil
handler:nil];
[self expectationForNotification:kGTLRServiceTicketParsingStoppedNotification
object:nil
handler:nil];
}
- (void)setCountingUIAppWithExpirations:(BOOL)shouldExpireTasks {
#if GTM_BACKGROUND_TASK_FETCHING
CountingUIApplication *countingUIApp = [[CountingUIApplication alloc] init];
countingUIApp.shouldExpireTasks = shouldExpireTasks;
[GTMSessionFetcher setSubstituteUIApplication:countingUIApp];
#endif
}
- (void)verifyCountingUIAppWithExpectedCount:(NSUInteger)expectedCount
expectedExpirations:(NSUInteger)expectedExpirations {
#if GTM_BACKGROUND_TASK_FETCHING
CountingUIApplication *countingUIApp = [GTMSessionFetcher substituteUIApplication];
XCTAssertNotNil(countingUIApp);
XCTAssertEqual(countingUIApp.beginTaskIDs.count, expectedCount);
XCTAssertEqualObjects(countingUIApp.beginTaskIDs, countingUIApp.endTaskIDs);
XCTAssertEqual(countingUIApp.numberOfExpiredTasks, expectedExpirations);
[self clearCountingUIApp];
#endif
}
- (void)clearCountingUIApp {
#if GTM_BACKGROUND_TASK_FETCHING
[GTMSessionFetcher setSubstituteUIApplication:nil];
#endif
}
#pragma mark - Query Execution Tests
- (void)testService_SingleQuery {
// Successful request with valid authorization.
//
// Response is file Drive1.response.txt
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1.response.txt" status:200];
service.serviceProperties = @{ @"Marsupial" : @"Koala", @"Dolphin" : @"Spinner" };
[self expectTicketAndParsingNotifications];
[self setCountingUIAppWithExpirations:NO];
NSString *timeParam = @"2011-05-04T23:28:20.888Z";
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(mimeType,id,kind,name,webViewLink,thumbnailLink,trashed)";
query.pageSize = 10;
query.requestID = @"gtlr_1234";
query.timeParamForTesting = [GTLRDateTime dateTimeWithRFC3339String:timeParam];
query.additionalHTTPHeaders = @{ @"X-Feline": @"Fluffy",
@"X-Canine": @"Spot" };
query.additionalURLQueryParameters = @{ @"meowParam": @"Meow" };
query.executionParameters.retryBlock = ^(GTLRServiceTicket *ticket, BOOL suggestedWillRetry,
NSError *error) {
XCTFail(@"No retry expected.");
return NO;
};
query.executionParameters.uploadProgressBlock = ^(GTLRServiceTicket *ticket,
unsigned long long numberOfBytesRead,
unsigned long long dataLength) {
XCTFail(@"No body upload expected.");
};
query.executionParameters.ticketProperties = @{ @"Bird" : @"Kookaburra", @"Dolphin" : @"Tucuxi" };
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
__block GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
// Verify the top-level object and one of its items.
XCTAssertEqualObjects([object class], [GTLRTestingSvc_FileList class]);
XCTAssertNil(error);
XCTAssertEqualObjects(object.kind, @"drive#fileList");
XCTAssertEqual(object.files.count, 2U, @"%@", object.files);
GTLRTestingSvc_File *item0 = object.files[0];
XCTAssertEqualObjects([item0 class], [GTLRTestingSvc_File class]);
XCTAssertEqualObjects(item0.kind, @"drive#file");
XCTAssertEqualObjects(object.timeFieldForTesting.RFC3339String,
@"2011-01-02T03:04:05.067Z");
XCTAssertEqualObjects(callbackTicket, queryTicket);
// The ticket's query should be a copy of the original; query execution leaves
// the original unmolested so the client can modify and reuse it.
GTLRQuery *ticketQuery = callbackTicket.executingQuery;
XCTAssertNotEqual(ticketQuery, query);
XCTAssertEqualObjects(ticketQuery.requestID, query.requestID);
// Service properties should be copied to the ticket.
XCTAssertEqualObjects(callbackTicket.ticketProperties[@"Marsupial"], @"Koala");
XCTAssertEqualObjects(callbackTicket.ticketProperties[@"Bird"], @"Kookaburra");
XCTAssertEqualObjects(callbackTicket.ticketProperties[@"Dolphin"], @"Tucuxi");
XCTAssert([NSThread isMainThread]);
[queryFinished fulfill];
}];
XCTAssertFalse(queryTicket.hasCalledCallback);
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// GTLRQuery query parameters.
NSURLRequest *fetcherRequest = queryTicket.objectFetcher.request;
XCTAssertEqualObjects(QueryValueForURLItem(fetcherRequest.URL, @"fields"), query.fields);
XCTAssertEqualObjects(QueryValueForURLItem(fetcherRequest.URL, @"pageSize"), @"10");
XCTAssertEqualObjects(QueryValueForURLItem(fetcherRequest.URL, @"prettyPrint"), @"false");
XCTAssertEqualObjects(QueryValueForURLItem(fetcherRequest.URL, @"timeParamForTesting"),
timeParam);
// Test things added to the request:
// Authorization header, additionalHTTPHeaders, and additionalURLQueryParameters
//
// Headers.
XCTAssertEqualObjects([fetcherRequest valueForHTTPHeaderField:@"Authorization"],
@"Bearer catpaws");
XCTAssertEqualObjects([fetcherRequest valueForHTTPHeaderField:@"X-Feline"], @"Fluffy");
// URL query parameters.
XCTAssertEqualObjects(QueryValueForURLItem(fetcherRequest.URL, @"meowParam"), @"Meow");
// The fetcher releases its authorizer upon completion, so to test the request,
// we'll use the service's authorizer.
id<GTMFetcherAuthorizationProtocol> authorizer = service.authorizer;
XCTAssertNotNil(authorizer);
XCTAssert([authorizer isAuthorizedRequest:fetcherRequest],
@"%@", fetcherRequest.allHTTPHeaderFields);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
// The fetcher and GTLRService both start and end background tasks, so we expect 2 invocations of
// beginBackgroundTask and endBackgroundTask.
[self verifyCountingUIAppWithExpectedCount:2
expectedExpirations:0];
}
- (void)testService_SingleQuery_LongURLToFormPost {
// Successful request with valid authorization but with URL query arguments
// forcing it into a www forms post.
//
// Response is file Drive1.response.txt
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1.response.txt" status:200];
service.serviceProperties = @{ @"Marsupial" : @"Koala", @"Dolphin" : @"Spinner" };
[self expectTicketAndParsingNotifications];
[self setCountingUIAppWithExpirations:NO];
NSString *timeParam = @"2011-05-04T23:28:20.888Z";
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(mimeType,id,kind,name,webViewLink,thumbnailLink,trashed)";
query.pageSize = 10;
query.requestID = @"gtlr_1234";
query.timeParamForTesting = [GTLRDateTime dateTimeWithRFC3339String:timeParam];
// Add a bunch of entries to ensure we go over the URL size limit.
NSMutableArray<NSString *> *extras = [NSMutableArray array];
for (NSUInteger x = 0 ; x < 1024; ++x) {
[extras addObject:[@(x) description]];
}
query.extras = extras;
query.additionalHTTPHeaders = @{ @"X-Feline": @"Fluffy",
@"X-Canine": @"Spot" };
query.additionalURLQueryParameters = @{ @"meowParam": @"Meow" };
query.executionParameters.retryBlock = ^(GTLRServiceTicket *ticket, BOOL suggestedWillRetry,
NSError *error) {
XCTFail(@"No retry expected.");
return NO;
};
XCTestExpectation *uploadedSomeBytes = [self expectationWithDescription:@"uploadedSomeBytes"];
query.executionParameters.uploadProgressBlock = ^(GTLRServiceTicket *ticket,
unsigned long long numberOfBytesRead,
unsigned long long dataLength) {
// The arg will go in the body, so uploadProgressBlock will see them.
if (numberOfBytesRead == dataLength) {
[uploadedSomeBytes fulfill];
}
XCTAssert([NSThread isMainThread]);
};
query.executionParameters.ticketProperties = @{ @"Bird" : @"Kookaburra", @"Dolphin" : @"Tucuxi" };
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
__block GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
// Verify the top-level object and one of its items.
XCTAssertEqualObjects([object class], [GTLRTestingSvc_FileList class]);
XCTAssertNil(error);
XCTAssertEqualObjects(object.kind, @"drive#fileList");
XCTAssertEqual(object.files.count, 2U, @"%@", object.files);
GTLRTestingSvc_File *item0 = object.files[0];
XCTAssertEqualObjects([item0 class], [GTLRTestingSvc_File class]);
XCTAssertEqualObjects(item0.kind, @"drive#file");
XCTAssertEqualObjects(object.timeFieldForTesting.RFC3339String,
@"2011-01-02T03:04:05.067Z");
XCTAssertEqualObjects(callbackTicket, queryTicket);
// The ticket's query should be a copy of the original; query execution leaves
// the original unmolested so the client can modify and reuse it.
GTLRQuery *ticketQuery = callbackTicket.executingQuery;
XCTAssertNotEqual(ticketQuery, query);
XCTAssertEqualObjects(ticketQuery.requestID, query.requestID);
// Service properties should be copied to the ticket.
XCTAssertEqualObjects(callbackTicket.ticketProperties[@"Marsupial"], @"Koala");
XCTAssertEqualObjects(callbackTicket.ticketProperties[@"Bird"], @"Kookaburra");
XCTAssertEqualObjects(callbackTicket.ticketProperties[@"Dolphin"], @"Tucuxi");
XCTAssert([NSThread isMainThread]);
[queryFinished fulfill];
}];
XCTAssertFalse(queryTicket.hasCalledCallback);
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
NSURLRequest *fetcherRequest = queryTicket.objectFetcher.request;
// Should be a POST with the X-HTTP-Method-Override header and correct
// Content-Type.
XCTAssertEqualObjects(fetcherRequest.HTTPMethod, @"POST");
XCTAssertEqualObjects([fetcherRequest valueForHTTPHeaderField:@"X-HTTP-Method-Override"],
@"GET");
XCTAssertEqualObjects([fetcherRequest valueForHTTPHeaderField:@"Content-Type"],
@"application/x-www-form-urlencoded");
// GTLRQuery query parameters.
XCTAssertNil(fetcherRequest.URL.query); // Should be in the body instead.
NSData *dataPosted = queryTicket.objectFetcher.bodyData;
XCTAssertNotNil(dataPosted);
XCTAssertTrue(WWWFormDataHasValue(dataPosted, @"fields", query.fields));
XCTAssertTrue(WWWFormDataHasValue(dataPosted, @"pageSize", @"10"));
XCTAssertTrue(WWWFormDataHasValue(dataPosted, @"prettyPrint", @"false"));
XCTAssertTrue(WWWFormDataHasValue(dataPosted, @"timeParamForTesting", timeParam));
// Test things added to the request:
// Authorization header, additionalHTTPHeaders, and additionalURLQueryParameters
//
// Headers.
XCTAssertEqualObjects([fetcherRequest valueForHTTPHeaderField:@"Authorization"],
@"Bearer catpaws");
XCTAssertEqualObjects([fetcherRequest valueForHTTPHeaderField:@"X-Feline"], @"Fluffy");
// URL query parameters.
XCTAssertTrue(WWWFormDataHasValue(dataPosted, @"meowParam", @"Meow"));
// The fetcher releases its authorizer upon completion, so to test the request,
// we'll use the service's authorizer.
id<GTMFetcherAuthorizationProtocol> authorizer = service.authorizer;
XCTAssertNotNil(authorizer);
XCTAssert([authorizer isAuthorizedRequest:fetcherRequest],
@"%@", fetcherRequest.allHTTPHeaderFields);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
// The fetcher and GTLRService both start and end background tasks, so we expect 2 invocations of
// beginBackgroundTask and endBackgroundTask.
[self verifyCountingUIAppWithExpectedCount:2
expectedExpirations:0];
}
- (void)testService_SingleQuery_SelectorCallback {
// Successful request with selector callback.
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1.response.txt" status:200];
[self expectTicketAndParsingNotifications];
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(mimeType,id,kind,name)";
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
// We'll put the callback tests in a block in the ticket properties.
query.executionParameters.ticketProperties =
@{ @"callback tests" : ^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object,
NSError *error){
// Verify the top-level object and one of its items.
XCTAssertEqualObjects([object class], [GTLRTestingSvc_FileList class]);
XCTAssertNil(error);
XCTAssertEqualObjects(object.kind, @"drive#fileList");
XCTAssertEqual(object.files.count, 2U, @"%@", object.files);
// The ticket's query should be a copy of the original; query execution leaves
// the original unmolested so the client can modify and reuse it.
GTLRQuery *ticketQuery = callbackTicket.executingQuery;
XCTAssertNotEqual(ticketQuery, query);
XCTAssertEqualObjects(ticketQuery.requestID, query.requestID);
XCTAssert([NSThread isMainThread]);
[queryFinished fulfill];
}};
GTLRServiceTicket *queryTicket =
[service executeQuery:query
delegate:self
didFinishSelector:@selector(serviceTicket:finishedWithObject:error:)];
XCTAssertFalse(queryTicket.hasCalledCallback);
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
// Callback method for use in testService_SingleQuery_SelectorCallback
//
// The ticket properties have a block to be executed.
- (void)serviceTicket:(GTLRServiceTicket *)callbackTicket
finishedWithObject:(GTLRTestingSvc_FileList *)object
error:(NSError *)callbackError {
GTLRServiceCompletionHandler block = callbackTicket.ticketProperties[@"callback tests"];
block(callbackTicket, object, callbackError);
}
- (void)testService_SingleQuery_QueryWithResourceURL {
// Successful based on fixed URL.
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1.response.txt" status:200];
[self expectTicketAndParsingNotifications];
GTLRTestingSvcQuery_FilesList *templateQuery = [GTLRTestingSvcQuery_FilesList query];
templateQuery.fields = @"kind,files(id,kind,name)";
// Set a specific request URL by getting the actual query URL.
NSURLRequest *request = [service requestForQuery:templateQuery];
NSURL *requestURL = request.URL;
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
GTLRServiceTicket *queryTicket =
[service fetchObjectWithURL:requestURL
objectClass:[GTLRTestingSvc_FileList class]
executionParameters:nil
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object,
NSError *callbackError) {
// Verify the top-level object and one of its items.
XCTAssertEqualObjects([object class], [GTLRTestingSvc_FileList class]);
XCTAssertNil(callbackError);
XCTAssertEqualObjects(object.kind, @"drive#fileList");
XCTAssertEqual(object.files.count, 2U, @"%@", object.files);
XCTAssert([NSThread isMainThread]);
[queryFinished fulfill];
}];
XCTAssertFalse(queryTicket.hasCalledCallback);
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
NSURL *fetcherRequestURL = queryTicket.objectFetcher.request.URL;
XCTAssertEqualObjects(fetcherRequestURL.host, @"www.googleapis.com");
XCTAssertEqualObjects(fetcherRequestURL.path, @"/drive/v3/files");
}
- (void)testService_SingleQuery_ExpiredBackgroundTasks {
// Successful request with expired background tasks.
[self setCountingUIAppWithExpirations:YES];
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1.response.txt"
status:456
numberOfStatusErrors:1
multipartBoundary:nil];
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(mimeType,id,kind)";
XCTestExpectation *completionExp = [self expectationWithDescription:@"completionExp"];
GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
[completionExp fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
// The fetcher and GTLRService both start and end background tasks, so we expect 2 invocations of
// beginBackgroundTask and endBackgroundTask, both expired.
[self verifyCountingUIAppWithExpectedCount:2
expectedExpirations:2];
}
- (void)testService_SingleQuery_Paging {
// Successful request with 3 pages of results, 8 total result items.
//
// Response is file Drive1.response.txt
GTLRService *service = [self driveServiceForTest];
[self setCountingUIAppWithExpirations:NO];
__block GTLRServiceTicket *queryTicket;
__block int pageCounter = 0; // 1-based as page requests come in to the fetcher.
void (^checkRequestParamsAndHeaders)(NSURLRequest *) = ^(NSURLRequest *request){
// GTLRQuery query parameters.
XCTAssertEqualObjects(QueryValueForURLItem(request.URL, @"pageSize"), @"3");
if (pageCounter == 0) {
XCTAssertNil(QueryValueForURLItem(request.URL, @"pageToken"));
} else {
XCTAssertEqualObjects(QueryValueForURLItem(request.URL, @"pageToken"),
@(pageCounter + 1).stringValue);
}
// All queries should have all header fields and added params.
XCTAssertEqualObjects([request valueForHTTPHeaderField:@"Authorization"], @"Bearer catpaws");
XCTAssertEqualObjects([request valueForHTTPHeaderField:@"X-Feline"], @"Fluffy");
XCTAssertEqualObjects(QueryValueForURLItem(request.URL, @"meowParam"), @"Meow");
};
service.fetcherService.testBlock = ^(GTMSessionFetcher *fetcherToTest,
GTMSessionFetcherTestResponse testResponse) {
checkRequestParamsAndHeaders(fetcherToTest.request);
XCTAssertEqual(queryTicket.pagesFetchedCounter, (NSUInteger)pageCounter);
++pageCounter;
NSHTTPURLResponse *response = QueryResponseWithURL(fetcherToTest.request.URL,
200, @"application/json");
NSString *fileName = [NSString stringWithFormat:@"Drive1Paging%d.response.txt", pageCounter];
NSData *responseData = [[self class] dataForTestFileName:fileName];;
XCTAssertNotNil(responseData, @"%@", fileName);
testResponse(response, responseData, nil);
};
[self expectTicketAndParsingNotifications];
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(mimeType,id,kind,name,trashed)";
query.pageSize = 3;
query.additionalHTTPHeaders = @{ @"X-Feline": @"Fluffy",
@"X-Canine": @"Spot" };
query.additionalURLQueryParameters = @{ @"meowParam": @"Meow" };
query.executionParameters.shouldFetchNextPages = @YES;
query.executionParameters.retryBlock = ^(GTLRServiceTicket *ticket, BOOL suggestedWillRetry,
NSError *error) {
XCTFail(@"No retry expected.");
return NO;
};
query.executionParameters.uploadProgressBlock = ^(GTLRServiceTicket *ticket,
unsigned long long numberOfBytesRead,
unsigned long long dataLength) {
XCTFail(@"No body upload expected.");
};
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
queryTicket = [service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
XCTAssertEqual(pageCounter, 3);
// Verify the top-level object and one of its items.
XCTAssertEqualObjects([object class], [GTLRTestingSvc_FileList class]);
XCTAssertNil(error);
XCTAssertEqualObjects(object.kind, @"drive#fileList");
XCTAssertEqual(object.files.count, 8U, @"%@", object.files);
GTLRTestingSvc_File *item0 = object.files[0];
XCTAssertEqualObjects([item0 class], [GTLRTestingSvc_File class]);
XCTAssertEqualObjects(item0.kind, @"drive#file");
XCTAssertEqualObjects(item0.identifier, @"1_tKaUhjk3YkF3RYk1d");
GTLRTestingSvc_File *item7 = object.files[7];
XCTAssertEqualObjects([item7 class], [GTLRTestingSvc_File class]);
XCTAssertEqualObjects(item7.kind, @"drive#file");
XCTAssertEqualObjects(item7.identifier, @"1M0XYjGhEbY1BzHs5srOpQ");
XCTAssert([NSThread isMainThread]);
[queryFinished fulfill];
}];
XCTAssertFalse(queryTicket.hasCalledCallback);
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
// We expect six calls to beginBackgroundTask and endBackgroundTask, one for
// fetcher and GTLRService for each page.
[self verifyCountingUIAppWithExpectedCount:6
expectedExpirations:0];
}
- (void)testService_SingleQuery_SkipAuth {
// Disallow authorizing request.
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1.response.txt" status:200];
// Request with an auth error.
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(id,kind,name)";
query.requestID = @"gtlr_1234";
query.shouldSkipAuthorization = YES;
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
XCTAssertEqualObjects([object class], [GTLRTestingSvc_FileList class]);
XCTAssertNil(error);
[queryFinished fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Authorization should have been skipped.
id<GTMFetcherAuthorizationProtocol> authorizer = service.authorizer;
NSURLRequest *fetcherRequest = queryTicket.objectFetcher.request;
XCTAssertNotNil(authorizer);
XCTAssertFalse([authorizer isAuthorizedRequest:fetcherRequest],
@"%@", fetcherRequest.allHTTPHeaderFields);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)performTestService_SingleQuery_InvalidParam:(NSString *)responseFileName {
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:responseFileName status:400];
// Request an invalid page.
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(id,kind,name)";
query.pageToken = @"NotARealToken";
query.requestID = @"gtlr_1234";
XCTestExpectation *queryFinishedExp = [self expectationWithDescription:@"queryFinished"];
XCTestExpectation *retryExp = [self expectationWithDescription:@"queryFinished"];
query.executionParameters.retryBlock = ^(GTLRServiceTicket *ticket,
BOOL suggestedWillRetry,
NSError *error) {
[retryExp fulfill];
return NO;
};
query.executionParameters.uploadProgressBlock = ^(GTLRServiceTicket *ticket,
unsigned long long numberOfBytesRead,
unsigned long long dataLength) {
XCTFail(@"No body upload expected.");
};
GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
XCTAssertNil(object);
XCTAssertEqualObjects(error.domain, kGTLRErrorObjectDomain);
XCTAssertEqual(error.code, 400);
GTLRErrorObject *errorObject = [error.userInfo objectForKey:kGTLRStructuredErrorKey];
XCTAssertEqual(errorObject.code.intValue, 400);
XCTAssertEqualObjects(errorObject.message, @"Invalid Value");
XCTAssertEqual(errorObject.errors.count, (NSUInteger)1);
XCTAssertEqualObjects(errorObject.errors[0].reason, @"invalid");
XCTAssertEqualObjects(errorObject.errors[0].message, @"Invalid Value");
[queryFinishedExp fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
NSURLRequest *fetcherRequest = queryTicket.objectFetcher.request;
XCTAssertEqualObjects(QueryValueForURLItem(fetcherRequest.URL, @"pageToken"), @"NotARealToken");
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_SingleQuery_InvalidParam {
// Unsuccessful request (invalid page token).
//
// Response is file Drive1ParamError.response.txt
[self performTestService_SingleQuery_InvalidParam:@"Drive1ParamError.response.txt"];
}
- (void)testService_SingleQuery_InvalidParam_ListResult {
// Unsuccessful request (invalid page token).
//
// This is the same as testService_SingleQuery_InvalidParam, expect the response
// is what happens if the server sees this as a HTTP Streaming call, meaning the
// error will come back in an array.
//
// Response is file Drive1ParamErrorAsList.response.txt
[self performTestService_SingleQuery_InvalidParam:@"Drive1ParamErrorAsList.response.txt"];
}
- (void)testService_SingleQuery_InvalidAuth {
// Unsuccessful request (invalid auth).
//
// Response is file Drive1AuthError.response.txt
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1AuthError.response.txt" status:401];
// Request with an auth error.
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(id,kind,name)";
query.requestID = @"gtlr_1234";
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
XCTAssertNil(object);
XCTAssertEqualObjects(error.domain, kGTLRErrorObjectDomain);
XCTAssertEqual(error.code, 401);
[queryFinished fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_SingleQuery_CorruptResponse {
// Successful request with invalid JSON in the response.
//
// Response is file Drive1Corrupt.response.txt
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1Corrupt.response.txt" status:200];
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(mimeType,id,kind,name)";
query.requestID = @"gtlr_1234";
query.executionParameters.retryBlock = ^(GTLRServiceTicket *ticket,
BOOL suggestedWillRetry,
NSError * error) {
XCTFail(@"No retry expected.");
return NO;
};
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
__block GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
// Verify the top-level object and one of its items.
XCTAssertNil(object);
// NSPropertyListReadCorruptError = 3840
XCTAssertEqual(error.code, NSPropertyListReadCorruptError, @"%@", error);
[queryFinished fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_SingleQuery_ServiceCallbackQueue {
[self performCallbackQueueTestUsingExecutionParams:NO];
}
- (void)testService_SingleQuery_ExecutionParamsCallbackQueue {
[self performCallbackQueueTestUsingExecutionParams:YES];
}
- (void)performCallbackQueueTestUsingExecutionParams:(BOOL)useExecutionParams {
// Retry once then successful request with callbacks on a specific queue.
//
// Response is file Drive1.response.txt
dispatch_queue_t myCallbackQueue = dispatch_queue_create("myCallbackQueue",
DISPATCH_QUEUE_SERIAL);
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1.response.txt"
status:456
numberOfStatusErrors:1
multipartBoundary:nil];
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(mimeType,id,kind)";
if (useExecutionParams) {
query.executionParameters.callbackQueue = myCallbackQueue;
} else {
service.callbackQueue = myCallbackQueue;
}
XCTestExpectation *completionExp = [self expectationWithDescription:@"completionExp"];
XCTestExpectation *retryBlockExp = [self expectationWithDescription:@"retryBlockExp"];
service.retryBlock = ^(GTLRServiceTicket *ticket,
BOOL suggestedWillRetry,
NSError *error) {
XCTAssert(IsCurrentQueue(myCallbackQueue));
[retryBlockExp fulfill];
return YES;
};
service.retryEnabled = YES;
GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
XCTAssert(IsCurrentQueue(myCallbackQueue));
[completionExp fulfill];
}];
// Changing the callback queue doesn't affect tickets already issued.
service.callbackQueue = dispatch_get_main_queue();
XCTAssert([self service:service waitForTicket:queryTicket]);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_SingleQuery_NoCallbacks {
// Successful request but without any callback blocks provided.
//
// Response is file Drive1.response.txt
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1.response.txt" status:200];
[self expectTicketAndParsingNotifications];
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(mimeType,id,kind,name,webViewLink,thumbnailLink,trashed)";
query.pageSize = 10;
query.requestID = @"gtlr_1234";
GTLRServiceTicket *queryTicket = [service executeQuery:query
completionHandler:nil];
XCTAssertFalse(queryTicket.hasCalledCallback);
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_SingleQuery_CancelDuringFetch {
// Successful request, canceled before the fetch returns.
GTLRService *service = [self driveServiceForTest];
XCTestExpectation *fetchExp = [self expectationWithDescription:@"fetch happened"];
__block GTLRServiceTicket *queryTicket;
service.fetcherService.testBlock = ^(GTMSessionFetcher *fetcherToTest,
GTMSessionFetcherTestResponse testResponse) {
// Cancel the ticket during the fetch. This must be async so the ticket variable is non-nil.
dispatch_async(dispatch_get_main_queue(), ^{
XCTAssertNotNil(queryTicket);
[queryTicket cancelTicket];
NSHTTPURLResponse *response = QueryResponseWithURL(fetcherToTest.request.URL,
200, @"text/plain");
NSData *responseData = [NSData data];
testResponse(response, responseData, nil);
[fetchExp fulfill];
});
};
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(mimeType,id,kind,name)";
query.completionBlock = ^(GTLRServiceTicket *callbackTicket, GTLRObject *callbackObj,
NSError *callbackError) {
XCTFail(@"Cancel should skip callbacks");
};
query.executionParameters.retryBlock = ^(GTLRServiceTicket *ticket,
BOOL suggestedWillRetry,
NSError *error) {
XCTFail(@"No retry expected.");
return NO;
};
queryTicket = [service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
XCTFail(@"Cancel should skip callbacks");
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssertFalse(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_SingleQuery_CancelDuringParse {
// Successful request, canceled after the fetch returns.
GTLRService *service = [self driveServiceForTest];
XCTestExpectation *parseExp = [self expectationWithDescription:@"parse notification"];
__block GTLRServiceTicket *queryTicket;
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1.response.txt" status:200];
// Cancel the ticket when we're notified synchronously during parsing.
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
id observer = [nc addObserverForName:@"kGTLRServiceTicketParsingStartedForTestNotification"
object:nil
queue:nil
usingBlock:^(NSNotification * _Nonnull note) {
XCTAssertNotNil(queryTicket);
XCTAssertEqualObjects(queryTicket, note.object);
[queryTicket cancelTicket];
[parseExp fulfill];
}];
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(mimeType,id,kind,name)";
query.completionBlock = ^(GTLRServiceTicket *callbackTicket, GTLRObject *callbackObj,
NSError *callbackError) {
XCTFail(@"Cancel should skip callbacks");
};
query.executionParameters.retryBlock = ^(GTLRServiceTicket *ticket,
BOOL suggestedWillRetry,
NSError *error) {
XCTFail(@"No retry expected.");
return NO;
};
queryTicket = [service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
XCTFail(@"Cancel should skip callbacks");
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssertFalse(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
[nc removeObserver:observer];
}
- (void)testService_Delete {
// Successful deletion.
//
// Response is empty with status 204 "No content"
[self expectTicketNotifications];
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:nil status:204];
GTLRTestingSvcQuery_FilesDelete *query =
[GTLRTestingSvcQuery_FilesDelete queryWithFileId:@"1234"];
query.fields = @"kind,nextPageToken";
query.requestID = @"gtlr_1234";
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRObject *object, NSError *error) {
// Verify that there was no error, but no object returned.
XCTAssertNil(object);
XCTAssertNil(error);
[queryFinished fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// File ID should be the tail of the query URL.
NSURLRequest *fetcherRequest = queryTicket.objectFetcher.request;
XCTAssertEqualObjects(fetcherRequest.URL.lastPathComponent, @"1234");
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)performBodyObjectTestWithQuery:(GTLRTestingSvcQuery_PermissionsCreate *)query
expectedBody:(NSDictionary *)expectedJSONBody {
XCTestExpectation *fetchExp = [self expectationWithDescription:@"Fetched"];
XCTestExpectation *executeCompletionExp = [self expectationWithDescription:@"Execute block"];
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock = ^(GTMSessionFetcher *fetcherToTest,
GTMSessionFetcherTestResponse testResponse) {
NSData *fetchBody = fetcherToTest.bodyData;
NSJSONSerialization *fetchJSONBody = nil;
if (fetchBody) {
NSError *parseError;
fetchJSONBody = [NSJSONSerialization JSONObjectWithData:fetchBody
options:NSJSONReadingMutableContainers
error:&parseError];
XCTAssertNil(parseError);
}
XCTAssertEqualObjects(fetchJSONBody, expectedJSONBody);
NSHTTPURLResponse *response = QueryResponseWithURL(fetcherToTest.request.URL,
200,
@"text/plain");
testResponse(response, [NSData data], nil);
[fetchExp fulfill];
};
GTLRServiceTicket *queryTicket = [service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_File *uploadedFile,
NSError *error) {
[executeCompletionExp fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_BodyObject {
GTLRTestingSvc_Permission *permissionObj = [GTLRTestingSvc_Permission object];
permissionObj.displayName = @"Rex the Tyrannosaurus";
permissionObj.type = @"reader";
GTLRTestingSvcQuery_PermissionsCreate *query =
[GTLRTestingSvcQuery_PermissionsCreate queryWithObject:permissionObj
fileId:@"12345"];
NSDictionary *expectedJSON = @{ @"type" : @"reader", @"displayName" : @"Rex the Tyrannosaurus" };
[self performBodyObjectTestWithQuery:query
expectedBody:expectedJSON];
}
- (void)testService_EmptyBodyObject {
// Verify that a non-nil but empty body object creates an empty JSON request body.
GTLRTestingSvc_Permission *emptyPermissionObj = [GTLRTestingSvc_Permission object];
GTLRTestingSvcQuery_PermissionsCreate *query =
[GTLRTestingSvcQuery_PermissionsCreate queryWithObject:emptyPermissionObj
fileId:@"12345"];
NSDictionary *expectedJSON = [NSDictionary dictionary];
[self performBodyObjectTestWithQuery:query
expectedBody:expectedJSON];
}
- (void)testService_NilBodyObject {
// Verify that a nil body object creates a nil request body.
// We need to force a query to have a nil body object, so create a query with a non-nil
// body object and then remove it.
GTLRTestingSvc_Permission *emptyPermissionObj = [GTLRTestingSvc_Permission object];
GTLRTestingSvcQuery_PermissionsCreate *query =
[GTLRTestingSvcQuery_PermissionsCreate queryWithObject:emptyPermissionObj
fileId:@"12345"];
query.bodyObject = nil;
[self performBodyObjectTestWithQuery:query
expectedBody:nil];
}
- (void)testService_SingleQuery_NoFields {
// Successful request with valid authorization.
//
// Response is file Drive1Empty.response
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1Empty.response.txt" status:200];
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"";
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
// Verify a bare object with no JSON was returned.
XCTAssertEqualObjects([object class], [GTLRTestingSvc_FileList class]);
XCTAssertNil(object.JSON);
XCTAssertNil(error);
[queryFinished fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_MediaQuery_UsingMediaRedirect {
[self performMediaQueryTestUsingMediaService:NO];
}
- (void)testService_MediaQuery_UsingMediaService {
[self performMediaQueryTestUsingMediaService:YES];
}
- (void)performMediaQueryTestUsingMediaService:(BOOL)useMediaService {
// Successful request with data object response.
[self expectTicketAndParsingNotifications];
NSData *expectedData = [self tempDataForUploading];
NSString *expectedContentType = @"text/plain";
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock = ^(GTMSessionFetcher *fetcherToTest,
GTMSessionFetcherTestResponse testResponse) {
NSHTTPURLResponse *response = QueryResponseWithURL(fetcherToTest.request.URL,
200, expectedContentType);
NSData *responseData = expectedData;
testResponse(response, responseData, nil);
};
GTLRTestingSvcQuery_FilesGet *query = [GTLRTestingSvcQuery_FilesGet queryForMediaWithFileId:@"abcde"];
query.useMediaDownloadService = useMediaService;
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRDataObject *object, NSError *error) {
NSURL *fetcherURL = callbackTicket.fetchRequest.URL;
if (useMediaService) {
XCTAssertEqualObjects(fetcherURL.path, @"/download/drive/v3/files/abcde");
} else {
XCTAssertEqualObjects(fetcherURL.path, @"/drive/v3/files/abcde");
}
XCTAssertEqualObjects(QueryValueForURLItem(fetcherURL, @"alt"), @"media");
XCTAssertEqualObjects([object class], [GTLRDataObject class]);
XCTAssertNil(error);
XCTAssertEqualObjects(object.data, expectedData);
XCTAssertEqualObjects(object.contentType, expectedContentType);
XCTAssert([NSThread isMainThread]);
[queryFinished fulfill];
}];
XCTAssertFalse(queryTicket.hasCalledCallback);
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_BatchQuery {
[self performBatchQueryTestSkippingAuthorization:NO];
}
- (void)testService_BatchQuery_SkipAuth {
[self performBatchQueryTestSkippingAuthorization:YES];
}
- (void)performBatchQueryTestSkippingAuthorization:(BOOL)shouldSkipAuthorization {
// Mixed failure and success batch request with valid authorization.
//
// Response is file Drive1Batch1.response.txt
//
// WARNING: Batch response file must be saved with CRLF line endings and checked in as a binary
// file to be valid multipart MIME. (Edit it with TextWrangler/BBEdit set to "Windows (CRLF)"
// for the document.)
[self expectTicketAndParsingNotifications];
[self setCountingUIAppWithExpirations:NO];
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1Batch.response.txt"
status:200
numberOfStatusErrors:0
multipartBoundary:@"batch_3ajN40YpXZQ_ABf5ww_gxyg"];
XCTestExpectation *permissionExp = [self expectationWithDescription:@"permissionCallback"];
XCTestExpectation *childExp = [self expectationWithDescription:@"childCallback"];
XCTestExpectation *parentsExp = [self expectationWithDescription:@"parentsCallback"];
XCTestExpectation *batchFinished = [self expectationWithDescription:@"batchFinished"];
GTLRTestingSvcQuery_PermissionsList *permissionQuery =
[GTLRTestingSvcQuery_PermissionsList queryWithFileId:@"badID"];
permissionQuery.requestID = @"gtlr_4";
permissionQuery.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_PermissionList *obj,
NSError *permissionError) {
XCTAssertNil(obj);
XCTAssertEqual(permissionError.code, 404);
GTLRErrorObject *errorObject = permissionError.userInfo[kGTLRStructuredErrorKey];
XCTAssertEqual(errorObject.code.intValue, 404);
XCTAssert([NSThread isMainThread]);
[permissionExp fulfill];
};
GTLRTestingSvcQuery_FilesList *childQuery = [GTLRTestingSvcQuery_FilesList query];
childQuery.q = [NSString stringWithFormat:@"'0B7svZDDwtKrhS2FDS2JZclU1U0E' in parents"];
childQuery.requestID = @"gtlr_5";
childQuery.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_FileList *fileList,
NSError *childError) {
XCTAssertEqualObjects([fileList class], [GTLRTestingSvc_FileList class]);
XCTAssertEqualObjects(fileList.kind, @"drive#fileList");
XCTAssertNil(childError);
XCTAssert([NSThread isMainThread]);
[childExp fulfill];
};
GTLRTestingSvcQuery_FilesGet *parentsQuery =
[GTLRTestingSvcQuery_FilesGet queryWithFileId:@"0B7svZDDwtKrhS2FDS2JZclU1U0E"];
parentsQuery.fields = @"parents";
parentsQuery.requestID = @"gtlr_6";
parentsQuery.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_File *file,
NSError *parentsError) {
XCTAssertEqualObjects([file class], [GTLRTestingSvc_File class]);
XCTAssertEqualObjects(file.parents, @[ @"0ALsvZDDwtKrhUk9PVA" ]);
XCTAssertNil(parentsError);
XCTAssert([NSThread isMainThread]);
[parentsExp fulfill];
};
parentsQuery.additionalHTTPHeaders = @{ @"Tiger" : @"Siberian" };
// Combine the separate queries into one batch.
GTLRBatchQuery *batchQuery = [GTLRBatchQuery batchQuery];
[batchQuery addQuery:permissionQuery];
[batchQuery addQuery:childQuery];
[batchQuery addQuery:parentsQuery];
// Test adding http headers to the query
batchQuery.additionalHTTPHeaders = @{ @"X-Feline": @"Fluffy",
@"X-Canine": @"Spot" };
batchQuery.executionParameters.retryBlock = ^(GTLRServiceTicket *ticket,
BOOL suggestedWillRetry,
NSError *error) {
XCTFail(@"No retry expected.");
return NO;
};
XCTestExpectation *uploadedSomeBytes = [self expectationWithDescription:@"uploadedSomeBytes"];
batchQuery.executionParameters.uploadProgressBlock = ^(GTLRServiceTicket *ticket,
unsigned long long numberOfBytesRead,
unsigned long long dataLength) {
if (numberOfBytesRead == dataLength) {
[uploadedSomeBytes fulfill];
}
XCTAssert([NSThread isMainThread]);
};
batchQuery.shouldSkipAuthorization = shouldSkipAuthorization;
__block GTLRServiceTicket *queryTicket =
[service executeQuery:batchQuery
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRBatchResult *batchResult, NSError *error) {
// Verify the batch result and each success and failure result.
XCTAssertEqualObjects([batchResult class], [GTLRBatchResult class]);
XCTAssertNil(error);
XCTAssertEqual(batchResult.successes.count, 2U);
XCTAssertEqual(batchResult.failures.count, 1U);
XCTAssertEqual(batchResult.responseHeaders.count, 3U);
GTLRErrorObject *failureError = batchResult.failures[@"gtlr_4"];
XCTAssertEqualObjects([failureError class], [GTLRErrorObject class]);
XCTAssertEqual(failureError.code.intValue, 404);
NSDictionary *responseHeaderGTLR_4 = batchResult.responseHeaders[@"gtlr_4"];
XCTAssertEqualObjects(responseHeaderGTLR_4[@"X-Rejected-Reason"],
@"Failed to remove Excalibur from stone");
GTLRTestingSvc_FileList *fileList = batchResult.successes[@"gtlr_5"];
XCTAssertEqualObjects([fileList class], [GTLRTestingSvc_FileList class]);
XCTAssertEqualObjects(fileList.kind, @"drive#fileList");
NSDictionary *responseHeaderGTLR_5 = batchResult.responseHeaders[@"gtlr_5"];
XCTAssertEqual(((NSNumber *)responseHeaderGTLR_5[@"Retry-After"]).intValue, 300);
GTLRTestingSvc_File *file = batchResult.successes[@"gtlr_6"];
XCTAssertEqualObjects([file class], [GTLRTestingSvc_File class]);
XCTAssertEqualObjects(file.parents, @[ @"0ALsvZDDwtKrhUk9PVA" ]);
// As with a single query, the batch ticket's query should be a copy of the original;
// query execution leaves the original unmolested so the client can modify and reuse it.
GTLRBatchQuery *ticketQuery = callbackTicket.executingQuery;
XCTAssertNotEqual(ticketQuery, batchQuery);
// Since the batch has a copy of the original query, all request IDs of both batch
// queries should be the same.
XCTAssertEqualObjects([ticketQuery.queries valueForKey:@"requestID"],
[batchQuery.queries valueForKey:@"requestID"]);
NSDictionary *responseHeaderGTLR_6 = batchResult.responseHeaders[@"gtlr_6"];
XCTAssertNil(responseHeaderGTLR_6[@"X-Rejected-Reason"]);
XCTAssertNil(responseHeaderGTLR_6[@"Retry-After"]);
XCTAssert([NSThread isMainThread]);
[batchFinished fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// The request is to a fixed URL.
NSURLRequest *fetcherRequest = queryTicket.objectFetcher.request;
XCTAssertEqualObjects(fetcherRequest.URL.absoluteString,
@"https://www.googleapis.com/batch");
// Test additionalHTTPHeaders.
XCTAssertEqualObjects([fetcherRequest valueForHTTPHeaderField:@"X-Feline"], @"Fluffy");
XCTAssertEqualObjects([fetcherRequest valueForHTTPHeaderField:@"X-Canine"], @"Spot");
// Verify the payload was the expected multipart MIME.
NSData *requestBody = fetcherRequest.HTTPBody;
NSString *requestBoundary = ((GTLRBatchQuery *)queryTicket.executingQuery).boundary;
NSArray *requestMIMEParts = [GTMMIMEDocument MIMEPartsWithBoundary:requestBoundary
data:requestBody];
XCTAssertEqual(requestMIMEParts.count, 3U);
GTMMIMEDocumentPart *part0 = requestMIMEParts[0];
NSString *body0Str = [[NSString alloc] initWithData:part0.body encoding:NSUTF8StringEncoding];
XCTAssertEqualObjects(part0.headers[@"Content-ID"], @"gtlr_4");
XCTAssert([body0Str hasPrefix:@"GET /drive/v3/files/badID/permissions"],
@"%@", body0Str);
// Verify the additional HTTP headers were added to the third query.
//
// We'll parse the request MIME part as a response part since request parts also have outer
// and inner headers, similar to response parts.
GTMMIMEDocumentPart *part2 = requestMIMEParts[2];
XCTAssertEqualObjects(part2.headers[@"Content-ID"], @"gtlr_6");
GTLRBatchResponsePart *requestPart = [service responsePartWithMIMEPart:part2];
NSDictionary *requestPartHeaders = requestPart.headers;
XCTAssertEqualObjects(requestPartHeaders[@"Tiger"], @"Siberian");
NSString *part2BodyStr = [[NSString alloc] initWithData:part2.body encoding:NSUTF8StringEncoding];
NSString *expectedBodyGet = @"GET /drive/v3/files/0B7svZDDwtKrhS2FDS2JZclU1U0E?fields=parents";
XCTAssert([part2BodyStr hasPrefix:expectedBodyGet], @"%@", part2BodyStr);
// Verify authorization.
id<GTMFetcherAuthorizationProtocol> authorizer = service.authorizer;
XCTAssertNotNil(authorizer);
bool didAuthorize = [authorizer isAuthorizedRequest:fetcherRequest];
XCTAssertEqual(didAuthorize, !shouldSkipAuthorization, @"%@", fetcherRequest.allHTTPHeaderFields);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
// We expect two calls to beginBackgroundTask and endBackgroundTask, one each for
// fetcher and GTLRService.
[self verifyCountingUIAppWithExpectedCount:2
expectedExpirations:0];
}
- (GTMSessionFetcherTestBlock)fetcherTestBlockForBatchPaging {
__block int pageCounter = 0;
GTMSessionFetcherTestBlock testBlock = ^(GTMSessionFetcher *fetcherToTest,
GTMSessionFetcherTestResponse testResponse) {
// Not currently done here (like it is above in the batch test) but to be thorough,
// we could inspect inside the batch request MIME parts to ensure any additional headers
// and query parameters were passed through.
++pageCounter;
NSString *contentType = @"multipart/mixed; boundary=batch_3ajN40YpXZQ_ABf5ww_gxyg";
NSHTTPURLResponse *response = QueryResponseWithURL(fetcherToTest.request.URL,
200, contentType);
NSString *fileName = [NSString stringWithFormat:@"Drive1BatchPaging%d.response.txt",
pageCounter];
NSData *responseData = [[self class] dataForTestFileName:fileName];;
XCTAssertNotNil(responseData, @"%@", fileName);
testResponse(response, responseData, nil);
};
return testBlock;
}
- (void)testService_BatchQuery_Paging {
// WARNING: Batch response file must be saved with CRLF line endings and checked in as a binary
// file to be valid multipart MIME.
[self expectTicketAndParsingNotifications];
[self setCountingUIAppWithExpirations:NO];
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock = [self fetcherTestBlockForBatchPaging];
XCTestExpectation *query1Exp = [self expectationWithDescription:@"query1Callback"];
XCTestExpectation *query2Exp = [self expectationWithDescription:@"query2Callback"];
XCTestExpectation *query3Exp = [self expectationWithDescription:@"query3Callback"];
XCTestExpectation *query4Exp = [self expectationWithDescription:@"query4Callback"];
XCTestExpectation *batchFinished = [self expectationWithDescription:@"batchFinished"];
// First query succeeds in three pages.
// Second query fails on first page.
// Third query succeeds on first page but fails on second.
// Fourth query succeeds on first page.
GTLRTestingSvcQuery_FilesList *childQuery1 = [GTLRTestingSvcQuery_FilesList query];
childQuery1.q = [NSString stringWithFormat:@"'0B7svZDDwtKrhS2FDS2JZclU1U0E' in parents"];
childQuery1.requestID = @"gtlr_5";
childQuery1.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_FileList *fileList,
NSError *childError) {
XCTAssertEqualObjects([fileList class], [GTLRTestingSvc_FileList class]);
XCTAssertEqualObjects(fileList.kind, @"drive#fileList");
XCTAssertEqual(fileList.files.count, (NSUInteger)8);
XCTAssertEqualObjects(fileList.files[7].name, @"Mouse Types.pdf");
XCTAssertEqualObjects(fileList.files[7].identifier, @"0B7svZDDwtKr1BVVk3N1k");
XCTAssertNil(childError);
XCTAssert([NSThread isMainThread]);
[query1Exp fulfill];
};
GTLRTestingSvcQuery_FilesList *childQuery2 = [GTLRTestingSvcQuery_FilesList query];
childQuery2.q = [NSString stringWithFormat:@"'0B7svZABCtKrhS2FDS2JZclU1U0E' in parents"];
childQuery2.requestID = @"gtlr_6";
childQuery2.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_FileList *fileList,
NSError *childError) {
XCTAssertNil(fileList);
XCTAssertEqual(childError.code, 404);
XCTAssert([NSThread isMainThread]);
[query2Exp fulfill];
};
GTLRTestingSvcQuery_FilesList *childQuery3 = [GTLRTestingSvcQuery_FilesList query];
childQuery3.q = [NSString stringWithFormat:@"'0B7svZDEFtKrhS2FDS2JZclU1U0E' in parents"];
childQuery3.requestID = @"gtlr_7";
childQuery3.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_FileList *fileList,
NSError *childError) {
XCTAssertNil(fileList);
XCTAssertEqual(childError.code, 400);
XCTAssert([NSThread isMainThread]);
[query3Exp fulfill];
};
GTLRTestingSvcQuery_FilesGet *parentsQuery =
[GTLRTestingSvcQuery_FilesGet queryWithFileId:@"0B7svZDDwtKrhS2FDS2JZclU1U0E"];
parentsQuery.fields = @"parents";
parentsQuery.requestID = @"gtlr_8";
parentsQuery.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_File *file,
NSError *parentsError) {
XCTAssertEqualObjects([file class], [GTLRTestingSvc_File class]);
XCTAssertEqualObjects(file.parents, @[ @"0ALsvZDDwtKrhUk9PVA" ]);
XCTAssertNil(parentsError);
XCTAssert([NSThread isMainThread]);
[query4Exp fulfill];
};
// Combine the separate queries into one batch.
NSArray *queries = @[ childQuery1, childQuery2, childQuery3, parentsQuery ];
GTLRBatchQuery *batchQuery = [GTLRBatchQuery batchQueryWithQueries:queries];
batchQuery.executionParameters.shouldFetchNextPages = @YES;
GTLRServiceTicket *queryTicket =
[service executeQuery:batchQuery
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRBatchResult *batchResult, NSError *error) {
// Verify the batch result and each success and failure result.
XCTAssertEqualObjects([batchResult class], [GTLRBatchResult class]);
XCTAssertNil(error);
XCTAssertEqual(batchResult.successes.count, (NSUInteger)2);
XCTAssertEqual(batchResult.failures.count, (NSUInteger)2);
XCTAssertEqual(batchResult.responseHeaders.count, (NSUInteger)4);
XCTAssertEqual(callbackTicket.pagesFetchedCounter, 3U);
// The first query, gtlr_5, requires all three pages to fetch the full file list.
GTLRTestingSvc_FileList *fileList = batchResult.successes[@"gtlr_5"];
XCTAssertEqualObjects([fileList class], [GTLRTestingSvc_FileList class]);
XCTAssertEqualObjects(fileList.kind, @"drive#fileList");
XCTAssertEqual(fileList.files.count, (NSUInteger)8);
GTLRErrorObject *errorObj = batchResult.failures[@"gtlr_6"];
errorObj.code = @404;
errorObj = batchResult.failures[@"gtlr_7"];
errorObj.code = @400;
GTLRTestingSvc_File *file = batchResult.successes[@"gtlr_8"];
XCTAssertEqualObjects([file class], [GTLRTestingSvc_File class]);
XCTAssertEqualObjects(file.parents, @[ @"0ALsvZDDwtKrhUk9PVA" ]);
// As with a single query, the batch ticket's query should be a copy of the original;
// query execution leaves the original unmolested so the client can modify and reuse it.
GTLRBatchQuery *ticketQuery = callbackTicket.executingQuery;
XCTAssertNotEqual(ticketQuery, batchQuery);
// Since the batch has a copy of the original query, all request IDs of both batch
// queries should be the same.
XCTAssertEqualObjects([ticketQuery.queries valueForKey:@"requestID"],
[batchQuery.queries valueForKey:@"requestID"]);
XCTAssert([NSThread isMainThread]);
[batchFinished fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
// We expect six calls to beginBackgroundTask and endBackgroundTask, one each for
// fetcher and GTLRService for each page.
[self verifyCountingUIAppWithExpectedCount:6
expectedExpirations:0];
}
- (void)testService_BatchQuery_CorruptResponse {
// Mixed failure and success batch request with on corrupt JSON resonse.
//
// Response is file Drive1BatchCorrupt.response.txt
//
// WARNING: Batch response file must be saved with CRLF line endings and checked in as a binary
// file to be valid multipart MIME. (Edit it with TextWrangler/BBEdit set to "Windows (CRLF)"
// for the document.)
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1BatchCorrupt.response.txt"
status:200
numberOfStatusErrors:0
multipartBoundary:@"batch_3ajN40YpXZQ_ABf5ww_gxyg"];
XCTestExpectation *permissionExp = [self expectationWithDescription:@"permissionCallback"];
XCTestExpectation *childExp = [self expectationWithDescription:@"childCallback"];
XCTestExpectation *parentsExp = [self expectationWithDescription:@"parentsCallback"];
XCTestExpectation *batchFinished = [self expectationWithDescription:@"batchFinished"];
GTLRTestingSvcQuery_PermissionsList *permissionQuery =
[GTLRTestingSvcQuery_PermissionsList queryWithFileId:@"badID"];
permissionQuery.requestID = @"gtlr_4";
permissionQuery.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_PermissionList *obj,
NSError *permissionError) {
XCTAssertNil(obj);
XCTAssertEqual(permissionError.code, 404);
GTLRErrorObject *errorObject = permissionError.userInfo[kGTLRStructuredErrorKey];
XCTAssertEqual(errorObject.code.intValue, 404);
[permissionExp fulfill];
};
// The second part has corrupt JSON.
GTLRTestingSvcQuery_FilesList *childQuery = [GTLRTestingSvcQuery_FilesList query];
childQuery.q = [NSString stringWithFormat:@"'0B7svZDDwtKrhS2FDS2JZclU1U0E' in parents"];
childQuery.requestID = @"gtlr_5";
childQuery.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_FileList *fileList,
NSError *childError) {
XCTAssertNil(fileList);
XCTAssertEqual(childError.code, NSPropertyListReadCorruptError);
[childExp fulfill];
};
GTLRTestingSvcQuery_FilesGet *parentsQuery =
[GTLRTestingSvcQuery_FilesGet queryWithFileId:@"0B7svZDDwtKrhS2FDS2JZclU1U0E"];
parentsQuery.fields = @"parents";
parentsQuery.requestID = @"gtlr_6";
parentsQuery.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_File *file,
NSError *parentsError) {
XCTAssertEqualObjects([file class], [GTLRTestingSvc_File class]);
XCTAssertEqualObjects(file.parents, @[ @"0ALsvZDDwtKrhUk9PVA" ]);
XCTAssertNil(parentsError);
[parentsExp fulfill];
};
// Combine the separate queries into one batch.
GTLRBatchQuery *batchQuery = [GTLRBatchQuery batchQuery];
[batchQuery addQuery:permissionQuery];
[batchQuery addQuery:childQuery];
[batchQuery addQuery:parentsQuery];
batchQuery.executionParameters.retryBlock = ^(GTLRServiceTicket *ticket,
BOOL suggestedWillRetry,
NSError *error) {
XCTFail(@"No retry expected.");
return NO;
};
__block GTLRServiceTicket *queryTicket =
[service executeQuery:batchQuery
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRBatchResult *batchResult, NSError *error) {
// Verify the batch result and each success and failure result.
XCTAssertEqualObjects([batchResult class], [GTLRBatchResult class]);
XCTAssertNil(error);
XCTAssertEqual(batchResult.successes.count, 1U);
XCTAssertEqual(batchResult.failures.count, 2U);
XCTAssertEqual(batchResult.responseHeaders.count, 3U);
GTLRErrorObject *failureError = batchResult.failures[@"gtlr_4"];
XCTAssertEqualObjects([failureError class], [GTLRErrorObject class]);
XCTAssertEqual(failureError.code.intValue, 404);
// This object has corrupt JSON.
GTLRErrorObject *fileListError = batchResult.failures[@"gtlr_5"];
XCTAssertEqual(fileListError.code.intValue, NSPropertyListReadCorruptError);
GTLRTestingSvc_File *file = batchResult.successes[@"gtlr_6"];
XCTAssertEqualObjects([file class], [GTLRTestingSvc_File class]);
XCTAssertEqualObjects(file.parents, @[ @"0ALsvZDDwtKrhUk9PVA" ]);
// As with a single query, the batch ticket's query should be a copy of the original;
// query execution leaves the original unmolested so the client can modify and reuse it.
GTLRBatchQuery *ticketQuery = callbackTicket.executingQuery;
XCTAssertNotEqual(ticketQuery, batchQuery);
// Since the batch has a copy of the original query, all request IDs of both batch
// queries should be the same.
XCTAssertEqualObjects([ticketQuery.queries valueForKey:@"requestID"],
[batchQuery.queries valueForKey:@"requestID"]);
[batchFinished fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_BatchQuery_CallbackQueue {
// Batch with callbacks on a specified GCD queue.
//
// Response is file Drive1Batch1.response.txt
dispatch_queue_t myCallbackQueue = dispatch_queue_create("myCallbackQueue",
DISPATCH_QUEUE_SERIAL);
GTLRService *service = [self driveServiceForTest];
service.callbackQueue = myCallbackQueue;
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1Batch.response.txt"
status:200
numberOfStatusErrors:0
multipartBoundary:@"batch_3ajN40YpXZQ_ABf5ww_gxyg"];
XCTestExpectation *permissionExp = [self expectationWithDescription:@"permissionCallback"];
XCTestExpectation *childExp = [self expectationWithDescription:@"childCallback"];
XCTestExpectation *parentsExp = [self expectationWithDescription:@"parentsCallback"];
XCTestExpectation *batchFinished = [self expectationWithDescription:@"batchFinished"];
GTLRTestingSvcQuery_PermissionsList *permissionQuery =
[GTLRTestingSvcQuery_PermissionsList queryWithFileId:@"badID"];
permissionQuery.requestID = @"gtlr_4";
permissionQuery.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_PermissionList *obj,
NSError *permissionError) {
XCTAssert(IsCurrentQueue(myCallbackQueue));
[permissionExp fulfill];
};
GTLRTestingSvcQuery_FilesList *childQuery = [GTLRTestingSvcQuery_FilesList query];
childQuery.q = [NSString stringWithFormat:@"'0B7svZDDwtKrhS2FDS2JZclU1U0E' in parents"];
childQuery.requestID = @"gtlr_5";
childQuery.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_FileList *fileList,
NSError *childError) {
XCTAssert(IsCurrentQueue(myCallbackQueue));
[childExp fulfill];
};
GTLRTestingSvcQuery_FilesGet *parentsQuery =
[GTLRTestingSvcQuery_FilesGet queryWithFileId:@"0B7svZDDwtKrhS2FDS2JZclU1U0E"];
parentsQuery.fields = @"parents";
parentsQuery.requestID = @"gtlr_6";
parentsQuery.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_File *file,
NSError *parentsError) {
XCTAssert(IsCurrentQueue(myCallbackQueue));
[parentsExp fulfill];
};
// Combine the separate queries into one batch.
GTLRBatchQuery *batchQuery = [GTLRBatchQuery batchQuery];
[batchQuery addQuery:permissionQuery];
[batchQuery addQuery:childQuery];
[batchQuery addQuery:parentsQuery];
// Test adding http headers to the query
batchQuery.additionalHTTPHeaders = @{ @"X-Feline": @"Fluffy",
@"X-Canine": @"Spot" };
batchQuery.executionParameters.retryBlock = ^(GTLRServiceTicket *ticket,
BOOL suggestedWillRetry,
NSError *error) {
XCTFail(@"No retry expected.");
return NO;
};
XCTestExpectation *uploadedSomeBytes = [self expectationWithDescription:@"uploadedSomeBytes"];
batchQuery.executionParameters.uploadProgressBlock = ^(GTLRServiceTicket *ticket,
unsigned long long numberOfBytesRead,
unsigned long long dataLength) {
if (numberOfBytesRead == dataLength) {
[uploadedSomeBytes fulfill];
}
XCTAssert(IsCurrentQueue(myCallbackQueue));
};
__block GTLRServiceTicket *queryTicket =
[service executeQuery:batchQuery
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRBatchResult *batchResult, NSError *error) {
XCTAssert(IsCurrentQueue(myCallbackQueue));
[batchFinished fulfill];
}];
// Changing the callback queue doesn't affect tickets already issued.
service.callbackQueue = dispatch_get_main_queue();
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_SingleQuery_Surrogates {
// Successful request with surrogates.
//
// Response is file Drive1.response.txt
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1.response.txt" status:200];
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(mimeType,id,kind)";
NSDictionary *serviceSurrogates = @{
(id<NSCopying>)[GTLRTestingSvc_File class] : [GTLRTestingSvc_File_Surrogate class],
(id<NSCopying>)[GTLRTestingSvc_FileList class] : [GTLRTestingSvc_FileList_Surrogate class]
};
[service setSurrogates:serviceSurrogates];
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
// Override a resolver in the query's executionParameters.
NSDictionary *querySurrogates = @{
(id<NSCopying>)[GTLRTestingSvc_File class] : [GTLRTestingSvc_File_Surrogate class],
(id<NSCopying>)[GTLRTestingSvc_FileList class] : [GTLRTestingSvc_FileList_Surrogate2 class]
};
NSDictionary *kindMap = [[service class] kindStringToClassMap];
query.executionParameters.objectClassResolver =
[GTLRObjectClassResolver resolverWithKindMap:kindMap surrogates:querySurrogates];
__block GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
// Verify the top-level object and one of its items.
XCTAssertEqualObjects([object class], [GTLRTestingSvc_FileList_Surrogate2 class]);
XCTAssertNil(error);
XCTAssertEqualObjects(object.kind, @"drive#fileList");
XCTAssertEqual(object.files.count, 2U, @"%@", object.files);
[queryFinished fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_BatchQuery_Surrogates {
// Mixed failure and success batch request with surrogates.
//
// Response is file Drive1Batch1.response.txt
//
// See warning above about batch file needing CRLF line endings.
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1Batch.response.txt"
status:200
numberOfStatusErrors:0
multipartBoundary:@"batch_3ajN40YpXZQ_ABf5ww_gxyg"];
XCTestExpectation *batchFinished = [self expectationWithDescription:@"batchFinished"];
GTLRTestingSvcQuery_PermissionsList *permissionQuery =
[GTLRTestingSvcQuery_PermissionsList queryWithFileId:@"badID"];
permissionQuery.requestID = @"gtlr_4";
GTLRTestingSvcQuery_FilesList *childQuery = [GTLRTestingSvcQuery_FilesList query];
childQuery.q = [NSString stringWithFormat:@"'0B7svZDDwtKrhS2FDS2JZclU1U0E' in parents"];
childQuery.requestID = @"gtlr_5";
GTLRTestingSvcQuery_FilesGet *parentsQuery =
[GTLRTestingSvcQuery_FilesGet queryWithFileId:@"0B7svZDDwtKrhS2FDS2JZclU1U0E"];
parentsQuery.fields = @"parents";
parentsQuery.requestID = @"gtlr_6";
// Combine the separate queries into one batch.
GTLRBatchQuery *batchQuery = [GTLRBatchQuery batchQuery];
[batchQuery addQuery:permissionQuery];
[batchQuery addQuery:childQuery];
[batchQuery addQuery:parentsQuery];
NSDictionary *kindMap = [[service class] kindStringToClassMap];
NSDictionary *surrogates = @{
(id<NSCopying>)[GTLRTestingSvc_File class] : [GTLRTestingSvc_File_Surrogate class],
(id<NSCopying>)[GTLRTestingSvc_FileList class] : [GTLRTestingSvc_FileList_Surrogate class]
};
batchQuery.executionParameters.objectClassResolver =
[GTLRObjectClassResolver resolverWithKindMap:kindMap surrogates:surrogates];
__block GTLRServiceTicket *queryTicket =
[service executeQuery:batchQuery
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRBatchResult *batchResult, NSError *error) {
// Verify the batch result and each success and failure result.
XCTAssertEqualObjects([batchResult class], [GTLRBatchResult class]);
XCTAssertNil(error);
XCTAssertEqual(batchResult.successes.count, 2U);
XCTAssertEqual(batchResult.failures.count, 1U);
XCTAssertEqual(batchResult.responseHeaders.count, 3U);
GTLRErrorObject *failureError = batchResult.failures[@"gtlr_4"];
XCTAssertEqualObjects([failureError class], [GTLRErrorObject class]);
XCTAssertEqual(failureError.code.intValue, 404);
GTLRTestingSvc_FileList *fileList = batchResult.successes[@"gtlr_5"];
XCTAssertEqualObjects([fileList class], [GTLRTestingSvc_FileList_Surrogate class]);
XCTAssertEqualObjects(fileList.kind, @"drive#fileList");
GTLRTestingSvc_File *file = batchResult.successes[@"gtlr_6"];
XCTAssertEqualObjects([file class], [GTLRTestingSvc_File_Surrogate class]);
XCTAssertEqualObjects(file.parents, @[ @"0ALsvZDDwtKrhUk9PVA" ]);
// As with a single query, the batch ticket's query should be a copy of the original;
// query execution leaves the original unmolested so the client can modify and reuse it.
GTLRBatchQuery *ticketQuery = callbackTicket.executingQuery;
XCTAssertNotEqual(ticketQuery, batchQuery);
// Since the batch has a copy of the original query, all request IDs of both batch
// queries should be the same.
XCTAssertEqualObjects([ticketQuery.queries valueForKey:@"requestID"],
[batchQuery.queries valueForKey:@"requestID"]);
[batchFinished fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_SingleQuery_RetrySucceeding {
// Successful request with retry.
//
// Response is file Drive1.response.txt
GTLRService *service = [self driveServiceForTest];
[self setCountingUIAppWithExpirations:NO];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1.response.txt"
status:456
numberOfStatusErrors:1
multipartBoundary:nil];
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(mimeType,id,kind)";
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
XCTestExpectation *retryBlockExp = [self expectationWithDescription:@"retryBlock"];
service.retryBlock = ^(GTLRServiceTicket *ticket,
BOOL suggestedWillRetry,
NSError *error) {
XCTAssertEqual(error.code, 456);
XCTAssertEqual(suggestedWillRetry, NO);
XCTAssert([NSThread isMainThread]);
[retryBlockExp fulfill];
return YES;
};
service.retryEnabled = YES;
GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
// Verify the top-level object and one of its items.
XCTAssertEqualObjects([object class], [GTLRTestingSvc_FileList class]);
XCTAssertNil(error);
XCTAssertEqualObjects(object.kind, @"drive#fileList");
XCTAssertEqual(object.files.count, 2U, @"%@", object.files);
[queryFinished fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
// We expect three calls to beginBackgroundTask and endBackgroundTask, two for
// fetchers and one for GTLRService.
[self verifyCountingUIAppWithExpectedCount:3
expectedExpirations:0];
}
- (void)testService_SingleQuery_RetryFailing {
// Failed request with a retry attempt.
//
// Response is file Drive1.response.txt
GTLRService *service = [self driveServiceForTest];
[self setCountingUIAppWithExpirations:NO];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:nil
status:456
numberOfStatusErrors:100
multipartBoundary:nil];
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(mimeType,id,kind)";
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
XCTestExpectation *retryBlockExp = [self expectationWithDescription:@"retryBlock"];
service.retryBlock = ^(GTLRServiceTicket *ticket,
BOOL suggestedWillRetry,
NSError *error) {
XCTAssertEqual(error.code, 456);
XCTAssertEqual(suggestedWillRetry, NO);
if (ticket.objectFetcher.retryCount == 0) {
return YES;
}
// Give up after one retry.
[retryBlockExp fulfill];
return NO;
};
service.retryEnabled = YES;
GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
XCTAssertNil(object);
XCTAssertEqual(error.code, 456);
[queryFinished fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
// We expect three calls to beginBackgroundTask and endBackgroundTask, two for
// fetchers and one for GTLRService.
[self verifyCountingUIAppWithExpectedCount:3
expectedExpirations:0];
}
#pragma mark - Lifetime Tests
- (void)testService_SingleQuery_Retry_ObjectLifetimes {
// This test is based on a fallacy, that we can assume objects are dealloc'd at
// a certain point in time,
//
// If this test turns out to be a source of headaches, we can just give up and do
// these checks manually on occasion.
XCTestExpectation *ticketDealloc = [self expectationWithDescription:@"ticketDealloc"];
XCTestExpectation *initialQueryDealloc = [self expectationWithDescription:@"initialQueryDealloc"];
XCTestExpectation *executedQueryDealloc = [self expectationWithDescription:@"execdQueryDealloc"];
XCTestExpectation *resultObjectDealloc = [self expectationWithDescription:@"resultObjectDealloc"];
XCTestExpectation *fetcherDealloc = [self expectationWithDescription:@"fetcherDealloc"];
@autoreleasepool {
// Successful request with one retry.
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock =
[self fetcherTestBlockWithResponseForFileName:@"Drive1.response.txt"
status:456
numberOfStatusErrors:1
multipartBoundary:nil];
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(mimeType,id,kind)";
// Create a retain cycle in the query callback.
id queryHolder = query;
query.completionBlock = ^(GTLRServiceTicket *callbackTicket,
id _Nullable object,
NSError * _Nullable callbackError) {
XCTAssertNotNil(queryHolder);
};
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
XCTestExpectation *retryBlockExp = [self expectationWithDescription:@"retryBlock"];
service.retryBlock = ^(GTLRServiceTicket *ticket,
BOOL suggestedWillRetry,
NSError *error) {
[retryBlockExp fulfill];
return YES;
};
service.retryEnabled = YES;
[GTLRTestLifetimeObject trackLifetimeOfObject:query
expectation:initialQueryDealloc];
__block GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *resultObject, NSError *error) {
// Retain the original query and original ticket in the completion handler
// to try to create a cycle.
XCTAssertNotNil(query);
XCTAssertNotNil(queryTicket);
[GTLRTestLifetimeObject trackLifetimeOfObject:callbackTicket
expectation:ticketDealloc];
[GTLRTestLifetimeObject trackLifetimeOfObject:callbackTicket.executingQuery
expectation:executedQueryDealloc];
[GTLRTestLifetimeObject trackLifetimeOfObject:resultObject
expectation:resultObjectDealloc];
[GTLRTestLifetimeObject trackLifetimeOfObject:callbackTicket.objectFetcher
expectation:fetcherDealloc];
[queryFinished fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
} // @autoreleasepool
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_BatchQuery_Paging_Lifetime {
// Object lifetime expectations.
XCTestExpectation *ticketDealloc = [self expectationWithDescription:@"ticketDealloc"];
XCTestExpectation *initialBatchQueryDealloc = [self expectationWithDescription:@"initialBatchQueryDealloc"];
XCTestExpectation *initialQuery1Dealloc = [self expectationWithDescription:@"initialQuery1Dealloc"];
XCTestExpectation *initialQuery2Dealloc = [self expectationWithDescription:@"initialQuery2Dealloc"];
XCTestExpectation *initialQuery3Dealloc = [self expectationWithDescription:@"initialQuery3Dealloc"];
XCTestExpectation *initialQuery4Dealloc = [self expectationWithDescription:@"initialQuery4Dealloc"];
XCTestExpectation *executedBatchQueryDealloc = [self expectationWithDescription:@"execdBatchQueryDealloc"];
XCTestExpectation *executedQuery1Dealloc = [self expectationWithDescription:@"execdQuery1Dealloc"];
XCTestExpectation *executedQuery2Dealloc = [self expectationWithDescription:@"execdQuery2Dealloc"];
XCTestExpectation *executedQuery3Dealloc = [self expectationWithDescription:@"execdQuery3Dealloc"];
XCTestExpectation *executedQuery4Dealloc = [self expectationWithDescription:@"execdQuery4Dealloc"];
XCTestExpectation *resultObjectDealloc = [self expectationWithDescription:@"resultObjectDealloc"];
XCTestExpectation *fetcherDealloc = [self expectationWithDescription:@"fetcherDealloc"];
@autoreleasepool {
__block GTLRServiceTicket *queryTicket;
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock = [self fetcherTestBlockForBatchPaging];
// Callback expectations.
XCTestExpectation *query1Exp = [self expectationWithDescription:@"query1Callback"];
XCTestExpectation *query2Exp = [self expectationWithDescription:@"query2Callback"];
XCTestExpectation *query3Exp = [self expectationWithDescription:@"query3Callback"];
XCTestExpectation *query4Exp = [self expectationWithDescription:@"query4Callback"];
XCTestExpectation *batchFinished = [self expectationWithDescription:@"batchFinished"];
// First query succeeds in three pages.
// Second query fails on first page.
// Third query succeeds on first page but fails on second.
// Fourth query succeeds on first page.
GTLRTestingSvcQuery_FilesList *childQuery1 = [GTLRTestingSvcQuery_FilesList query];
childQuery1.q = [NSString stringWithFormat:@"'0B7svZDDwtKrhS2FDS2JZclU1U0E' in parents"];
childQuery1.requestID = @"gtlr_5";
childQuery1.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_FileList *fileList,
NSError *childError) {
XCTAssertNotNil(fileList);
XCTAssertNil(childError);
// Force a reference to the original ticket.
XCTAssertNotNil(queryTicket);
[query1Exp fulfill];
};
GTLRTestingSvcQuery_FilesList *childQuery2 = [GTLRTestingSvcQuery_FilesList query];
childQuery2.q = [NSString stringWithFormat:@"'0B7svZABCtKrhS2FDS2JZclU1U0E' in parents"];
childQuery2.requestID = @"gtlr_6";
childQuery2.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_FileList *fileList,
NSError *childError) {
XCTAssertNil(fileList);
XCTAssertNotNil(childError);
[query2Exp fulfill];
};
GTLRTestingSvcQuery_FilesList *childQuery3 = [GTLRTestingSvcQuery_FilesList query];
childQuery3.q = [NSString stringWithFormat:@"'0B7svZDEFtKrhS2FDS2JZclU1U0E' in parents"];
childQuery3.requestID = @"gtlr_7";
childQuery3.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_FileList *fileList,
NSError *childError) {
XCTAssertNil(fileList);
XCTAssertNotNil(childError);
[query3Exp fulfill];
};
GTLRTestingSvcQuery_FilesGet *parentsQuery =
[GTLRTestingSvcQuery_FilesGet queryWithFileId:@"0B7svZDDwtKrhS2FDS2JZclU1U0E"];
parentsQuery.fields = @"parents";
parentsQuery.requestID = @"gtlr_8";
parentsQuery.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_File *file,
NSError *parentsError) {
XCTAssertNotNil(file);
XCTAssertNil(parentsError);
[query4Exp fulfill];
};
[GTLRTestLifetimeObject trackLifetimeOfObject:childQuery1
expectation:initialQuery1Dealloc];
[GTLRTestLifetimeObject trackLifetimeOfObject:childQuery2
expectation:initialQuery2Dealloc];
[GTLRTestLifetimeObject trackLifetimeOfObject:childQuery3
expectation:initialQuery3Dealloc];
[GTLRTestLifetimeObject trackLifetimeOfObject:parentsQuery
expectation:initialQuery4Dealloc];
// Combine the separate queries into one batch.
NSArray *queries = @[ childQuery1, childQuery2, childQuery3, parentsQuery ];
GTLRBatchQuery *batchQuery = [GTLRBatchQuery batchQueryWithQueries:queries];
batchQuery.executionParameters.shouldFetchNextPages = @YES;
[GTLRTestLifetimeObject trackLifetimeOfObject:batchQuery
expectation:initialBatchQueryDealloc];
queryTicket =
[service executeQuery:batchQuery
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRBatchResult *batchResult, NSError *error) {
// Force a reference to the original ticket.
XCTAssertNotNil(queryTicket);
// Verify the batch result and each success and failure result.
XCTAssertEqualObjects([batchResult class], [GTLRBatchResult class]);
XCTAssertNil(error);
XCTAssertEqual(batchResult.successes.count, (NSUInteger)2);
XCTAssertEqual(batchResult.failures.count, (NSUInteger)2);
XCTAssertEqual(callbackTicket.pagesFetchedCounter, 3U);
GTLRTestingSvc_FileList *fileList = batchResult.successes[@"gtlr_5"];
XCTAssertEqual(fileList.files.count, (NSUInteger)8);
[GTLRTestLifetimeObject trackLifetimeOfObject:callbackTicket
expectation:ticketDealloc];
// The ticket has a copy of the original batch query, which has copies of the individual
// queries.
GTLRBatchQuery *ticketQuery = callbackTicket.executingQuery;
[GTLRTestLifetimeObject trackLifetimeOfObject:ticketQuery
expectation:executedBatchQueryDealloc];
[GTLRTestLifetimeObject trackLifetimeOfObject:[ticketQuery queryForRequestID:@"gtlr_5"]
expectation:executedQuery1Dealloc];
[GTLRTestLifetimeObject trackLifetimeOfObject:[ticketQuery queryForRequestID:@"gtlr_6"]
expectation:executedQuery2Dealloc];
[GTLRTestLifetimeObject trackLifetimeOfObject:[ticketQuery queryForRequestID:@"gtlr_7"]
expectation:executedQuery3Dealloc];
[GTLRTestLifetimeObject trackLifetimeOfObject:[ticketQuery queryForRequestID:@"gtlr_8"]
expectation:executedQuery4Dealloc];
[GTLRTestLifetimeObject trackLifetimeOfObject:batchResult
expectation:resultObjectDealloc];
[GTLRTestLifetimeObject trackLifetimeOfObject:callbackTicket.objectFetcher
expectation:fetcherDealloc];
[batchFinished fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
} // @autoreleasepool
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
#pragma mark - Upload Tests
- (NSData *)tempDataForUploading {
NSMutableString *string = [NSMutableString string];
for (int index = 0; index < 10000; index++) {
[string appendFormat:@"%c", 'A' + (index % 26)];
}
return [string dataUsingEncoding:NSUTF8StringEncoding];
}
- (NSURL *)tempFileURLForUploading {
// Write a file that we can test uploading.
NSURL *tempDir = [NSURL fileURLWithPath:NSTemporaryDirectory()];
NSURL *tempFileURL = [tempDir URLByAppendingPathComponent:@"tempFileURLForUploading"
isDirectory:NO];
NSError *writeError;
NSData *data = [self tempDataForUploading];
BOOL didWrite = [data writeToURL:tempFileURL
options:NSDataWritingAtomic
error:&writeError];
XCTAssert(didWrite, @"%@", writeError);
if (didWrite) {
_tempFileURLToDelete = tempFileURL;
}
return tempFileURL;
}
- (void)testService_Upload_FileHandle {
NSURL *fileURL = [self tempFileURLForUploading];
NSError *readingError;
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL:fileURL
error:&readingError];
XCTAssertNotNil(fileHandle, @"%@", readingError);
GTLRUploadParameters *uploadParameters =
[GTLRUploadParameters uploadParametersWithFileHandle:fileHandle
MIMEType:@"text/plain"];
uploadParameters.useBackgroundSession = NO;
[self performServiceUploadTestWithParameters:uploadParameters];
}
- (void)testService_Upload_FileURL {
NSURL *fileURL = [self tempFileURLForUploading];
NSError *fileError;
XCTAssert([fileURL checkResourceIsReachableAndReturnError:&fileError], @"%@", fileError);
GTLRUploadParameters *uploadParameters =
[GTLRUploadParameters uploadParametersWithFileURL:fileURL
MIMEType:@"text/plain"];
uploadParameters.useBackgroundSession = YES;
[self performServiceUploadTestWithParameters:uploadParameters];
}
- (void)testService_Upload_FileURL_WithoutMetadata {
NSURL *fileURL = [self tempFileURLForUploading];
NSError *fileError;
XCTAssert([fileURL checkResourceIsReachableAndReturnError:&fileError], @"%@", fileError);
GTLRUploadParameters *uploadParameters =
[GTLRUploadParameters uploadParametersWithFileURL:fileURL
MIMEType:@"text/plain"];
uploadParameters.shouldSendUploadOnly = YES;
uploadParameters.useBackgroundSession = YES;
[self performServiceUploadTestWithParameters:uploadParameters];
}
- (void)testService_Upload_NSData {
NSData *uploadData = [self tempDataForUploading];
XCTAssert(uploadData.length > 1000);
GTLRUploadParameters *uploadParameters =
[GTLRUploadParameters uploadParametersWithData:uploadData
MIMEType:@"text/plain"];
uploadParameters.useBackgroundSession = NO;
[self performServiceUploadTestWithParameters:uploadParameters];
}
- (void)testService_Upload_NSData_WithoutMetadata {
NSData *uploadData = [self tempDataForUploading];
GTLRUploadParameters *uploadParameters =
[GTLRUploadParameters uploadParametersWithData:uploadData
MIMEType:@"text/plain"];
uploadParameters.shouldSendUploadOnly = YES;
uploadParameters.useBackgroundSession = NO;
[self performServiceUploadTestWithParameters:uploadParameters];
}
- (void)testService_Upload_NSData_SingleRequest {
NSData *uploadData = [self tempDataForUploading];
GTLRUploadParameters *uploadParameters =
[GTLRUploadParameters uploadParametersWithData:uploadData
MIMEType:@"text/plain"];
uploadParameters.shouldUploadWithSingleRequest = YES;
uploadParameters.useBackgroundSession = NO;
[self performServiceUploadTestWithParameters:uploadParameters];
}
- (void)testService_Upload_NSData_SingleRequest_WithoutMetadata {
NSData *uploadData = [self tempDataForUploading];
GTLRUploadParameters *uploadParameters =
[GTLRUploadParameters uploadParametersWithData:uploadData
MIMEType:@"text/plain"];
uploadParameters.shouldUploadWithSingleRequest = YES;
uploadParameters.shouldSendUploadOnly = YES;
uploadParameters.useBackgroundSession = NO;
[self performServiceUploadTestWithParameters:uploadParameters];
}
- (void)performServiceUploadTestWithParameters:(GTLRUploadParameters *)uploadParameters {
[self expectTicketAndParsingNotifications];
XCTestExpectation *executeCompletionExp = [self expectationWithDescription:@"Execute block"];
XCTestExpectation *queryCompletionExp = [self expectationWithDescription:@"Query block"];
XCTestExpectation *progressExp;
if (uploadParameters.shouldUploadWithSingleRequest || !uploadParameters.shouldSendUploadOnly) {
// A request body is present except when doing chunked upload without metadata.
progressExp = [self expectationWithDescription:@"Upload progress"];
}
GTLRTestingSvc_File *newFile = [GTLRTestingSvc_File object];
newFile.name = @"File de Feline";
GTLRService *service = [self driveServiceForTest];
service.fetcherService.testBlock = ^(GTMSessionFetcher *fetcherToTest,
GTMSessionFetcherTestResponse testResponse) {
NSURL *fetchURL = fetcherToTest.request.URL;
// Upload fetcher testBlock doesn't do chunk fetches, so this must be the initial upload fetch,
// without an upload_id in the URL.
NSString *uploadID = QueryValueForURLItem(fetchURL, @"upload_id");
XCTAssertNil(uploadID);
NSError *parseError;
NSData *fetchBody = fetcherToTest.bodyData;
if (uploadParameters.shouldUploadWithSingleRequest) {
if (uploadParameters.shouldSendUploadOnly) {
// Single part, no metadata.
XCTAssertEqualObjects(fetchBody, [self tempDataForUploading]);
} else {
// Multipart.
NSArray <GTMMIMEDocumentPart *>*bodyParts =
[GTMMIMEDocument MIMEPartsWithBoundary:@"END_OF_PART" data:fetchBody];
XCTAssertEqual(bodyParts.count, 2U);
XCTAssertEqualObjects(bodyParts[0].headers[@"Content-Type"],
@"application/json; charset=utf-8");
XCTAssertEqualObjects(bodyParts[1].headers[@"Content-Type"], @"text/plain");
XCTAssertEqualObjects(bodyParts[1].body, [self tempDataForUploading]);
}
} else {
// Chunked, resumable upload.
if (uploadParameters.shouldSendUploadOnly) {
// No metadata, so no initial fetch body.
XCTAssertNil(fetchBody);
} else {
// The initial fetch body is the metadata.
NSDictionary *jsonBody = [NSJSONSerialization JSONObjectWithData:fetchBody
options:0
error:&parseError];
XCTAssertEqualObjects(jsonBody, newFile.JSON);
}
}
// This is the initial request from the client.
uploadID = @"AEnB2Uqzt_QIERV0PsZrB";
NSDictionary *responseHeaders = @{
@"X-Goog-Upload-Status" : @"final",
@"Content-Type" : @"application/json; charset=UTF-8"
};
NSHTTPURLResponse *response =
[[NSHTTPURLResponse alloc] initWithURL:(NSURL * _Nonnull)fetcherToTest.request.URL
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:responseHeaders];
NSDictionary *responseBodyDict = @{
@"mimeType" : @"text/plain",
@"id" : @"0svZDDwtKrhcHh2dmcyZ05MZWc",
@"kind" : @"drive#file",
@"name" : @"abcdefg.txt"
};
NSData *responseData = [NSJSONSerialization dataWithJSONObject:responseBodyDict
options:0
error:NULL];
NSError *responseError = nil;
testResponse(response, responseData, responseError);
};
GTLRTestingSvcQuery_FilesCreate *query =
[GTLRTestingSvcQuery_FilesCreate queryWithObject:newFile
uploadParameters:uploadParameters];
query.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_File *uploadedFile,
NSError *permissionError) {
XCTAssertEqualObjects(uploadedFile.name, @"abcdefg.txt");
XCTAssertEqualObjects(uploadedFile.kind, @"drive#file");
[queryCompletionExp fulfill];
};
query.executionParameters.uploadProgressBlock = ^(GTLRServiceTicket *progressTicket,
unsigned long long numberOfBytesRead,
unsigned long long dataLength) {
if (numberOfBytesRead == dataLength) {
[progressExp fulfill];
}
};
GTLRServiceTicket *queryTicket = [service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_File *uploadedFile,
NSError *error) {
XCTAssertEqualObjects(uploadedFile.name, @"abcdefg.txt");
XCTAssertEqualObjects(uploadedFile.kind, @"drive#file");
[executeCompletionExp fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
#pragma mark - testBlock Tests
- (GTLRTestingSvc_FileList *)fileListObjectForTest {
GTLRTestingSvc_File *item0 = [GTLRTestingSvc_File object];
item0.kind = @"drive#file";
item0.name = @"frogfile";
GTLRTestingSvc_File *item1 = [GTLRTestingSvc_File object];
item1.kind = @"drive#file";
item1.name = @"possumfile";
GTLRTestingSvc_FileList *object = [GTLRTestingSvc_FileList object];
object.kind = @"drive#fileList";
object.files = @[ item0, item1 ];
return object;
}
- (void)testService_MockService_Succeeding {
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(id,kind,name)";
GTLRTestingSvc_FileList *fileObj = [self fileListObjectForTest];
GTLRService *service = [GTLRService mockServiceWithFakedObject:fileObj
fakedError:nil];
XCTestExpectation *queryFinishedExp = [self expectationWithDescription:@"queryFinished"];
GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
// Verify the top-level object and one of its items.
XCTAssertEqualObjects([object class], [GTLRTestingSvc_FileList class]);
XCTAssertNil(error);
XCTAssertEqualObjects(object.kind, @"drive#fileList");
XCTAssertEqual(object.files.count, 2U, @"%@", object.files);
[queryFinishedExp fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_MockService_Failing {
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(id,kind,name)";
NSError *expectedError = [NSError errorWithDomain:NSURLErrorDomain
code:NSURLErrorResourceUnavailable
userInfo:nil];
GTLRService *service = [GTLRService mockServiceWithFakedObject:nil
fakedError:expectedError];
XCTestExpectation *queryFinishedExp = [self expectationWithDescription:@"queryFinished"];
GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
XCTAssertNil(object);
XCTAssertEqual(error.code, NSURLErrorResourceUnavailable);
[queryFinishedExp fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_testBlock_Succeeding {
// No need for a fetcher testBlock.
[self expectTicketAndParsingNotifications];
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(id,kind,name)";
query.requestID = @"gtlr_1234";
GTLRService *service = [self driveServiceForTest];
service.rootURLString = @"https://example.invalid/";
service.testBlock = ^(GTLRServiceTicket *ticket, GTLRServiceTestResponse testResponse) {
XCTAssertEqualObjects(((GTLRQuery *)ticket.originalQuery).requestID, query.requestID);
XCTAssertEqualObjects(((GTLRQuery *)ticket.executingQuery).requestID, query.requestID);
XCTAssertEqualObjects(ticket.fetchRequest.URL.absoluteString,
@"https://example.invalid/drive/v3/files?"
@"fields=kind%2CnextPageToken%2Cfiles%28id%2Ckind%2Cname%29&"
@"prettyPrint=false");
GTLRTestingSvc_FileList *obj = [self fileListObjectForTest];
testResponse(obj, nil);
};
query.executionParameters.retryBlock = ^(GTLRServiceTicket *ticket,
BOOL suggestedWillRetry,
NSError *error) {
XCTFail(@"No retry expected.");
return NO;
};
query.executionParameters.uploadProgressBlock = ^(GTLRServiceTicket *ticket,
unsigned long long numberOfBytesRead,
unsigned long long dataLength) {
XCTFail(@"No body upload expected.");
};
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
__block GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
// Verify the top-level object and one of its items.
XCTAssertEqualObjects([object class], [GTLRTestingSvc_FileList class]);
XCTAssertNil(error);
XCTAssertEqualObjects(object.kind, @"drive#fileList");
XCTAssertEqual(object.files.count, 2U, @"%@", object.files);
GTLRTestingSvc_File *item0 = object.files[0];
XCTAssertEqualObjects([item0 class], [GTLRTestingSvc_File class]);
XCTAssertEqualObjects(item0.kind, @"drive#file");
XCTAssertEqualObjects(callbackTicket, queryTicket);
// The ticket's query should be a copy of the original; query execution leaves
// the original unmolested so the client can modify and reuse it.
GTLRQuery *ticketQuery = callbackTicket.executingQuery;
XCTAssertNotEqual(ticketQuery, query);
XCTAssertEqualObjects(ticketQuery.requestID, query.requestID);
XCTAssert([NSThread isMainThread]);
[queryFinished fulfill];
}];
XCTAssertFalse(queryTicket.hasCalledCallback);
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_testBlock_Failing {
[self expectTicketNotifications];
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(id,kind,name)";
query.requestID = @"gtlr_1234";
NSError *expectedError = [NSError errorWithDomain:NSURLErrorDomain
code:NSURLErrorResourceUnavailable
userInfo:nil];
GTLRService *service = [self driveServiceForTest];
service.rootURLString = @"https://example.invalid/";
service.testBlock = ^(GTLRServiceTicket *ticket, GTLRServiceTestResponse testResponse) {
testResponse(nil, expectedError);
};
XCTestExpectation *queryFinishedExp = [self expectationWithDescription:@"queryFinishedExp"];
XCTestExpectation *retryBlockExp = [self expectationWithDescription:@"retryBlockExp"];
query.executionParameters.retryBlock = ^(GTLRServiceTicket *ticket,
BOOL suggestedWillRetry,
NSError *error) {
XCTAssertEqual(suggestedWillRetry, NO);
[retryBlockExp fulfill];
return NO;
};
GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
// Verify the top-level object and one of its items.
XCTAssertNil(object);
XCTAssertEqualObjects(error, expectedError);
[queryFinishedExp fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_testBlock_CallbackQueue {
// No need for a fetcher testBlock.
dispatch_queue_t myCallbackQueue = dispatch_queue_create("myCallbackQueue",
DISPATCH_QUEUE_SERIAL);
GTLRTestingSvcQuery_FilesList *query = [GTLRTestingSvcQuery_FilesList query];
query.fields = @"kind,nextPageToken,files(id,kind,name)";
query.requestID = @"gtlr_1234";
GTLRService *service = [self driveServiceForTest];
service.rootURLString = @"https://example.invalid/";
service.testBlock = ^(GTLRServiceTicket *ticket, GTLRServiceTestResponse testResponse) {
GTLRTestingSvc_FileList *obj = [self fileListObjectForTest];
testResponse(obj, nil);
};
service.callbackQueue = myCallbackQueue;
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_FileList *object, NSError *error) {
// Verify the top-level object and one of its items.
XCTAssertEqualObjects([object class], [GTLRTestingSvc_FileList class]);
XCTAssertNil(error);
XCTAssert(IsCurrentQueue(myCallbackQueue));
[queryFinished fulfill];
}];
service.callbackQueue = dispatch_get_main_queue();
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_testBlock_MediaQuery {
// Successful request with data object response.
NSData *expectedData = [self tempDataForUploading];
NSString *expectedContentType = @"text/plain";
GTLRService *service = [self driveServiceForTest];
GTLRTestingSvcQuery_FilesGet *query =
[GTLRTestingSvcQuery_FilesGet queryForMediaWithFileId:@"abcde"];
// For variety, we'll attach the testBlock to the query's service execution parameters.
query.executionParameters.testBlock = ^(GTLRServiceTicket *ticket,
GTLRServiceTestResponse testResponse) {
GTLRDataObject *obj = [GTLRDataObject object];
obj.data = expectedData;
obj.contentType = expectedContentType;
testResponse(obj, nil);
};
XCTestExpectation *queryFinished = [self expectationWithDescription:@"queryFinished"];
GTLRServiceTicket *queryTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRDataObject *object, NSError *error) {
NSURL *fetcherURL = callbackTicket.fetchRequest.URL;
XCTAssertEqualObjects(QueryValueForURLItem(fetcherURL, @"alt"), @"media");
XCTAssertEqualObjects([object class], [GTLRDataObject class]);
XCTAssertNil(error);
XCTAssertEqualObjects(object.data, expectedData);
XCTAssertEqualObjects(object.contentType, expectedContentType);
XCTAssert([NSThread isMainThread]);
[queryFinished fulfill];
}];
XCTAssertFalse(queryTicket.hasCalledCallback);
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_testBlock_BatchQuery {
// The test block will return a batch with one success and one failure.
GTLRService *service = [self driveServiceForTest];
service.rootURLString = @"https://example.invalid/";
XCTestExpectation *childExp = [self expectationWithDescription:@"childCallback"];
XCTestExpectation *parentsExp = [self expectationWithDescription:@"parentsCallback"];
XCTestExpectation *batchFinished = [self expectationWithDescription:@"batchFinished"];
GTLRTestingSvcQuery_FilesList *childQuery = [GTLRTestingSvcQuery_FilesList query];
childQuery.q = [NSString stringWithFormat:@"'ABCDE' in parents"];
childQuery.requestID = @"gtlr_5";
childQuery.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_FileList *fileList,
NSError *childError) {
XCTAssertEqualObjects([fileList class], [GTLRTestingSvc_FileList class]);
XCTAssertEqualObjects(fileList.kind, @"drive#fileList");
XCTAssertNil(childError);
[childExp fulfill];
};
NSError *expectedError = [NSError errorWithDomain:NSURLErrorDomain
code:NSURLErrorResourceUnavailable
userInfo:nil];
GTLRErrorObject *expectedErrorObject = [GTLRErrorObject objectWithFoundationError:expectedError];
GTLRTestingSvcQuery_FilesGet *parentsQuery =
[GTLRTestingSvcQuery_FilesGet queryWithFileId:@"0B7svZDDwtKrhS2FDS2JZclU1U0E"];
parentsQuery.fields = @"parents";
parentsQuery.requestID = @"gtlr_6";
parentsQuery.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_File *file,
NSError *parentsError) {
XCTAssertNil(file);
XCTAssertEqualObjects(parentsError.domain, expectedError.domain);
XCTAssertEqual(parentsError.code, expectedError.code);
[parentsExp fulfill];
};
parentsQuery.additionalHTTPHeaders = @{ @"Tiger" : @"Siberian" };
// Combine the separate queries into one batch.
GTLRBatchQuery *batchQuery = [GTLRBatchQuery batchQuery];
[batchQuery addQuery:childQuery];
[batchQuery addQuery:parentsQuery];
// Test adding http headers to the query
batchQuery.additionalHTTPHeaders = @{ @"X-Feline": @"Fluffy",
@"X-Canine": @"Spot" };
service.testBlock = ^(GTLRServiceTicket *ticket, GTLRServiceTestResponse testResponse) {
XCTAssert([ticket.fetchRequest.URL.absoluteString hasPrefix:@"https://example.invalid/"],
@"%@", ticket.fetchRequest);
GTLRTestingSvc_FileList *fileList = [self fileListObjectForTest];
GTLRBatchResult *batchResult = [GTLRBatchResult object];
batchResult.successes = @{ childQuery.requestID : fileList };
batchResult.failures = @{ parentsQuery.requestID : expectedErrorObject };
batchResult.responseHeaders = @{ childQuery.requestID : @{} , parentsQuery.requestID : @{} };
testResponse(batchResult, nil);
};
batchQuery.executionParameters.retryBlock = ^(GTLRServiceTicket *ticket,
BOOL suggestedWillRetry,
NSError *error) {
XCTFail(@"No retry expected.");
return NO;
};
XCTestExpectation *uploadedSomeBytes = [self expectationWithDescription:@"uploadedSomeBytes"];
batchQuery.executionParameters.uploadProgressBlock = ^(GTLRServiceTicket *ticket,
unsigned long long numberOfBytesRead,
unsigned long long dataLength) {
if (numberOfBytesRead == dataLength) {
[uploadedSomeBytes fulfill];
}
XCTAssert([NSThread isMainThread]);
};
GTLRServiceTicket *queryTicket =
[service executeQuery:batchQuery
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRBatchResult *batchResult, NSError *batchError) {
// Verify the batch result and each success and failure result.
XCTAssertEqualObjects([batchResult class], [GTLRBatchResult class]);
XCTAssertNil(batchError);
XCTAssertEqual(batchResult.successes.count, 1U);
XCTAssertEqual(batchResult.failures.count, 1U);
XCTAssertEqual(batchResult.responseHeaders.count, 2U);
GTLRTestingSvc_FileList *fileList = batchResult.successes[@"gtlr_5"];
XCTAssertEqualObjects([fileList class], [GTLRTestingSvc_FileList class]);
XCTAssertEqualObjects(fileList.kind, @"drive#fileList");
GTLRErrorObject *failureObject = batchResult.failures[@"gtlr_6"];
XCTAssertEqualObjects([failureObject class], [GTLRErrorObject class]);
XCTAssertEqual(failureObject.code.intValue, NSURLErrorResourceUnavailable);
// As with a single query, the batch ticket's query should be a copy of the original;
// query execution leaves the original unmolested so the client can modify and reuse it.
GTLRBatchQuery *ticketQuery = callbackTicket.executingQuery;
XCTAssertNotEqual(ticketQuery, batchQuery);
// Since the batch has a copy of the original query, all request IDs of both batch
// queries should be the same.
XCTAssertEqualObjects([ticketQuery.queries valueForKey:@"requestID"],
[batchQuery.queries valueForKey:@"requestID"]);
XCTAssert([NSThread isMainThread]);
[batchFinished fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// The request is to a fixed URL.
XCTAssertEqualObjects(queryTicket.fetchRequest.URL.absoluteString,
@"https://example.invalid/batch");
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testService_testBlock_Upload_FileHandle {
NSURL *fileURL = [self tempFileURLForUploading];
NSError *readingError;
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL:fileURL
error:&readingError];
XCTAssertNotNil(fileHandle, @"%@", readingError);
GTLRUploadParameters *uploadParameters =
[GTLRUploadParameters uploadParametersWithFileHandle:fileHandle
MIMEType:@"text/plain"];
[self performServiceUploadTestBlockTestWithParameters:uploadParameters];
}
- (void)testService_testBlock_Upload_FileURL {
NSURL *fileURL = [self tempFileURLForUploading];
GTLRUploadParameters *uploadParameters =
[GTLRUploadParameters uploadParametersWithFileURL:fileURL
MIMEType:@"text/plain"];
[self performServiceUploadTestBlockTestWithParameters:uploadParameters];
}
- (void)testService_testBlock_Upload_NSData {
NSData *uploadData = [self tempDataForUploading];
XCTAssert(uploadData.length > 1000);
GTLRUploadParameters *uploadParameters =
[GTLRUploadParameters uploadParametersWithData:uploadData
MIMEType:@"text/plain"];
[self performServiceUploadTestBlockTestWithParameters:uploadParameters];
}
- (void)performServiceUploadTestBlockTestWithParameters:(GTLRUploadParameters *)uploadParameters {
XCTestExpectation *executeCompletionExp = [self expectationWithDescription:@"Execute block"];
XCTestExpectation *queryCompletionExp = [self expectationWithDescription:@"Query block"];
XCTestExpectation *progressExp = [self expectationWithDescription:@"Upload progress"];
XCTestExpectation *testBlockExp = [self expectationWithDescription:@"testBlock"];
GTLRTestingSvc_File *newFile = [GTLRTestingSvc_File object];
newFile.name = @"File de Feline";
GTLRService *service = [self driveServiceForTest];
service.rootURLString = @"https://example.invalid/";
service.testBlock = ^(GTLRServiceTicket *ticket, GTLRServiceTestResponse testResponse) {
NSString *uploadID = QueryValueForURLItem(ticket.fetchRequest.URL, @"upload_id");
XCTAssertNil(uploadID);
// Below we attach an ETag header to the upload object, and force the http method to PUT,
// so the ETag should be present in the initial request header.
NSString *headerETag = ticket.fetchRequest.allHTTPHeaderFields[@"If-Match"];
XCTAssertEqualObjects(headerETag, newFile.ETag);
GTLRTestingSvc_File *fileObj = [GTLRTestingSvc_File object];
fileObj.kind = @"drive#file";
fileObj.name = @"abcdefg.txt";
fileObj.identifier = @"0svZDDwtKrhcHh2dmcyZ05MZWc";
testResponse(fileObj, nil);
[testBlockExp fulfill];
};
GTLRTestingSvcQuery_FilesCreate *query =
[GTLRTestingSvcQuery_FilesCreate queryWithObject:newFile
uploadParameters:uploadParameters];
query.completionBlock = ^(GTLRServiceTicket *ticket, GTLRTestingSvc_File *uploadedFile,
NSError *permissionError) {
XCTAssertEqualObjects(uploadedFile.name, @"abcdefg.txt");
XCTAssertEqualObjects(uploadedFile.kind, @"drive#file");
[queryCompletionExp fulfill];
};
query.executionParameters.uploadProgressBlock = ^(GTLRServiceTicket *progressTicket,
unsigned long long numberOfBytesRead,
unsigned long long dataLength) {
if (numberOfBytesRead == dataLength) {
[progressExp fulfill];
}
};
// To test that the ETag is copied from the query's object, we'll change the
// request from POST to PUT, which will be ignored by the test block simulation
// of the upload server, but will cause the ETag to be added as an If-Match request
// header.
newFile.ETag = @"wheeTag";
[query setValue:@"PUT" forKey:@"httpMethod"];
GTLRServiceTicket *queryTicket = [service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRTestingSvc_File *uploadedFile,
NSError *error) {
XCTAssertEqualObjects(uploadedFile.name, @"abcdefg.txt");
XCTAssertEqualObjects(uploadedFile.kind, @"drive#file");
[executeCompletionExp fulfill];
}];
XCTAssert([self service:service waitForTicket:queryTicket]);
XCTAssert(queryTicket.hasCalledCallback);
// Ensure all expectations were satisfied.
[self waitForExpectationsWithTimeout:10 handler:nil];
}
#pragma mark - Utility Method Tests
- (void)testRequestForQuery {
GTLRService *service = [[GTLRService alloc] init];
service.rootURLString = @"https://www.test.com/";
service.servicePath = @"api/";
// Basic query.
GTLRQuery *query = [[GTLRQuery alloc] initWithPathURITemplate:@"path/{path_part}"
HTTPMethod:nil
pathParameterNames:@[ @"path_part" ]];
query.JSON = [@{
@"path_part" : @"foo",
@"arg" : @"mumble",
} mutableCopy];
NSString *userAgent = service.requestUserAgent;
NSDictionary *baseHTTPHeaders = @{ @"User-Agent" : userAgent };
NSMutableDictionary *expectedHTTPHeaders = [baseHTTPHeaders mutableCopy];
NSString *expectedURLString = @"https://www.test.com/api/path/foo?arg=mumble";
NSMutableURLRequest *result = [service requestForQuery:query];
XCTAssertEqualObjects(result.URL.absoluteString, expectedURLString);
XCTAssertEqualObjects(result.HTTPMethod, @"GET");
XCTAssertEqualObjects(result.allHTTPHeaderFields, expectedHTTPHeaders);
// Extra query arg and HTTP header.
query.additionalURLQueryParameters = @{ @"queryArg" : @YES };
query.additionalHTTPHeaders = @{ @"X-Query" : @"All Good!" };
expectedURLString = @"https://www.test.com/api/path/foo?arg=mumble&queryArg=true";
[expectedHTTPHeaders setObject:@"All Good!" forKey:@"X-Query"];
result = [service requestForQuery:query];
XCTAssertEqualObjects(result.URL.absoluteString, expectedURLString);
XCTAssertEqualObjects(result.HTTPMethod, @"GET");
XCTAssertEqualObjects(result.allHTTPHeaderFields, expectedHTTPHeaders);
// With a service arg and HTTP header.
service.additionalURLQueryParameters = @{ @"serviceArg" : @42 };
service.additionalHTTPHeaders = @{ @"X-Service" : @"Grumble" };
expectedURLString = @"https://www.test.com/api/path/foo?arg=mumble&queryArg=true&serviceArg=42";
[expectedHTTPHeaders setObject:@"Grumble" forKey:@"X-Service"];
result = [service requestForQuery:query];
XCTAssertEqualObjects(result.URL.absoluteString, expectedURLString);
XCTAssertEqualObjects(result.HTTPMethod, @"GET");
XCTAssertEqualObjects(result.allHTTPHeaderFields, expectedHTTPHeaders);
// Overlap between the query and service for an arg and HTTP header (query
// wins).
query.additionalURLQueryParameters = @{ @"arg1" : @"query", @"arg2" : @"query" };
query.additionalHTTPHeaders = @{ @"X-1" : @"Query", @"X-2" : @"Query" };
service.additionalURLQueryParameters = @{ @"arg1" : @"service", @"arg3" : @"service" };
service.additionalHTTPHeaders = @{ @"X-1" : @"Service", @"X-3" : @"Service" };
expectedURLString = @"https://www.test.com/api/path/foo?arg=mumble&arg1=query&arg2=query&arg3=service";
expectedHTTPHeaders = [baseHTTPHeaders mutableCopy];
[expectedHTTPHeaders addEntriesFromDictionary:@{
@"X-1" : @"Query",
@"X-2" : @"Query",
@"X-3" : @"Service",
}];
result = [service requestForQuery:query];
XCTAssertEqualObjects(result.URL.absoluteString, expectedURLString);
XCTAssertEqualObjects(result.HTTPMethod, @"GET");
XCTAssertEqualObjects(result.allHTTPHeaderFields, expectedHTTPHeaders);
// Different HTTPMethod.
query = [[GTLRQuery alloc] initWithPathURITemplate:@"blah"
HTTPMethod:@"POST"
pathParameterNames:nil];
expectedURLString = @"https://www.test.com/api/blah";
service.additionalURLQueryParameters = nil;
service.additionalHTTPHeaders = nil;
expectedHTTPHeaders = [baseHTTPHeaders mutableCopy];
result = [service requestForQuery:query];
XCTAssertEqualObjects(result.URL.absoluteString, expectedURLString);
XCTAssertEqualObjects(result.HTTPMethod, @"POST");
XCTAssertEqualObjects(result.allHTTPHeaderFields, expectedHTTPHeaders);
// Add an APIKey.
service.APIKey = @"Abracadabra!";
result = [service requestForQuery:query];
expectedURLString = @"https://www.test.com/api/blah?key=Abracadabra%21";
XCTAssertEqualObjects(result.URL.absoluteString, expectedURLString);
XCTAssertEqualObjects(result.HTTPMethod, @"POST");
XCTAssertEqualObjects(result.allHTTPHeaderFields, expectedHTTPHeaders);
// Add an APIKey Restriction
service.APIKeyRestrictionBundleID = @"foo.bar.baz";
expectedHTTPHeaders[kXIosBundleIdHeader] = @"foo.bar.baz";
result = [service requestForQuery:query];
XCTAssertEqualObjects(result.URL.absoluteString, expectedURLString);
XCTAssertEqualObjects(result.HTTPMethod, @"POST");
XCTAssertEqualObjects(result.allHTTPHeaderFields, expectedHTTPHeaders);
}
- (void)testRequestForQuery_MediaDownload {
GTLRTestingSvcQuery_FilesGet *query =
[GTLRTestingSvcQuery_FilesGet queryForMediaWithFileId:@"abcde"];
query.useMediaDownloadService = NO;
GTLRService *service = [self driveServiceForTest];
// Without download service.
NSURLRequest *result = [service requestForQuery:query];
NSString *expectedURLString = @"https://www.googleapis.com/drive/v3/files/abcde?alt=media";
XCTAssertEqualObjects(result.URL.absoluteString, expectedURLString);
// With download service.
query.useMediaDownloadService = YES;
result = [service requestForQuery:query];
expectedURLString = @"https://www.googleapis.com/download/drive/v3/files/abcde?alt=media";
XCTAssertEqualObjects(result.URL.absoluteString, expectedURLString);
}
#pragma mark - Internal Utility Method Tests
- (void)testFullURLFromStringQueryParameters {
NSString *inputStr;
NSDictionary *inputDict;
NSURL *output;
NSString *expectedStr;
NSURL *expected;
inputStr = nil;
inputDict = nil;
output = [GTLRService URLWithString:inputStr queryParameters:inputDict];
XCTAssertNil(output);
inputStr = @"";
output = [GTLRService URLWithString:inputStr queryParameters:inputDict];
XCTAssertNil(output);
inputStr = nil;
inputDict = @{ @"b" : @"1" };
output = [GTLRService URLWithString:inputStr queryParameters:inputDict];
XCTAssertNil(output);
inputStr = @"";
output = [GTLRService URLWithString:inputStr queryParameters:inputDict];
XCTAssertNil(output);
inputStr = @"http://www.google.com";
inputDict = nil;
output = [GTLRService URLWithString:inputStr queryParameters:inputDict];
expected = [NSURL URLWithString:@"http://www.google.com"];
XCTAssertEqualObjects(output, expected);
inputDict = @{ };
output = [GTLRService URLWithString:inputStr queryParameters:inputDict];
XCTAssertEqualObjects(output, expected);
// Keys will be sorted, but order within any arrays is preserved.
inputDict = @{
@"g" : @42,
@"f" : @[ @YES, @NO ],
@"e" : @[ @1, @2, @3 ],
@"d" : @[ @"d1", @"d3", @"d2" ],
@"c" : @YES,
@"b" : @"&",
@"a" : @"1",
};
output = [GTLRService URLWithString:inputStr queryParameters:inputDict];
expectedStr = @"http://www.google.com?a=1&b=%26&c=true&d=d1&d=d3&d=d2&e=1&e=2&e=3&f=true&f=false&g=42";
expected = [NSURL URLWithString:expectedStr];
XCTAssertEqualObjects(output, expected);
inputStr = @"http://www.google.com?q=spam";
output = [GTLRService URLWithString:inputStr queryParameters:inputDict];
expectedStr = @"http://www.google.com?q=spam&a=1&b=%26&c=true&d=d1&d=d3&d=d2&e=1&e=2&e=3&f=true&f=false&g=42";
expected = [NSURL URLWithString:expectedStr];
XCTAssertEqualObjects(output, expected);
}
@end
//
// Simple authorizer object for testing - implementation
//
@implementation GTLRTestAuthorizer
@synthesize value = _value,
error = _error;
+ (GTLRTestAuthorizer *)authorizerWithValue:(NSString *)value {
GTLRTestAuthorizer *obj = [[self alloc] init];
obj.value = value;
return obj;
}
- (NSString *)authorizationValue {
NSString *str = [NSString stringWithFormat:@"Bearer %@", _value];
return str;
}
- (void)authorizeRequest:(NSMutableURLRequest *)request
delegate:(id)delegate
didFinishSelector:(SEL)sel {
NSString *str = [self authorizationValue];
[request setValue:str forHTTPHeaderField:@"Authorization"];
id localSelf = self;
NSMethodSignature *sig = [delegate methodSignatureForSelector:sel];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:sig];
[invocation setSelector:sel];
[invocation setTarget:delegate];
[invocation setArgument:&localSelf atIndex:2];
[invocation setArgument:&request atIndex:3];
[invocation setArgument:&_error atIndex:4];
[invocation invoke];
}
- (void)stopAuthorization {
}
- (void)stopAuthorizationForRequest:(NSURLRequest *)request {
}
- (BOOL)isAuthorizingRequest:(NSURLRequest *)request {
return NO;
}
- (BOOL)isAuthorizedRequest:(NSURLRequest *)request {
NSString *requestValue = [request valueForHTTPHeaderField:@"Authorization"];
NSString *str = [self authorizationValue];
return GTLR_AreEqualOrBothNil(requestValue, str);
}
- (NSString *)userEmail {
return @"test@example.com";
}
@end
@implementation GTLRTestLifetimeObject {
XCTestExpectation *_expectation;
}
+ (instancetype)trackLifetimeOfObject:(id)object expectation:(XCTestExpectation *)expectation {
NSAssert(expectation != nil, @"missing expectation for %@", object);
NSAssert(object != nil, @"missing object for %@", expectation);
GTLRTestLifetimeObject *lifetime = [[self alloc] initWithExpectation:expectation];
objc_setAssociatedObject(object, "GTLRTestLifetimeObject", lifetime, OBJC_ASSOCIATION_RETAIN);
return lifetime;
}
- (instancetype)initWithExpectation:(XCTestExpectation *)expectation {
self = [super init];
if (self) {
_expectation = expectation;
}
return self;
}
- (void)dealloc {
[_expectation fulfill];
}
@end
#if GTM_BACKGROUND_TASK_FETCHING
@implementation CountingUIApplication
@synthesize beginTaskIDs = _beginTaskIDs,
endTaskIDs = _endTaskIDs,
shouldExpireTasks = _shouldExpireTasks,
numberOfExpiredTasks = _numberOfExpiredTasks;
UIBackgroundTaskIdentifier gTaskID = 1000;
- (instancetype)init {
self = [super init];
if (self) {
_beginTaskIDs = [NSCountedSet set];
_endTaskIDs = [NSCountedSet set];
}
return self;
}
- (UIBackgroundTaskIdentifier)beginBackgroundTaskWithName:(NSString *)taskName
expirationHandler:(dispatch_block_t)handler {
UIBackgroundTaskIdentifier taskID;
@synchronized(self) {
taskID = ++gTaskID;
[self.beginTaskIDs addObject:@(taskID)];
}
if (_shouldExpireTasks) {
dispatch_async(dispatch_get_main_queue(), ^{
handler();
self->_numberOfExpiredTasks++;
});
}
return taskID;
}
- (void)endBackgroundTask:(UIBackgroundTaskIdentifier)taskID {
@synchronized(self) {
[self.endTaskIDs addObject:@(taskID)];
}
}
@end
#endif // GTM_BACKGROUND_TASK_FETCHING