diff options
Diffstat (limited to 'platform/ios/test/OHHTTPStubs/OHHTTPStubs/Sources/OHHTTPStubs.m')
-rw-r--r-- | platform/ios/test/OHHTTPStubs/OHHTTPStubs/Sources/OHHTTPStubs.m | 530 |
1 files changed, 530 insertions, 0 deletions
diff --git a/platform/ios/test/OHHTTPStubs/OHHTTPStubs/Sources/OHHTTPStubs.m b/platform/ios/test/OHHTTPStubs/OHHTTPStubs/Sources/OHHTTPStubs.m new file mode 100644 index 0000000000..4fa31f907b --- /dev/null +++ b/platform/ios/test/OHHTTPStubs/OHHTTPStubs/Sources/OHHTTPStubs.m @@ -0,0 +1,530 @@ +/*********************************************************************************** + * + * Copyright (c) 2012 Olivier Halligon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + ***********************************************************************************/ + +#if ! __has_feature(objc_arc) +#error This file is expected to be compiled with ARC turned ON +#endif + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Imports + +#import "OHHTTPStubs.h" + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Types & Constants + +@interface OHHTTPStubsProtocol : NSURLProtocol @end + +static NSTimeInterval const kSlotTime = 0.25; // Must be >0. We will send a chunk of the data from the stream each 'slotTime' seconds + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Private Interfaces + +@interface OHHTTPStubs() ++ (instancetype)sharedInstance; +@property(atomic, copy) NSMutableArray* stubDescriptors; +@property(atomic, copy) void (^onStubActivationBlock)(NSURLRequest*, id<OHHTTPStubsDescriptor>); +@end + +@interface OHHTTPStubsDescriptor : NSObject <OHHTTPStubsDescriptor> +@property(atomic, copy) OHHTTPStubsTestBlock testBlock; +@property(atomic, copy) OHHTTPStubsResponseBlock responseBlock; +@end + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - OHHTTPStubsDescriptor Implementation + +@implementation OHHTTPStubsDescriptor + +@synthesize name = _name; + ++(instancetype)stubDescriptorWithTestBlock:(OHHTTPStubsTestBlock)testBlock + responseBlock:(OHHTTPStubsResponseBlock)responseBlock +{ + OHHTTPStubsDescriptor* stub = [OHHTTPStubsDescriptor new]; + stub.testBlock = testBlock; + stub.responseBlock = responseBlock; + return stub; +} + +-(NSString*)description +{ + return [NSString stringWithFormat:@"<%@ %p : %@>", self.class, self, self.name]; +} + +@end + + + + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - OHHTTPStubs Implementation + +@implementation OHHTTPStubs + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Singleton methods + ++ (instancetype)sharedInstance +{ + static OHHTTPStubs *sharedInstance = nil; + + static dispatch_once_t predicate; + dispatch_once(&predicate, ^{ + sharedInstance = [[self alloc] init]; + }); + + return sharedInstance; +} + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Setup & Teardown + ++ (void)initialize +{ + if (self == [OHHTTPStubs class]) + { + [self setEnabled:YES]; + } +} +- (instancetype)init +{ + self = [super init]; + if (self) + { + _stubDescriptors = [NSMutableArray array]; + } + return self; +} + +- (void)dealloc +{ + [self.class setEnabled:NO]; +} + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Public class methods + +#pragma mark > Adding & Removing stubs + ++(id<OHHTTPStubsDescriptor>)stubRequestsPassingTest:(OHHTTPStubsTestBlock)testBlock + withStubResponse:(OHHTTPStubsResponseBlock)responseBlock +{ + OHHTTPStubsDescriptor* stub = [OHHTTPStubsDescriptor stubDescriptorWithTestBlock:testBlock + responseBlock:responseBlock]; + [OHHTTPStubs.sharedInstance addStub:stub]; + return stub; +} + ++(BOOL)removeStub:(id<OHHTTPStubsDescriptor>)stubDesc +{ + return [OHHTTPStubs.sharedInstance removeStub:stubDesc]; +} + ++(void)removeAllStubs +{ + [OHHTTPStubs.sharedInstance removeAllStubs]; +} + +#pragma mark > Disabling & Re-Enabling stubs + ++(void)setEnabled:(BOOL)enable +{ + static BOOL currentEnabledState = NO; + if (enable && !currentEnabledState) + { + [NSURLProtocol registerClass:OHHTTPStubsProtocol.class]; + } + else if (!enable && currentEnabledState) + { + [NSURLProtocol unregisterClass:OHHTTPStubsProtocol.class]; + } + currentEnabledState = enable; +} + +#if defined(__IPHONE_7_0) || defined(__MAC_10_9) ++ (void)setEnabled:(BOOL)enable forSessionConfiguration:(NSURLSessionConfiguration*)sessionConfig +{ + // Runtime check to make sure the API is available on this version + if ( [sessionConfig respondsToSelector:@selector(protocolClasses)] + && [sessionConfig respondsToSelector:@selector(setProtocolClasses:)]) + { + NSMutableArray * urlProtocolClasses = [NSMutableArray arrayWithArray:sessionConfig.protocolClasses]; + Class protoCls = OHHTTPStubsProtocol.class; + if (enable && ![urlProtocolClasses containsObject:protoCls]) + { + [urlProtocolClasses insertObject:protoCls atIndex:0]; + } + else if (!enable && [urlProtocolClasses containsObject:protoCls]) + { + [urlProtocolClasses removeObject:protoCls]; + } + sessionConfig.protocolClasses = urlProtocolClasses; + } + else + { + NSLog(@"[OHHTTPStubs] %@ is only available when running on iOS7+/OSX9+. " + @"Use conditions like 'if ([NSURLSessionConfiguration class])' to only call " + @"this method if the user is running iOS7+/OSX9+.", NSStringFromSelector(_cmd)); + } +} +#endif + +#pragma mark > Debug Methods + ++(NSArray*)allStubs +{ + return [OHHTTPStubs.sharedInstance stubDescriptors]; +} + ++(void)onStubActivation:( void(^)(NSURLRequest* request, id<OHHTTPStubsDescriptor> stub) )block +{ + [OHHTTPStubs.sharedInstance setOnStubActivationBlock:block]; +} + + + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Private instance methods + +-(void)addStub:(OHHTTPStubsDescriptor*)stubDesc +{ + @synchronized(_stubDescriptors) + { + [_stubDescriptors addObject:stubDesc]; + } +} + +-(BOOL)removeStub:(id<OHHTTPStubsDescriptor>)stubDesc +{ + BOOL handlerFound = NO; + @synchronized(_stubDescriptors) + { + handlerFound = [_stubDescriptors containsObject:stubDesc]; + [_stubDescriptors removeObject:stubDesc]; + } + return handlerFound; +} + +-(void)removeAllStubs +{ + @synchronized(_stubDescriptors) + { + [_stubDescriptors removeAllObjects]; + } +} + +- (OHHTTPStubsDescriptor*)firstStubPassingTestForRequest:(NSURLRequest*)request +{ + OHHTTPStubsDescriptor* foundStub = nil; + @synchronized(_stubDescriptors) + { + for(OHHTTPStubsDescriptor* stub in _stubDescriptors.reverseObjectEnumerator) + { + if (stub.testBlock(request)) + { + foundStub = stub; + break; + } + } + } + return foundStub; +} + +@end + + + + + + + + + + +//////////////////////////////////////////////////////////////////////////////// +#pragma mark - Private Protocol Class + +@interface OHHTTPStubsProtocol() +@property(assign) BOOL stopped; +@property(strong) OHHTTPStubsDescriptor* stub; +@property(assign) CFRunLoopRef clientRunLoop; +- (void)executeOnClientRunLoopAfterDelay:(NSTimeInterval)delayInSeconds block:(dispatch_block_t)block; +@end + +@implementation OHHTTPStubsProtocol + ++ (BOOL)canInitWithRequest:(NSURLRequest *)request +{ + return ([OHHTTPStubs.sharedInstance firstStubPassingTestForRequest:request] != nil); +} + +- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)response client:(id<NSURLProtocolClient>)client +{ + // Make super sure that we never use a cached response. + OHHTTPStubsProtocol* proto = [super initWithRequest:request cachedResponse:nil client:client]; + proto.stub = [OHHTTPStubs.sharedInstance firstStubPassingTestForRequest:request]; + return proto; +} + ++ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request +{ + return request; +} + +- (NSCachedURLResponse *)cachedResponse +{ + return nil; +} + +- (void)startLoading +{ + self.clientRunLoop = CFRunLoopGetCurrent(); + NSURLRequest* request = self.request; + id<NSURLProtocolClient> client = self.client; + + if (!self.stub) + { + NSDictionary* userInfo = [NSDictionary dictionaryWithObjectsAndKeys: + @"It seems like the stub has been removed BEFORE the response had time to be sent.", + NSLocalizedFailureReasonErrorKey, + @"For more info, see https://github.com/AliSoftware/OHHTTPStubs/wiki/OHHTTPStubs-and-asynchronous-tests", + NSLocalizedRecoverySuggestionErrorKey, + request.URL, // Stop right here if request.URL is nil + NSURLErrorFailingURLErrorKey, + nil]; + NSError* error = [NSError errorWithDomain:@"OHHTTPStubs" code:500 userInfo:userInfo]; + [client URLProtocol:self didFailWithError:error]; + return; + } + + OHHTTPStubsResponse* responseStub = self.stub.responseBlock(request); + + if (OHHTTPStubs.sharedInstance.onStubActivationBlock) + { + OHHTTPStubs.sharedInstance.onStubActivationBlock(request, self.stub); + } + + if (responseStub.error == nil) + { + NSHTTPURLResponse* urlResponse = [[NSHTTPURLResponse alloc] initWithURL:request.URL + statusCode:responseStub.statusCode + HTTPVersion:@"HTTP/1.1" + headerFields:responseStub.httpHeaders]; + + // Cookies handling + if (request.HTTPShouldHandleCookies && request.URL) + { + NSArray* cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:responseStub.httpHeaders forURL:request.URL]; + if (cookies) + { + [NSHTTPCookieStorage.sharedHTTPCookieStorage setCookies:cookies forURL:request.URL mainDocumentURL:request.mainDocumentURL]; + } + } + + + NSString* redirectLocation = (responseStub.httpHeaders)[@"Location"]; + NSURL* redirectLocationURL; + if (redirectLocation) + { + redirectLocationURL = [NSURL URLWithString:redirectLocation]; + } + else + { + redirectLocationURL = nil; + } + if (((responseStub.statusCode > 300) && (responseStub.statusCode < 400)) && redirectLocationURL) + { + NSURLRequest* redirectRequest = [NSURLRequest requestWithURL:redirectLocationURL]; + [self executeOnClientRunLoopAfterDelay:responseStub.requestTime block:^{ + if (!self.stopped) + { + [client URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:urlResponse]; + } + }]; + } + else + { + [self executeOnClientRunLoopAfterDelay:responseStub.requestTime block:^{ + if (!self.stopped) + { + [client URLProtocol:self didReceiveResponse:urlResponse cacheStoragePolicy:NSURLCacheStorageNotAllowed]; + if(responseStub.inputStream.streamStatus == NSStreamStatusNotOpen) + { + [responseStub.inputStream open]; + } + [self streamDataForClient:client + withStubResponse:responseStub + completion:^(NSError * error) + { + [responseStub.inputStream close]; + if (error==nil) + { + [client URLProtocolDidFinishLoading:self]; + } + else + { + [client URLProtocol:self didFailWithError:responseStub.error]; + } + }]; + } + }]; + } + } else { + // Send the canned error + [self executeOnClientRunLoopAfterDelay:responseStub.responseTime block:^{ + if (!self.stopped) + { + [client URLProtocol:self didFailWithError:responseStub.error]; + } + }]; + } +} + +- (void)stopLoading +{ + self.stopped = YES; +} + +typedef struct { + NSTimeInterval slotTime; + double chunkSizePerSlot; + double cumulativeChunkSize; +} OHHTTPStubsStreamTimingInfo; + +- (void)streamDataForClient:(id<NSURLProtocolClient>)client + withStubResponse:(OHHTTPStubsResponse*)stubResponse + completion:(void(^)(NSError * error))completion +{ + if ((stubResponse.dataSize>0) && stubResponse.inputStream.hasBytesAvailable && (!self.stopped)) + { + // Compute timing data once and for all for this stub + + OHHTTPStubsStreamTimingInfo timingInfo = { + .slotTime = kSlotTime, // Must be >0. We will send a chunk of data from the stream each 'slotTime' seconds + .cumulativeChunkSize = 0 + }; + + if(stubResponse.responseTime < 0) + { + // Bytes send each 'slotTime' seconds = Speed in KB/s * 1000 * slotTime in seconds + timingInfo.chunkSizePerSlot = (fabs(stubResponse.responseTime) * 1000) * timingInfo.slotTime; + } + else if (stubResponse.responseTime < kSlotTime) // includes case when responseTime == 0 + { + // We want to send the whole data quicker than the slotTime, so send it all in one chunk. + timingInfo.chunkSizePerSlot = stubResponse.dataSize; + timingInfo.slotTime = stubResponse.responseTime; + } + else + { + // Bytes send each 'slotTime' seconds = (Whole size in bytes / response time) * slotTime = speed in bps * slotTime in seconds + timingInfo.chunkSizePerSlot = ((stubResponse.dataSize/stubResponse.responseTime) * timingInfo.slotTime); + } + + [self streamDataForClient:client + fromStream:stubResponse.inputStream + timingInfo:timingInfo + completion:completion]; + } + else + { + if (completion) + { + completion(nil); + } + } +} + +- (void) streamDataForClient:(id<NSURLProtocolClient>)client + fromStream:(NSInputStream*)inputStream + timingInfo:(OHHTTPStubsStreamTimingInfo)timingInfo + completion:(void(^)(NSError * error))completion +{ + NSParameterAssert(timingInfo.chunkSizePerSlot > 0); + + if (inputStream.hasBytesAvailable && (!self.stopped)) + { + // This is needed in case we computed a non-integer chunkSizePerSlot, to avoid cumulative errors + double cumulativeChunkSizeAfterRead = timingInfo.cumulativeChunkSize + timingInfo.chunkSizePerSlot; + NSUInteger chunkSizeToRead = floor(cumulativeChunkSizeAfterRead) - floor(timingInfo.cumulativeChunkSize); + timingInfo.cumulativeChunkSize = cumulativeChunkSizeAfterRead; + + if (chunkSizeToRead == 0) + { + // Nothing to read at this pass, but probably later + [self executeOnClientRunLoopAfterDelay:timingInfo.slotTime block:^{ + [self streamDataForClient:client fromStream:inputStream + timingInfo:timingInfo completion:completion]; + }]; + } else { + uint8_t* buffer = (uint8_t*)malloc(sizeof(uint8_t)*chunkSizeToRead); + NSInteger bytesRead = [inputStream read:buffer maxLength:chunkSizeToRead]; + if (bytesRead > 0) + { + NSData * data = [NSData dataWithBytes:buffer length:bytesRead]; + // Wait for 'slotTime' seconds before sending the chunk. + // If bytesRead < chunkSizePerSlot (because we are near the EOF), adjust slotTime proportionally to the bytes remaining + [self executeOnClientRunLoopAfterDelay:((double)bytesRead / (double)chunkSizeToRead) * timingInfo.slotTime block:^{ + [client URLProtocol:self didLoadData:data]; + [self streamDataForClient:client fromStream:inputStream + timingInfo:timingInfo completion:completion]; + }]; + } + else + { + if (completion) + { + // Note: We may also arrive here with no error if we were just at the end of the stream (EOF) + // In that case, hasBytesAvailable did return YES (because at the limit of OEF) but nothing were read (because EOF) + // But then in that case inputStream.streamError will be nil so that's cool, we won't return an error anyway + completion(inputStream.streamError); + } + } + free(buffer); + } + } + else + { + if (completion) + { + completion(nil); + } + } +} + +///////////////////////////////////////////// +// Delayed execution utility methods +///////////////////////////////////////////// + +- (void)executeOnClientRunLoopAfterDelay:(NSTimeInterval)delayInSeconds block:(dispatch_block_t)block +{ + dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); + dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + CFRunLoopPerformBlock(self.clientRunLoop, kCFRunLoopDefaultMode, block); + CFRunLoopWakeUp(self.clientRunLoop); + }); +} + +@end |