Martijn The avatar Martijn The committed 658b26f Merge

Merge branch 'master' of bitbucket.org:martijnthe/nsurlconnectionvcr

Comments (0)

Files changed (2)

NSURLConnectionVCR/NSURLConnectionVCR.m

 #import <objc/runtime.h>
 #import <objc/objc.h>
 
+#ifdef __IPHONE_6_0
+#define CAST_TO_BLOCK id
+#else
+#define CAST_TO_BLOCK __bridge void *
+#endif
+
 NSString* NSURLConnectionVCRErrorDomain = @"NSURLConnectionVCRErrorDomain";
 struct objc_class;
 __strong static NSString* casettesPath;
                 theSelector = swizzleSelectors[i];
                 origMethod = class_getInstanceMethod(theClass, theSelector);
                 origImps[i] = method_getImplementation(origMethod);
-                
+
                 // Depending on your SDK, you might need a bridged cast here:
 #if TARGET_IPHONE_SIMULATOR || TARGET_OS_IPHONE
-                poseImplementation = imp_implementationWithBlock((__bridge void*)poseImplementationBlockForSelector(theSelector));
+                poseImplementation = imp_implementationWithBlock((CAST_TO_BLOCK)poseImplementationBlockForSelector(theSelector));
 #else
                 poseImplementation = imp_implementationWithBlock(poseImplementationBlockForSelector(theSelector));
 #endif
     return (origImps != NULL);
 }
 
+- (void)sendVCRCachedResultFor:(NSURLRequest *)request to:(id)realDelegate {
+  VCRCache* cache = [VCRCache loadCacheForRequest:request];
+
+  NSOperationQueue* queue = [NSOperationQueue currentQueue];
+  if (!queue) {
+    queue = [[NSOperationQueue alloc] init];
+  }
+  [queue addOperationWithBlock:^{
+    // yes, we want self and realDelegate to be retained by this block...
+    // it's perhaps a bit unnatural to call these delegate methods right after each other, but let's see if it is sufficient.
+    [realDelegate connection:(NSURLConnection*)self didReceiveResponse:cache.response];
+    NSUInteger loc = 0;
+    NSUInteger len = 256u * 1024u;
+    NSUInteger totalLen = [cache.responseBody length];
+    do {
+      if (len + loc > totalLen) {
+        len = totalLen - loc;
+      }
+      NSData* subData = [cache.responseBody subdataWithRange:NSMakeRange(loc, len)];
+      [realDelegate connection:(NSURLConnection*)self didReceiveData:subData];
+      loc += len;
+    } while (loc < totalLen);
+    [realDelegate connectionDidFinishLoading:(NSURLConnection*)self];
+  }];
+}
+
 #pragma mark Class posing / Swizzle bizz
 
 static const unsigned char swizzleCount = 3;
 
 - (id)initWithRequest:(NSURLRequest *)request delegate:(id)realDelegate startImmediately:(BOOL)startImmediately {
     VCRCache* cache = [VCRCache loadCacheForRequest:request];
-    if (cache) {
-        NSOperationQueue* queue = [NSOperationQueue currentQueue];
-        [queue addOperationWithBlock:^{
-            // yes, we want self and realDelegate to be retained by this block...
-            // it's perhaps a bit unnatural to call these delegate methods right after each other, but let's see if it is sufficient.
-            [realDelegate connection:(NSURLConnection*)self didReceiveResponse:cache.response];
-            NSUInteger loc = 0;
-            NSUInteger len = 256u * 1024u;
-            NSUInteger totalLen = [cache.responseBody length];
-            do {
-                if (len + loc > totalLen) {
-                    len = totalLen - loc;
-                }
-                NSData* subData = [cache.responseBody subdataWithRange:NSMakeRange(loc, len)];
-                [realDelegate connection:(NSURLConnection*)self didReceiveData:subData];
-                loc += len;
-            } while (loc < totalLen);
-            [realDelegate connectionDidFinishLoading:(NSURLConnection*)self];
-        }];
+    if (cache && startImmediately) {
+        [self sendVCRCachedResultFor:request to:realDelegate];
         return [self init];
     } else {
         // NSURLConnection retains its delegate until done loading --> VCRConnectionDelegate retains realDelegate, vcr, request until done loading
         delegate.realDelegate = realDelegate;
         delegate.request = request;
         delegate.vcr = self;
-        if ([connection initWithRequest:request delegate:delegate startImmediately:startImmediately]) {};  // wrapped in if() to silence `Expression result unused` compiler warning
+
+        if (!cache) {
+          if ([connection initWithRequest:request delegate:delegate startImmediately:startImmediately]) {};  // wrapped in if() to silence `Expression result unused` compiler warning
+        }
         return [self init];
     }
 }
     return [delegate realDelegate];
 }
 
