├── .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 | [](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 |
--------------------------------------------------------------------------------