├── .gitignore ├── Tests ├── CCLRequestReplayTests │ ├── en.lproj │ │ └── InfoPlist.strings │ ├── CCLRequestReplayTests-Prefix.pch │ ├── CCLRequestRecordingXCTest.m │ ├── CCLRequestReplayTests-Info.plist │ ├── CCLRequestRecordProtocolTest.m │ ├── CCLRequestRecordingTest.m │ ├── CCLRequestReplayProtocolTest.m │ ├── CCLRequestReplayManagerTest.m │ └── CCLRequestReplayManagerBlueprint.m ├── Podfile ├── CCLRequestReplay.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── CCLRequestReplay.xccheckout ├── CCLRequestReplay.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ ├── xcshareddata │ │ └── xcschemes │ │ │ └── CCLRequestReplayTests.xcscheme │ └── project.pbxproj └── Podfile.lock ├── .travis.yml ├── CCLRequestReplay ├── CCLRequestReplayProtocolPrivate.h ├── CCLRequestRecordProtocolPrivate.h ├── CCLRequestReplay.h ├── CCLRequestRecordProtocol.h ├── XCTest+CCLRequestReplay.h ├── CCLRequestReplayProtocol.h ├── XCTest+CCLRequestReplay.m ├── CCLRequestReplayManager+Blueprint.h ├── CCLRequestReplayProtocol.m ├── CCLRequestRecording.m ├── CCLRequestRecording.h ├── CCLRequestReplayManager.h ├── CCLRequestReplayManager.m ├── CCLRequestRecordProtocol.m └── CCLRequestReplayManager+Blueprint.m ├── Makefile ├── LICENSE ├── CCLRequestReplay.podspec └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Tests/Pods 2 | -------------------------------------------------------------------------------- /Tests/CCLRequestReplayTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Tests/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.7' 2 | 3 | pod 'CCLRequestReplay', :path => '../' 4 | 5 | pod 'Expecta' 6 | pod 'Specta' 7 | pod 'OCMock' 8 | 9 | -------------------------------------------------------------------------------- /Tests/CCLRequestReplay.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | before_install: 3 | - gem install cocoapods --no-rdoc --no-ri --no-document --quiet 4 | - gem install xcpretty --no-rdoc --no-ri --no-document --quiet 5 | - make bootstrap 6 | script: make test 7 | 8 | -------------------------------------------------------------------------------- /Tests/CCLRequestReplay.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Tests/CCLRequestReplayTests/CCLRequestReplayTests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | #import 9 | #endif 10 | -------------------------------------------------------------------------------- /CCLRequestReplay/CCLRequestReplayProtocolPrivate.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestReplayProtocolPrivate.h 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 22/01/2014. 6 | // Copyright (c) 2013 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CCLRequestReplayProtocol : NSURLProtocol 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /CCLRequestReplay/CCLRequestRecordProtocolPrivate.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestRecordProtocolPrivate.h 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 22/01/2014. 6 | // Copyright (c) 2013 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @interface CCLRequestRecordProtocol : NSURLProtocol 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT_NAME=CCLRequestReplay 2 | WORKSPACE=Tests/$(PROJECT_NAME).xcworkspace 3 | XCODEBUILD=xcodebuild -workspace $(WORKSPACE) -scheme CCLRequestReplayTests 4 | 5 | bootstrap: 6 | @printf "\e[32m=> Installing pods\033[0m\n" 7 | @cd Tests && pod install | sed "s/^/ /" 8 | 9 | test: 10 | @printf "\e[32m=> Running OS X Tests\033[0m\n" 11 | @$(XCODEBUILD) test | xcpretty -cs && exit ${PIPESTATUS[0]} 12 | 13 | test-podspec: 14 | pod lib lint CCLRequestReplay.podspec 15 | 16 | all: bootstrap test test-podspec 17 | 18 | -------------------------------------------------------------------------------- /CCLRequestReplay/CCLRequestReplay.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestReplay.h 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 31/07/2014. 6 | // Copyright (c) 2013 Cocode LTD. All rights reserved. 7 | // 8 | 9 | // Manager 10 | #import "CCLRequestReplayManager.h" 11 | #import "CCLRequestRecording.h" 12 | 13 | // Record 14 | #import "CCLRequestRecordProtocol.h" 15 | 16 | // Replay 17 | #import "CCLRequestReplayProtocol.h" 18 | 19 | // XCTest 20 | #import "XCTest+CCLRequestReplay.h" 21 | 22 | // Blueprint 23 | #import "CCLRequestReplayManager+Blueprint.h" 24 | -------------------------------------------------------------------------------- /CCLRequestReplay/CCLRequestRecordProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestRecordProtocol.h 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 22/01/2014. 6 | // Copyright (c) 2013 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "CCLRequestReplayManager.h" 11 | 12 | /** An extension to CCLRequestReplayManager to add support for recording 13 | requests and responses from real NSURLProtocol connections */ 14 | @interface CCLRequestReplayManager (Record) 15 | 16 | /// Start recording all NSURLProtocol connections 17 | - (void)record; 18 | 19 | /// Stop recording NSURLProtocol connections 20 | - (void)stopRecording; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Tests/CCLRequestReplayTests/CCLRequestRecordingXCTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestRecordingXCTest.m 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 19/04/2014. 6 | // 7 | // 8 | 9 | #define EXP_SHORTHAND YES 10 | 11 | #import 12 | #import 13 | #import 14 | 15 | #import 16 | 17 | 18 | SpecBegin(CCLRequestReplay) 19 | 20 | describe(@"the xctest extension", ^{ 21 | it(@"should add a request replay manager during setup", ^{ 22 | CCLRequestReplayManager *manager = [self requestReplayManager]; 23 | expect(manager).notTo.beNil(); 24 | }); 25 | }); 26 | 27 | SpecEnd -------------------------------------------------------------------------------- /CCLRequestReplay/XCTest+CCLRequestReplay.h: -------------------------------------------------------------------------------- 1 | // 2 | // XCTest+CCLRequestReplay.m 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 22/01/2014. 6 | // Copyright (c) 2013 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "CCLRequestReplayManager.h" 11 | 12 | 13 | /** An extension to XCTest to create and cleanup a request 14 | replay manager around your test cases. */ 15 | @interface XCTest (CCLRequestReplay) 16 | 17 | /** Returns a request replay manager 18 | @note This manager only exists inside a test case, and is cleaned up afterwards. 19 | @return A request replay manager */ 20 | - (CCLRequestReplayManager *)requestReplayManager; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /CCLRequestReplay/CCLRequestReplayProtocol.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestReplayProtocol.h 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 22/01/2014. 6 | // Copyright (c) 2013 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "CCLRequestReplayManager.h" 11 | 12 | /** An extension to CCLRequestReplayManager to add support for 13 | replaying recording requests and responses when any matching 14 | request is made using NSURLProtocol. */ 15 | @interface CCLRequestReplayManager (Replay) 16 | 17 | /// Start re-playing matching requests made using NSURLProtocol 18 | - (void)replay; 19 | 20 | /// Stop re-playing matching requests 21 | - (void)stopReplay; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /Tests/CCLRequestReplayTests/CCLRequestReplayTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | org.cocode.${PRODUCT_NAME:rfc1034identifier} 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Tests/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - CCLRequestReplay (0.9.0): 3 | - CCLRequestReplay/Blueprint 4 | - CCLRequestReplay/Manager 5 | - CCLRequestReplay/Record 6 | - CCLRequestReplay/Replay 7 | - CCLRequestReplay/XCTest 8 | - CCLRequestReplay/Blueprint (0.9.0) 9 | - CCLRequestReplay/Manager (0.9.0) 10 | - CCLRequestReplay/Record (0.9.0): 11 | - CCLRequestReplay/Manager 12 | - CCLRequestReplay/Replay (0.9.0): 13 | - CCLRequestReplay/Manager 14 | - CCLRequestReplay/XCTest (0.9.0): 15 | - CCLRequestReplay/Manager 16 | - CCLRequestReplay/Replay 17 | - Expecta (0.3.1) 18 | - OCMock (3.0.2) 19 | - Specta (0.2.1) 20 | 21 | DEPENDENCIES: 22 | - CCLRequestReplay (from `../`) 23 | - Expecta 24 | - OCMock 25 | - Specta 26 | 27 | EXTERNAL SOURCES: 28 | CCLRequestReplay: 29 | :path: ../ 30 | 31 | SPEC CHECKSUMS: 32 | CCLRequestReplay: 7120feb1c1467f832a604f152abe6cf0c1cfc88a 33 | Expecta: 03aabd0a89d8dea843baecb19a7fd7466a69a31d 34 | OCMock: b9836ab89d8d5e66cbe6333f93857037c310ee62 35 | Specta: 9141310f46b1f68b676650ff2854e1ed0b74163a 36 | 37 | COCOAPODS: 0.33.1 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Cocode LTD. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 14 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 15 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 16 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 17 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 18 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 19 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 20 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 22 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | 24 | -------------------------------------------------------------------------------- /CCLRequestReplay/XCTest+CCLRequestReplay.m: -------------------------------------------------------------------------------- 1 | // 2 | // XCTest+CCLRequestReplay.m 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 22/01/2014. 6 | // Copyright (c) 2013 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import "CCLRequestReplayProtocol.h" 12 | 13 | static CCLRequestReplayManager *_requestReplayManager; 14 | 15 | @implementation XCTest (CCLRequestReplay) 16 | 17 | + (void)initialize { 18 | if (self == [XCTest class]) { 19 | Method setUp = class_getInstanceMethod(self, @selector(setUp)); 20 | Method cclSetUp = class_getInstanceMethod(self, @selector(cclRequestReplay_setUp)); 21 | method_exchangeImplementations(setUp, cclSetUp); 22 | 23 | Method tearDown = class_getInstanceMethod(self, @selector(tearDown)); 24 | Method cclTearDown = class_getInstanceMethod(self, @selector(cclRequestReplay_tearDown)); 25 | method_exchangeImplementations(tearDown, cclTearDown); 26 | } 27 | } 28 | 29 | - (CCLRequestReplayManager *)requestReplayManager { 30 | return _requestReplayManager; 31 | } 32 | 33 | - (void)cclRequestReplay_setUp { 34 | [self cclRequestReplay_setUp]; 35 | _requestReplayManager = [[CCLRequestReplayManager alloc] init]; 36 | [_requestReplayManager replay]; 37 | } 38 | 39 | - (void)cclRequestReplay_tearDown { 40 | [self cclRequestReplay_tearDown]; 41 | [_requestReplayManager stopReplay]; 42 | _requestReplayManager = nil; 43 | } 44 | 45 | @end -------------------------------------------------------------------------------- /CCLRequestReplay/CCLRequestReplayManager+Blueprint.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestReplayManager+Blueprint.h 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 22/01/2014. 6 | // Copyright (c) 2013 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "CCLRequestReplayManager.h" 11 | 12 | 13 | /** An extension to CCLRequestReplayManager to add support to 14 | read recordings from an API Blueprint file. */ 15 | @interface CCLRequestReplayManager (Blueprint) 16 | 17 | /** A convinience method to create a manager from a blueprint file. 18 | @param URL The URL of the blueprint file on disk. 19 | @param error An error that occured when reading or parsing the API blueprint 20 | @return A new manager or nil on error. 21 | */ 22 | + (instancetype)managerFromBlueprintURL:(NSURL *)URL error:(NSError **)error; 23 | 24 | /** Add recordings from an API Blueprint file. 25 | @param URL The URL of the blueprint file on disk. 26 | @param error An error that occured when reading or parsing the API blueprint 27 | @return YES on success or NO on failure along with providing an error. 28 | */ 29 | - (BOOL)addRecordingsFromBlueprintURL:(NSURL *)URL error:(NSError **)error; 30 | 31 | /** Add recordings from API Blueprint data. 32 | @param data The API Blueprint data 33 | @param error An error that occured when reading or parsing the API blueprint 34 | @return YES on success or NO on failure along with providing an error. 35 | */ 36 | - (BOOL)addRecordingsFromBlueprintData:(NSData *)data error:(NSError **)error; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /Tests/CCLRequestReplay.xcworkspace/xcshareddata/CCLRequestReplay.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 879BD9EC-3C7D-4DC7-AE96-BEA8A6AC74BF 9 | IDESourceControlProjectName 10 | CCLRequestReplay 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | D6523866-B315-4069-BB01-522DCDE78EF2 14 | https://github.com/cocodelabs/CCLRequestReplay 15 | 16 | IDESourceControlProjectPath 17 | Tests/CCLRequestReplay.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | D6523866-B315-4069-BB01-522DCDE78EF2 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/cocodelabs/CCLRequestReplay 25 | IDESourceControlProjectVersion 26 | 110 27 | IDESourceControlProjectWCCIdentifier 28 | D6523866-B315-4069-BB01-522DCDE78EF2 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | D6523866-B315-4069-BB01-522DCDE78EF2 36 | IDESourceControlWCCName 37 | CCLRequestReplay 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /CCLRequestReplay.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |spec| 2 | spec.name = 'CCLRequestReplay' 3 | spec.version = '0.9.0' 4 | spec.license = 'BSD' 5 | spec.summary = 'Powerful library to replay HTTP responses' 6 | spec.homepage = 'https://github.com/cocodelabs/CCLRequestReplay' 7 | spec.authors = { 'Kyle Fuller' => 'inbox@kylefuller.co.uk' } 8 | spec.social_media_url = 'https://twitter.com/kylefuller' 9 | spec.source = { :git => 'https://github.com/cocodelabs/CCLRequestReplay.git', :tag => spec.version.to_s } 10 | 11 | spec.requires_arc = true 12 | spec.osx.deployment_target = '10.7' 13 | spec.ios.deployment_target = '5.0' 14 | spec.header_dir = 'CCLRequestReplay' 15 | spec.source_files = 'CCLRequestReplay/CCLRequestReplay.h' 16 | 17 | spec.subspec 'Manager' do |core_spec| 18 | core_spec.source_files = 'CCLRequestReplay/CCLRequestReplayManager.{h,m}', 'CCLRequestReplay/CCLRequestRecording.{h,m}' 19 | end 20 | 21 | spec.subspec 'Replay' do |replay_spec| 22 | replay_spec.source_files = 'CCLRequestReplay/CCLRequestReplayProtocol{Private.h,.h,.m}' 23 | replay_spec.public_header_files = 'CCLRequestReplay/CCLRequestReplayProtocol.h' 24 | replay_spec.dependency 'CCLRequestReplay/Manager' 25 | end 26 | 27 | spec.subspec 'Record' do |record_spec| 28 | record_spec.source_files = 'CCLRequestReplay/CCLRequestRecordProtocol{Private.h,.h,.m}' 29 | record_spec.public_header_files = 'CCLRequestReplay/CCLRequestRecordProtocol.h' 30 | record_spec.dependency 'CCLRequestReplay/Manager' 31 | end 32 | 33 | spec.subspec 'XCTest' do |xctest_spec| 34 | xctest_spec.source_files = 'CCLRequestReplay/XCTest+CCLRequestReplay.{h,m}' 35 | xctest_spec.frameworks = 'XCTest' 36 | xctest_spec.dependency 'CCLRequestReplay/Manager' 37 | xctest_spec.dependency 'CCLRequestReplay/Replay' 38 | end 39 | 40 | spec.subspec 'Blueprint' do |blueprint_spec| 41 | blueprint_spec.source_files = 'CCLRequestReplay/CCLRequestReplayManager+Blueprint.{h,m}' 42 | end 43 | end 44 | 45 | -------------------------------------------------------------------------------- /Tests/CCLRequestReplay.xcodeproj/xcshareddata/xcschemes/CCLRequestReplayTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 36 | 37 | 38 | 39 | 45 | 46 | 48 | 49 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CCLRequestReplay 2 | 3 | [![Build Status](https://travis-ci.org/cocodelabs/CCLRequestReplay.png?branch=master)](https://travis-ci.org/cocodelabs/CCLRequestReplay) 4 | 5 | CCLRequestReplay is greatly inspired by VCRURLConnection, however it supports 6 | creating a recording purely from code instead of having to actually record the 7 | requests manually and store them in a json file. It also supports using [API 8 | Blueprint](http://apiblueprint.org/)'s directly so you can write your iOS and 9 | OS X tests directly to your API specification without writing any extra code. 10 | 11 | ## Usage 12 | 13 | ### Recording 14 | 15 | ```objective-c 16 | CCLRequestReplayManager *manager = [[CCLRequestReplayManager alloc] init]; 17 | [manager record]; 18 | 19 | /* Make an NSURLConnection */ 20 | 21 | [manager stopRecording]; 22 | ``` 23 | 24 | ### Re-playing 25 | 26 | ```objective-c 27 | [manager replay]; 28 | 29 | /* Make an NSURLConnection, it will be served from the manager */ 30 | 31 | [manager stopReplay]; 32 | ``` 33 | 34 | ### API Blueprint 35 | 36 | To use CCLRequestReplay with API Blueprint, first you will need to convert your 37 | API Blueprint file from Markdown to JSON. This process can be done with 38 | [Snow Crash](https://github.com/apiaryio/snowcrash). Once installed, the 39 | conversion can be done by invoking it with your Markdown file as follows. 40 | 41 | ```bash 42 | $ snowcrash -o PalaverTests/Fixtures/palaver.apib.json -f json palaver-api-docs/palaver.apib 43 | ``` 44 | 45 | Then simple add your JSON file to your bundle so you can pull it out in your 46 | tests using the following: 47 | 48 | ```objective-c 49 | NSURL *blueprintURL = [[NSBundle mainBundle] URLForResource:@"fitnessfirst.apib" withExtension:@"json"]; 50 | CCLRequestReplayManager *replayManager = [CCLRequestReplayManager managerFromBlueprintURL:blueprintURL error:nil]; 51 | [replayManager replay]; 52 | ``` 53 | 54 | Be sure to keep the manager alive across all your tests that need it. 55 | 56 | ## Installation 57 | 58 | Installation is simple, add the following to your Podfile: 59 | 60 | ```ruby 61 | pod 'CCLRequestReplay', :git => 'https://github.com/cocodelabs/CCLRequestReplay' 62 | ``` 63 | 64 | ## License 65 | 66 | CCLRequestReplay is released under the BSD license. See [LICENSE](LICENSE). 67 | 68 | -------------------------------------------------------------------------------- /Tests/CCLRequestReplayTests/CCLRequestRecordProtocolTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestRecordProtocol.h 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 26/01/2014. 6 | // Copyright (c) 2014 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #define EXP_SHORTHAND YES 10 | 11 | #import 12 | #import 13 | #import 14 | 15 | #import 16 | #import 17 | 18 | 19 | SpecBegin(CCLRequestHTTPRecordProtocol) 20 | 21 | describe(@"CCLRequestRecordProtocol", ^{ 22 | it(@"should inherit from NSURLProtocol", ^{ 23 | expect([[NSClassFromString(@"CCLRequestRecordProtocol") alloc] init]).to.beKindOf([NSURLProtocol class]); 24 | }); 25 | 26 | it(@"should init with a request", ^{ 27 | NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://test.com/"]]; 28 | expect([NSClassFromString(@"CCLRequestRecordProtocol") canInitWithRequest:request]).to.beTruthy(); 29 | }); 30 | 31 | it(@"should record an errored request", ^{ 32 | CCLRequestReplayManager *manager = [[CCLRequestReplayManager alloc] init]; 33 | 34 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://cocode.org/error"]]; 35 | NSError *error = [NSError errorWithDomain:@"test.error" code:0 userInfo:nil]; 36 | [manager addRequest:request error:error]; 37 | 38 | [manager replay]; 39 | [manager record]; 40 | 41 | [NSURLConnection connectionWithRequest:request delegate:nil]; 42 | 43 | expect([[manager recordings] count]).will.equal(2); 44 | }); 45 | 46 | it(@"should record a successful request", ^{ 47 | CCLRequestReplayManager *manager = [[CCLRequestReplayManager alloc] init]; 48 | 49 | NSURL *URL = [NSURL URLWithString:@"http://cocode.org/success"]; 50 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL]; 51 | NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:URL statusCode:201 HTTPVersion:@"1.1" headerFields:@{}]; 52 | [manager addRequest:request response:response data:nil]; 53 | 54 | [manager replay]; 55 | [manager record]; 56 | 57 | [NSURLConnection connectionWithRequest:request delegate:nil]; 58 | 59 | expect([[manager recordings] count]).will.equal(2); 60 | }); 61 | }); 62 | 63 | SpecEnd 64 | -------------------------------------------------------------------------------- /CCLRequestReplay/CCLRequestReplayProtocol.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestReplayProtocol.m 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 22/01/2014. 6 | // Copyright (c) 2013 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #import "CCLRequestReplayProtocolPrivate.h" 10 | #import "CCLRequestReplayProtocol.h" 11 | #import "CCLRequestRecording.h" 12 | 13 | 14 | @implementation CCLRequestReplayProtocol 15 | 16 | static NSMutableSet *_managers; 17 | 18 | + (void)addManager:(CCLRequestReplayManager *)manager { 19 | if (_managers == nil) { 20 | [NSURLProtocol registerClass:[CCLRequestReplayProtocol class]]; 21 | _managers = [NSMutableSet new]; 22 | } 23 | 24 | [_managers addObject:manager]; 25 | } 26 | 27 | + (void)removeManager:(CCLRequestReplayManager *)manager { 28 | [_managers removeObject:manager]; 29 | 30 | if (_managers && [_managers count] == 0) { 31 | [NSURLProtocol unregisterClass:[CCLRequestReplayProtocol class]]; 32 | _managers = nil; 33 | } 34 | } 35 | 36 | + (id)recordingForRequest:(NSURLRequest *)request { 37 | if (_managers) { 38 | for (CCLRequestReplayManager *manager in _managers) { 39 | for (id recording in [manager recordings]) { 40 | if ([recording matchesRequest:request]) { 41 | return recording; 42 | } 43 | } 44 | } 45 | } 46 | 47 | return nil; 48 | } 49 | 50 | + (BOOL)canInitWithRequest:(NSURLRequest *)request { 51 | return [self recordingForRequest:request] != nil; 52 | } 53 | 54 | + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { 55 | return request; 56 | } 57 | 58 | - (void)startLoading { 59 | id recording = [[self class] recordingForRequest:[self request]]; 60 | 61 | NSError *error = [recording errorForRequest:[self request]]; 62 | 63 | if (error) { 64 | [[self client] URLProtocol:self didFailWithError:error]; 65 | } else { 66 | NSURLResponse *response = [recording responseForRequest:[self request]]; 67 | NSData *data = [recording dataForRequest:[self request]]; 68 | 69 | [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; 70 | 71 | if (data) { 72 | [[self client] URLProtocol:self didLoadData:data]; 73 | } 74 | 75 | [[self client] URLProtocolDidFinishLoading:self]; 76 | } 77 | } 78 | 79 | - (void)stopLoading { 80 | ; 81 | } 82 | 83 | @end 84 | 85 | @implementation CCLRequestReplayManager (Replay) 86 | 87 | - (void)replay { 88 | [CCLRequestReplayProtocol addManager:self]; 89 | } 90 | 91 | - (void)stopReplay { 92 | [CCLRequestReplayProtocol removeManager:self]; 93 | } 94 | 95 | @end 96 | 97 | -------------------------------------------------------------------------------- /CCLRequestReplay/CCLRequestRecording.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestRecording.h 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 22/01/2014. 6 | // Copyright (c) 2013 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #import "CCLRequestRecording.h" 10 | 11 | 12 | @implementation CCLRequestRecording 13 | 14 | - (instancetype)initWithRequest:(NSURLRequest *)request response:(NSURLResponse *)response data:(NSData *)data { 15 | if (self = [super init]) { 16 | _request = request; 17 | _response = response; 18 | _data = data; 19 | } 20 | 21 | return self; 22 | } 23 | 24 | - (instancetype)initWithRequest:(NSURLRequest *)request error:(NSError *)error { 25 | if (self = [super init]) { 26 | _request = request; 27 | _error = error; 28 | } 29 | 30 | return self; 31 | } 32 | 33 | #pragma mark - NSCoding 34 | 35 | + (BOOL)supportsSecureCoding { 36 | return YES; 37 | } 38 | 39 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 40 | if (self = [super init]) { 41 | _request = [aDecoder decodeObjectOfClass:[NSURLRequest class] forKey:@"request"]; 42 | _response = [aDecoder decodeObjectOfClass:[NSURLResponse class] forKey:@"response"]; 43 | _data = [aDecoder decodeObjectOfClass:[NSData class] forKey:@"data"]; 44 | _error = [aDecoder decodeObjectOfClass:[NSError class] forKey:@"error"]; 45 | } 46 | 47 | return self; 48 | } 49 | 50 | - (void)encodeWithCoder:(NSCoder *)aCoder { 51 | [aCoder encodeObject:_request forKey:@"request"]; 52 | [aCoder encodeObject:_response forKey:@"response"]; 53 | [aCoder encodeObject:_data forKey:@"data"]; 54 | [aCoder encodeObject:_error forKey:@"error"]; 55 | } 56 | 57 | #pragma mark - Equality 58 | 59 | - (BOOL)isEqual:(id)object { 60 | return (self == object) || ([object isKindOfClass:[self class]] && [self isEqualToRecording:object]); 61 | } 62 | 63 | - (BOOL)isEqualToRecording:(CCLRequestRecording *)recording { 64 | return [self.request isEqual:recording.request] && 65 | ((!self.error && !recording.error) || [self.error isEqual:recording.error]) && 66 | ((!self.response && !recording.response) || [self.response isEqual:recording.response]) && 67 | ((!self.data && !recording.data) || [self.data isEqual:recording.data]); 68 | } 69 | 70 | - (NSUInteger)hash { 71 | return [self.request hash]; 72 | } 73 | 74 | #pragma mark - Matching 75 | 76 | - (BOOL)matchesRequest:(NSURLRequest *)request { 77 | return [[[self request] URL] isEqual:[request URL]] && [[[self request] HTTPMethod] isEqualToString:[request HTTPMethod]]; 78 | } 79 | 80 | #pragma mark - Error 81 | 82 | - (NSError *)errorForRequest:(NSURLRequest *)request { 83 | return [self error]; 84 | } 85 | 86 | #pragma mark - Response + Data 87 | 88 | - (NSURLResponse *)responseForRequest:(NSURLRequest *)request { 89 | return [self response]; 90 | } 91 | 92 | - (NSData *)dataForRequest:(NSURLRequest *)request { 93 | return [self data]; 94 | } 95 | 96 | @end 97 | -------------------------------------------------------------------------------- /CCLRequestReplay/CCLRequestRecording.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestRecording.h 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 22/01/2014. 6 | // Copyright (c) 2013 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** The `CCLRequestRecordingProtocol` protocol is adopted by an object that 12 | can be used to replay a response or error for a matching NSURLRequest. */ 13 | @protocol CCLRequestRecordingProtocol 14 | 15 | /** A method to determine if the recording matches the supplied request 16 | @param request The NSURLRequest to compare 17 | @return YES if this recording matches the supplied request 18 | */ 19 | - (BOOL)matchesRequest:(NSURLRequest *)request; 20 | 21 | /** A method which can return an error as a response to the given request. 22 | @param request The NSURLRequest 23 | @return An error or nil to instead return a response 24 | */ 25 | - (NSError *)errorForRequest:(NSURLRequest *)request; 26 | 27 | /** A method which can return a response for the given request. 28 | @param request The request to respond to. 29 | @return A response, or nil if there was instead an error. 30 | */ 31 | - (NSURLResponse *)responseForRequest:(NSURLRequest *)request; 32 | 33 | /** A method which can optionally return the HTTP body for the given request. 34 | @param request The request to respond to. 35 | @return The data, or nil if there is not a HTTP body for the response. 36 | */ 37 | - (NSData *)dataForRequest:(NSURLRequest *)request; 38 | 39 | @end 40 | 41 | /// An implementation of the CCLRequestRecordingProtocol protocol 42 | @interface CCLRequestRecording : NSObject 43 | 44 | /** Initialize a request recording with a request and a response. 45 | @param request The request to match 46 | @param response The response to replay 47 | @param data Optional HTTP Body data to replay 48 | @return A request recording 49 | */ 50 | - (instancetype)initWithRequest:(NSURLRequest *)request response:(NSURLResponse *)response data:(NSData *)data; 51 | 52 | /** Initialize a request recording with a request and an error. 53 | @param request The request to match 54 | @param error An error to replay for the matching request 55 | @return A request recording 56 | */ 57 | - (instancetype)initWithRequest:(NSURLRequest *)request error:(NSError *)error; 58 | 59 | /** Returns a Boolean value that indicates whether a given recording is equal to the receiver. 60 | @param recording The recording with which to compare the receiver. 61 | @return YES if the recording is equal to the receiver. 62 | */ 63 | - (BOOL)isEqualToRecording:(CCLRequestRecording *)recording; 64 | 65 | /** The request to match, required. */ 66 | @property (nonatomic, copy, readonly) NSURLRequest *request; 67 | 68 | /** The response to replay, may be nil if this is an erroring response. */ 69 | @property (nonatomic, copy, readonly) NSURLResponse *response; 70 | 71 | /** The response data to replay, may be nil. */ 72 | @property (nonatomic, copy, readonly) NSData *data; 73 | 74 | /** An error to replay if there is no response. */ 75 | @property (nonatomic, copy, readonly) NSError *error; 76 | 77 | @end 78 | -------------------------------------------------------------------------------- /CCLRequestReplay/CCLRequestReplayManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestReplayManager.h 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 22/01/2014. 6 | // Copyright (c) 2013 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "CCLRequestRecording.h" 11 | 12 | /** `CCLRequestReplayManager` is a class to manage request recordings 13 | and allow you to record or replay recordings. 14 | */ 15 | @interface CCLRequestReplayManager : NSObject 16 | 17 | /** Returns an array of all registered recordings. */ 18 | - (NSArray *)recordings; 19 | 20 | /** Add a recording to the managers recordings 21 | @param recording The recording to add 22 | */ 23 | - (void)addRecording:(id)recording; 24 | 25 | /** A convinience method to add a recording by supplying a request, response and data. 26 | @param request The request to match 27 | @param response The response to replay when the request matches 28 | @param data The HTTP body for the response 29 | @return Returns the created recording that was added to the receiver 30 | */ 31 | - (CCLRequestRecording *)addRequest:(NSURLRequest *)request response:(NSHTTPURLResponse *)response data:(NSData *)data; 32 | 33 | /** A convinience method to add a recording by supplying a request which results in an error. 34 | @param request The request to match 35 | @param error The error to replay when the request matches 36 | @return Returns the created recording that was added to the receiver 37 | */ 38 | - (CCLRequestRecording *)addRequest:(NSURLRequest *)request error:(NSError *)error; 39 | 40 | /** Remove a recording from the replay manager 41 | @param recording The recording to remove from the replay manager 42 | */ 43 | - (void)removeRecording:(id)recording; 44 | 45 | /// Remove every registered recording on the replay manager 46 | - (void)removeAllRecordings; 47 | 48 | @end 49 | 50 | @interface CCLRequestReplayManager (Convenience) 51 | 52 | /** A convenience method to add a recording with a HTTP response. 53 | @param request The request to match 54 | @param statusCode The status code for the HTTP response 55 | @param headers Optional headers for the HTTP response 56 | @param contentType The content type for the HTTP response if there is content 57 | @param content The HTTP body for the HTTP response 58 | @return Returns the created recording that was added to the receiver 59 | */ 60 | - (CCLRequestRecording *)addRequest:(NSURLRequest *)request responseWithStatusCode:(NSUInteger)statusCode headers:(NSDictionary *)headers contentType:(NSString *)contentType content:(NSData *)content; 61 | 62 | /** A convenience method to add a recording with a HTTP response with a JSON payload. 63 | @param request The request to match 64 | @param statusCode The status code for the HTTP response 65 | @param headers Optional headers for the HTTP response 66 | @param content The content to be JSON encoded 67 | @return Returns the created recording that was added to the receiver 68 | */ 69 | - (CCLRequestRecording *)addRequest:(NSURLRequest *)request JSONResponseWithStatusCode:(NSUInteger)statusCode headers:(NSDictionary *)headers content:(id)content; 70 | 71 | @end 72 | -------------------------------------------------------------------------------- /CCLRequestReplay/CCLRequestReplayManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestReplayManager.m 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 22/01/2014. 6 | // Copyright (c) 2013 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #import "CCLRequestReplayManager.h" 10 | #import "CCLRequestRecording.h" 11 | #import "CCLRequestReplayProtocol.h" 12 | 13 | 14 | @interface CCLRequestReplayManager () { 15 | NSMutableArray *_recordings; 16 | } 17 | 18 | @end 19 | 20 | @implementation CCLRequestReplayManager 21 | 22 | - (instancetype)init { 23 | if (self = [super init]) { 24 | _recordings = [NSMutableArray new]; 25 | } 26 | 27 | return self; 28 | } 29 | 30 | - (NSArray *)recordings { 31 | return [_recordings copy]; 32 | } 33 | 34 | #pragma mark - NSSecureCoding 35 | 36 | + (BOOL)supportsSecureCoding { 37 | return YES; 38 | } 39 | 40 | - (instancetype)initWithCoder:(NSCoder *)aDecoder { 41 | if (self = [super init]) { 42 | _recordings = [[aDecoder decodeObjectOfClass:[NSArray class] forKey:@"recordings"] mutableCopy]; 43 | } 44 | 45 | return self; 46 | } 47 | 48 | - (void)encodeWithCoder:(NSCoder *)aCoder { 49 | [aCoder encodeObject:[self.recordings copy] forKey:@"recordings"]; 50 | } 51 | 52 | #pragma mark - Managing recordings 53 | 54 | - (void)addRecording:(id)recording { 55 | [_recordings addObject:recording]; 56 | } 57 | 58 | - (void)removeRecording:(id)recording { 59 | [_recordings removeObject:recording]; 60 | } 61 | 62 | - (void)removeAllRecordings { 63 | [_recordings removeAllObjects]; 64 | } 65 | 66 | #pragma mark - Adding a recording for a request/response 67 | 68 | - (CCLRequestRecording *)addRequest:(NSURLRequest *)request response:(NSURLResponse *)response data:(NSData *)data { 69 | CCLRequestRecording *recording = [[CCLRequestRecording alloc] initWithRequest:request response:response data:data]; 70 | [self addRecording:recording]; 71 | return recording; 72 | } 73 | 74 | - (CCLRequestRecording *)addRequest:(NSURLRequest *)request error:(NSError *)error { 75 | CCLRequestRecording *recording = [[CCLRequestRecording alloc] initWithRequest:request error:error]; 76 | [self addRecording:recording]; 77 | return recording; 78 | } 79 | 80 | @end 81 | 82 | @implementation CCLRequestReplayManager (Convenience) 83 | 84 | - (CCLRequestRecording *)addRequest:(NSURLRequest *)request responseWithStatusCode:(NSUInteger)statusCode headers:(NSDictionary *)headers contentType:(NSString *)contentType content:(NSData *)content { 85 | if (contentType && headers[@"Content-Type"] == nil) { 86 | NSMutableDictionary *mutableHeaders = headers ? [headers mutableCopy] : [NSMutableDictionary dictionary]; 87 | mutableHeaders[@"Content-Type"] = contentType; 88 | headers = [mutableHeaders copy]; 89 | } 90 | 91 | NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:request.URL statusCode:statusCode HTTPVersion:@"1.1" headerFields:headers]; 92 | return [self addRequest:request response:response data:content]; 93 | } 94 | 95 | - (CCLRequestRecording *)addRequest:(NSURLRequest *)request JSONResponseWithStatusCode:(NSUInteger)statusCode headers:(NSDictionary *)headers content:(id)content { 96 | NSData *data; 97 | 98 | if (content) { 99 | data = [NSJSONSerialization dataWithJSONObject:content options:0 error:NULL]; 100 | } 101 | 102 | return [self addRequest:request responseWithStatusCode:statusCode headers:headers contentType:@"application/json; charset=utf8" content:data]; 103 | } 104 | 105 | @end 106 | -------------------------------------------------------------------------------- /CCLRequestReplay/CCLRequestRecordProtocol.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestRecordProtocol.m 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 22/01/2014. 6 | // Copyright (c) 2013 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #import "CCLRequestRecordProtocolPrivate.h" 10 | #import "CCLRequestRecordProtocol.h" 11 | #import "CCLRequestReplayManager.h" 12 | #import "CCLRequestRecording.h" 13 | 14 | 15 | @interface CCLRequestRecordProtocol () 16 | 17 | @property (nonatomic, strong) NSURLConnection *connection; 18 | 19 | @property (nonatomic, strong) NSURLResponse *response; 20 | @property (nonatomic, strong) NSMutableData *data; 21 | 22 | @end 23 | 24 | 25 | static NSString * const CCLRequestRecordProtocolIsRecordingKey = @"CCLRequestRecordProtocolIsRecordingKey"; 26 | 27 | @implementation CCLRequestRecordProtocol 28 | 29 | static NSMutableSet *_managers; 30 | 31 | + (void)addManager:(CCLRequestReplayManager *)manager { 32 | if (_managers == nil) { 33 | [NSURLProtocol registerClass:[CCLRequestRecordProtocol class]]; 34 | _managers = [NSMutableSet new]; 35 | } 36 | 37 | [_managers addObject:manager]; 38 | } 39 | 40 | + (void)removeManager:(CCLRequestReplayManager *)manager { 41 | [_managers removeObject:manager]; 42 | 43 | if (_managers && [_managers count] == 0) { 44 | [NSURLProtocol unregisterClass:[CCLRequestRecordProtocol class]]; 45 | _managers = nil; 46 | } 47 | } 48 | 49 | + (void)addRecording:(CCLRequestRecording *)recording { 50 | if (_managers) { 51 | for (CCLRequestReplayManager *manager in _managers) { 52 | [manager addRecording:recording]; 53 | } 54 | } 55 | } 56 | 57 | + (BOOL)canInitWithRequest:(NSURLRequest *)request { 58 | return ![[self propertyForKey:CCLRequestRecordProtocolIsRecordingKey inRequest:request] boolValue]; 59 | } 60 | 61 | + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request { 62 | return request; 63 | } 64 | 65 | - (void)startLoading { 66 | NSMutableURLRequest *request = [[self request] mutableCopy]; 67 | [[self class] setProperty:@YES forKey:CCLRequestRecordProtocolIsRecordingKey inRequest:request]; 68 | 69 | self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; 70 | } 71 | 72 | - (void)stopLoading { 73 | [self.connection cancel]; 74 | } 75 | 76 | #pragma mark - NSURLConnectionDelegate(s) 77 | 78 | - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { 79 | CCLRequestRecording *recording = [[CCLRequestRecording alloc] initWithRequest:[self request] error:error]; 80 | [[self class] addRecording:recording]; 81 | 82 | [[self client] URLProtocol:self didFailWithError:error]; 83 | } 84 | 85 | - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { 86 | self.response = response; 87 | self.data = [[NSMutableData alloc] init]; 88 | 89 | [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; 90 | } 91 | 92 | - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { 93 | [self.data appendData:data]; 94 | [[self client] URLProtocol:self didLoadData:data]; 95 | } 96 | 97 | - (void)connectionDidFinishLoading:(NSURLConnection *)connection { 98 | CCLRequestRecording *recording = [[CCLRequestRecording alloc] initWithRequest:self.request response:self.response data:[self.data copy]]; 99 | [[self class] addRecording:recording]; 100 | 101 | [[self client] URLProtocolDidFinishLoading:self]; 102 | } 103 | 104 | @end 105 | 106 | @implementation CCLRequestReplayManager (Recording) 107 | 108 | - (void)record { 109 | [CCLRequestRecordProtocol addManager:self]; 110 | } 111 | 112 | - (void)stopRecording { 113 | [CCLRequestRecordProtocol removeManager:self]; 114 | } 115 | 116 | @end 117 | -------------------------------------------------------------------------------- /CCLRequestReplay/CCLRequestReplayManager+Blueprint.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestReplayManager+Blueprint.m 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 22/01/2014. 6 | // Copyright (c) 2013 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #import "CCLRequestReplayManager+Blueprint.h" 10 | 11 | 12 | @implementation CCLRequestReplayManager (Blueprint) 13 | 14 | + (instancetype)managerFromBlueprintURL:(NSURL *)URL error:(NSError **)error { 15 | CCLRequestReplayManager *manager = [[CCLRequestReplayManager alloc] init]; 16 | 17 | if ([manager addRecordingsFromBlueprintURL:URL error:error] == NO) { 18 | manager = nil; 19 | } 20 | 21 | return manager; 22 | } 23 | 24 | - (BOOL)addRecordingsFromBlueprintURL:(NSURL *)URL error:(NSError **)error { 25 | NSData *blueprintData = [NSData dataWithContentsOfURL:URL options:0 error:error]; 26 | BOOL didAddRecordingsFromFile = NO; 27 | 28 | if (blueprintData) { 29 | didAddRecordingsFromFile = [self addRecordingsFromBlueprintData:blueprintData error:error]; 30 | } 31 | 32 | return didAddRecordingsFromFile; 33 | } 34 | 35 | - (BOOL)addRecordingsFromBlueprintData:(NSData *)data error:(NSError **)error { 36 | BOOL didAddRecordingsFromData = NO; 37 | NSDictionary *blueprint = [NSJSONSerialization JSONObjectWithData:data options:0 error:error]; 38 | 39 | if (blueprint) { 40 | if ([blueprint isKindOfClass:[NSDictionary class]]) { 41 | didAddRecordingsFromData = [self addRecordingsFromBlueprintDictionary:blueprint error:error]; 42 | } else { 43 | // error 44 | } 45 | } 46 | 47 | return didAddRecordingsFromData; 48 | } 49 | 50 | - (BOOL)addRecordingsFromBlueprintDictionary:(NSDictionary *)dictionary error:(NSError **)error { 51 | NSURL *baseURL; 52 | 53 | if ([dictionary objectForKey:@"metadata"]) { 54 | for (NSDictionary *metadata in dictionary[@"metadata"]) { 55 | if ([metadata[@"name"] isEqualToString:@"HOST"]) { 56 | baseURL = [NSURL URLWithString:metadata[@"value"]]; 57 | break; 58 | } 59 | } 60 | } 61 | 62 | if ([dictionary objectForKey:@"resourceGroups"]) { 63 | for (NSDictionary *resourceGroup in dictionary[@"resourceGroups"]) { 64 | for (NSDictionary *resource in resourceGroup[@"resources"]) { 65 | NSString *uriTemplate = resource[@"uriTemplate"]; 66 | 67 | for (NSDictionary *action in resource[@"actions"]) { 68 | NSString *method = action[@"method"]; 69 | NSURL *URL = [[NSURL URLWithString:uriTemplate relativeToURL:baseURL] absoluteURL]; 70 | NSMutableURLRequest *baseRequest = [[NSMutableURLRequest alloc] initWithURL:URL]; 71 | baseRequest.HTTPMethod = method; 72 | 73 | for (NSDictionary *example in action[@"examples"]) { 74 | for (NSDictionary *requestDictionary in example[@"requests"]) { 75 | if ([requestDictionary objectForKey:@"headers"]) { 76 | for (NSDictionary *header in requestDictionary[@"headers"]) { 77 | [baseRequest setValue:header[@"value"] forHTTPHeaderField:header[@"name"]]; 78 | } 79 | } 80 | 81 | // We don't care about the body right no 82 | break; // TODO parse all the requests 83 | } 84 | 85 | for (NSDictionary *responseDictionary in example[@"responses"]) { 86 | NSInteger statusCode = [responseDictionary[@"name"] integerValue]; 87 | NSMutableDictionary *headers = [[NSMutableDictionary alloc] init]; 88 | 89 | if ([responseDictionary objectForKey:@"headers"]) { 90 | for (NSDictionary *header in responseDictionary[@"headers"]) { 91 | [headers setValue:header[@"value"] forKey:header[@"name"]]; 92 | } 93 | } 94 | 95 | NSData *data; 96 | 97 | if ([responseDictionary objectForKey:@"body"]) { 98 | NSString *body = responseDictionary[@"body"]; 99 | data = [body dataUsingEncoding:NSUTF8StringEncoding]; 100 | } 101 | 102 | NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:URL statusCode:statusCode HTTPVersion:@"1.1" headerFields:[headers copy]]; 103 | [self addRequest:baseRequest response:response data:data]; 104 | break; // TODO parse all the responses 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | 112 | return YES; 113 | } 114 | 115 | @end 116 | -------------------------------------------------------------------------------- /Tests/CCLRequestReplayTests/CCLRequestRecordingTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestRecordingTest.m 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 26/01/2014. 6 | // Copyright (c) 2014 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #define EXP_SHORTHAND YES 10 | 11 | #import 12 | #import 13 | #import 14 | 15 | #import 16 | 17 | 18 | SpecBegin(CCLRequestRecording) 19 | 20 | describe(@"CCLRequestRecording", ^{ 21 | it(@"should conform to CCLRequestRecordingProtocol", ^{ 22 | expect([[CCLRequestRecording alloc] init]).to.conformTo(@protocol(CCLRequestRecordingProtocol)); 23 | }); 24 | 25 | it(@"should should match for a URL", ^{ 26 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://test.com/"]]; 27 | CCLRequestRecording *recording = [[CCLRequestRecording alloc] initWithRequest:request error:nil]; 28 | 29 | expect([recording matchesRequest:request]).to.beTruthy(); 30 | }); 31 | 32 | it(@"shouldn't match when method is different", ^{ 33 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://test.com/"]]; 34 | CCLRequestRecording *recording = [[CCLRequestRecording alloc] initWithRequest:request error:nil]; 35 | 36 | NSMutableURLRequest *mutableRequest = [request mutableCopy]; 37 | [mutableRequest setHTTPMethod:@"POST"]; 38 | 39 | expect([recording matchesRequest:mutableRequest]).to.beFalsy(); 40 | }); 41 | 42 | it(@"should compare two recordings with same request and error as the same", ^{ 43 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://test.com/"]]; 44 | NSError *error = [NSError errorWithDomain:@"ErrorDomain" code:0 userInfo:nil]; 45 | 46 | CCLRequestRecording *recordingA = [[CCLRequestRecording alloc] initWithRequest:request error:error]; 47 | CCLRequestRecording *recordingB = [[CCLRequestRecording alloc] initWithRequest:request error:error]; 48 | 49 | expect([recordingA isEqualToRecording:recordingB]).to.beTruthy(); 50 | }); 51 | 52 | it(@"should compare two recordings with same request, response and data as the same", ^{ 53 | NSURL *URL = [NSURL URLWithString:@"http://test.com/"]; 54 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL]; 55 | NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:URL statusCode:200 HTTPVersion:@"1.1" headerFields:@{}]; 56 | NSData *data = [@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding]; 57 | 58 | CCLRequestRecording *recordingA = [[CCLRequestRecording alloc] initWithRequest:request response:response data:data]; 59 | CCLRequestRecording *recordingB = [[CCLRequestRecording alloc] initWithRequest:request response:response data:data]; 60 | 61 | expect([recordingA isEqualToRecording:recordingB]).to.beTruthy(); 62 | }); 63 | 64 | it(@"should hash two recordings with identical request and error with as the same hash", ^{ 65 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://test.com/"]]; 66 | NSError *error = [NSError errorWithDomain:@"ErrorDomain" code:0 userInfo:nil]; 67 | 68 | CCLRequestRecording *recordingA = [[CCLRequestRecording alloc] initWithRequest:request error:error]; 69 | CCLRequestRecording *recordingB = [[CCLRequestRecording alloc] initWithRequest:request error:error]; 70 | 71 | expect([recordingA hash]).to.equal([recordingB hash]); 72 | }); 73 | 74 | it(@"should hash two recordings with identical request and error with as the same hash", ^{ 75 | NSURL *URL = [NSURL URLWithString:@"http://test.com/"]; 76 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL]; 77 | NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:URL statusCode:200 HTTPVersion:@"1.1" headerFields:@{}]; 78 | NSData *data = [@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding]; 79 | 80 | CCLRequestRecording *recordingA = [[CCLRequestRecording alloc] initWithRequest:request response:response data:data]; 81 | CCLRequestRecording *recordingB = [[CCLRequestRecording alloc] initWithRequest:request response:response data:data]; 82 | 83 | expect([recordingA hash]).to.equal([recordingB hash]); 84 | }); 85 | 86 | it(@"should should support secure coding", ^{ 87 | expect([CCLRequestRecording supportsSecureCoding]).to.beTruthy(); 88 | }); 89 | 90 | it(@"should be able to encode and decode", ^{ 91 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://test.com/"]]; 92 | CCLRequestRecording *recording = [[CCLRequestRecording alloc] initWithRequest:request error:nil]; 93 | NSData *archivedRecording = [NSKeyedArchiver archivedDataWithRootObject:recording]; 94 | CCLRequestRecording *unarchivedRecording = [NSKeyedUnarchiver unarchiveObjectWithData:archivedRecording]; 95 | 96 | expect([recording isEqualToRecording:unarchivedRecording]).to.beTruthy(); 97 | }); 98 | }); 99 | 100 | SpecEnd 101 | -------------------------------------------------------------------------------- /Tests/CCLRequestReplayTests/CCLRequestReplayProtocolTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestReplayProtocolTest.m 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 22/01/2014. 6 | // Copyright (c) 2014 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #define EXP_SHORTHAND YES 10 | 11 | #import 12 | #import 13 | #import 14 | #import 15 | 16 | #import 17 | #import 18 | #import 19 | 20 | 21 | SpecBegin(CCLRequestReplayProtocol) 22 | 23 | describe(@"CCLRequestReplayProtocol", ^{ 24 | it(@"should inherit from NSURLProtocol", ^{ 25 | expect([[NSClassFromString(@"CCLRequestReplayProtocol") alloc] init]).to.beKindOf([NSURLProtocol class]); 26 | }); 27 | 28 | it(@"should init with matching request", ^{ 29 | CCLRequestReplayManager *manager = [[CCLRequestReplayManager alloc] init]; 30 | 31 | NSURL *URL = [NSURL URLWithString:@"http://cocode.org/test"]; 32 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL]; 33 | NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:URL statusCode:201 HTTPVersion:@"1.1" headerFields:@{}]; 34 | [manager addRequest:request response:response data:nil]; 35 | 36 | [manager replay]; 37 | 38 | expect([NSClassFromString(@"CCLRequestReplayProtocol") canInitWithRequest:request]).to.beTruthy(); 39 | 40 | [manager stopReplay]; 41 | }); 42 | 43 | it(@"shouldn't init after stop replaying", ^{ 44 | CCLRequestReplayManager *manager = [[CCLRequestReplayManager alloc] init]; 45 | 46 | NSURL *URL = [NSURL URLWithString:@"http://cocode.org/test"]; 47 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL]; 48 | NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:URL statusCode:201 HTTPVersion:@"1.1" headerFields:@{}]; 49 | [manager addRequest:request response:response data:nil]; 50 | 51 | [manager replay]; 52 | [manager stopReplay]; 53 | 54 | expect([NSClassFromString(@"CCLRequestReplayProtocol") canInitWithRequest:request]).to.beFalsy(); 55 | }); 56 | 57 | it(@"should canonicalize request", ^{ 58 | NSURLRequest *request = [[NSURLRequest alloc] init]; 59 | NSURLRequest *cannonicalizedRequest = [NSClassFromString(@"CCLRequestReplayProtocol") canonicalRequestForRequest:request]; 60 | expect(cannonicalizedRequest).notTo.beNil(); 61 | }); 62 | 63 | it(@"should replay response for matching request", ^{ 64 | CCLRequestReplayManager *manager = [[CCLRequestReplayManager alloc] init]; 65 | 66 | NSURL *badURL = [NSURL URLWithString:@"http://cocode.org/badTest"]; 67 | NSURLRequest *badRequest = [[NSURLRequest alloc] initWithURL:badURL]; 68 | NSHTTPURLResponse *badResponse = [[NSHTTPURLResponse alloc] initWithURL:badURL statusCode:201 HTTPVersion:@"1.1" headerFields:@{}]; 69 | [manager addRequest:badRequest response:badResponse data:nil]; 70 | 71 | NSURL *URL = [NSURL URLWithString:@"http://cocode.org/test"]; 72 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL]; 73 | NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:URL statusCode:201 HTTPVersion:@"1.1" headerFields:@{}]; 74 | [manager addRequest:request response:response data:nil]; 75 | 76 | [manager replay]; 77 | 78 | OCMockObject *client = [OCMockObject mockForProtocol:@protocol(NSURLProtocolClient)]; 79 | NSURLProtocol *protocol = [[NSClassFromString(@"CCLRequestReplayProtocol") alloc] initWithRequest:request cachedResponse:nil client:client]; 80 | [[client expect] URLProtocol:protocol didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; 81 | [[client expect] URLProtocolDidFinishLoading:protocol]; 82 | 83 | [protocol startLoading]; 84 | 85 | [client verify]; 86 | [manager stopReplay]; 87 | }); 88 | 89 | it(@"should replay error for matching request", ^{ 90 | CCLRequestReplayManager *manager = [[CCLRequestReplayManager alloc] init]; 91 | 92 | NSURL *badURL = [NSURL URLWithString:@"http://cocode.org/badTest"]; 93 | NSURLRequest *badRequest = [[NSURLRequest alloc] initWithURL:badURL]; 94 | NSHTTPURLResponse *badResponse = [[NSHTTPURLResponse alloc] initWithURL:badURL statusCode:201 HTTPVersion:@"1.1" headerFields:@{}]; 95 | [manager addRequest:badRequest response:badResponse data:nil]; 96 | 97 | NSURL *URL = [NSURL URLWithString:@"http://cocode.org/test"]; 98 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL]; 99 | NSError *error = [NSError errorWithDomain:@"Failure" code:200 userInfo:nil]; 100 | [manager addRequest:request error:error]; 101 | 102 | [manager replay]; 103 | 104 | OCMockObject *client = [OCMockObject mockForProtocol:@protocol(NSURLProtocolClient)]; 105 | NSURLProtocol *protocol = [[NSClassFromString(@"CCLRequestReplayProtocol") alloc] initWithRequest:request cachedResponse:nil client:client]; 106 | [[client expect] URLProtocol:protocol didFailWithError:error]; 107 | 108 | [protocol startLoading]; 109 | 110 | [client verify]; 111 | [manager stopReplay]; 112 | }); 113 | }); 114 | 115 | SpecEnd 116 | -------------------------------------------------------------------------------- /Tests/CCLRequestReplayTests/CCLRequestReplayManagerTest.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestReplayManagerTest.m 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 22/01/2014. 6 | // Copyright (c) 2014 Cocode LTD. All rights reserved. 7 | // 8 | 9 | #define EXP_SHORTHAND YES 10 | 11 | #import 12 | #import 13 | #import 14 | 15 | #import 16 | #import 17 | #import 18 | 19 | 20 | SpecBegin(CLRequestReplayManager) 21 | 22 | describe(@"CLRequestReplayManager", ^{ 23 | it(@"should not have any recordings by default", ^{ 24 | CCLRequestReplayManager *sut = [[CCLRequestReplayManager alloc] init]; 25 | expect([[sut recordings] count]).to.equal(0); 26 | }); 27 | 28 | it(@"should be able to register a request with a response", ^{ 29 | NSURLRequest *request = [[NSURLRequest alloc] init]; 30 | NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] init]; 31 | NSData *data = [@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding]; 32 | 33 | CCLRequestReplayManager *sut = [[CCLRequestReplayManager alloc] init]; 34 | CCLRequestRecording *recording = [sut addRequest:request response:response data:data]; 35 | 36 | CCLRequestRecording *urlResponse = [[sut recordings] firstObject]; 37 | expect(recording).to.equal(urlResponse); 38 | expect([urlResponse request]).to.equal(request); 39 | expect([urlResponse response]).to.equal(response); 40 | expect([urlResponse data]).to.equal(data); 41 | expect([urlResponse error]).to.beNil(); 42 | }); 43 | 44 | it(@"should be able to register a request with an error", ^{ 45 | NSURLRequest *request = [[NSURLRequest alloc] init]; 46 | NSError *error = [[NSError alloc] init]; 47 | 48 | CCLRequestReplayManager *sut = [[CCLRequestReplayManager alloc] init]; 49 | CCLRequestRecording *recording = [sut addRequest:request error:error]; 50 | 51 | CCLRequestRecording *urlResponse = [[sut recordings] firstObject]; 52 | expect(recording).to.equal(urlResponse); 53 | expect([urlResponse request]).to.equal(request); 54 | expect([urlResponse response]).to.beNil(); 55 | expect([urlResponse data]).to.beNil(); 56 | expect([urlResponse error]).to.equal(error); 57 | }); 58 | 59 | it(@"should be able to remove a recording", ^{ 60 | NSURLRequest *requestA = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://testA.com"]]; 61 | NSURLRequest *requestB = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://testB.com"]]; 62 | NSError *error = [[NSError alloc] init]; 63 | 64 | CCLRequestReplayManager *sut = [[CCLRequestReplayManager alloc] init]; 65 | [sut addRequest:requestA error:error]; 66 | [sut addRequest:requestB error:error]; 67 | 68 | [sut removeRecording:[[sut recordings] firstObject]]; 69 | 70 | expect([[sut recordings] count]).to.equal(1); 71 | }); 72 | 73 | it(@"should be able to remove all recordings", ^{ 74 | NSURLRequest *request = [[NSURLRequest alloc] init]; 75 | NSError *error = [[NSError alloc] init]; 76 | 77 | CCLRequestReplayManager *sut = [[CCLRequestReplayManager alloc] init]; 78 | [sut addRequest:request error:error]; 79 | [sut addRequest:request error:error]; 80 | 81 | [sut removeAllRecordings]; 82 | 83 | expect([[sut recordings] count]).to.equal(0); 84 | }); 85 | 86 | it(@"should register a protocol for replaying", ^{ 87 | NSURL *URL = [NSURL URLWithString:@"ccl://cocode.org/test"]; 88 | NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL]; 89 | NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:URL statusCode:200 HTTPVersion:@"1.1" headerFields:@{}]; 90 | NSData *data = [@"Hello World!" dataUsingEncoding:NSUTF8StringEncoding]; 91 | 92 | CCLRequestReplayManager *sut = [[CCLRequestReplayManager alloc] init]; 93 | [sut addRequest:request response:response data:data]; 94 | [sut replay]; 95 | 96 | expect([NSURLConnection canHandleRequest:request]).to.beTruthy(); 97 | 98 | [sut stopReplay]; 99 | }); 100 | 101 | it(@"should support secure encoding", ^{ 102 | expect([CCLRequestReplayManager supportsSecureCoding]).to.beTruthy(); 103 | }); 104 | 105 | it(@"should should encode and decode a manager", ^{ 106 | CCLRequestReplayManager *manager = [[CCLRequestReplayManager alloc] init]; 107 | [manager addRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://test/"]] error:nil]; 108 | 109 | NSData *archivedManager = [NSKeyedArchiver archivedDataWithRootObject:manager]; 110 | CCLRequestReplayManager *unarchivedManager = [NSKeyedUnarchiver unarchiveObjectWithData:archivedManager]; 111 | 112 | expect([manager recordings]).to.equal([unarchivedManager recordings]); 113 | }); 114 | }); 115 | 116 | describe(@"CLRequestReplayManager convenience extension", ^{ 117 | it(@"should be able to add a recording with status code, headers and content", ^{ 118 | NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://test/helloworld"]]; 119 | NSData *content = [@"Hello World" dataUsingEncoding:NSUTF8StringEncoding]; 120 | 121 | CCLRequestReplayManager *manager = [[CCLRequestReplayManager alloc] init]; 122 | CCLRequestRecording *recording = [manager addRequest:request responseWithStatusCode:200 headers:@{@"Accepts": @"plain/text"} contentType:@"plain/text" content:content]; 123 | 124 | expect(recording).notTo.beNil(); 125 | expect(@[recording]).to.equal(manager.recordings); 126 | expect(recording.request).to.equal(request); 127 | expect(recording.error).to.beNil(); 128 | expect(recording.response).notTo.beNil(); 129 | 130 | NSHTTPURLResponse *response = (NSHTTPURLResponse *)recording.response; 131 | expect(response.statusCode).to.equal(200); 132 | expect(response.allHeaderFields).to.equal(@{@"Accepts": @"plain/text", @"Content-Type": @"plain/text"}); 133 | expect(recording.data).to.equal(content); 134 | }); 135 | 136 | it(@"should be able to add a JSON recording", ^{ 137 | NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://test/helloworld.json"]]; 138 | 139 | CCLRequestReplayManager *manager = [[CCLRequestReplayManager alloc] init]; 140 | CCLRequestRecording *recording = [manager addRequest:request JSONResponseWithStatusCode:200 headers:@{@"Accepts": @"application/json"} content:@{@"text": @"Hello World"}]; 141 | 142 | expect(recording).notTo.beNil(); 143 | expect(@[recording]).to.equal(manager.recordings); 144 | expect(recording.request).to.equal(request); 145 | expect(recording.error).to.beNil(); 146 | expect(recording.response).notTo.beNil(); 147 | 148 | NSHTTPURLResponse *response = (NSHTTPURLResponse *)recording.response; 149 | expect(response.statusCode).to.equal(200); 150 | expect(response.allHeaderFields).to.equal(@{@"Accepts": @"application/json", @"Content-Type": @"application/json; charset=utf8"}); 151 | expect(recording.data).to.equal([@"{\"text\":\"Hello World\"}" dataUsingEncoding:NSUTF8StringEncoding]); 152 | }); 153 | }); 154 | 155 | SpecEnd 156 | -------------------------------------------------------------------------------- /Tests/CCLRequestReplayTests/CCLRequestReplayManagerBlueprint.m: -------------------------------------------------------------------------------- 1 | // 2 | // CCLRequestReplayManagerBlueprint.m 3 | // CCLRequestReplay 4 | // 5 | // Created by Kyle Fuller on 20/04/2014. 6 | // 7 | // 8 | 9 | #define EXP_SHORTHAND YES 10 | 11 | #import 12 | #import 13 | #import 14 | 15 | #import 16 | 17 | static NSString * const BlueprintFixture = @"ew0KICAiX3ZlcnNpb24iOiAiMi4wIiwNCiAgIm1ldGFkYXRhIjogWw0KICAgIHsNCiAgICAgICJuYW1lIjogIkZPUk1BVCIsDQogICAgICAidmFsdWUiOiAiMUEiDQogICAgfSwNCiAgICB7DQogICAgICAibmFtZSI6ICJIT1NUIiwNCiAgICAgICJ2YWx1ZSI6ICJodHRwOi8vdHJ1bmsuY29jb2Fwb2RzLm9yZyINCiAgICB9DQogIF0sDQogICJuYW1lIjogImNvY29hcG9kcyIsDQogICJkZXNjcmlwdGlvbiI6ICJUaGUgQ29jb2FQb2RzIFRydW5rIEFQSS5cblxuIiwNCiAgInJlc291cmNlR3JvdXBzIjogWw0KICAgIHsNCiAgICAgICJuYW1lIjogIlNlc3Npb25zIiwNCiAgICAgICJkZXNjcmlwdGlvbiI6ICJBUEkgZm9yIG1hbmFnaW5nIHNlc3Npb25zLlxuXG4iLA0KICAgICAgInJlc291cmNlcyI6IFsNCiAgICAgICAgew0KICAgICAgICAgICJuYW1lIjogIlNlc3Npb24gQ29sbGVjdGlvbiIsDQogICAgICAgICAgImRlc2NyaXB0aW9uIjogIiIsDQogICAgICAgICAgInVyaVRlbXBsYXRlIjogIi9hcGkvdjEvc2Vzc2lvbnMiLA0KICAgICAgICAgICJtb2RlbCI6IHt9LA0KICAgICAgICAgICJwYXJhbWV0ZXJzIjogW10sDQogICAgICAgICAgImFjdGlvbnMiOiBbDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICJuYW1lIjogIkdldCB5b3VyIGN1cnJlbnQgU2Vzc2lvbiIsDQogICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICIiLA0KICAgICAgICAgICAgICAibWV0aG9kIjogIkdFVCIsDQogICAgICAgICAgICAgICJwYXJhbWV0ZXJzIjogW10sDQogICAgICAgICAgICAgICJleGFtcGxlcyI6IFsNCiAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAibmFtZSI6ICIiLA0KICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIiIsDQogICAgICAgICAgICAgICAgICAicmVxdWVzdHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICIiLA0KICAgICAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICIiLA0KICAgICAgICAgICAgICAgICAgICAgICJoZWFkZXJzIjogWw0KICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJBdXRob3JpemF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgInZhbHVlIjogIlRva2VuIDUyN2QxMWZlNDI5ZjM0MjZjYjhkYmViYTE4M2EwZDgwIg0KICAgICAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgICAgIF0sDQogICAgICAgICAgICAgICAgICAgICAgImJvZHkiOiAiIiwNCiAgICAgICAgICAgICAgICAgICAgICAic2NoZW1hIjogIiINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAgICJyZXNwb25zZXMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICIyMDAiLA0KICAgICAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICIiLA0KICAgICAgICAgICAgICAgICAgICAgICJoZWFkZXJzIjogWw0KICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJDb250ZW50LVR5cGUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiYXBwbGljYXRpb24vanNvbiINCiAgICAgICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICAgICAgICJib2R5IjogInsgXCJlbWFpbFwiOiBcImt5bGVAY29jb2Fwb2RzLm9yZ1wiLCBcIm5hbWVcIjogXCJLeWxlIEZ1bGxlclwiLCBcInRva2VuXCI6IFwiNTI3ZDExZmU0MjlmMzQyNmNiOGRiZWJhMTgzYTBkODBcIiB9XG4iLA0KICAgICAgICAgICAgICAgICAgICAgICJzY2hlbWEiOiAiIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgICBdDQogICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICBdDQogICAgICAgICAgICB9LA0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAibmFtZSI6ICJDcmVhdGUgYSBTZXNzaW9uIiwNCiAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIiIsDQogICAgICAgICAgICAgICJtZXRob2QiOiAiUE9TVCIsDQogICAgICAgICAgICAgICJwYXJhbWV0ZXJzIjogW10sDQogICAgICAgICAgICAgICJleGFtcGxlcyI6IFsNCiAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAibmFtZSI6ICIiLA0KICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIiIsDQogICAgICAgICAgICAgICAgICAicmVxdWVzdHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICIiLA0KICAgICAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICIiLA0KICAgICAgICAgICAgICAgICAgICAgICJoZWFkZXJzIjogWw0KICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJDb250ZW50LVR5cGUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiYXBwbGljYXRpb24vanNvbiINCiAgICAgICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICAgICAgICJib2R5IjogInsgXCJlbWFpbFwiOiBcImt5bGVAY29jb2Fwb2RzLm9yZ1wiLCBcIm5hbWVcIjogXCJLeWxlIEZ1bGxlclwiIH1cbiIsDQogICAgICAgICAgICAgICAgICAgICAgInNjaGVtYSI6ICIiDQogICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgIF0sDQogICAgICAgICAgICAgICAgICAicmVzcG9uc2VzIjogWw0KICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgIm5hbWUiOiAiMjAxIiwNCiAgICAgICAgICAgICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiIiwNCiAgICAgICAgICAgICAgICAgICAgICAiaGVhZGVycyI6IFsNCiAgICAgICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIm5hbWUiOiAiQ29udGVudC1UeXBlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgInZhbHVlIjogImFwcGxpY2F0aW9uL2pzb24iDQogICAgICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAgICAgICAiYm9keSI6ICJ7IFwiZW1haWxcIjogXCJreWxlQGNvY29hcG9kcy5vcmdcIiwgXCJuYW1lXCI6IFwiS3lsZSBGdWxsZXJcIiwgXCJ0b2tlblwiOiBcIjUyN2QxMWZlNDI5ZjM0MjZjYjhkYmViYTE4M2EwZDgwXCIgfVxuIiwNCiAgICAgICAgICAgICAgICAgICAgICAic2NoZW1hIjogIiINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgXQ0KICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgXQ0KICAgICAgICAgICAgfSwNCiAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgIm5hbWUiOiAiRGVsZXRlcyB5b3VyIGN1cnJlbnQgU2Vzc2lvbiIsDQogICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICIiLA0KICAgICAgICAgICAgICAibWV0aG9kIjogIkRFTEVURSIsDQogICAgICAgICAgICAgICJwYXJhbWV0ZXJzIjogW10sDQogICAgICAgICAgICAgICJleGFtcGxlcyI6IFsNCiAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAibmFtZSI6ICIiLA0KICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIiIsDQogICAgICAgICAgICAgICAgICAicmVxdWVzdHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICIiLA0KICAgICAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICIiLA0KICAgICAgICAgICAgICAgICAgICAgICJoZWFkZXJzIjogWw0KICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJBdXRob3JpemF0aW9uIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgInZhbHVlIjogIlRva2VuIDUyN2QxMWZlNDI5ZjM0MjZjYjhkYmViYTE4M2EwZDgwIg0KICAgICAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgICAgIF0sDQogICAgICAgICAgICAgICAgICAgICAgImJvZHkiOiAiIiwNCiAgICAgICAgICAgICAgICAgICAgICAic2NoZW1hIjogIiINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgXSwNCiAgICAgICAgICAgICAgICAgICJyZXNwb25zZXMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICIyMDEiLA0KICAgICAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICIiLA0KICAgICAgICAgICAgICAgICAgICAgICJoZWFkZXJzIjogW10sDQogICAgICAgICAgICAgICAgICAgICAgImJvZHkiOiAiIiwNCiAgICAgICAgICAgICAgICAgICAgICAic2NoZW1hIjogIiINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgXQ0KICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgXQ0KICAgICAgICAgICAgfQ0KICAgICAgICAgIF0NCiAgICAgICAgfQ0KICAgICAgXQ0KICAgIH0sDQogICAgew0KICAgICAgIm5hbWUiOiAiUG9kcyIsDQogICAgICAiZGVzY3JpcHRpb24iOiAiQVBJIGZvciBtYW5hZ2luZyBwb2RzLlxuXG4jIyBQT1NUIHBvZHNcblxuKyBSZXF1ZXN0IChhcHBsaWNhdGlvbi9qc29uKVxuXG4gICAgICAgIHt9XG5cblxuKyBSZXNwb25zZSAyMDEgKGFwcGxpY2F0aW9uL2pzb24pXG5cbiAgICAgICAge31cbiAgICAgICAgXG4iLA0KICAgICAgInJlc291cmNlcyI6IFsNCiAgICAgICAgew0KICAgICAgICAgICJuYW1lIjogIiIsDQogICAgICAgICAgImRlc2NyaXB0aW9uIjogIiIsDQogICAgICAgICAgInVyaVRlbXBsYXRlIjogIi9hcGkvdjEvcG9kcy97bmFtZX0iLA0KICAgICAgICAgICJtb2RlbCI6IHt9LA0KICAgICAgICAgICJwYXJhbWV0ZXJzIjogW10sDQogICAgICAgICAgImFjdGlvbnMiOiBbDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICJuYW1lIjogIiIsDQogICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICIiLA0KICAgICAgICAgICAgICAibWV0aG9kIjogIkdFVCIsDQogICAgICAgICAgICAgICJwYXJhbWV0ZXJzIjogW10sDQogICAgICAgICAgICAgICJleGFtcGxlcyI6IFsNCiAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAibmFtZSI6ICIiLA0KICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIiIsDQogICAgICAgICAgICAgICAgICAicmVxdWVzdHMiOiBbXSwNCiAgICAgICAgICAgICAgICAgICJyZXNwb25zZXMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICIyMDAiLA0KICAgICAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICIiLA0KICAgICAgICAgICAgICAgICAgICAgICJoZWFkZXJzIjogWw0KICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJDb250ZW50LVR5cGUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiYXBwbGljYXRpb24vanNvbiINCiAgICAgICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICAgICAgICJib2R5IjogIntcbiAgXCJ2ZXJzaW9uc1wiOiBbXG4gICAge1xuICAgICAgXCJjcmVhdGVkX2F0XCI6IFwiMjAxNC0wNC0xMyAyMzowNDozNyArMDAwMFwiLFxuICAgICAgXCJuYW1lXCI6IFwiMS4wLjBcIlxuICAgIH1cbiAgXSxcbiAgXCJvd25lcnNcIjogW1xuICAgIHtcbiAgICAgIFwiY3JlYXRlZF9hdFwiOiBcIjIwMTQtMDQtMTMgMjI6NDk6NTYgKzAwMDBcIixcbiAgICAgIFwiZW1haWxcIjogXCJpbmJveEBreWxlZnVsbGVyLmNvLnVrXCIsXG4gICAgICBcIm5hbWVcIjogXCJLeWxlIEZ1bGxlclwiXG4gICAgfVxuICBdXG59XG4iLA0KICAgICAgICAgICAgICAgICAgICAgICJzY2hlbWEiOiAiIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgICBdDQogICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICBdDQogICAgICAgICAgICB9DQogICAgICAgICAgXQ0KICAgICAgICB9LA0KICAgICAgICB7DQogICAgICAgICAgIm5hbWUiOiAiIiwNCiAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiIiwNCiAgICAgICAgICAidXJpVGVtcGxhdGUiOiAiL2FwaS92MS9wb2RzL3tuYW1lfS92ZXJzaW9ucy97dmVyc2lvbn0iLA0KICAgICAgICAgICJtb2RlbCI6IHt9LA0KICAgICAgICAgICJwYXJhbWV0ZXJzIjogW10sDQogICAgICAgICAgImFjdGlvbnMiOiBbDQogICAgICAgICAgICB7DQogICAgICAgICAgICAgICJuYW1lIjogIiIsDQogICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICIiLA0KICAgICAgICAgICAgICAibWV0aG9kIjogIkdFVCIsDQogICAgICAgICAgICAgICJwYXJhbWV0ZXJzIjogW10sDQogICAgICAgICAgICAgICJleGFtcGxlcyI6IFsNCiAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAibmFtZSI6ICIiLA0KICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIiIsDQogICAgICAgICAgICAgICAgICAicmVxdWVzdHMiOiBbXSwNCiAgICAgICAgICAgICAgICAgICJyZXNwb25zZXMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICIyMDAiLA0KICAgICAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICIiLA0KICAgICAgICAgICAgICAgICAgICAgICJoZWFkZXJzIjogWw0KICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJDb250ZW50LVR5cGUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiYXBwbGljYXRpb24vanNvbiINCiAgICAgICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICAgICAgICJib2R5IjogIntcbiAgXCJtZXNzYWdlc1wiOiBbXG4gICAge1xuICAgICAgXCIyMDE0LTA0LTEzIDIzOjA0OjM3ICswMDAwXCI6IFwiVmVyc2lvbiBgTlNBdHRyaWJ1dGVkU3RyaW5nK0NDTEZvcm1hdCAxLjAuMCcgY3JlYXRlZCB2aWEgR2l0aHViIGhvb2suXCJcbiAgICB9XG4gIF0sXG4gIFwiZGF0YV91cmxcIjogXCJodHRwczpcL1wvcmF3LmdpdGh1Yi5jb21cL0NvY29hUG9kc1wvU3BlY3NcLzZkMmM3MWU3Njg1NWVkMDY2ZmU0OGRiNmFlM2JlZTliYmY1OTcyYmFcL1NwZWNzXC9OU0F0dHJpYnV0ZWRTdHJpbmcrQ0NMRm9ybWF0XC8xLjAuMFwvTlNBdHRyaWJ1dGVkU3RyaW5nK0NDTEZvcm1hdC5wb2RzcGVjLmpzb25cIlxufVxuIiwNCiAgICAgICAgICAgICAgICAgICAgICAic2NoZW1hIjogIiINCiAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgXQ0KICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgXQ0KICAgICAgICAgICAgfQ0KICAgICAgICAgIF0NCiAgICAgICAgfSwNCiAgICAgICAgew0KICAgICAgICAgICJuYW1lIjogIiIsDQogICAgICAgICAgImRlc2NyaXB0aW9uIjogIiIsDQogICAgICAgICAgInVyaVRlbXBsYXRlIjogIi9hcGkvdjEvcG9kcy97bmFtZX0vb3duZXJzIiwNCiAgICAgICAgICAibW9kZWwiOiB7fSwNCiAgICAgICAgICAicGFyYW1ldGVycyI6IFtdLA0KICAgICAgICAgICJhY3Rpb25zIjogWw0KICAgICAgICAgICAgew0KICAgICAgICAgICAgICAibmFtZSI6ICIiLA0KICAgICAgICAgICAgICAiZGVzY3JpcHRpb24iOiAiIiwNCiAgICAgICAgICAgICAgIm1ldGhvZCI6ICJQQVRDSCIsDQogICAgICAgICAgICAgICJwYXJhbWV0ZXJzIjogW10sDQogICAgICAgICAgICAgICJleGFtcGxlcyI6IFsNCiAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAibmFtZSI6ICIiLA0KICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIiIsDQogICAgICAgICAgICAgICAgICAicmVxdWVzdHMiOiBbDQogICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICIiLA0KICAgICAgICAgICAgICAgICAgICAgICJkZXNjcmlwdGlvbiI6ICIiLA0KICAgICAgICAgICAgICAgICAgICAgICJoZWFkZXJzIjogWw0KICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAibmFtZSI6ICJDb250ZW50LVR5cGUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAidmFsdWUiOiAiYXBwbGljYXRpb24vanNvbiINCiAgICAgICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICAgICAgICJib2R5IjogInt9XG4iLA0KICAgICAgICAgICAgICAgICAgICAgICJzY2hlbWEiOiAiIg0KICAgICAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgICAgICBdLA0KICAgICAgICAgICAgICAgICAgInJlc3BvbnNlcyI6IFsNCiAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICJuYW1lIjogIjIwMCIsDQogICAgICAgICAgICAgICAgICAgICAgImRlc2NyaXB0aW9uIjogIiIsDQogICAgICAgICAgICAgICAgICAgICAgImhlYWRlcnMiOiBbDQogICAgICAgICAgICAgICAgICAgICAgICB7DQogICAgICAgICAgICAgICAgICAgICAgICAgICJuYW1lIjogIkNvbnRlbnQtVHlwZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICJ2YWx1ZSI6ICJhcHBsaWNhdGlvbi9qc29uIg0KICAgICAgICAgICAgICAgICAgICAgICAgfQ0KICAgICAgICAgICAgICAgICAgICAgIF0sDQogICAgICAgICAgICAgICAgICAgICAgImJvZHkiOiAie31cbiIsDQogICAgICAgICAgICAgICAgICAgICAgInNjaGVtYSI6ICIiDQogICAgICAgICAgICAgICAgICAgIH0NCiAgICAgICAgICAgICAgICAgIF0NCiAgICAgICAgICAgICAgICB9DQogICAgICAgICAgICAgIF0NCiAgICAgICAgICAgIH0NCiAgICAgICAgICBdDQogICAgICAgIH0NCiAgICAgIF0NCiAgICB9DQogIF0NCn0NCg=="; 18 | 19 | 20 | SpecBegin(CCLRequestReplayManagerBlueprint) 21 | 22 | describe(@"CLRequestReplayManager", ^{ 23 | __block CCLRequestReplayManager *manager; 24 | 25 | beforeEach(^{ 26 | manager = [[CCLRequestReplayManager alloc] init]; 27 | }); 28 | 29 | it(@"should load a blueprint with all recordings", ^{ 30 | NSData *data = [[NSData alloc] initWithBase64EncodedString:BlueprintFixture options:0]; 31 | 32 | NSError *error; 33 | BOOL didAddRecordings = [manager addRecordingsFromBlueprintData:data error:&error]; 34 | 35 | expect(didAddRecordings).to.beTruthy(); 36 | expect(error).to.beNil(); 37 | expect([[manager recordings] count]).to.equal(6); 38 | }); 39 | }); 40 | 41 | SpecEnd 42 | -------------------------------------------------------------------------------- /Tests/CCLRequestReplay.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 7762CD7C1936BD1600D57ED8 /* CCLRequestRecordingXCTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 7762CD7B1936BD1600D57ED8 /* CCLRequestRecordingXCTest.m */; }; 11 | 77756B3E18965FE400A55DB0 /* CCLRequestRecordingTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 77756B3D18965FE400A55DB0 /* CCLRequestRecordingTest.m */; }; 12 | 77756B401896626600A55DB0 /* CCLRequestRecordProtocolTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 77756B3F1896626600A55DB0 /* CCLRequestRecordProtocolTest.m */; }; 13 | 77D7FD781903416E00D76614 /* CCLRequestReplayManagerBlueprint.m in Sources */ = {isa = PBXBuildFile; fileRef = 77D7FD771903416E00D76614 /* CCLRequestReplayManagerBlueprint.m */; }; 14 | 77F7102F1891A40D00828235 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 77F7102E1891A40D00828235 /* XCTest.framework */; }; 15 | 77F710351891A40D00828235 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 77F710331891A40D00828235 /* InfoPlist.strings */; }; 16 | 77F710371891A40D00828235 /* CCLRequestReplayManagerTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 77F710361891A40D00828235 /* CCLRequestReplayManagerTest.m */; }; 17 | 77F7103D1891BC8200828235 /* CCLRequestReplayProtocolTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 77F7103C1891BC8200828235 /* CCLRequestReplayProtocolTest.m */; }; 18 | F56A9D47B4424EA5B363B21C /* libPods.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C65DFCB8E8DB4891902A78A9 /* libPods.a */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | 7762CD7B1936BD1600D57ED8 /* CCLRequestRecordingXCTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCLRequestRecordingXCTest.m; sourceTree = ""; }; 23 | 77756B3D18965FE400A55DB0 /* CCLRequestRecordingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCLRequestRecordingTest.m; sourceTree = ""; }; 24 | 77756B3F1896626600A55DB0 /* CCLRequestRecordProtocolTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCLRequestRecordProtocolTest.m; sourceTree = ""; }; 25 | 77D7FD771903416E00D76614 /* CCLRequestReplayManagerBlueprint.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCLRequestReplayManagerBlueprint.m; sourceTree = ""; }; 26 | 77F7102B1891A40D00828235 /* CCLRequestReplayTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CCLRequestReplayTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | 77F7102E1891A40D00828235 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 28 | 77F710321891A40D00828235 /* CCLRequestReplayTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "CCLRequestReplayTests-Info.plist"; sourceTree = ""; }; 29 | 77F710341891A40D00828235 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 30 | 77F710361891A40D00828235 /* CCLRequestReplayManagerTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CCLRequestReplayManagerTest.m; sourceTree = ""; }; 31 | 77F710381891A40D00828235 /* CCLRequestReplayTests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CCLRequestReplayTests-Prefix.pch"; sourceTree = ""; }; 32 | 77F7103C1891BC8200828235 /* CCLRequestReplayProtocolTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CCLRequestReplayProtocolTest.m; sourceTree = ""; }; 33 | AE0C05A30E1A465B91E56DC3 /* Pods.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.xcconfig; path = Pods/Pods.xcconfig; sourceTree = ""; }; 34 | C65DFCB8E8DB4891902A78A9 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | 77F710281891A40D00828235 /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | 77F7102F1891A40D00828235 /* XCTest.framework in Frameworks */, 43 | F56A9D47B4424EA5B363B21C /* libPods.a in Frameworks */, 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | 77F710201891A3D900828235 = { 51 | isa = PBXGroup; 52 | children = ( 53 | 77F710301891A40D00828235 /* CCLRequestReplayTests */, 54 | 77F7102D1891A40D00828235 /* Frameworks */, 55 | 77F7102C1891A40D00828235 /* Products */, 56 | AE0C05A30E1A465B91E56DC3 /* Pods.xcconfig */, 57 | ); 58 | sourceTree = ""; 59 | }; 60 | 77F7102C1891A40D00828235 /* Products */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 77F7102B1891A40D00828235 /* CCLRequestReplayTests.xctest */, 64 | ); 65 | name = Products; 66 | sourceTree = ""; 67 | }; 68 | 77F7102D1891A40D00828235 /* Frameworks */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 77F7102E1891A40D00828235 /* XCTest.framework */, 72 | C65DFCB8E8DB4891902A78A9 /* libPods.a */, 73 | ); 74 | name = Frameworks; 75 | sourceTree = ""; 76 | }; 77 | 77F710301891A40D00828235 /* CCLRequestReplayTests */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 77756B3F1896626600A55DB0 /* CCLRequestRecordProtocolTest.m */, 81 | 77F710361891A40D00828235 /* CCLRequestReplayManagerTest.m */, 82 | 77F7103C1891BC8200828235 /* CCLRequestReplayProtocolTest.m */, 83 | 77756B3D18965FE400A55DB0 /* CCLRequestRecordingTest.m */, 84 | 77D7FD771903416E00D76614 /* CCLRequestReplayManagerBlueprint.m */, 85 | 7762CD7B1936BD1600D57ED8 /* CCLRequestRecordingXCTest.m */, 86 | 77F710311891A40D00828235 /* Supporting Files */, 87 | ); 88 | path = CCLRequestReplayTests; 89 | sourceTree = ""; 90 | }; 91 | 77F710311891A40D00828235 /* Supporting Files */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 77F710321891A40D00828235 /* CCLRequestReplayTests-Info.plist */, 95 | 77F710331891A40D00828235 /* InfoPlist.strings */, 96 | 77F710381891A40D00828235 /* CCLRequestReplayTests-Prefix.pch */, 97 | ); 98 | name = "Supporting Files"; 99 | sourceTree = ""; 100 | }; 101 | /* End PBXGroup section */ 102 | 103 | /* Begin PBXNativeTarget section */ 104 | 77F7102A1891A40D00828235 /* CCLRequestReplayTests */ = { 105 | isa = PBXNativeTarget; 106 | buildConfigurationList = 77F7103B1891A40D00828235 /* Build configuration list for PBXNativeTarget "CCLRequestReplayTests" */; 107 | buildPhases = ( 108 | 5E6EADDBCBA9490BAE5F5131 /* Check Pods Manifest.lock */, 109 | 77F710271891A40D00828235 /* Sources */, 110 | 77F710281891A40D00828235 /* Frameworks */, 111 | 77F710291891A40D00828235 /* Resources */, 112 | 9271F3E759284ADC8E46A3E5 /* Copy Pods Resources */, 113 | ); 114 | buildRules = ( 115 | ); 116 | dependencies = ( 117 | ); 118 | name = CCLRequestReplayTests; 119 | productName = CCLRequestReplayTests; 120 | productReference = 77F7102B1891A40D00828235 /* CCLRequestReplayTests.xctest */; 121 | productType = "com.apple.product-type.bundle.unit-test"; 122 | }; 123 | /* End PBXNativeTarget section */ 124 | 125 | /* Begin PBXProject section */ 126 | 77F710211891A3D900828235 /* Project object */ = { 127 | isa = PBXProject; 128 | attributes = { 129 | LastUpgradeCheck = 0500; 130 | }; 131 | buildConfigurationList = 77F710241891A3D900828235 /* Build configuration list for PBXProject "CCLRequestReplay" */; 132 | compatibilityVersion = "Xcode 3.2"; 133 | developmentRegion = English; 134 | hasScannedForEncodings = 0; 135 | knownRegions = ( 136 | en, 137 | ); 138 | mainGroup = 77F710201891A3D900828235; 139 | productRefGroup = 77F7102C1891A40D00828235 /* Products */; 140 | projectDirPath = ""; 141 | projectRoot = ""; 142 | targets = ( 143 | 77F7102A1891A40D00828235 /* CCLRequestReplayTests */, 144 | ); 145 | }; 146 | /* End PBXProject section */ 147 | 148 | /* Begin PBXResourcesBuildPhase section */ 149 | 77F710291891A40D00828235 /* Resources */ = { 150 | isa = PBXResourcesBuildPhase; 151 | buildActionMask = 2147483647; 152 | files = ( 153 | 77F710351891A40D00828235 /* InfoPlist.strings in Resources */, 154 | ); 155 | runOnlyForDeploymentPostprocessing = 0; 156 | }; 157 | /* End PBXResourcesBuildPhase section */ 158 | 159 | /* Begin PBXShellScriptBuildPhase section */ 160 | 5E6EADDBCBA9490BAE5F5131 /* Check Pods Manifest.lock */ = { 161 | isa = PBXShellScriptBuildPhase; 162 | buildActionMask = 2147483647; 163 | files = ( 164 | ); 165 | inputPaths = ( 166 | ); 167 | name = "Check Pods Manifest.lock"; 168 | outputPaths = ( 169 | ); 170 | runOnlyForDeploymentPostprocessing = 0; 171 | shellPath = /bin/sh; 172 | shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; 173 | showEnvVarsInLog = 0; 174 | }; 175 | 9271F3E759284ADC8E46A3E5 /* Copy Pods Resources */ = { 176 | isa = PBXShellScriptBuildPhase; 177 | buildActionMask = 2147483647; 178 | files = ( 179 | ); 180 | inputPaths = ( 181 | ); 182 | name = "Copy Pods Resources"; 183 | outputPaths = ( 184 | ); 185 | runOnlyForDeploymentPostprocessing = 0; 186 | shellPath = /bin/sh; 187 | shellScript = "\"${SRCROOT}/Pods/Pods-resources.sh\"\n"; 188 | showEnvVarsInLog = 0; 189 | }; 190 | /* End PBXShellScriptBuildPhase section */ 191 | 192 | /* Begin PBXSourcesBuildPhase section */ 193 | 77F710271891A40D00828235 /* Sources */ = { 194 | isa = PBXSourcesBuildPhase; 195 | buildActionMask = 2147483647; 196 | files = ( 197 | 77756B3E18965FE400A55DB0 /* CCLRequestRecordingTest.m in Sources */, 198 | 77D7FD781903416E00D76614 /* CCLRequestReplayManagerBlueprint.m in Sources */, 199 | 77756B401896626600A55DB0 /* CCLRequestRecordProtocolTest.m in Sources */, 200 | 7762CD7C1936BD1600D57ED8 /* CCLRequestRecordingXCTest.m in Sources */, 201 | 77F710371891A40D00828235 /* CCLRequestReplayManagerTest.m in Sources */, 202 | 77F7103D1891BC8200828235 /* CCLRequestReplayProtocolTest.m in Sources */, 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | }; 206 | /* End PBXSourcesBuildPhase section */ 207 | 208 | /* Begin PBXVariantGroup section */ 209 | 77F710331891A40D00828235 /* InfoPlist.strings */ = { 210 | isa = PBXVariantGroup; 211 | children = ( 212 | 77F710341891A40D00828235 /* en */, 213 | ); 214 | name = InfoPlist.strings; 215 | sourceTree = ""; 216 | }; 217 | /* End PBXVariantGroup section */ 218 | 219 | /* Begin XCBuildConfiguration section */ 220 | 77F710251891A3D900828235 /* Debug */ = { 221 | isa = XCBuildConfiguration; 222 | buildSettings = { 223 | }; 224 | name = Debug; 225 | }; 226 | 77F710261891A3D900828235 /* Release */ = { 227 | isa = XCBuildConfiguration; 228 | buildSettings = { 229 | }; 230 | name = Release; 231 | }; 232 | 77F710391891A40D00828235 /* Debug */ = { 233 | isa = XCBuildConfiguration; 234 | baseConfigurationReference = AE0C05A30E1A465B91E56DC3 /* Pods.xcconfig */; 235 | buildSettings = { 236 | ALWAYS_SEARCH_USER_PATHS = NO; 237 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 238 | CLANG_CXX_LIBRARY = "libc++"; 239 | CLANG_ENABLE_OBJC_ARC = YES; 240 | CLANG_WARN_BOOL_CONVERSION = YES; 241 | CLANG_WARN_CONSTANT_CONVERSION = YES; 242 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 243 | CLANG_WARN_EMPTY_BODY = YES; 244 | CLANG_WARN_ENUM_CONVERSION = YES; 245 | CLANG_WARN_INT_CONVERSION = YES; 246 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 247 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 248 | COPY_PHASE_STRIP = NO; 249 | FRAMEWORK_SEARCH_PATHS = ( 250 | "$(DEVELOPER_FRAMEWORKS_DIR)", 251 | "$(inherited)", 252 | ); 253 | GCC_C_LANGUAGE_STANDARD = gnu99; 254 | GCC_DYNAMIC_NO_PIC = NO; 255 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 256 | GCC_OPTIMIZATION_LEVEL = 0; 257 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 258 | GCC_PREFIX_HEADER = "CCLRequestReplayTests/CCLRequestReplayTests-Prefix.pch"; 259 | GCC_PREPROCESSOR_DEFINITIONS = ( 260 | "DEBUG=1", 261 | "$(inherited)", 262 | ); 263 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 264 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 265 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 266 | GCC_WARN_UNDECLARED_SELECTOR = YES; 267 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 268 | GCC_WARN_UNUSED_FUNCTION = YES; 269 | GCC_WARN_UNUSED_VARIABLE = YES; 270 | INFOPLIST_FILE = "CCLRequestReplayTests/CCLRequestReplayTests-Info.plist"; 271 | MACOSX_DEPLOYMENT_TARGET = 10.9; 272 | ONLY_ACTIVE_ARCH = YES; 273 | PRODUCT_NAME = "$(TARGET_NAME)"; 274 | SDKROOT = macosx; 275 | WRAPPER_EXTENSION = xctest; 276 | }; 277 | name = Debug; 278 | }; 279 | 77F7103A1891A40D00828235 /* Release */ = { 280 | isa = XCBuildConfiguration; 281 | baseConfigurationReference = AE0C05A30E1A465B91E56DC3 /* Pods.xcconfig */; 282 | buildSettings = { 283 | ALWAYS_SEARCH_USER_PATHS = NO; 284 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 285 | CLANG_CXX_LIBRARY = "libc++"; 286 | CLANG_ENABLE_OBJC_ARC = YES; 287 | CLANG_WARN_BOOL_CONVERSION = YES; 288 | CLANG_WARN_CONSTANT_CONVERSION = YES; 289 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 290 | CLANG_WARN_EMPTY_BODY = YES; 291 | CLANG_WARN_ENUM_CONVERSION = YES; 292 | CLANG_WARN_INT_CONVERSION = YES; 293 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 294 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 295 | COPY_PHASE_STRIP = YES; 296 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 297 | ENABLE_NS_ASSERTIONS = NO; 298 | FRAMEWORK_SEARCH_PATHS = ( 299 | "$(DEVELOPER_FRAMEWORKS_DIR)", 300 | "$(inherited)", 301 | ); 302 | GCC_C_LANGUAGE_STANDARD = gnu99; 303 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 304 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 305 | GCC_PREFIX_HEADER = "CCLRequestReplayTests/CCLRequestReplayTests-Prefix.pch"; 306 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 307 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 308 | GCC_WARN_UNDECLARED_SELECTOR = YES; 309 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 310 | GCC_WARN_UNUSED_FUNCTION = YES; 311 | GCC_WARN_UNUSED_VARIABLE = YES; 312 | INFOPLIST_FILE = "CCLRequestReplayTests/CCLRequestReplayTests-Info.plist"; 313 | MACOSX_DEPLOYMENT_TARGET = 10.9; 314 | PRODUCT_NAME = "$(TARGET_NAME)"; 315 | SDKROOT = macosx; 316 | WRAPPER_EXTENSION = xctest; 317 | }; 318 | name = Release; 319 | }; 320 | /* End XCBuildConfiguration section */ 321 | 322 | /* Begin XCConfigurationList section */ 323 | 77F710241891A3D900828235 /* Build configuration list for PBXProject "CCLRequestReplay" */ = { 324 | isa = XCConfigurationList; 325 | buildConfigurations = ( 326 | 77F710251891A3D900828235 /* Debug */, 327 | 77F710261891A3D900828235 /* Release */, 328 | ); 329 | defaultConfigurationIsVisible = 0; 330 | defaultConfigurationName = Release; 331 | }; 332 | 77F7103B1891A40D00828235 /* Build configuration list for PBXNativeTarget "CCLRequestReplayTests" */ = { 333 | isa = XCConfigurationList; 334 | buildConfigurations = ( 335 | 77F710391891A40D00828235 /* Debug */, 336 | 77F7103A1891A40D00828235 /* Release */, 337 | ); 338 | defaultConfigurationIsVisible = 0; 339 | defaultConfigurationName = Release; 340 | }; 341 | /* End XCConfigurationList section */ 342 | }; 343 | rootObject = 77F710211891A3D900828235 /* Project object */; 344 | } 345 | --------------------------------------------------------------------------------