TwitterNetworkLayerTests/TNLPseudoRequestOperationTest.m (762 lines of code) (raw):

// // TNLPseudoRequestOperationTest.m // TwitterNetworkLayer // // Created on 10/29/14. // Copyright © 2020 Twitter. All rights reserved. // #import "NSDictionary+TNLAdditions.h" #import "TNLError.h" #import "TNLHTTPRequest.h" #import "TNLPseudoURLProtocol.h" #import "TNLRequestDelegate.h" #import "TNLRequestOperationCancelSource.h" #import "TNLRequestOperationQueue.h" #import "TNLRequestRetryPolicyProvider.h" #import "TNLResponse.h" @import XCTest; #define ENABLE_PSEUDO_REQUEST_TESTS 1 #if ENABLE_PSEUDO_REQUEST_TESTS #define ENABLE_TIMING_TESTS 0 // timing is not reliable on CI machines @interface TNLPseudoRequestOperationTest : XCTestCase <TNLRequestRetryPolicyProvider, TNLRequestDelegate> @end #define PSEUDO_ORIGIN @"http://www.pseudo.com" #define PSEUDO_REDIRECT PSEUDO_ORIGIN @"/redirect" #if ENABLE_TIMING_TESTS #define TIME_BUFFER (0.25) #endif static TNLRequestOperationQueue *sQueue; static TNLRequestConfiguration *sConfig; static NSURL *sURL; static NSHTTPURLResponse *sResponse; static NSData *sData; @implementation TNLPseudoRequestOperationTest + (void)setUp { sURL = [NSURL URLWithString:PSEUDO_ORIGIN]; sData = [@"{ garbage : \"data\" }" dataUsingEncoding:NSUTF8StringEncoding]; sResponse = [[NSHTTPURLResponse alloc] initWithURL:sURL statusCode:200 HTTPVersion:@"HTTP/1.1" headerFields:@{ @"Header1" : @"Value1" }]; TNLMutableRequestConfiguration *config = [TNLMutableRequestConfiguration defaultConfiguration]; config.protocolOptions = TNLRequestProtocolOptionPseudo; sConfig = [config copy]; sQueue = [[TNLRequestOperationQueue alloc] initWithIdentifier:@"pseudo.request.test.queue"]; } + (void)tearDown { [TNLPseudoURLProtocol unregisterEndpoint:sURL]; sQueue = nil; sConfig = nil; sURL = nil; sData = nil; sResponse = nil; } - (void)tearDown { [TNLPseudoURLProtocol unregisterAllEndpoints]; [super tearDown]; } - (void)registerCannedResponseWithConfig:(TNLPseudoURLResponseConfig *)config { [TNLPseudoURLProtocol registerURLResponse:sResponse body:sData config:config withEndpoint:sURL]; } - (void)registerRedirectWithBehavior:(TNLPseudoURLProtocolRedirectBehavior)behavior { NSURL *redirectURL = [NSURL URLWithString:PSEUDO_REDIRECT]; NSHTTPURLResponse *redirectResponse = [[NSHTTPURLResponse alloc] initWithURL:redirectURL statusCode:302 HTTPVersion:@"HTTP/1.1" headerFields:@{ @"Location" : PSEUDO_ORIGIN }]; TNLPseudoURLResponseConfig *config = [[TNLPseudoURLResponseConfig alloc] init]; config.redirectBehavior = behavior; [TNLPseudoURLProtocol registerURLResponse:redirectResponse body:nil config:config withEndpoint:redirectURL]; } #pragma mark Tests - (void)testOperation200 { TNLMutableRequestConfiguration *mConfig = [sConfig mutableCopy]; TNLMutableHTTPRequest *mRequest = [[TNLMutableHTTPRequest alloc] initWithURL:sURL]; [self registerCannedResponseWithConfig:nil]; __block TNLResponse *response; TNLRequestOperation *op; XCTestExpectation *expect; // 200 op = [TNLRequestOperation operationWithRequest:mRequest configuration:mConfig delegate:self]; expect = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue enqueueRequestOperation:op]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateSucceeded); XCTAssertEqual(200, response.info.statusCode); XCTAssertTrue(response.info.data.length > 0); XCTAssertEqual(1.0, op.downloadProgress); XCTAssertEqual(1.0, op.uploadProgress); XCTAssertNil(response.operationError); XCTAssertEqual(response, op.response); #if ENABLE_TIMING_TESTS XCTAssertEqualWithAccuracy(response.metrics.queuedDuration, 0.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.allAttemptsDuration, 0.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.currentAttemptDuration, 0.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.totalDuration, 0.0, TIME_BUFFER); #endif XCTAssertEqual(1UL, response.metrics.attemptCount); } - (void)testOperation200_TempSuspension { TNLMutableRequestConfiguration *mConfig = [sConfig mutableCopy]; TNLMutableHTTPRequest *mRequest = [[TNLMutableHTTPRequest alloc] initWithURL:sURL]; [self registerCannedResponseWithConfig:nil]; __block TNLResponse *response; TNLRequestOperation *op; XCTestExpectation *expect; // Temprorary Suspend op = [TNLRequestOperation operationWithRequest:mRequest configuration:mConfig delegate:self]; expect = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue suspend]; [sQueue enqueueRequestOperation:op]; [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.1]]; [sQueue resume]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateSucceeded); XCTAssertEqual(200, response.info.statusCode); XCTAssertTrue(response.info.data.length > 0); XCTAssertEqual(1.0, op.downloadProgress); XCTAssertEqual(1.0, op.uploadProgress); XCTAssertNil(response.operationError); XCTAssertEqual(response, op.response); #if ENABLE_TIMING_TESTS XCTAssertEqualWithAccuracy(response.metrics.queuedDuration, 1.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.allAttemptsDuration, 0.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.currentAttemptDuration, 0.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.totalDuration, 1.0, TIME_BUFFER); #endif XCTAssertEqual(1UL, response.metrics.attemptCount); } - (void)testOperation200_Delay { TNLMutableRequestConfiguration *mConfig = [sConfig mutableCopy]; TNLMutableHTTPRequest *mRequest = [[TNLMutableHTTPRequest alloc] initWithURL:sURL]; TNLPseudoURLResponseConfig *pseudoConfig = [[TNLPseudoURLResponseConfig alloc] init]; __block TNLResponse *response; TNLRequestOperation *op; XCTestExpectation *expect; // Delay pseudoConfig.delay = 1000; [self registerCannedResponseWithConfig:pseudoConfig]; op = [TNLRequestOperation operationWithRequest:mRequest configuration:mConfig delegate:self]; expect = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue enqueueRequestOperation:op]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateSucceeded); XCTAssertEqual(200, response.info.statusCode); XCTAssertTrue(response.info.data.length > 0); XCTAssertEqual(1.0, op.downloadProgress); XCTAssertEqual(1.0, op.uploadProgress); XCTAssertNil(response.operationError); XCTAssertEqual(response, op.response); #if ENABLE_TIMING_TESTS XCTAssertEqualWithAccuracy(response.metrics.queuedDuration, 0.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.allAttemptsDuration, 1.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.currentAttemptDuration, 1.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.totalDuration, 1.0, TIME_BUFFER); #endif XCTAssertEqual(1UL, response.metrics.attemptCount); } - (void)testOperation200_Delay_and_Latency { TNLMutableRequestConfiguration *mConfig = [sConfig mutableCopy]; TNLMutableHTTPRequest *mRequest = [[TNLMutableHTTPRequest alloc] initWithURL:sURL]; TNLPseudoURLResponseConfig *pseudoConfig = [[TNLPseudoURLResponseConfig alloc] init]; __block TNLResponse *response; TNLRequestOperation *op; XCTestExpectation *expect; // Latency + Delay pseudoConfig.delay = 1000; pseudoConfig.latency = 250; [self registerCannedResponseWithConfig:pseudoConfig]; op = [TNLRequestOperation operationWithRequest:mRequest configuration:mConfig delegate:self]; expect = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue enqueueRequestOperation:op]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateSucceeded); XCTAssertEqual(200, response.info.statusCode); XCTAssertTrue(response.info.data.length > 0); XCTAssertEqual(1.0, op.downloadProgress); XCTAssertEqual(1.0, op.uploadProgress); XCTAssertNil(response.operationError); XCTAssertEqual(response, op.response); #if ENABLE_TIMING_TESTS XCTAssertEqualWithAccuracy(response.metrics.queuedDuration, 0.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.allAttemptsDuration, 1.5, TIME_BUFFER * 2); XCTAssertEqualWithAccuracy(response.metrics.currentAttemptDuration, 1.5, TIME_BUFFER * 2); XCTAssertEqualWithAccuracy(response.metrics.totalDuration, 1.5, TIME_BUFFER * 2); #endif XCTAssertEqual(1UL, response.metrics.attemptCount); } - (void)testOperation200_Latency { TNLMutableRequestConfiguration *mConfig = [sConfig mutableCopy]; TNLMutableHTTPRequest *mRequest = [[TNLMutableHTTPRequest alloc] initWithURL:sURL]; TNLPseudoURLResponseConfig *pseudoConfig = [[TNLPseudoURLResponseConfig alloc] init]; __block TNLResponse *response; TNLRequestOperation *op; XCTestExpectation *expect; // Latency pseudoConfig.latency = 250; [self registerCannedResponseWithConfig:pseudoConfig]; op = [TNLRequestOperation operationWithRequest:mRequest configuration:mConfig delegate:self]; expect = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue enqueueRequestOperation:op]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateSucceeded); XCTAssertEqual(200, response.info.statusCode); XCTAssertTrue(response.info.data.length > 0); XCTAssertEqual(1.0, op.downloadProgress); XCTAssertEqual(1.0, op.uploadProgress); XCTAssertNil(response.operationError); XCTAssertEqual(response, op.response); #if ENABLE_TIMING_TESTS XCTAssertEqualWithAccuracy(response.metrics.queuedDuration, 0.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.allAttemptsDuration, 0.5, TIME_BUFFER * 2); XCTAssertEqualWithAccuracy(response.metrics.currentAttemptDuration, 0.5, TIME_BUFFER * 2); XCTAssertEqualWithAccuracy(response.metrics.totalDuration, 0.5, TIME_BUFFER * 2); #endif XCTAssertEqual(1UL, response.metrics.attemptCount); } - (void)testOperation302_Follow { NSURL *redirectURL = [NSURL URLWithString:PSEUDO_REDIRECT]; [self registerRedirectWithBehavior:TNLPseudoURLProtocolRedirectBehaviorFollowLocation]; [self registerCannedResponseWithConfig:nil]; TNLMutableHTTPRequest *request = [[TNLMutableHTTPRequest alloc] initWithURL:redirectURL]; TNLRequestOperation *op = [TNLRequestOperation operationWithRequest:request configuration:sConfig delegate:self]; // expect redirection __block NSURLRequest *fromRequest; __block NSURLRequest *toRequest; XCTestExpectation *expectRedirect; expectRedirect = [self expectationForNotification:@"Redirect" object:op handler:^BOOL(NSNotification *notification) { fromRequest = notification.userInfo[@"fromRequest"]; toRequest = notification.userInfo[@"toRequest"]; return YES; }]; // expect completion __block TNLResponse *response; XCTestExpectation *expectComplete; expectComplete = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; // enqueue the operation and wait for it to finish XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue enqueueRequestOperation:op]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateSucceeded); // check that the redirect happend correctly XCTAssertEqualObjects(PSEUDO_REDIRECT, fromRequest.URL.absoluteString); XCTAssertEqualObjects(PSEUDO_ORIGIN, toRequest.URL.absoluteString); XCTAssertEqualObjects(sURL, toRequest.URL); XCTAssertEqual(response.metrics.attemptCount, (NSUInteger)2); XCTAssertEqual(response.metrics.attemptMetrics.firstObject.attemptType, TNLAttemptTypeInitial); XCTAssertEqual(response.metrics.attemptMetrics.lastObject.attemptType, TNLAttemptTypeRedirect); // check that the completion happened correctly XCTAssertEqual(200, response.info.statusCode); XCTAssertGreaterThan(response.info.data.length, 0); XCTAssertEqual(1.0, op.downloadProgress); XCTAssertEqual(1.0, op.uploadProgress); XCTAssertNil(response.operationError); XCTAssertEqualObjects(response, op.response); } - (void)testOperation302_FollowIfRegistered { NSURL *redirectURL = [NSURL URLWithString:PSEUDO_REDIRECT]; [self registerRedirectWithBehavior:TNLPseudoURLProtocolRedirectBehaviorFollowLocationIfRedirectResponseIsRegistered]; [self registerCannedResponseWithConfig:nil]; TNLMutableHTTPRequest *request = [[TNLMutableHTTPRequest alloc] initWithURL:redirectURL]; TNLRequestOperation *op = [TNLRequestOperation operationWithRequest:request configuration:sConfig delegate:self]; // expect redirection __block NSURLRequest *fromRequest; __block NSURLRequest *toRequest; XCTestExpectation *expectRedirect; expectRedirect = [self expectationForNotification:@"Redirect" object:op handler:^BOOL(NSNotification *notification) { fromRequest = notification.userInfo[@"fromRequest"]; toRequest = notification.userInfo[@"toRequest"]; return YES; }]; // expect completion __block TNLResponse *response; XCTestExpectation *expectComplete; expectComplete = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; // enqueue the operation and wait for it to finish XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue enqueueRequestOperation:op]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateSucceeded); // check that the redirect happend correctly XCTAssertEqualObjects(PSEUDO_REDIRECT, fromRequest.URL.absoluteString); XCTAssertEqualObjects(PSEUDO_ORIGIN, toRequest.URL.absoluteString); XCTAssertEqualObjects(sURL, toRequest.URL); XCTAssertEqual(response.metrics.attemptCount, (NSUInteger)2); XCTAssertEqual(response.metrics.attemptMetrics.firstObject.attemptType, TNLAttemptTypeInitial); XCTAssertEqual(response.metrics.attemptMetrics.lastObject.attemptType, TNLAttemptTypeRedirect); // check that the completion happened correctly XCTAssertEqual(200, response.info.statusCode); XCTAssertGreaterThan(response.info.data.length, 0); XCTAssertEqual(1.0, op.downloadProgress); XCTAssertEqual(1.0, op.uploadProgress); XCTAssertNil(response.operationError); XCTAssertEqualObjects(response, op.response); } - (void)testOperation302_DontFollowIfNotRegistered { NSURL *redirectURL = [NSURL URLWithString:PSEUDO_REDIRECT]; [self registerRedirectWithBehavior:TNLPseudoURLProtocolRedirectBehaviorFollowLocationIfRedirectResponseIsRegistered]; TNLMutableHTTPRequest *request = [[TNLMutableHTTPRequest alloc] initWithURL:redirectURL]; TNLRequestOperation *op = [TNLRequestOperation operationWithRequest:request configuration:sConfig delegate:self]; // expect completion __block TNLResponse *response; XCTestExpectation *expectComplete; expectComplete = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; // enqueue the operation and wait for it to finish XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue enqueueRequestOperation:op]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateSucceeded); // check that the redirect did not happen XCTAssertEqual(response.metrics.attemptCount, (NSUInteger)1); XCTAssertEqual(response.metrics.attemptMetrics.firstObject.attemptType, TNLAttemptTypeInitial); // check that the completion happened correctly XCTAssertEqual(302, response.info.statusCode); XCTAssertEqual(1.0, op.downloadProgress); XCTAssertEqual(1.0, op.uploadProgress); XCTAssertNil(response.operationError); XCTAssertEqualObjects(response, op.response); XCTAssertEqualObjects([response.info valueForResponseHeaderField:@"Location"], PSEUDO_ORIGIN); } - (void)testOperation302_DontFollow { NSURL *redirectURL = [NSURL URLWithString:PSEUDO_REDIRECT]; [self registerRedirectWithBehavior:TNLPseudoURLProtocolRedirectBehaviorDontFollowLocation]; [self registerCannedResponseWithConfig:nil]; TNLMutableHTTPRequest *request = [[TNLMutableHTTPRequest alloc] initWithURL:redirectURL]; TNLRequestOperation *op = [TNLRequestOperation operationWithRequest:request configuration:sConfig delegate:self]; // expect completion __block TNLResponse *response; XCTestExpectation *expectComplete; expectComplete = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; // enqueue the operation and wait for it to finish XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue enqueueRequestOperation:op]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateSucceeded); // check that the redirect did not happen XCTAssertEqual(response.metrics.attemptCount, (NSUInteger)1); XCTAssertEqual(response.metrics.attemptMetrics.firstObject.attemptType, TNLAttemptTypeInitial); // check that the completion happened correctly XCTAssertEqual(302, response.info.statusCode); XCTAssertEqual(1.0, op.downloadProgress); XCTAssertEqual(1.0, op.uploadProgress); XCTAssertNil(response.operationError); XCTAssertEqualObjects(response, op.response); XCTAssertEqualObjects([response.info valueForResponseHeaderField:@"Location"], PSEUDO_ORIGIN); } - (void)testOperationError_AttemptTimeout { TNLMutableRequestConfiguration *mConfig = [sConfig mutableCopy]; TNLMutableHTTPRequest *mRequest = [[TNLMutableHTTPRequest alloc] initWithURL:sURL]; TNLPseudoURLResponseConfig *pseudoConfig = [[TNLPseudoURLResponseConfig alloc] init]; __block TNLResponse *response; TNLRequestOperation *op; XCTestExpectation *expect; // Attempt Timeout (own by NSURL stack and uses a fairly sizeable leeway) mConfig.attemptTimeout = 1.0; mConfig.operationTimeout = 4.0; pseudoConfig.latency = 1500; [self registerCannedResponseWithConfig:pseudoConfig]; op = [TNLRequestOperation operationWithRequest:mRequest configuration:mConfig delegate:self]; expect = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue enqueueRequestOperation:op]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateFailed); XCTAssertEqualObjects(response.operationError.domain, TNLErrorDomain); XCTAssertEqual(response.operationError.code, TNLErrorCodeRequestOperationAttemptTimedOut); XCTAssertEqual(response, op.response); #if ENABLE_TIMING_TESTS XCTAssertEqualWithAccuracy(response.metrics.queuedDuration, 0.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.allAttemptsDuration, 1.0, 1.0); XCTAssertEqualWithAccuracy(response.metrics.currentAttemptDuration, 1.0, 1.0); XCTAssertEqualWithAccuracy(response.metrics.totalDuration, 1.0, 1.0); #endif XCTAssertEqual(1UL, response.metrics.attemptCount); } - (void)testOperationError_OperationTimeout { TNLMutableRequestConfiguration *mConfig = [sConfig mutableCopy]; TNLMutableHTTPRequest *mRequest = [[TNLMutableHTTPRequest alloc] initWithURL:sURL]; TNLPseudoURLResponseConfig *pseudoConfig = [[TNLPseudoURLResponseConfig alloc] init]; __block TNLResponse *response; TNLRequestOperation *op; XCTestExpectation *expect; // Operation Timeout mConfig.attemptTimeout = 1.0; mConfig.operationTimeout = 2.0; mConfig.retryPolicyProvider = self; pseudoConfig.latency = 1500; // [mRequest setValue:@"2000" forHTTPHeaderField:@"RETRY_DELAY"]; [self registerCannedResponseWithConfig:pseudoConfig]; op = [TNLRequestOperation operationWithRequest:mRequest configuration:mConfig delegate:self]; expect = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue enqueueRequestOperation:op]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateFailed); XCTAssertEqualObjects(response.operationError.domain, TNLErrorDomain); XCTAssertEqual(response.operationError.code, TNLErrorCodeRequestOperationOperationTimedOut); XCTAssertEqual(response, op.response, @"%@ vs %@", response, op.response); #if ENABLE_TIMING_TESTS XCTAssertEqualWithAccuracy(response.metrics.queuedDuration, 0.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.allAttemptsDuration, 1.5, 1.0); XCTAssertEqualWithAccuracy(response.metrics.totalDuration, 1.5, 1.0); #endif XCTAssertEqual(2UL, response.metrics.attemptCount); } - (void)testOperationError_OperationTimeout2 { TNLMutableRequestConfiguration *mConfig = [sConfig mutableCopy]; TNLMutableHTTPRequest *mRequest = [[TNLMutableHTTPRequest alloc] initWithURL:sURL]; TNLPseudoURLResponseConfig *pseudoConfig = [[TNLPseudoURLResponseConfig alloc] init]; __block TNLResponse *response; TNLRequestOperation *op; XCTestExpectation *expect; // Operation Timeout 2 mConfig.attemptTimeout = 1.0; mConfig.operationTimeout = 2.0; mConfig.retryPolicyProvider = self; pseudoConfig.latency = 1500; [mRequest setValue:@"2000" forHTTPHeaderField:@"RETRY_DELAY"]; [self registerCannedResponseWithConfig:pseudoConfig]; op = [TNLRequestOperation operationWithRequest:mRequest configuration:mConfig delegate:self]; expect = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue enqueueRequestOperation:op]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateFailed); XCTAssertNotEqual(response.operationError.code, TNLErrorCodeRequestOperationOperationTimedOut); XCTAssertEqual(response, op.response); #if ENABLE_TIMING_TESTS XCTAssertEqualWithAccuracy(response.metrics.queuedDuration, 0.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.allAttemptsDuration, 1.5, TIME_BUFFER * 2); XCTAssertEqualWithAccuracy(response.metrics.currentAttemptDuration, 1.5, TIME_BUFFER * 2); XCTAssertEqualWithAccuracy(response.metrics.totalDuration, 1.5, TIME_BUFFER * 2); #endif XCTAssertEqual(1UL, response.metrics.attemptCount); } - (void)testOperationError_OperationTimeout3 { TNLMutableRequestConfiguration *mConfig = [sConfig mutableCopy]; TNLMutableHTTPRequest *mRequest = [[TNLMutableHTTPRequest alloc] initWithURL:sURL]; TNLPseudoURLResponseConfig *pseudoConfig = [[TNLPseudoURLResponseConfig alloc] init]; __block TNLResponse *response; TNLRequestOperation *op; XCTestExpectation *expect; // Operation Timeout 3 pseudoConfig.latency = 1500; [mRequest removeAllValuesForHTTPHeaderField:@"RETRY_DELAY"]; mConfig.attemptTimeout = 3.0; mConfig.operationTimeout = 1.2; mConfig.retryPolicyProvider = self; // [mRequest setValue:@"2000" forHTTPHeaderField:@"RETRY_DELAY"]; [self registerCannedResponseWithConfig:pseudoConfig]; op = [TNLRequestOperation operationWithRequest:mRequest configuration:mConfig delegate:self]; expect = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue enqueueRequestOperation:op]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateFailed); XCTAssertEqualObjects(response.operationError.domain, TNLErrorDomain); XCTAssertEqual(response.operationError.code, TNLErrorCodeRequestOperationOperationTimedOut); XCTAssertEqual(response, op.response); #if ENABLE_TIMING_TESTS XCTAssertEqualWithAccuracy(response.metrics.queuedDuration, 0.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.allAttemptsDuration, 1.2, TIME_BUFFER + .1); XCTAssertEqualWithAccuracy(response.metrics.currentAttemptDuration, 1.2, TIME_BUFFER + .1); XCTAssertEqualWithAccuracy(response.metrics.totalDuration, 1.2, TIME_BUFFER + .1); #endif XCTAssertEqual(1UL, response.metrics.attemptCount); } - (void)testOperationError_Disconnect { TNLMutableRequestConfiguration *mConfig = [sConfig mutableCopy]; TNLMutableHTTPRequest *mRequest = [[TNLMutableHTTPRequest alloc] initWithURL:sURL]; TNLPseudoURLResponseConfig *pseudoConfig = [[TNLPseudoURLResponseConfig alloc] init]; __block TNLResponse *response; TNLRequestOperation *op; XCTestExpectation *expect; NSError *expectedError = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorNetworkConnectionLost userInfo:nil]; // Disconnect pseudoConfig.failureError = expectedError; pseudoConfig.delay = 1000; pseudoConfig.latency = 250; [self registerCannedResponseWithConfig:pseudoConfig]; op = [TNLRequestOperation operationWithRequest:mRequest configuration:mConfig delegate:self]; expect = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue enqueueRequestOperation:op]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateFailed); XCTAssertEqual(0, response.info.statusCode); XCTAssertNotNil(response.operationError); XCTAssertEqualObjects(expectedError.domain, response.operationError.domain); XCTAssertEqual(expectedError.code, response.operationError.code); XCTAssertEqual(response, op.response); } - (void)testOperation404 { TNLMutableRequestConfiguration *mConfig = [sConfig mutableCopy]; TNLMutableHTTPRequest *mRequest = [[TNLMutableHTTPRequest alloc] initWithURL:sURL]; TNLPseudoURLResponseConfig *pseudoConfig = [[TNLPseudoURLResponseConfig alloc] init]; __block TNLResponse *response; TNLRequestOperation *op; XCTestExpectation *expect; // 404 pseudoConfig.statusCode = 404; [self registerCannedResponseWithConfig:pseudoConfig]; op = [TNLRequestOperation operationWithRequest:mRequest configuration:mConfig delegate:self]; expect = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue enqueueRequestOperation:op]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateSucceeded); XCTAssertEqual(404, response.info.statusCode); XCTAssertTrue(response.info.data.length > 0); XCTAssertEqual(1.0, op.downloadProgress); XCTAssertEqual(1.0, op.uploadProgress); XCTAssertNil(response.operationError); XCTAssertEqual(response, op.response); #if ENABLE_TIMING_TESTS XCTAssertEqualWithAccuracy(response.metrics.queuedDuration, 0.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.allAttemptsDuration, 0.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.currentAttemptDuration, 0.0, TIME_BUFFER); XCTAssertEqualWithAccuracy(response.metrics.totalDuration, 0.0, TIME_BUFFER); #endif XCTAssertEqual(1UL, response.metrics.attemptCount); } - (void)testOperation404_Cancel { TNLMutableRequestConfiguration *mConfig = [sConfig mutableCopy]; TNLMutableHTTPRequest *mRequest = [[TNLMutableHTTPRequest alloc] initWithURL:sURL]; TNLPseudoURLResponseConfig *pseudoConfig = [[TNLPseudoURLResponseConfig alloc] init]; __block TNLResponse *response; TNLRequestOperation *op; XCTestExpectation *expect; // Cancel pseudoConfig.delay = 1000; pseudoConfig.statusCode = 404; mConfig.retryPolicyProvider = self; [self registerCannedResponseWithConfig:pseudoConfig]; op = [TNLRequestOperation operationWithRequest:mRequest configuration:mConfig delegate:self]; expect = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue enqueueRequestOperation:op]; [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.5]]; [op cancelWithSource:@"FORCE_CANCEL_SOURCE"]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertTrue(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateCancelled); XCTAssertEqual(0, response.info.statusCode); XCTAssertNotNil(response.operationError); XCTAssertEqualObjects(response.operationError.domain, TNLErrorDomain); XCTAssertEqual(response.operationError.code, TNLErrorCodeRequestOperationCancelled); XCTAssertEqualObjects(response.operationError.userInfo[TNLErrorCancelSourceKey], @"FORCE_CANCEL_SOURCE"); XCTAssertEqual(response, op.response); } - (void)testOperation404_EarlyCancel1 { TNLMutableRequestConfiguration *mConfig = [sConfig mutableCopy]; TNLMutableHTTPRequest *mRequest = [[TNLMutableHTTPRequest alloc] initWithURL:sURL]; __block TNLResponse *response; TNLRequestOperation *op; XCTestExpectation *expect; // Early Cancel 1 mConfig.retryPolicyProvider = self; op = [TNLRequestOperation operationWithRequest:mRequest configuration:mConfig delegate:self]; expect = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [op cancelWithSource:@"FORCE_CANCEL_SOURCE"]; [sQueue enqueueRequestOperation:op]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertTrue(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateCancelled); XCTAssertEqual(0, response.info.statusCode); XCTAssertNotNil(response.operationError); XCTAssertEqualObjects(response.operationError.domain, TNLErrorDomain); XCTAssertEqual(response.operationError.code, TNLErrorCodeRequestOperationCancelled); XCTAssertEqualObjects(response.operationError.userInfo[TNLErrorCancelSourceKey], @"FORCE_CANCEL_SOURCE"); XCTAssertEqual(response, op.response); } - (void)testOperation404_EarlyCancel2 { TNLMutableRequestConfiguration *mConfig = [sConfig mutableCopy]; TNLMutableHTTPRequest *mRequest = [[TNLMutableHTTPRequest alloc] initWithURL:sURL]; __block TNLResponse *response; TNLRequestOperation *op; XCTestExpectation *expect; // Early Cancel 2 mConfig.retryPolicyProvider = self; op = [TNLRequestOperation operationWithRequest:mRequest configuration:mConfig delegate:self]; expect = [self expectationForNotification:@"Complete" object:op handler:^BOOL(NSNotification *notification) { response = notification.userInfo[@"response"]; return YES; }]; XCTAssertFalse(op.isCancelled); XCTAssertFalse(op.isFinished); XCTAssertFalse(op.isExecuting); XCTAssertEqual(op.state, TNLRequestOperationStateIdle); [sQueue enqueueRequestOperation:op]; [op cancelWithSource:@"FORCE_CANCEL_SOURCE"]; [self waitForExpectationsWithTimeout:5.0 handler:nil]; XCTAssertTrue(op.isCancelled); XCTAssertFalse(op.isExecuting); XCTAssertTrue(op.isFinished); XCTAssertEqual(op.state, TNLRequestOperationStateCancelled); XCTAssertEqual(0, response.info.statusCode); XCTAssertNotNil(response.operationError); XCTAssertEqualObjects(response.operationError.domain, TNLErrorDomain); XCTAssertEqual(response.operationError.code, TNLErrorCodeRequestOperationCancelled); XCTAssertEqualObjects(response.operationError.userInfo[TNLErrorCancelSourceKey], @"FORCE_CANCEL_SOURCE"); XCTAssertEqual(response, op.response); } #pragma mark Retry Policy - (BOOL)tnl_shouldRetryRequestOperation:(TNLRequestOperation *)op withResponse:(TNLResponse *)response { return response.info.statusCode != 200; } - (NSTimeInterval)tnl_delayBeforeRetryForRequestOperation:(TNLRequestOperation *)op withResponse:(TNLResponse *)response { return [[op.originalRequest.allHTTPHeaderFields tnl_objectForCaseInsensitiveKey:@"RETRY_DELAY"] integerValue]; } #pragma mark TNLRequestDelegate - (void)tnl_requestOperation:(TNLRequestOperation *)op didRedirectFromURLRequest:(NSURLRequest *)fromRequest toURLRequest:(NSURLRequest *)toRequest { [[NSNotificationCenter defaultCenter] postNotificationName:@"Redirect" object:op userInfo:@{ @"fromRequest" : fromRequest, @"toRequest" : toRequest }]; } - (void)tnl_requestOperation:(TNLRequestOperation *)op didCompleteWithResponse:(TNLResponse *)response { [[NSNotificationCenter defaultCenter] postNotificationName:@"Complete" object:op userInfo:@{ @"response" : response }]; } - (void)tnl_requestOperation:(TNLRequestOperation *)op hydrateRequest:(id<TNLRequest>)request completion:(TNLRequestHydrateCompletionBlock)complete { complete(request, nil); } @end #endif // ENABLE_PSEUDO_REQUEST_TESTS