+- (void)start {
+  VCRCache* cache = [VCRCache loadCacheForRequest:delegate.request];
+  if (cache) {
+    [self sendVCRCachedResultFor:delegate.request to:delegate.realDelegate];
+  } else {
+    [connection start];
+  }
+}
+
 @end
 
 @protocol NSURLConnectionDataDelegate;

NSURLConnectionVCRTests/NSURLConnectionVCRTests.m

 @property (nonatomic, readonly) NSURLResponse* response;
 @property (nonatomic, readonly) NSData* responseData;
 @property (nonatomic, readonly) NSError* error;
+@property (nonatomic, readonly) BOOL isStarted;
 @property (nonatomic, readonly) BOOL isDone;
 @end
 
     STAssertTrue([hotVCRDelegate.response isEqualIgnoringVolatileHTTPHeaders:noVCRDelegate.response], @"Response without VCR is expected to be equal to response with cold VCR.");
 }
 
+- (void)testNotStartingImmediately {
+    NSURLConnection* connection;
+    NSURLRequest* request = [NSURLRequest requestWithURL:testURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30.0];
+
+    // Do request with no VCR:
+    [NSURLConnectionVCR stopVCRWithError:nil];
+    ConnectionDelegate *noVCRDelegate = [[ConnectionDelegate alloc] init];
+    connection = [[NSURLConnection alloc] initWithRequest:request delegate:noVCRDelegate startImmediately:NO];
+    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
+    STAssertFalse(noVCRDelegate.isStarted, @"Request should not start emmediately");
+
+    [connection start];
+    while ([noVCRDelegate isDone] == NO) {
+        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
+    }
+    if (noVCRDelegate.responseData == nil || noVCRDelegate.error) {
+        STFail(@"Problem fetching testURL (%@): %@", testURL, noVCRDelegate.error);
+    }
+
+    // Do request with cold VCR:
+    [NSURLConnectionVCR startVCRWithPath:tapesPath error:nil];
+    if ([NSURLConnectionVCR hasCacheForRequest:request]) {
+        [NSURLConnectionVCR deleteCacheForRequest:request error:nil];
+    }
+    ConnectionDelegate *coldVCRDelegate = [[ConnectionDelegate alloc] init];
+    connection = [[NSURLConnection alloc] initWithRequest:request delegate:coldVCRDelegate startImmediately:NO];
+    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
+    STAssertFalse(coldVCRDelegate.isStarted, @"Request should not start emmediately");
+
+    [connection start];
+    while ([coldVCRDelegate isDone] == NO) {
+        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
+    }
+    if (coldVCRDelegate.responseData == nil || coldVCRDelegate.error) {
+        STFail(@"Problem fetching testURL with cold VCR (%@): %@", testURL, coldVCRDelegate.error);
+    }
+    STAssertTrue([coldVCRDelegate.responseData isEqual:noVCRDelegate.responseData], @"Response body without VCR is expected to be equal to response body with cold VCR.");
+    STAssertTrue([coldVCRDelegate.response isEqualIgnoringVolatileHTTPHeaders:noVCRDelegate.response], @"Response without VCR is expected to be equal to response with cold VCR.");
+
+    // Do request with hot VCR:
+    [BreakableHTTPConnection setBroken:YES];
+    ConnectionDelegate *hotVCRDelegate = [[ConnectionDelegate alloc] init];
+    connection = [[NSURLConnection alloc] initWithRequest:request delegate:hotVCRDelegate startImmediately:NO];
+    [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
+    STAssertFalse(hotVCRDelegate.isStarted, @"Request should not start emmediately");
+
+    [connection start];
+    while ([hotVCRDelegate isDone] == NO) {
+        [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
+    }
+    if (hotVCRDelegate.responseData == nil || hotVCRDelegate.error) {
+        STFail(@"Problem fetching testURL with hot VCR (%@): %@", testURL, hotVCRDelegate.error);
+    }
+    STAssertTrue([hotVCRDelegate.responseData isEqual:noVCRDelegate.responseData], @"Response body without VCR is expected to be equal to response body with cold VCR.");
+    STAssertTrue([hotVCRDelegate.response isEqualIgnoringVolatileHTTPHeaders:noVCRDelegate.response], @"Response without VCR is expected to be equal to response with cold VCR.");
+}
+
 //- (void)testZzzSleepSoInstrumentsCanFindLeaks {
 //    [NSThread sleepForTimeInterval:11.];
 //}
     NSURLResponse* response;
     NSMutableData* responseData;
     NSError* error;
+    BOOL isStarted;
     BOOL isDone;
 }
 @synthesize response;
 @synthesize responseData;
 @synthesize error;
+@synthesize isStarted;
 @synthesize isDone;
 
 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)theError {
 }
 
 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)theResponse {
+    isStarted = YES;
     response = theResponse;
 }
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.