├── .gitignore
├── .slather.yml
├── .travis.yml
├── CONTRIBUTING.md
├── Cartfile
├── Cartfile.resolved
├── Gemfile
├── Gemfile.lock
├── LICENSE
├── Podfile
├── Podfile.lock
├── README.md
├── Rakefile
├── VENCore.podspec
├── VENCore.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── xcshareddata
│ └── xcschemes
│ └── VENCore.xcscheme
├── VENCore
├── Categories
│ ├── NSArray+VENCore.h
│ ├── NSArray+VENCore.m
│ ├── NSDictionary+VENCore.h
│ ├── NSDictionary+VENCore.m
│ ├── NSError+VENCore.h
│ ├── NSError+VENCore.m
│ ├── NSString+VENCore.h
│ ├── NSString+VENCore.m
│ ├── UIDevice+VENCore.h
│ └── UIDevice+VENCore.m
├── Info.plist
├── Models
│ ├── Transactions
│ │ ├── VENCreateTransactionRequest.h
│ │ ├── VENCreateTransactionRequest.m
│ │ ├── VENTransaction.h
│ │ ├── VENTransaction.m
│ │ ├── VENTransactionPayloadKeys.h
│ │ ├── VENTransactionTarget.h
│ │ └── VENTransactionTarget.m
│ └── Users
│ │ ├── VENUser.h
│ │ ├── VENUser.m
│ │ └── VENUserPayloadKeys.h
├── Networking
│ ├── VENHTTP.h
│ ├── VENHTTP.m
│ ├── VENHTTPResponse.h
│ └── VENHTTPResponse.m
├── VENCore-Prefix.pch
├── VENCore.h
└── VENCore.m
├── VENCoreIntegrationTests
├── PaymentSandboxSpec.m
├── VENCoreIntegrationTests-Info.plist
├── VENCoreIntegrationTests-Prefix.pch
├── VENUserIntegrationSpec.m
└── en.lproj
│ └── InfoPlist.strings
├── VENCoreUnitTests
├── API
│ ├── errors
│ │ ├── invalidAccessTokenError.json
│ │ ├── invalidAmountError.json
│ │ └── invalidAudienceError.json
│ ├── payments
│ │ ├── paymentToEmail.json
│ │ └── paymentToUser.json
│ └── users
│ │ ├── fetchChrisUser.json
│ │ ├── fetchFriends.json
│ │ ├── fetchInvalidFriends.json
│ │ └── fetchInvalidUser.json
├── Categories
│ ├── NSArray+VENCoreSpec.m
│ ├── NSDictionary+VENCoreSpec.m
│ ├── NSError+VENCoreSpec.m
│ └── NSString+VENCoreSpec.m
├── Models
│ ├── Transactions
│ │ ├── VENCreateTransactionRequestSpec.m
│ │ ├── VENTransactionSpec.m
│ │ └── VENTransactionTargetSpec.m
│ └── Users
│ │ └── VENUserSpec.m
├── Networking
│ ├── VENHTTPResponseSpec.m
│ └── VENHTTPSpec.m
├── Utilities
│ ├── EXPMatchers+Venmo.h
│ ├── EXPMatchers+Venmo.m
│ ├── VENTestUtilities.h
│ └── VENTestUtilities.m
├── VENCoreSpec.m
├── VENCoreUnitTests-Info.plist
├── VENCoreUnitTests-Prefix.pch
└── en.lproj
│ └── InfoPlist.strings
└── docs
└── DESIGN.md
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io/api/objective-c
2 |
3 | ### Objective-C ###
4 | # Xcode
5 | #
6 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
7 |
8 | ## Build generated
9 | build/
10 | DerivedData
11 |
12 | ## Various settings
13 | *.pbxuser
14 | !default.pbxuser
15 | *.mode1v3
16 | !default.mode1v3
17 | *.mode2v3
18 | !default.mode2v3
19 | *.perspectivev3
20 | !default.perspectivev3
21 | xcuserdata
22 |
23 | ## Other
24 | *.xccheckout
25 | *.moved-aside
26 | *.xcuserstate
27 | *.xcscmblueprint
28 |
29 | ## Obj-C/Swift specific
30 | *.hmap
31 | *.ipa
32 |
33 | # CocoaPods
34 | #
35 | # We recommend against adding the Pods directory to your .gitignore. However
36 | # you should judge for yourself, the pros and cons are mentioned at:
37 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
38 | #
39 | Pods/
40 |
41 | # Carthage
42 | Carthage/Checkouts
43 | Carthage/Build
44 |
45 | ### Objective-C Patch ###
46 | *.xcscmblueprint
47 |
48 | # Created by https://www.gitignore.io/api/osx
49 |
50 | ### OSX ###
51 | .DS_Store
52 | .AppleDouble
53 | .LSOverride
54 |
55 | # Icon must end with two \r
56 | Icon
57 |
58 | # Thumbnails
59 | ._*
60 |
61 | # Files that might appear in the root of a volume
62 | .DocumentRevisions-V100
63 | .fseventsd
64 | .Spotlight-V100
65 | .TemporaryItems
66 | .Trashes
67 | .VolumeIcon.icns
68 |
69 | # Directories potentially created on remote AFP share
70 | .AppleDB
71 | .AppleDesktop
72 | Network Trash Folder
73 | Temporary Items
74 | .apdisk
75 |
76 | *.gcno
77 | *.gcda
78 |
79 | VENCore.xcworkspace
80 |
--------------------------------------------------------------------------------
/.slather.yml:
--------------------------------------------------------------------------------
1 | coverage_service: coveralls
2 | xcodeproj: VENCore.xcodeproj
3 | ignore:
4 | - VENCoreUnitTests/.*
5 | - VENCoreIntegrationTests/.*
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: objective-c
2 | install:
3 | - bundle install --jobs=3 --retry=3
4 | script: bundle exec rake test
5 | after_success: slather
6 | cache:
7 | directories:
8 | - vendor/bundle
9 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | 1. Make sure the tests pass.
2 | - To run the integration tests (disabled in the scheme by default), you'll need to add a "config.plist" file to the VENCoreIntegrationTests target. This plist should contain an "access_token" key with your Venmo access token as the value.
3 |
4 |
--------------------------------------------------------------------------------
/Cartfile:
--------------------------------------------------------------------------------
1 | github "calebd/CMDQueryStringSerialization" ~> 0.4
2 |
--------------------------------------------------------------------------------
/Cartfile.resolved:
--------------------------------------------------------------------------------
1 | github "soffes/ISO8601" "v0.3.0"
2 | github "calebd/CMDQueryStringSerialization" "v0.4.3"
3 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'rake'
4 | gem 'cocoapods'
5 | gem 'xcpretty'
6 | gem 'plist'
7 | gem 'slather'
8 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: https://rubygems.org/
3 | specs:
4 | activesupport (4.2.4)
5 | i18n (~> 0.7)
6 | json (~> 1.7, >= 1.7.7)
7 | minitest (~> 5.1)
8 | thread_safe (~> 0.3, >= 0.3.4)
9 | tzinfo (~> 1.1)
10 | claide (0.9.1)
11 | clamp (0.6.5)
12 | cocoapods (0.39.0)
13 | activesupport (>= 4.0.2)
14 | claide (~> 0.9.1)
15 | cocoapods-core (= 0.39.0)
16 | cocoapods-downloader (~> 0.9.3)
17 | cocoapods-plugins (~> 0.4.2)
18 | cocoapods-search (~> 0.1.0)
19 | cocoapods-stats (~> 0.6.2)
20 | cocoapods-trunk (~> 0.6.4)
21 | cocoapods-try (~> 0.5.1)
22 | colored (~> 1.2)
23 | escape (~> 0.0.4)
24 | molinillo (~> 0.4.0)
25 | nap (~> 1.0)
26 | xcodeproj (~> 0.28.2)
27 | cocoapods-core (0.39.0)
28 | activesupport (>= 4.0.2)
29 | fuzzy_match (~> 2.0.4)
30 | nap (~> 1.0)
31 | cocoapods-downloader (0.9.3)
32 | cocoapods-plugins (0.4.2)
33 | nap
34 | cocoapods-search (0.1.0)
35 | cocoapods-stats (0.6.2)
36 | cocoapods-trunk (0.6.4)
37 | nap (>= 0.8, < 2.0)
38 | netrc (= 0.7.8)
39 | cocoapods-try (0.5.1)
40 | colored (1.2)
41 | escape (0.0.4)
42 | fuzzy_match (2.0.4)
43 | i18n (0.7.0)
44 | json (1.8.3)
45 | minitest (5.8.2)
46 | molinillo (0.4.0)
47 | nap (1.0.0)
48 | netrc (0.7.8)
49 | plist (3.1.0)
50 | rake (10.4.2)
51 | rouge (1.10.1)
52 | slather (1.3.0)
53 | clamp (~> 0.6)
54 | xcodeproj (~> 0.17)
55 | thread_safe (0.3.5)
56 | tzinfo (1.2.2)
57 | thread_safe (~> 0.1)
58 | xcodeproj (0.28.2)
59 | activesupport (>= 3)
60 | claide (~> 0.9.1)
61 | colored (~> 1.2)
62 | xcpretty (0.2.1)
63 | rouge (~> 1.8)
64 |
65 | PLATFORMS
66 | ruby
67 |
68 | DEPENDENCIES
69 | cocoapods
70 | plist
71 | rake
72 | slather
73 | xcpretty
74 |
75 | BUNDLED WITH
76 | 1.10.6
77 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Venmo
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | source 'https://github.com/CocoaPods/Specs.git'
2 |
3 | platform :ios, '8.0'
4 |
5 | inhibit_all_warnings!
6 |
7 | use_frameworks!
8 |
9 | podspec
10 |
11 | target 'VENCoreUnitTests', :exclusive => true do
12 | pod 'Expecta'
13 | pod 'Nocilla'
14 | pod 'OCMock', '~> 2.2'
15 | pod 'OCHamcrest'
16 | pod 'Specta'
17 | end
18 |
19 | target 'VENCoreIntegrationTests', :exclusive => true do
20 | pod 'Expecta'
21 | pod 'Nocilla'
22 | pod 'OCMock', '~> 2.2'
23 | pod 'OCHamcrest'
24 | pod 'Specta'
25 | end
26 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - CMDQueryStringSerialization (0.4.1):
3 | - ISO8601
4 | - Expecta (1.0.3)
5 | - ISO8601 (0.3.0)
6 | - Nocilla (0.10.0)
7 | - OCHamcrest (4.3.0)
8 | - OCMock (2.2.4)
9 | - Specta (1.0.4)
10 |
11 | DEPENDENCIES:
12 | - CMDQueryStringSerialization (~> 0.4)
13 | - Expecta
14 | - Nocilla
15 | - OCHamcrest
16 | - OCMock (~> 2.2)
17 | - Specta
18 |
19 | SPEC CHECKSUMS:
20 | CMDQueryStringSerialization: 4bb0a2f5e7d8d8678d911d88072274952cbef4e5
21 | Expecta: 9d1bff6c8b0eeee73a166a2ee898892478927a15
22 | ISO8601: 8d8a22d5edf0554a1cf75bac028c76c1dc0ffaef
23 | Nocilla: ae0a2b05f3087b473624ac2b25903695df51246a
24 | OCHamcrest: cd63d27f48a266d4412c0b295b01b8f0940efa81
25 | OCMock: a6a7dc0e3997fb9f35d99f72528698ebf60d64f2
26 | Specta: 69bb134672aae190a1379ff91df07dad8dd1f869
27 |
28 | COCOAPODS: 0.39.0
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | VENCore [](https://travis-ci.org/venmo/VENCore) [](https://coveralls.io/r/venmo/VENCore?branch=master)
2 | =======
3 |
4 | VENCore is the core Objective-C client library for the Venmo API. If you're looking for a simple way to send Venmo payments & charges from your iOS app, you should use the [Venmo iOS SDK](https://github.com/venmo/venmo-ios-sdk).
5 |
6 | ## Usage
7 |
8 | ### Initialization
9 | ```objective-c
10 | // Create a VENCore instance
11 | VENCore *core = [[VENCore alloc] init];
12 |
13 | // Give it an access token
14 | [core setAccessToken:accessToken];
15 |
16 | // Set the default core
17 | [VENCore setDefaultCore:core];
18 | ```
19 |
20 | ### Sending a transaction
21 | ```objective-c
22 | // Create a request
23 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
24 |
25 | // Add a note
26 | transactionService.note = @"hi";
27 |
28 | // Add a target
29 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"name@example.com" amount:30];
30 | [transactionService addTransactionTarget:target];
31 |
32 | // Send the request
33 | [transactionService sendWithSuccess:^(NSArray *sentTransactions, VENHTTPResponse *response) {
34 | // :)
35 | } failure:^(NSArray *sentTransactions, VENHTTPResponse *response, NSError *error) {
36 | // :(
37 | }];
38 | ```
39 |
40 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | WORKSPACE = 'VENCore.xcworkspace'
2 | SCHEME = 'VENCore'
3 |
4 | desc 'Run the tests'
5 | task :test do
6 | require_binary 'bundle', 'gem install bundler'
7 | sh 'bundle exec pod install'
8 |
9 | require_binary 'xcodebuild', 'brew install xcodebuild'
10 | require_binary 'xcpretty', 'bundle install'
11 | sh "xcodebuild test -workspace #{WORKSPACE} -scheme #{SCHEME} -destination 'platform=iOS Simulator,name=iPhone 4s,OS=latest' | bundle exec xcpretty --color; exit ${PIPESTATUS[0]}"
12 | end
13 |
14 | task :default => :test
15 |
16 | desc 'Print test coverage of the last test run.'
17 | task :coverage do
18 | require_binary 'slather', 'bundle install'
19 | sh 'slather coverage -s'
20 | end
21 |
22 |
23 | private
24 |
25 | def require_binary(binary, install)
26 | if `which #{binary}`.length == 0
27 | fail "\nERROR: #{binary} isn't installed. Please install #{binary} with the following command:\n\n $ #{install}\n\n"
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/VENCore.podspec:
--------------------------------------------------------------------------------
1 | Pod::Spec.new do |s|
2 | s.name = 'VENCore'
3 | s.version = '3.1.3'
4 | s.summary = 'Core Venmo client library'
5 | s.description = 'Core iOS client library for the Venmo api'
6 | s.homepage = 'https://github.com/venmo/VENCore'
7 | s.license = { :type => 'MIT', :file => 'LICENSE' }
8 | s.author = { 'Venmo' => 'ios@venmo.com' }
9 | s.platform = :ios, '7.1'
10 | s.source = { :git => 'https://github.com/venmo/VENCore.git',
11 | :tag => "v#{s.version}" }
12 | s.source_files = 'VENCore/**/*.{h,m}'
13 | s.dependency 'CMDQueryStringSerialization', '~> 0.4'
14 | end
15 |
--------------------------------------------------------------------------------
/VENCore.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/VENCore.xcodeproj/xcshareddata/xcschemes/VENCore.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
29 |
35 |
36 |
37 |
43 |
49 |
50 |
51 |
52 |
53 |
58 |
59 |
61 |
67 |
68 |
69 |
70 |
71 |
77 |
78 |
79 |
80 |
81 |
82 |
92 |
93 |
99 |
100 |
101 |
102 |
103 |
104 |
110 |
111 |
117 |
118 |
119 |
120 |
122 |
123 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/VENCore/Categories/NSArray+VENCore.h:
--------------------------------------------------------------------------------
1 | @import Foundation;
2 |
3 | @interface NSMutableArray (VENCore)
4 |
5 | /**
6 | * Removes all elements which have NULL values from the array
7 | */
8 | - (void)cleanseResponseArray;
9 |
10 | @end
11 |
12 | @interface NSArray (VENCore)
13 | /**
14 | * Returns an array containing all non-Null elements from the receiving array
15 | * @return Array with no NULL values
16 | */
17 | - (instancetype)arrayByCleansingResponseArray;
18 | @end
19 |
--------------------------------------------------------------------------------
/VENCore/Categories/NSArray+VENCore.m:
--------------------------------------------------------------------------------
1 | #import "NSArray+VENCore.h"
2 | #import "NSDictionary+VENCore.h"
3 |
4 | @implementation NSMutableArray (VENCore)
5 |
6 | - (void)cleanseResponseArray
7 | {
8 | NSMutableArray *elementsToRemoveArray = [[NSMutableArray alloc] initWithCapacity:[self count]];
9 | for (NSUInteger i=0; i<[self count]; i++) {
10 | if ([self[i] isKindOfClass:[NSNumber class]]) {
11 | self[i] = [(NSNumber *)self[i] stringValue];
12 | }
13 | else if (self[i] == [NSNull null]) {
14 | [elementsToRemoveArray addObject:self[i]];
15 | }
16 | else if ([self[i] isKindOfClass:[NSDictionary class]]) {
17 | self[i] = [(NSMutableDictionary *)self[i] dictionaryByCleansingResponseDictionary];
18 | }
19 | else if ([self[i] isKindOfClass:[NSArray class]]) {
20 | self[i] = [(NSArray *)self[i] arrayByCleansingResponseArray];
21 | }
22 | }
23 | for (NSObject *objectToRemove in elementsToRemoveArray) {
24 | [self removeObject:objectToRemove];
25 | }
26 | }
27 |
28 | @end
29 |
30 | @implementation NSArray (VENCore)
31 |
32 | - (instancetype)arrayByCleansingResponseArray
33 | {
34 | NSMutableArray *array = [self mutableCopy];
35 | [array cleanseResponseArray];
36 | return [NSArray arrayWithArray:array];
37 | }
38 |
39 | @end
40 |
--------------------------------------------------------------------------------
/VENCore/Categories/NSDictionary+VENCore.h:
--------------------------------------------------------------------------------
1 | @import Foundation;
2 |
3 | @interface NSMutableDictionary (VENCore)
4 |
5 | /**
6 | * Removes all keys which have NULL values from the dictionary
7 | */
8 | - (void)cleanseResponseDictionary;
9 |
10 | @end
11 |
12 | @interface NSDictionary (VENCore)
13 |
14 | /**
15 | * Returns the object associated with the given key
16 | * @param key An object identifying the value
17 | * @return The value associated with key, or nil if no value is associated with the key.
18 | */
19 | - (id)objectOrNilForKey:(id)key;
20 |
21 |
22 | /**
23 | * Returns the bool value associated with the given key
24 | * @param key An object identifying the value
25 | * @return The bool value associated with the key, or nil if no bool value can be associated with the key.
26 | */
27 | - (BOOL)boolForKey:(id)key;
28 |
29 | /**
30 | * Returns the string value associated with the given key
31 | * @param key An object identifying the value
32 | * @return The string value associated with the key, or nil if no string value can be associated
33 | * with the key.
34 | */
35 | - (NSString *)stringForKey:(id)key;
36 |
37 | /**
38 | * Returns a dictionary containing all non-Null key-value pairs from the receiving dictionary
39 | * @return Dictionary with no NULL values
40 | */
41 | - (instancetype)dictionaryByCleansingResponseDictionary;
42 |
43 | @end
44 |
--------------------------------------------------------------------------------
/VENCore/Categories/NSDictionary+VENCore.m:
--------------------------------------------------------------------------------
1 | #import "NSDictionary+VENCore.h"
2 | #import "NSArray+VENCore.h"
3 |
4 | @implementation NSMutableDictionary (VENCore)
5 |
6 | - (void)cleanseResponseDictionary {
7 | for (NSString *key in [self allKeys]) {
8 | NSObject *object = (NSObject *) self[key];
9 | if (object == [NSNull null]) {
10 | [self removeObjectForKey:key];
11 | }
12 | else if ([object isKindOfClass:[NSNumber class]]) {
13 | self[key] = [((NSNumber *)object) stringValue];
14 | }
15 | else if ([object isKindOfClass:[NSDictionary class]]) {
16 | self[key] = [((NSDictionary *)object) dictionaryByCleansingResponseDictionary];
17 | }
18 | else if([object isKindOfClass:[NSArray class]]) {
19 | NSArray *array = [(NSArray *)object copy];
20 | self[key] = [array arrayByCleansingResponseArray];
21 | }
22 | }
23 | }
24 |
25 | @end
26 |
27 | @implementation NSDictionary (VENCore)
28 |
29 | - (id)objectOrNilForKey:(id)key {
30 | id object = self[key];
31 | return object == [NSNull null] ? nil : object;
32 | }
33 |
34 |
35 | - (BOOL)boolForKey:(id)key {
36 | id object = [self objectOrNilForKey:key];
37 | if ([object respondsToSelector:@selector(boolValue)]) {
38 | return [object boolValue];
39 | } else {
40 | return object != nil;
41 | }
42 | }
43 |
44 |
45 | - (NSString *)stringForKey:(id)key {
46 | id object = [self objectOrNilForKey:key];
47 | return [object respondsToSelector:@selector(stringValue)] ? [object stringValue] : object;
48 | }
49 |
50 |
51 | - (instancetype)dictionaryByCleansingResponseDictionary {
52 |
53 | NSMutableDictionary *dictionary = [self mutableCopy];
54 | [dictionary cleanseResponseDictionary];
55 |
56 | return [NSDictionary dictionaryWithDictionary:dictionary];
57 | }
58 |
59 | @end
60 |
61 |
--------------------------------------------------------------------------------
/VENCore/Categories/NSError+VENCore.h:
--------------------------------------------------------------------------------
1 | @import Foundation;
2 |
3 | @interface NSError (VENCore)
4 |
5 | /**
6 | * Returns an NSError object with the given domain, code, description, and recovery suggestion.
7 | * @param code The error code
8 | * @param description A description of the error
9 | * @param recoverySuggestion A description of how to recover from the error
10 | */
11 | + (instancetype)errorWithDomain:(NSString *)domain
12 | code:(NSInteger)code
13 | description:(NSString *)description
14 | recoverySuggestion:(NSString *)recoverySuggestion;
15 |
16 | /**
17 | * Returns the default error for failing VENHTTP responses.
18 | */
19 | + (instancetype)defaultResponseError;
20 |
21 |
22 | /**
23 | * Returns an error indicating that no default core has been set.
24 | */
25 | + (instancetype)noDefaultCoreError;
26 |
27 |
28 | /**
29 | * Returns an error indicating that the default VENCore instance
30 | * doesn't have an access token.
31 | */
32 | + (instancetype)noAccessTokenError;
33 |
34 | @end
35 |
--------------------------------------------------------------------------------
/VENCore/Categories/NSError+VENCore.m:
--------------------------------------------------------------------------------
1 | #import "NSError+VENCore.h"
2 | #import "VENHTTPResponse.h"
3 | #import "VENCore.h"
4 |
5 | @implementation NSError (VENCore)
6 |
7 | + (instancetype)errorWithDomain:(NSString *)domain
8 | code:(NSInteger)code
9 | description:(NSString *)description
10 | recoverySuggestion:(NSString *)recoverySuggestion {
11 |
12 | NSDictionary *errorUserInfo =
13 | [NSDictionary dictionaryWithObjectsAndKeys:
14 | NSLocalizedString(description, nil), NSLocalizedDescriptionKey,
15 | NSLocalizedString(recoverySuggestion, nil), NSLocalizedRecoverySuggestionErrorKey, nil];
16 |
17 | return [self errorWithDomain:domain code:code userInfo:errorUserInfo];
18 |
19 | }
20 |
21 |
22 | + (instancetype)defaultResponseError {
23 | return [self errorWithDomain:VENErrorDomainHTTPResponse
24 | code:VENErrorCodeHTTPResponseBadResponse
25 | description:NSLocalizedString(@"Bad response", nil)
26 | recoverySuggestion:nil];
27 | }
28 |
29 |
30 | + (instancetype)noDefaultCoreError {
31 | return [self errorWithDomain:VENErrorDomainCore
32 | code:VENCoreErrorCodeNoDefaultCore
33 | description:NSLocalizedString(@"No default core", nil)
34 | recoverySuggestion:NSLocalizedString(@"Use setDefaultCore to set the default VENCore instance.", nil)];
35 | }
36 |
37 |
38 | + (instancetype)noAccessTokenError {
39 | return [self errorWithDomain:VENErrorDomainCore
40 | code:VENCoreErrorCodeNoAccessToken
41 | description:NSLocalizedString(@"No access token", nil)
42 | recoverySuggestion:NSLocalizedString(@"Set the default core's access token.", nil)];
43 | }
44 |
45 | @end
46 |
--------------------------------------------------------------------------------
/VENCore/Categories/NSString+VENCore.h:
--------------------------------------------------------------------------------
1 | @import Foundation;
2 |
3 | #import
4 |
5 | @interface NSString (VENCore)
6 |
7 | - (BOOL)isUSPhone;
8 | - (BOOL)isEmail;
9 | - (BOOL)isUserId;
10 | - (VENTargetType)targetType;
11 | - (BOOL)hasContent;
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/VENCore/Categories/NSString+VENCore.m:
--------------------------------------------------------------------------------
1 | #import "NSString+VENCore.h"
2 |
3 | @implementation NSString (VENCore)
4 |
5 | - (BOOL)isUSPhone {
6 | NSString *phoneRegex = @"^\\+?1?\\D{0,2}(\\d{3})\\D{0,2}\\D?(\\d{3})\\D?(\\d{4})$";
7 | NSPredicate *phoneTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", phoneRegex];
8 | return [phoneTest evaluateWithObject:self];
9 | }
10 |
11 |
12 | - (BOOL)isEmail {
13 | NSString *lowerCaseSelf = [self lowercaseString];
14 | NSString *pattern = @"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?";
15 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", pattern];
16 | return [predicate evaluateWithObject:lowerCaseSelf];
17 | }
18 |
19 |
20 | - (BOOL)isUserId {
21 | NSCharacterSet *notDigitSet = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789"] invertedSet];
22 | return ![self isUSPhone] &&
23 | [self rangeOfCharacterFromSet:notDigitSet].location == NSNotFound;
24 | }
25 |
26 |
27 | - (VENTargetType)targetType {
28 | if ([self isUSPhone]) {
29 | return VENTargetTypePhone;
30 | }
31 | else if ([self isEmail]) {
32 | return VENTargetTypeEmail;
33 | }
34 | else if ([self isUserId]) {
35 | return VENTargetTypeUserId;
36 | }
37 | else {
38 | return VENTargetTypeUnknown;
39 | }
40 | }
41 |
42 |
43 | - (BOOL)hasContent {
44 | NSCharacterSet *set = [NSCharacterSet whitespaceCharacterSet];
45 | if ([[self stringByTrimmingCharactersInSet: set] length] == 0)
46 | {
47 | return NO;
48 | }
49 | return YES;
50 | }
51 |
52 | @end
53 |
--------------------------------------------------------------------------------
/VENCore/Categories/UIDevice+VENCore.h:
--------------------------------------------------------------------------------
1 | @import UIKit;
2 |
3 | @interface UIDevice (VENCore)
4 |
5 | - (NSString *)VEN_platformString;
6 |
7 | - (NSString *)VEN_deviceIDString;
8 |
9 | @end
10 |
--------------------------------------------------------------------------------
/VENCore/Categories/UIDevice+VENCore.m:
--------------------------------------------------------------------------------
1 | #import "UIDevice+VENCore.h"
2 | #import
3 |
4 | NSString *const VENUserDefaultsKeyDeviceID = @"VenmoDeviceID";
5 |
6 | @implementation UIDevice (VENCore)
7 |
8 |
9 | - (NSString *)VEN_platformString {
10 | size_t size;
11 | sysctlbyname("hw.machine", NULL, &size, NULL, 0);
12 | char *machine = malloc(size);
13 | sysctlbyname("hw.machine", machine, &size, NULL, 0);
14 | NSString *platform = [NSString stringWithCString:machine encoding:NSUTF8StringEncoding];
15 | free(machine);
16 | return platform;
17 | }
18 |
19 |
20 | - (NSString *)VEN_deviceIDString {
21 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
22 | NSString *uniqueIdentifier = [userDefaults stringForKey:VENUserDefaultsKeyDeviceID];
23 | if (!uniqueIdentifier) {
24 | CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
25 | uniqueIdentifier = CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid));
26 | CFRelease(uuid);
27 | [userDefaults setObject:uniqueIdentifier forKey:VENUserDefaultsKeyDeviceID];
28 | [userDefaults synchronize];
29 | }
30 | return uniqueIdentifier;
31 | }
32 |
33 | @end
34 |
--------------------------------------------------------------------------------
/VENCore/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/VENCore/Models/Transactions/VENCreateTransactionRequest.h:
--------------------------------------------------------------------------------
1 | @import Foundation;
2 |
3 | #import
4 |
5 | @interface VENCreateTransactionRequest : NSObject
6 |
7 | @property (strong, nonatomic, readonly) NSOrderedSet *targets;
8 | @property (strong, nonatomic) NSString *note;
9 | @property (assign, nonatomic) VENTransactionType transactionType;
10 | @property (assign, nonatomic) VENTransactionAudience audience;
11 |
12 | /**
13 | * Sends transactions from the current user to all added targets.
14 | * @param successBlock The block to be executed after transactions are successfully sent to all targets. This block has no return value and takes two arguments: an array of the VENTransaction objects returned by the sent transactions, and the response from the last transaction.
15 | * @param failureBlock The block to be executed after a transaction fails. This block has no return value and takes three arguments: an array of the VENTransaction objects returned by the sent transactions, the response from the transaction that failed to send, and an error object.
16 | */
17 | - (void)sendWithSuccess:(void(^)(NSArray *sentTransactions,
18 | VENHTTPResponse *response))successBlock
19 | failure:(void(^)(NSArray *sentTransactions,
20 | VENHTTPResponse *response,
21 | NSError *error))failureBlock;
22 | /**
23 | * Adds a transaction target
24 | * @note If the target is invalid or a duplicate, addTarget: will return NO
25 | * and no target will be added to the transaction.
26 | * @return Returns a Boolean value indicating whether the target was successfully added.
27 | */
28 | - (BOOL)addTransactionTarget:(VENTransactionTarget *)target;
29 |
30 | /**
31 | * Indicates whether the transaction service is valid and ready to send transactions to its current targets
32 | */
33 | - (BOOL)readyToSend;
34 |
35 | @end
36 |
--------------------------------------------------------------------------------
/VENCore/Models/Transactions/VENCreateTransactionRequest.m:
--------------------------------------------------------------------------------
1 | #import "VENCreateTransactionRequest.h"
2 | #import "VENCore.h"
3 | #import "NSString+VENCore.h"
4 | #import "NSError+VENCore.h"
5 | #import "NSDictionary+VENCore.h"
6 |
7 | @import CoreGraphics;
8 |
9 | @interface VENCreateTransactionRequest ()
10 |
11 | @property (nonatomic, strong) NSMutableOrderedSet *mutableTargets;
12 |
13 | @end
14 |
15 |
16 | @implementation VENCreateTransactionRequest
17 |
18 | - (id)init {
19 | self = [super init];
20 | if (self) {
21 | self.mutableTargets = [[NSMutableOrderedSet alloc] init];
22 | }
23 | return self;
24 | }
25 |
26 | - (BOOL)readyToSend {
27 | if (![self.mutableTargets count] ||
28 | ![self.note hasContent] ||
29 | self.transactionType == VENTransactionTypeUnknown) {
30 | return NO;
31 | }
32 | return YES;
33 | }
34 |
35 | - (void)sendWithSuccess:(void(^)(NSArray *sentTransactions,
36 | VENHTTPResponse *response))successBlock
37 | failure:(void(^)(NSArray *sentTransactions,
38 | VENHTTPResponse *response,
39 | NSError *error))failureBlock {
40 | [self sendTargets:[self.targets mutableCopy]
41 | sentTransactions:nil
42 | withSuccess:successBlock
43 | failure:failureBlock];
44 | }
45 |
46 |
47 | - (void)sendTargets:(NSMutableOrderedSet *)targets
48 | sentTransactions:(NSMutableArray *)sentTransactions
49 | withSuccess:(void (^)(NSArray *sentTransactions,
50 | VENHTTPResponse *response))successBlock
51 | failure:(void(^)(NSArray *sentTransactions,
52 | VENHTTPResponse *response,
53 | NSError *error))failureBlock {
54 | if (!sentTransactions) {
55 | sentTransactions = [[NSMutableArray alloc] init];
56 | }
57 |
58 | if ([targets count] == 0) {
59 | if (successBlock) {
60 | successBlock(sentTransactions, nil);
61 | }
62 | return;
63 | }
64 |
65 | VENTransactionTarget *target = [targets firstObject];
66 | [targets removeObjectAtIndex:0];
67 | NSString *accessToken = [VENCore defaultCore].accessToken;
68 | if (!accessToken) {
69 | failureBlock(nil, nil, [NSError noAccessTokenError]);
70 | return;
71 | }
72 | NSMutableDictionary *postParameters = [NSMutableDictionary dictionaryWithDictionary:@{@"access_token" : accessToken}];
73 | [postParameters addEntriesFromDictionary:[self dictionaryWithParametersForTarget:target]];
74 | [[VENCore defaultCore].httpClient POST:VENAPIPathPayments
75 | parameters:postParameters
76 | success:^(VENHTTPResponse *response) {
77 | NSDictionary *data = [response.object objectOrNilForKey:@"data"];
78 | NSDictionary *payment = [data objectOrNilForKey:@"payment"];
79 | VENTransaction *newTransaction;
80 | if (payment) {
81 | newTransaction = [[VENTransaction alloc] initWithDictionary:payment];
82 | }
83 | [sentTransactions addObject:newTransaction];
84 |
85 | [self sendTargets:targets
86 | sentTransactions:sentTransactions
87 | withSuccess:successBlock
88 | failure:failureBlock];
89 | }
90 | failure:^(VENHTTPResponse *response, NSError *error) {
91 | if (failureBlock) {
92 | failureBlock(sentTransactions, response, error);
93 | }
94 | }];
95 | }
96 |
97 |
98 | - (BOOL)addTransactionTarget:(VENTransactionTarget *)target {
99 |
100 | if (![target isKindOfClass:[VENTransactionTarget class]]
101 | || ![target isValid]
102 | || [self containsDuplicateOfTarget:target]) {
103 | return NO;
104 | }
105 |
106 | [self.mutableTargets addObject:target];
107 | return YES;
108 | }
109 |
110 |
111 | #pragma mark - Other Methods
112 |
113 | - (NSOrderedSet *)targets {
114 | return [self.mutableTargets copy];
115 | }
116 |
117 | - (NSDictionary *)dictionaryWithParametersForTarget:(VENTransactionTarget *)target {
118 | NSString *recipientTypeKey;
119 | NSString *audienceString;
120 | NSString*amountString;
121 | switch (target.targetType) {
122 | case VENTargetTypeEmail:
123 | recipientTypeKey = @"email";
124 | break;
125 | case VENTargetTypePhone:
126 | recipientTypeKey = @"phone";
127 | break;
128 | case VENTargetTypeUserId:
129 | recipientTypeKey = @"user_id";
130 | break;
131 | default:
132 | return nil;
133 | break;
134 | }
135 |
136 | switch (self.audience) {
137 | case VENTransactionAudienceFriends:
138 | audienceString = @"friends";
139 | break;
140 | case VENTransactionAudiencePublic:
141 | audienceString = @"public";
142 | break;
143 | default:
144 | audienceString = @"private";
145 | break;
146 | }
147 | CGFloat dollarAmount = (CGFloat)target.amount/100.;
148 | amountString = [NSString stringWithFormat:@"%.2f", dollarAmount];
149 | if (self.transactionType == VENTransactionTypeCharge) {
150 | amountString = [@"-" stringByAppendingString:amountString];
151 | }
152 | NSMutableDictionary *parameters = [NSMutableDictionary dictionaryWithDictionary:
153 | @{recipientTypeKey: target.handle,
154 | @"note": self.note,
155 | @"amount": amountString}];
156 |
157 | if (self.audience != VENTransactionAudienceUserDefault) {
158 | [parameters addEntriesFromDictionary:@{@"audience": audienceString}];
159 | }
160 |
161 | return parameters;
162 | }
163 |
164 |
165 | - (BOOL)containsDuplicateOfTarget:(VENTransactionTarget *)target {
166 | NSString *handle = target.handle;
167 | for (VENTransactionTarget *currentTarget in self.targets) {
168 | if ([handle isEqualToString:currentTarget.handle]) {
169 | return YES;
170 | }
171 | }
172 | return NO;
173 | }
174 |
175 | @end
176 |
--------------------------------------------------------------------------------
/VENCore/Models/Transactions/VENTransaction.h:
--------------------------------------------------------------------------------
1 | @import Foundation;
2 |
3 | @class VENMutableTransaction, VENUser, VENHTTPResponse, VENTransactionTarget;
4 |
5 | typedef NS_ENUM(NSUInteger, VENTransactionType) {
6 | VENTransactionTypeUnknown,
7 | VENTransactionTypePay,
8 | VENTransactionTypeCharge
9 | };
10 | extern NSString *const VENTransactionTypeStrings[];
11 |
12 | typedef NS_ENUM(NSUInteger, VENTransactionStatus) {
13 | VENTransactionStatusUnknown,
14 | VENTransactionStatusPending,
15 | VENTransactionStatusSettled,
16 | VENTransactionStatusFailed
17 | };
18 | extern NSString *const VENTransactionStatusStrings[];
19 |
20 | typedef NS_ENUM(NSUInteger, VENTransactionAudience) {
21 | // Indicates that the transaction uses/should use the user's default sharing setting
22 | VENTransactionAudienceUserDefault,
23 | VENTransactionAudiencePrivate,
24 | VENTransactionAudienceFriends,
25 | VENTransactionAudiencePublic
26 | };
27 | extern NSString *const VENTransactionAudienceStrings[];
28 |
29 | extern NSString *const VENErrorDomainTransaction;
30 |
31 | typedef NS_ENUM(NSUInteger, VENErrorCodeTransaction) {
32 | VENErrorCodeTransactionDuplicateTarget,
33 | VENErrorCodeTransactionInvalidTarget
34 | };
35 |
36 | @interface VENTransaction : NSObject
37 |
38 | @property (copy, nonatomic, readonly) NSString *transactionID;
39 | @property (strong, nonatomic, readonly) VENTransactionTarget *target;
40 | @property (copy, nonatomic, readonly) NSString *note;
41 | @property (copy, nonatomic, readonly) VENUser *actor;
42 | @property (assign, nonatomic, readonly) VENTransactionType transactionType;
43 | @property (assign, nonatomic, readonly) VENTransactionStatus status;
44 | @property (assign, nonatomic, readonly) VENTransactionAudience audience;
45 |
46 | + (BOOL)canInitWithDictionary:(NSDictionary *)dictionary;
47 |
48 | /**
49 | * Creates a VENTransaction from a dictionary representation
50 | * @note should call canInitWithDictionary first
51 | * @return Returns an instance of VENTransaction
52 | */
53 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary;
54 |
55 | @end
56 |
--------------------------------------------------------------------------------
/VENCore/Models/Transactions/VENTransaction.m:
--------------------------------------------------------------------------------
1 | #import "VENTransaction.h"
2 | #import "VENCore.h"
3 | #import "VENTransactionPayloadKeys.h"
4 | #import "NSDictionary+VENCore.h"
5 | #import "VENTransactionTarget.h"
6 |
7 | NSString *const VENErrorDomainTransaction = @"com.venmo.VENCore.ErrorDomain.VENTransaction";
8 | NSString *const VENTransactionTypeStrings[] = {@"unknown", @"pay", @"charge"};
9 | NSString *const VENTransactionStatusStrings[] = {@"not_sent", @"pending", @"settled", @"failed"};
10 | NSString *const VENTransactionAudienceStrings[] = {@"default", @"private", @"friends", @"public"};
11 |
12 | @interface VENTransaction ()
13 |
14 | @property (copy, nonatomic, readwrite) NSString *transactionID;
15 | @property (strong, nonatomic, readwrite) VENTransactionTarget *target;
16 | @property (copy, nonatomic, readwrite) NSString *note;
17 | @property (copy, nonatomic, readwrite) VENUser *actor;
18 | @property (assign, nonatomic, readwrite) VENTransactionType transactionType;
19 | @property (assign, nonatomic, readwrite) VENTransactionStatus status;
20 | @property (assign, nonatomic, readwrite) VENTransactionAudience audience;
21 |
22 | @end
23 |
24 | @implementation VENTransaction
25 |
26 | #pragma mark - Class Methods
27 |
28 | + (BOOL)canInitWithDictionary:(NSDictionary *)dictionary {
29 | NSArray *requiredKeys = @[VENTransactionAmountKey, VENTransactionNoteKey, VENTransactionActorKey, VENTransactionIDKey, VENTransactionTargetKey];
30 | for (NSString *key in requiredKeys) {
31 | if (!dictionary[key] || [dictionary[key] isKindOfClass:[NSNull class]]
32 | || ([dictionary[key] respondsToSelector:@selector(isEqualToString:)]
33 | && [dictionary[key] isEqualToString:@""])) {
34 | return NO;
35 | }
36 | }
37 | return YES;
38 | }
39 |
40 | #pragma mark - Public Instance Methods
41 |
42 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary {
43 |
44 | self = [super init];
45 |
46 | if (self) {
47 | if (!dictionary || ![dictionary isKindOfClass:[NSDictionary class]]) {
48 | return self;
49 | }
50 |
51 | NSDictionary *cleanDictionary = [dictionary dictionaryByCleansingResponseDictionary];
52 |
53 | // Main Transaction Body
54 | self.transactionID = cleanDictionary[VENTransactionIDKey];
55 | self.note = cleanDictionary[VENTransactionNoteKey];
56 |
57 | NSString *transactionType = cleanDictionary[VENTransactionTypeKey];
58 | NSString *transactionStatus = cleanDictionary[VENTransactionStatusKey];
59 | NSString *transactionAudience = cleanDictionary[VENTransactionAudienceKey];
60 |
61 |
62 | // Set transaction type enumeration
63 | if ([transactionType isEqualToString:VENTransactionTypeStrings[VENTransactionTypeCharge]]) {
64 | self.transactionType = VENTransactionTypeCharge;
65 | }
66 | else if ([transactionType isEqualToString:VENTransactionTypeStrings[VENTransactionTypePay]]) {
67 | self.transactionType = VENTransactionTypePay;
68 | }
69 | else {
70 | self.transactionType = VENTransactionTypeUnknown;
71 | }
72 |
73 |
74 | // Set status enumeration
75 | if ([transactionStatus isEqualToString:VENTransactionStatusStrings[VENTransactionStatusPending]]) {
76 | self.status = VENTransactionStatusPending;
77 | }
78 | else if ([transactionStatus isEqualToString:VENTransactionStatusStrings[VENTransactionStatusSettled]]) {
79 | self.status = VENTransactionStatusSettled;
80 | }
81 | else if ([transactionStatus isEqualToString:VENTransactionStatusStrings[VENTransactionStatusFailed]]) {
82 | self.status = VENTransactionStatusFailed;
83 | }
84 | else {
85 | self.status = VENTransactionStatusUnknown;
86 | }
87 |
88 |
89 | // Set audience enumeration
90 | if ([transactionAudience isEqualToString:VENTransactionAudienceStrings[VENTransactionAudiencePublic]]) {
91 | self.audience = VENTransactionAudiencePublic;
92 | }
93 | else if ([transactionAudience isEqualToString:VENTransactionAudienceStrings[VENTransactionAudienceFriends]]) {
94 | self.audience = VENTransactionAudienceFriends;
95 | }
96 | else if ([transactionAudience isEqualToString:VENTransactionAudienceStrings[VENTransactionAudiencePrivate]]) {
97 | self.audience = VENTransactionAudiencePrivate;
98 | }
99 | else {
100 | self.audience = VENTransactionAudiencePrivate;
101 | }
102 |
103 |
104 | // Set up VENUser actor
105 | NSDictionary *userDictionary = cleanDictionary[VENTransactionActorKey];
106 | if ([VENUser canInitWithDictionary:userDictionary]) {
107 | VENUser *user = [[VENUser alloc] initWithDictionary:userDictionary];
108 | self.actor = user;
109 | }
110 |
111 |
112 | // Set up VENTransactionTargets
113 | NSMutableDictionary *targetDictionary = [cleanDictionary[VENTransactionTargetKey] mutableCopy];
114 | if (cleanDictionary[VENTransactionAmountKey]) {
115 | targetDictionary[VENTransactionAmountKey] = cleanDictionary[VENTransactionAmountKey];
116 | }
117 | if ([VENTransactionTarget canInitWithDictionary:targetDictionary]) {
118 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithDictionary:targetDictionary];
119 | self.target = target;
120 | }
121 | }
122 | return self;
123 | }
124 |
125 |
126 | #pragma mark - Private Instance Methods
127 |
128 | - (BOOL)isEqual:(id)object {
129 | VENTransaction *otherObject = (VENTransaction *)object;
130 |
131 | if (![otherObject.transactionID isEqualToString:self.transactionID]
132 | || otherObject.transactionType != self.transactionType) {
133 | return NO;
134 | }
135 |
136 | return YES;
137 | }
138 |
139 | @end
140 |
--------------------------------------------------------------------------------
/VENCore/Models/Transactions/VENTransactionPayloadKeys.h:
--------------------------------------------------------------------------------
1 | #ifndef VENCore_VENTransactionPayloadKeys_h
2 | #define VENCore_VENTransactionPayloadKeys_h
3 |
4 | //Payment Section
5 | #define VENTransactionIDKey @"id"
6 | #define VENTransactionStatusKey @"status"
7 | #define VENTransactionNoteKey @"note"
8 | #define VENTransactionAmountKey @"amount"
9 | #define VENTransactionTypeKey @"action"
10 | #define VENTransactionAudienceKey @"audience"
11 | #define VENTransactionActorKey @"actor"
12 | #define VENTransactionTargetKey @"target"
13 |
14 | //Target Subsection
15 | #define VENTransactionTargetTypeKey @"type"
16 | #define VENTransactionTargetPhoneKey @"phone"
17 | #define VENTransactionTargetEmailKey @"email"
18 | #define VENTransactionTargetUserKey @"user"
19 |
20 | //User Subsection
21 | #define VENTransactionTargetUserIDKey @"id"
22 |
23 |
24 | //Actor Subsection
25 | #define VENTransactionActorUserID @"id"
26 |
27 | #endif
28 |
--------------------------------------------------------------------------------
/VENCore/Models/Transactions/VENTransactionTarget.h:
--------------------------------------------------------------------------------
1 | @import Foundation;
2 |
3 | @class VENUser;
4 |
5 | typedef NS_ENUM(NSUInteger, VENTargetType) {
6 | VENTargetTypeUnknown,
7 | VENTargetTypePhone,
8 | VENTargetTypeEmail,
9 | VENTargetTypeUserId
10 | };
11 |
12 | @interface VENTransactionTarget : NSObject
13 |
14 | @property (assign, nonatomic) VENTargetType targetType;
15 | @property (copy, nonatomic) NSString *handle; // cell number, email, or Venmo user ID.
16 | @property (assign, nonatomic) NSUInteger amount;
17 | @property (copy, nonatomic) VENUser *user;
18 |
19 | /**
20 | * Determines whether the passed NSDictionary can construct a valid TransactionTarget
21 | */
22 | + (BOOL)canInitWithDictionary:(NSDictionary *)dictionary;
23 |
24 |
25 | /**
26 | * Initializes a target with the given handle.
27 | * @param handle A phone number, email, or Venmo external user ID.
28 | * @param amount The amount in pennies.
29 | * @return A VENTransactionTarget instance
30 | */
31 | - (instancetype)initWithHandle:(NSString *)phoneEmailOrUserID amount:(NSInteger)amount;
32 |
33 |
34 | /**
35 | * Initializes a target from a payload dictionary
36 | * @param dictionary A dictionary supplied in a payload
37 | * @return A VENTransactionTarget instance
38 | */
39 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary;
40 |
41 |
42 | /**
43 | * Returns YES if the target is valid.
44 | */
45 | - (BOOL)isValid;
46 |
47 |
48 | /**
49 | * Sets the target's user object and sets the target's handle to the user's external ID.
50 | */
51 | - (void)setUser:(VENUser *)user;
52 |
53 |
54 | /**
55 | * Creates a dictionary representation of the object
56 | * @return A dictionary representation of the object
57 | */
58 | - (NSDictionary *)dictionaryRepresentation;
59 |
60 | @end
61 |
--------------------------------------------------------------------------------
/VENCore/Models/Transactions/VENTransactionTarget.m:
--------------------------------------------------------------------------------
1 | #import "VENTransactionTarget.h"
2 | #import "VENCore.h"
3 | #import "VENTransactionPayloadKeys.h"
4 | #import "NSString+VENCore.h"
5 | #import "NSDictionary+VENCore.h"
6 | #import "VENUser.h"
7 |
8 | @import CoreGraphics;
9 |
10 | @implementation VENTransactionTarget
11 |
12 | #pragma mark - Class Methods
13 |
14 | + (BOOL)canInitWithDictionary:(NSDictionary *)dictionary {
15 |
16 | NSArray *requiredKeys = @[VENTransactionAmountKey, VENTransactionTargetTypeKey];
17 |
18 | for (NSString *key in requiredKeys) {
19 | if (!dictionary[key] || [dictionary[key] isKindOfClass:[NSNull class]]) {
20 | return NO;
21 | }
22 | }
23 |
24 | NSArray *validTargetTypes = @[VENTransactionTargetPhoneKey, VENTransactionTargetEmailKey, VENTransactionTargetUserKey];
25 |
26 | NSString *targetType = dictionary[VENTransactionTargetTypeKey];
27 | if (![validTargetTypes containsObject:targetType]) {
28 | return NO;
29 | }
30 |
31 | id amount = dictionary[VENTransactionAmountKey];
32 |
33 | if ([amount isKindOfClass:[NSString class]] && ![amount intValue]) {
34 | return NO;
35 | }
36 | else if ([amount respondsToSelector:@selector(doubleValue)]) {
37 | amount = @([amount doubleValue] * 100.);
38 | }
39 | else {
40 | return NO;
41 | }
42 |
43 | return YES;
44 | }
45 |
46 |
47 | #pragma mark - Public Instance Methods
48 |
49 | - (instancetype)initWithHandle:(NSString *)phoneEmailOrUserID amount:(NSInteger)amount {
50 | if (amount < 0) {
51 | return nil;
52 | }
53 |
54 | self = [super init];
55 | if (self) {
56 | self.handle = phoneEmailOrUserID;
57 | self.amount = amount;
58 | self.targetType = [phoneEmailOrUserID targetType];
59 | }
60 | return self;
61 | }
62 |
63 |
64 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary {
65 |
66 | self = [super init];
67 | if (self) {
68 |
69 | if (!dictionary || ![dictionary isKindOfClass:[NSDictionary class]]) {
70 | return self;
71 | }
72 |
73 | NSDictionary *cleanDictionary = [dictionary dictionaryByCleansingResponseDictionary];
74 |
75 | NSString *targetType = cleanDictionary[VENTransactionTargetTypeKey];
76 |
77 | if ([targetType isEqualToString:VENTransactionTargetEmailKey]) {
78 | self.targetType = VENTargetTypeEmail;
79 | }
80 | else if ([targetType isEqualToString:VENTransactionTargetUserKey]) {
81 | self.targetType = VENTargetTypeUserId;
82 | }
83 | else if ([targetType isEqualToString:VENTransactionTargetPhoneKey]) {
84 | self.targetType = VENTargetTypePhone;
85 | }
86 | else {
87 | self.targetType = VENTargetTypeUnknown;
88 | }
89 |
90 | self.handle = cleanDictionary[targetType];
91 | self.amount = (NSUInteger)([cleanDictionary[VENTransactionAmountKey] doubleValue] * (double)100);
92 | }
93 | return self;
94 | }
95 |
96 |
97 | - (NSDictionary *)dictionaryRepresentation {
98 | NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
99 | if (self.handle) {
100 | //dictionary[VENTransactionTargetTypeKey] =
101 | VENTargetType targetType = [self.handle targetType];
102 | switch (targetType) {
103 | case VENTargetTypeEmail:
104 | dictionary[VENTransactionTargetTypeKey] = VENTransactionTargetEmailKey;
105 | dictionary[VENTransactionTargetEmailKey] = self.handle;
106 | break;
107 | case VENTargetTypePhone:
108 | dictionary[VENTransactionTargetTypeKey] = VENTransactionTargetPhoneKey;
109 | dictionary[VENTransactionTargetPhoneKey] = self.handle;
110 | break;
111 | case VENTargetTypeUserId:
112 | dictionary[VENTransactionTargetTypeKey] = VENTransactionTargetUserKey;
113 | dictionary[VENTransactionTargetUserKey] = self.handle;
114 | break;
115 | default:
116 | break;
117 | }
118 | }
119 |
120 | if (self.amount) {
121 | dictionary[VENTransactionAmountKey] = @((CGFloat)self.amount/100.);
122 | }
123 |
124 | return dictionary;
125 | }
126 |
127 |
128 | - (BOOL)isValid {
129 | BOOL hasValidHandle = [self.handle isUserId] || [self.handle isUSPhone] || [self.handle isEmail];
130 | return hasValidHandle && self.targetType != VENTargetTypeUnknown && self.amount > 0;
131 | }
132 |
133 |
134 | #pragma mark - Other Methods
135 |
136 | - (void)setUser:(VENUser *)user {
137 | _user = user;
138 | self.handle = user.externalId;
139 | self.targetType = [self.handle targetType];
140 | }
141 |
142 |
143 | - (BOOL)isEqual:(id)object {
144 | if (![object isKindOfClass:[self class]]) {
145 | return NO;
146 | }
147 |
148 | VENTransactionTarget *otherTarget = (VENTransactionTarget *)object;
149 |
150 | if ((otherTarget.handle || self.handle) && ![otherTarget.handle isEqualToString:self.handle]) {
151 | return NO;
152 | }
153 | if (otherTarget.amount != self.amount) {
154 | return NO;
155 | }
156 |
157 | return YES;
158 | }
159 |
160 | @end
161 |
--------------------------------------------------------------------------------
/VENCore/Models/Users/VENUser.h:
--------------------------------------------------------------------------------
1 | @import Foundation;
2 |
3 | @class VENUser;
4 |
5 | typedef void(^VENUserFetchSuccessBlock)(VENUser *user);
6 | typedef void(^VENUserFetchFailureBlock)(NSError *error);
7 | typedef void(^VENFriendsFetchSuccessBlock)(NSArray *friends);
8 | typedef void(^VENFriendsFetchFailureBlock)(NSError *error);
9 | /**
10 | * @note Users are considered equal if and only if their external IDs are the same
11 | */
12 | @interface VENUser : NSObject
13 |
14 | @property (copy, nonatomic) NSString *username;
15 | @property (copy, nonatomic) NSString *firstName;
16 | @property (copy, nonatomic) NSString *lastName;
17 | @property (copy, nonatomic) NSString *displayName;
18 | @property (copy, nonatomic) NSString *about;
19 | @property (copy, nonatomic) NSString *profileImageUrl;
20 | @property (copy, nonatomic) NSString *primaryPhone;
21 | @property (copy, nonatomic) NSString *primaryEmail;
22 | @property (copy, nonatomic) NSString *internalId;
23 | @property (copy, nonatomic) NSString *externalId;
24 | @property (strong, nonatomic) NSDate *dateJoined;
25 |
26 |
27 | /**
28 | * Initializes a user with a dictionary of User object keys.
29 | * @param The dictionary to extract keys from to create a VENUser
30 | * @return VENUser object described in dictionary
31 | * @note Calling code should call canInitWithDictionary first
32 | */
33 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary;
34 |
35 |
36 | /**
37 | * Creates a dictionary containing all keys set on the user.
38 | * This dictionary can be passed to initWithDictionary to recreate the object.
39 |
40 | * @return NSDictionary object with all fields set on the VENUser
41 | */
42 | - (NSDictionary *)dictionaryRepresentation;
43 |
44 |
45 | /**
46 | * Determines whether the dictionary represents a valid user
47 | * @param The dictionary for evaluation of whether it can create a valid VENUser
48 | * @return BOOL indicating whether this dictionary can create a valid VENUser
49 | * @note This should be called before initWithDictionary:
50 | */
51 | + (BOOL)canInitWithDictionary:(NSDictionary *)dictionary;
52 |
53 |
54 | /**
55 | * Asynchronously fetch a user with a given externalId
56 | * @param externalId An NSString of the desired users externalId
57 | * @param successBlock A block to be executed with the fetched user
58 | * @param failureBlock A block to be executed in case of failure / error
59 | * @note This may return from a cache or pull from the network
60 | */
61 | + (void)fetchUserWithExternalId:(NSString *)externalId
62 | success:(VENUserFetchSuccessBlock)successBlock
63 | failure:(VENUserFetchFailureBlock)failureBlock;
64 |
65 |
66 | /**
67 | * Asynchronously fetch a user's friends with a given externalId
68 | * @param externalID An NSString of the desired user's externalId
69 | * @param successBlock A block to be executed with the fetched array of friends
70 | * @param failureBlock A block to be executed in case of failure / error
71 | */
72 | + (void)fetchFriendsWithExternalId:(NSString *)externalId
73 | success:(VENFriendsFetchSuccessBlock)successBlock
74 | failure:(VENFriendsFetchFailureBlock)failureBlock;
75 |
76 |
77 | @end
--------------------------------------------------------------------------------
/VENCore/Models/Users/VENUser.m:
--------------------------------------------------------------------------------
1 | #import "VENUser.h"
2 | #import "VENCore.h"
3 | #import "NSDictionary+VENCore.h"
4 | #import "VENUserPayloadKeys.h"
5 |
6 | @implementation VENUser
7 |
8 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary {
9 | self = [super init];
10 |
11 | if (self) {
12 |
13 | if (!dictionary || ![dictionary isKindOfClass:[NSDictionary class]]) {
14 | return self;
15 | }
16 |
17 | NSDictionary *cleanDictionary = [dictionary dictionaryByCleansingResponseDictionary];
18 |
19 | self.username = cleanDictionary[VENUserKeyUsername];
20 | self.firstName = cleanDictionary[VENUserKeyFirstName];
21 | self.lastName = cleanDictionary[VENUserKeyLastName];
22 | self.displayName = cleanDictionary[VENUserKeyDisplayName];
23 | self.about = cleanDictionary[VENUserKeyAbout];
24 | self.primaryPhone = cleanDictionary[VENUserKeyPhone];
25 | self.internalId = cleanDictionary[VENUserKeyInternalId];
26 | self.externalId = cleanDictionary[VENUserKeyExternalId];
27 | self.dateJoined = cleanDictionary[VENUserKeyDateJoined];
28 | self.primaryEmail = cleanDictionary[VENUserKeyEmail];
29 | self.profileImageUrl= cleanDictionary[VENUserKeyProfileImageUrl];
30 | }
31 |
32 | return self;
33 | }
34 |
35 |
36 | - (instancetype)copyWithZone:(NSZone *)zone {
37 |
38 | VENUser *newUser = [[[self class] alloc] init];
39 |
40 | newUser.username = self.username;
41 | newUser.firstName = self.firstName;
42 | newUser.lastName = self.lastName;
43 | newUser.displayName = self.displayName;
44 | newUser.about = self.about;
45 | newUser.primaryPhone = self.primaryPhone;
46 | newUser.primaryEmail = self.primaryEmail;
47 | newUser.internalId = self.internalId;
48 | newUser.externalId = self.externalId;
49 | newUser.dateJoined = self.dateJoined;
50 | newUser.profileImageUrl = self.profileImageUrl;
51 |
52 | return newUser;
53 | }
54 |
55 |
56 | - (BOOL)isEqual:(id)object {
57 |
58 | if ([object class] != [self class]) {
59 | return NO;
60 | }
61 | VENUser *comparisonUser = (VENUser *)object;
62 |
63 | BOOL result = [self.externalId isEqualToString:comparisonUser.externalId];
64 | return result;
65 | }
66 |
67 |
68 | - (NSString *)description {
69 | return [NSString stringWithFormat:@"%@ :: %@", [self class], [self dictionaryRepresentation]];
70 | }
71 |
72 |
73 | + (BOOL)canInitWithDictionary:(NSDictionary *)dictionary {
74 | if (![dictionary isKindOfClass:[NSDictionary class]]) {
75 | return NO;
76 | }
77 |
78 | NSArray *requiredKeys = @[VENUserKeyExternalId, VENUserKeyUsername];
79 |
80 | for (NSString *key in requiredKeys) {
81 | if (!dictionary[key] || [dictionary[key] isEqualToString:@""]) {
82 | return NO;
83 | }
84 | }
85 | return YES;
86 | }
87 |
88 | - (NSDictionary *)dictionaryRepresentation {
89 | NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
90 |
91 | if (self.username) {
92 | dictionary[VENUserKeyUsername] = self.username;
93 | }
94 |
95 | if (self.firstName) {
96 | dictionary[VENUserKeyFirstName] = self.firstName;
97 | }
98 |
99 | if (self.lastName) {
100 | dictionary[VENUserKeyLastName] = self.lastName;
101 | }
102 |
103 | if (self.displayName) {
104 | dictionary[VENUserKeyDisplayName] = self.displayName;
105 | }
106 |
107 | if (self.about) {
108 | dictionary[VENUserKeyAbout] = self.about;
109 | }
110 |
111 | if (self.primaryPhone) {
112 | dictionary[VENUserKeyPhone] = self.primaryPhone;
113 | }
114 |
115 | if (self.primaryEmail) {
116 | dictionary[VENUserKeyEmail] = self.primaryEmail;
117 | }
118 |
119 | if (self.internalId) {
120 | dictionary[VENUserKeyInternalId] = self.internalId;
121 | }
122 |
123 | if (self.externalId) {
124 | dictionary[VENUserKeyExternalId] = self.externalId;
125 | }
126 |
127 | if (self.profileImageUrl) {
128 | dictionary[VENUserKeyProfileImageUrl] = self.profileImageUrl;
129 | }
130 |
131 | if (self.dateJoined) {
132 | dictionary[VENUserKeyDateJoined] = self.dateJoined;
133 | }
134 |
135 | return dictionary;
136 | }
137 |
138 | + (void)fetchUserWithExternalId:(NSString *)externalId
139 | success:(VENUserFetchSuccessBlock)successBlock
140 | failure:(VENUserFetchFailureBlock)failureBlock {
141 |
142 | if((![externalId isKindOfClass:[NSString class]] || ![externalId length]) && failureBlock) {
143 | NSError *error = [[NSError alloc] initWithDomain:VENErrorDomainCore
144 | code:-999
145 | userInfo:@{}];
146 | failureBlock(error);
147 | return;
148 | }
149 |
150 | NSDictionary *parameters = @{};
151 |
152 | [[[VENCore defaultCore] httpClient] GET:[NSString stringWithFormat:@"users/%@", externalId]
153 | parameters:parameters
154 | success:^(VENHTTPResponse *response) {
155 |
156 | NSDictionary *userPayload = [NSDictionary dictionaryWithDictionary:response.object[@"data"]];
157 |
158 | if ([self canInitWithDictionary:userPayload] && successBlock) {
159 | VENUser *user = [[VENUser alloc] initWithDictionary:userPayload];
160 | successBlock(user);
161 | }
162 | else if (failureBlock) {
163 | failureBlock(response.error);
164 | }
165 | }
166 | failure:^(VENHTTPResponse *response, NSError *error) {
167 |
168 | if ([response error]) {
169 | error = [response error];
170 | }
171 |
172 | if (failureBlock) {
173 | failureBlock(error);
174 | }
175 | }];
176 | }
177 |
178 | + (void)fetchFriendsWithExternalId:(NSString *)externalId
179 | success:(VENFriendsFetchSuccessBlock)successBlock
180 | failure:(VENFriendsFetchFailureBlock)failureBlock {
181 | if((![externalId isKindOfClass:[NSString class]] || ![externalId length]) && failureBlock) {
182 | NSError *error = [[NSError alloc] initWithDomain:VENErrorDomainCore
183 | code:-999
184 | userInfo:@{}];
185 | failureBlock(error);
186 | return;
187 | }
188 | NSDictionary *parameters = @{@"limit": @"1000"};
189 | [[[VENCore defaultCore] httpClient] GET:[NSString stringWithFormat:@"users/%@/friends", externalId]
190 | parameters:parameters
191 | success:^(VENHTTPResponse *response) {
192 | NSArray *friendsPayload = [NSArray arrayWithArray:response.object[@"data"]];
193 | if(successBlock){
194 | NSMutableArray *friendsArray = [[NSMutableArray alloc] init];
195 | for (id object in friendsPayload) {
196 | if ([object isKindOfClass:[NSDictionary class]]) {
197 | NSDictionary *friendDictionary = (NSDictionary *)object;
198 | if ([VENUser canInitWithDictionary:friendDictionary]) {
199 | VENUser *friend = [[VENUser alloc] initWithDictionary:friendDictionary];
200 | [friendsArray addObject:friend];
201 | }
202 | }
203 | }
204 | successBlock(friendsArray);
205 | }
206 | else if (failureBlock){
207 | failureBlock(response.error);
208 | }
209 | }
210 | failure:^(VENHTTPResponse *response, NSError *error){
211 |
212 | if ([response error]) {
213 | error = [response error];
214 | }
215 |
216 | if (failureBlock) {
217 | failureBlock(error);
218 | }
219 |
220 | }];
221 | }
222 |
223 | @end
224 |
--------------------------------------------------------------------------------
/VENCore/Models/Users/VENUserPayloadKeys.h:
--------------------------------------------------------------------------------
1 | #ifndef VENCore_VENUserPayloadKeys_h
2 | #define VENCore_VENUserPayloadKeys_h
3 |
4 | #define VENUserKeyUsername @"username"
5 | #define VENUserKeyFirstName @"first_name"
6 | #define VENUserKeyLastName @"last_name"
7 | #define VENUserKeyDisplayName @"display_name"
8 | #define VENUserKeyAbout @"about"
9 | #define VENUserKeyPhone @"phone"
10 | #define VENUserKeyProfileImageUrl @"profile_picture_url"
11 | #define VENUserKeyEmail @"email"
12 | #define VENUserKeyInternalId @"internalid"
13 | #define VENUserKeyExternalId @"id"
14 | #define VENUserKeyDateJoined @"date_joined"
15 |
16 |
17 | #endif
18 |
--------------------------------------------------------------------------------
/VENCore/Networking/VENHTTP.h:
--------------------------------------------------------------------------------
1 | @import Foundation;
2 |
3 | extern NSString *const VENAPIPathPayments;
4 | extern NSString *const VENAPIPathUsers;
5 |
6 | @class VENHTTPResponse;
7 |
8 | @interface VENHTTP : NSObject
9 |
10 | @property (nonatomic, strong, readonly) NSURL *baseURL;
11 |
12 | - (instancetype)initWithBaseURL:(NSURL *)baseURL;
13 |
14 | - (void)setProtocolClasses:(NSArray *)protocolClasses;
15 |
16 | - (void)GET:(NSString *)path parameters:(NSDictionary *)parameters
17 | success:(void(^)(VENHTTPResponse *response))successBlock
18 | failure:(void(^)(VENHTTPResponse *response, NSError *error))failureBlock;
19 |
20 | - (void)POST:(NSString *)path parameters:(NSDictionary *)parameters
21 | success:(void(^)(VENHTTPResponse *response))successBlock
22 | failure:(void(^)(VENHTTPResponse *response, NSError *error))failureBlock;
23 |
24 | - (void)PUT:(NSString *)path parameters:(NSDictionary *)parameters
25 | success:(void(^)(VENHTTPResponse *response))successBlock
26 | failure:(void(^)(VENHTTPResponse *response, NSError *error))failureBlock;
27 |
28 | - (void)DELETE:(NSString *)path parameters:(NSDictionary *)parameters
29 | success:(void(^)(VENHTTPResponse *response))successBlock
30 | failure:(void(^)(VENHTTPResponse *response, NSError *error))failureBlock;
31 |
32 | - (void)setAccessToken:(NSString *)accessToken;
33 |
34 | - (NSDictionary *)defaultHeaders;;
35 |
36 | @end
37 |
--------------------------------------------------------------------------------
/VENCore/Networking/VENHTTP.m:
--------------------------------------------------------------------------------
1 | #import "VENHTTP.h"
2 |
3 | #import "NSError+VENCore.h"
4 | #import "VENHTTPResponse.h"
5 | #import "UIDevice+VENCore.h"
6 | #import "NSError+VENCore.h"
7 | #import "NSDictionary+VENCore.h"
8 | #import "NSArray+VENCore.h"
9 | @import CMDQueryStringSerialization;
10 |
11 | NSString *const VENAPIPathPayments = @"payments";
12 | NSString *const VENAPIPathUsers = @"users";
13 |
14 | @interface VENHTTP ()
15 |
16 | @property (strong, nonatomic) NSString *accessToken;
17 | @property (nonatomic, strong) NSURLSession *session;
18 | @property (nonatomic, strong, readwrite) NSURL *baseURL;
19 |
20 | @end
21 |
22 | @implementation VENHTTP
23 |
24 | - (instancetype)initWithBaseURL:(NSURL *)baseURL
25 | {
26 | self = [self init];
27 | if (self) {
28 | self.baseURL = baseURL;
29 | [self initializeSessionWithHeaders:self.defaultHeaders];
30 | }
31 | return self;
32 | }
33 |
34 |
35 | - (void)initializeSessionWithHeaders:(NSDictionary *)headers;
36 | {
37 | void(^createSessionBlock)() = ^() {
38 | NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
39 | configuration.HTTPAdditionalHeaders = self.defaultHeaders;
40 |
41 | NSOperationQueue *delegateQueue = [[NSOperationQueue alloc] init];
42 | delegateQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;
43 |
44 | self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:delegateQueue];
45 | };
46 |
47 | if (self.session) {
48 | [self.session resetWithCompletionHandler:createSessionBlock];
49 | }
50 | else {
51 | createSessionBlock();
52 | }
53 | }
54 |
55 |
56 | - (void)setProtocolClasses:(NSArray *)protocolClasses {
57 | NSURLSessionConfiguration *configuration = self.session.configuration;
58 | configuration.protocolClasses = protocolClasses;
59 | self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:self.session.delegateQueue];
60 | }
61 |
62 |
63 | - (void)GET:(NSString *)path parameters:(NSDictionary *)parameters
64 | success:(void(^)(VENHTTPResponse *response))successBlock
65 | failure:(void(^)(VENHTTPResponse *response, NSError *error))failureBlock
66 | {
67 | [self sendRequestWithMethod:@"GET" path:path parameters:parameters success:successBlock failure:failureBlock];
68 | }
69 |
70 |
71 | - (void)POST:(NSString *)path parameters:(NSDictionary *)parameters
72 | success:(void(^)(VENHTTPResponse *response))successBlock
73 | failure:(void(^)(VENHTTPResponse *response, NSError *error))failureBlock
74 | {
75 |
76 | [self sendRequestWithMethod:@"POST" path:path parameters:parameters success:successBlock failure:failureBlock];
77 | }
78 |
79 |
80 | - (void)PUT:(NSString *)path parameters:(NSDictionary *)parameters
81 | success:(void (^)(VENHTTPResponse *))successBlock
82 | failure:(void (^)(VENHTTPResponse *, NSError *))failureBlock
83 | {
84 | [self sendRequestWithMethod:@"PUT" path:path parameters:parameters success:successBlock failure:failureBlock];
85 | }
86 |
87 |
88 | - (void)DELETE:(NSString *)path parameters:(NSDictionary *)parameters
89 | success:(void(^)(VENHTTPResponse *response))successBlock
90 | failure:(void(^)(VENHTTPResponse *response, NSError *error))failureBlock
91 | {
92 | [self sendRequestWithMethod:@"DELETE" path:path parameters:parameters success:successBlock failure:failureBlock];
93 | }
94 |
95 |
96 | #pragma mark - Underlying HTTP
97 |
98 | // Modified from BTHTTP
99 | - (void)sendRequestWithMethod:(NSString *)method
100 | path:(NSString *)aPath
101 | parameters:(NSDictionary *)parameters
102 | success:(void(^)(VENHTTPResponse *response))successBlock
103 | failure:(void(^)(VENHTTPResponse *response, NSError *error))failureBlock
104 | {
105 | NSURL *fullPathURL = [self.baseURL URLByAppendingPathComponent:aPath];
106 | NSURLComponents *components = [NSURLComponents componentsWithString:fullPathURL.absoluteString];
107 |
108 | NSMutableURLRequest *request;
109 |
110 | NSString *percentEncodedQuery = [CMDQueryStringSerialization queryStringWithDictionary:parameters];
111 | if ([method isEqualToString:@"GET"] || [method isEqualToString:@"DELETE"]) {
112 | components.percentEncodedQuery = percentEncodedQuery;
113 | request = [NSMutableURLRequest requestWithURL:components.URL];
114 | } else {
115 | request = [NSMutableURLRequest requestWithURL:components.URL];
116 | NSData *body = [percentEncodedQuery dataUsingEncoding:NSUTF8StringEncoding];
117 | [request setHTTPBody:body];
118 | NSDictionary *headers = @{@"Content-Type": @"application/x-www-form-urlencoded; charset=utf-8"};
119 | [request setAllHTTPHeaderFields:headers];
120 | }
121 | // Add headers
122 | NSMutableDictionary *currentHeaders = [NSMutableDictionary dictionaryWithDictionary:request.allHTTPHeaderFields];
123 | [currentHeaders addEntriesFromDictionary:[self headersWithAccessToken:self.accessToken]];
124 | [request setAllHTTPHeaderFields:currentHeaders];
125 |
126 | [request setHTTPMethod:method];
127 |
128 | // Perform the actual request
129 | NSURLSessionTask *task = [self.session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
130 | [[self class] handleRequestCompletion:data response:response error:error success:successBlock failure:failureBlock];
131 | }];
132 | [task resume];
133 | }
134 |
135 | + (void)handleRequestCompletion:(NSData *)data
136 | response:(NSURLResponse *)response
137 | error:(NSError *)error
138 | success:(void(^)(VENHTTPResponse *response))successBlock
139 | failure:(void(^)(VENHTTPResponse *response, NSError *error))failureBlock
140 | {
141 | // Handle nil or non-HTTP requests, which are an unknown type of error
142 | if (![response isKindOfClass:[NSHTTPURLResponse class]]) {
143 | NSDictionary *userInfo = error ? @{NSUnderlyingErrorKey: error} : nil;
144 | NSError *error = [NSError errorWithDomain:VENErrorDomainHTTPResponse
145 | code:VENErrorCodeHTTPResponseBadResponse
146 | userInfo:userInfo];
147 | [self callFailureBlock:failureBlock response:nil error:error];
148 | return;
149 | }
150 |
151 | // Attempt to parse, and return an error if parsing fails
152 | NSError *jsonParseError;
153 | id responseObject = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonParseError];
154 |
155 | if (jsonParseError != nil) {
156 | [self callFailureBlock:failureBlock response:nil error:jsonParseError];
157 | return;
158 | }
159 |
160 | if ([responseObject isKindOfClass:[NSDictionary class]]) {
161 | NSDictionary *responseDictionary = (NSDictionary *)responseObject;
162 | NSDictionary *cleansedDictionary = [responseDictionary dictionaryByCleansingResponseDictionary];
163 |
164 | NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
165 | VENHTTPResponse *venHTTPResponse = [[VENHTTPResponse alloc] initWithStatusCode:httpResponse.statusCode responseObject:cleansedDictionary];
166 | if ([venHTTPResponse didError]) {
167 | [self callFailureBlock:failureBlock
168 | response:venHTTPResponse
169 | error:[venHTTPResponse error]];
170 | }
171 | else {
172 | [self callSuccessBlock:successBlock response:venHTTPResponse];
173 | }
174 | }
175 | else if ([responseObject isKindOfClass:[NSArray class]]) {
176 | NSArray *responseArray = (NSArray *)responseObject;
177 | NSArray *cleansedArray = [responseArray arrayByCleansingResponseArray];
178 | NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
179 | VENHTTPResponse *venHTTPResponse = [[VENHTTPResponse alloc] initWithStatusCode:httpResponse.statusCode responseObject:cleansedArray];
180 | if ([venHTTPResponse didError]) {
181 | [self callFailureBlock:failureBlock
182 | response:venHTTPResponse
183 | error:[venHTTPResponse error]];
184 | }
185 | else {
186 | [self callSuccessBlock:successBlock response:venHTTPResponse];
187 | }
188 | }
189 | else {
190 | NSDictionary *userInfo = error ? @{NSUnderlyingErrorKey: error} : nil;
191 | NSError *error = [NSError errorWithDomain:VENErrorDomainHTTPResponse
192 | code:VENErrorCodeHTTPResponseInvalidObjectType
193 | userInfo:userInfo];
194 | [self callFailureBlock:failureBlock response:nil error:error];
195 | }
196 | }
197 |
198 | + (void)callSuccessBlock:(void(^)(VENHTTPResponse *response))successBlock
199 | response:(VENHTTPResponse *)response
200 | {
201 | if (!successBlock) {
202 | return;
203 | }
204 | dispatch_async(dispatch_get_main_queue(), ^{
205 | successBlock(response);
206 | });
207 | }
208 |
209 | + (void)callFailureBlock:(void(^)(VENHTTPResponse *response, NSError *error))failureBlock
210 | response:(VENHTTPResponse *)response
211 | error:(NSError *)error {
212 | if (!failureBlock) {
213 | return;
214 | }
215 | dispatch_async(dispatch_get_main_queue(), ^{
216 | failureBlock(response, error);
217 | });
218 | }
219 |
220 | - (NSDictionary *)headersWithAccessToken:(NSString *)accessToken
221 | {
222 | if (!accessToken) {
223 | return [self defaultHeaders];
224 | }
225 |
226 | NSDictionary *cookieProperties = @{ NSHTTPCookieDomain : [self.baseURL host],
227 | NSHTTPCookiePath: @"/",
228 | NSHTTPCookieName: @"api_access_token",
229 | NSHTTPCookieValue: accessToken };
230 | NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:cookieProperties];
231 | // Add cookie to shared cookie storage for webview requests
232 | [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
233 | NSMutableDictionary *headers = [NSMutableDictionary dictionaryWithDictionary:[NSHTTPCookie requestHeaderFieldsWithCookies:@[cookie]]];
234 | [headers addEntriesFromDictionary:[self defaultHeaders]];
235 | return headers;
236 | }
237 |
238 | - (void)setAccessToken:(NSString *)accessToken
239 | {
240 | _accessToken = accessToken;
241 | NSDictionary *headers = [self headersWithAccessToken:accessToken];
242 | [self initializeSessionWithHeaders:headers];
243 | }
244 |
245 |
246 | - (NSDictionary *)defaultHeaders
247 | {
248 | NSMutableDictionary *defaultHeaders = [[NSMutableDictionary alloc] init];
249 | [defaultHeaders addEntriesFromDictionary:@{@"User-Agent" : [self userAgentString],
250 | @"Accept": [self acceptString],
251 | @"Accept-Language": [self acceptLanguageString],
252 | @"Device-ID" : [[UIDevice currentDevice] VEN_deviceIDString]}];
253 | return defaultHeaders;
254 | }
255 |
256 |
257 | - (NSString *)userAgentString
258 | {
259 | /**
260 | * Borrowed from AFNetworking 2.5.0, with modifications.
261 | * @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
262 | */
263 | NSDictionary *infoDict = [NSBundle mainBundle].infoDictionary;
264 | NSString *appName = infoDict[(__bridge NSString *)kCFBundleExecutableKey] ?: infoDict[(__bridge NSString *)kCFBundleIdentifierKey] ?: @"VENCore";
265 | NSString *appVersion = infoDict[@"CFBundleShortVersionString"] ?: infoDict[(__bridge NSString *)kCFBundleVersionKey] ?: @"0.0";
266 | NSString *model = [[UIDevice currentDevice] VEN_platformString];
267 | NSString *osVersion = [[UIDevice currentDevice] systemVersion];
268 |
269 | NSString *userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", appName, appVersion, model, osVersion, [UIScreen mainScreen].scale];
270 |
271 | if (userAgent) {
272 | if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
273 | NSMutableString *mutableUserAgent = [userAgent mutableCopy];
274 | if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef) @"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
275 | userAgent = mutableUserAgent;
276 | }
277 | }
278 | }
279 |
280 | return userAgent;
281 | }
282 |
283 |
284 | - (NSString *)acceptString
285 | {
286 | return @"application/json";
287 | }
288 |
289 |
290 | - (NSString *)acceptLanguageString
291 | {
292 | NSLocale *locale = [NSLocale currentLocale];
293 | return [NSString stringWithFormat:@"%@-%@",
294 | [locale objectForKey:NSLocaleLanguageCode],
295 | [locale objectForKey:NSLocaleCountryCode]];
296 | }
297 |
298 | @end
299 |
--------------------------------------------------------------------------------
/VENCore/Networking/VENHTTPResponse.h:
--------------------------------------------------------------------------------
1 | @import Foundation;
2 |
3 | extern NSString *const VENErrorDomainHTTPResponse;
4 |
5 | typedef NS_ENUM(NSInteger, VEErrorCodeHTTPResponse) {
6 | VENErrorCodeHTTPResponseUnauthorizedRequest,
7 | VENErrorCodeHTTPResponseBadResponse,
8 | VENErrorCodeHTTPResponseInvalidObjectType
9 | };
10 |
11 | @interface VENHTTPResponse : NSObject
12 |
13 | @property (nonatomic, readonly, strong) id object;
14 | @property (nonatomic, readonly, assign) NSInteger statusCode;
15 |
16 | - (instancetype)initWithStatusCode:(NSInteger)statusCode responseObject:(id)object;
17 |
18 | /**
19 | * Returns YES if the response represents an error state.
20 | */
21 | - (BOOL)didError;
22 |
23 |
24 | /**
25 | * Returns an NSError object or nil if the response does not represent an error state.
26 | */
27 | - (NSError *)error;
28 |
29 | @end
30 |
--------------------------------------------------------------------------------
/VENCore/Networking/VENHTTPResponse.m:
--------------------------------------------------------------------------------
1 | #import "VENHTTPResponse.h"
2 | #import "NSError+VENCore.h"
3 | #import "NSDictionary+VENCore.h"
4 |
5 | NSString *const VENErrorDomainHTTPResponse = @"com.venmo.VENCore.ErrorDomain.VENHTTPResponse";
6 |
7 | @interface VENHTTPResponse ()
8 |
9 | @property (nonatomic, readwrite, strong) id object;
10 | @property (nonatomic, readwrite, assign) NSInteger statusCode;
11 |
12 | @end
13 |
14 | @implementation VENHTTPResponse
15 |
16 | - (instancetype)initWithStatusCode:(NSInteger)statusCode responseObject:(id)object {
17 | self = [self init];
18 | if (self) {
19 | self.statusCode = statusCode;
20 | self.object = object;
21 | }
22 | return self;
23 | }
24 |
25 |
26 | - (NSString *)description {
27 | return [NSString stringWithFormat:@"", (int)self.statusCode, self.object];
28 | }
29 |
30 |
31 | - (BOOL)didError {
32 | return self.statusCode > 299;
33 | }
34 |
35 |
36 | - (NSError *)error {
37 | if (![self didError]) {
38 | return nil;
39 | }
40 |
41 | NSDictionary *errorObject = [self.object objectOrNilForKey:@"error"];
42 | NSString *message = [errorObject stringForKey:@"message"];
43 | NSError *error;
44 | if (message) {
45 | NSString *codeString = [errorObject objectOrNilForKey:@"code"] ?: [errorObject[@"errors"] lastObject][@"error_code"];
46 | NSInteger code = [codeString integerValue];
47 | error = [NSError errorWithDomain:VENErrorDomainHTTPResponse
48 | code:code
49 | description:message
50 | recoverySuggestion:nil];
51 | }
52 |
53 | return error;
54 | }
55 |
56 | @end
57 |
--------------------------------------------------------------------------------
/VENCore/VENCore-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 Foundation;
9 | #endif
10 |
--------------------------------------------------------------------------------
/VENCore/VENCore.h:
--------------------------------------------------------------------------------
1 | @import Foundation;
2 |
3 | #import
4 | #import
5 | #import
6 | #import
7 | #import
8 | #import
9 | #import
10 | #import
11 | #import
12 | #import
13 | #import
14 |
15 | extern NSString *const VENErrorDomainCore;
16 |
17 | typedef NS_ENUM(NSInteger, VENCoreErrorCode) {
18 | VENCoreErrorCodeNoDefaultCore,
19 | VENCoreErrorCodeNoAccessToken
20 | };
21 |
22 | @interface VENCore : NSObject
23 |
24 | @property (strong, nonatomic) VENHTTP *httpClient;
25 | @property (strong, nonatomic) NSString *accessToken;
26 |
27 |
28 | /**
29 | * Sets the shared core object.
30 | * @param core The core object to share.
31 | */
32 | + (void)setDefaultCore:(VENCore *)core;
33 |
34 |
35 | /**
36 | * Returns the shared core object.
37 | * @return A VENCore object.
38 | */
39 | + (instancetype)defaultCore;
40 |
41 |
42 | /**
43 | * Sets the core object's access token.
44 | */
45 | - (void)setAccessToken:(NSString *)accessToken;
46 |
47 | @end
48 |
--------------------------------------------------------------------------------
/VENCore/VENCore.m:
--------------------------------------------------------------------------------
1 | #import "VENCore.h"
2 |
3 | NSString *const VENErrorDomainCore = @"com.venmo.VENCore.ErrorDomain.VENCore";
4 |
5 | static VENCore *sharedInstance = nil;
6 |
7 | static NSString *const VENAPIBaseURL = @"https://api.venmo.com/v1";
8 |
9 | @implementation VENCore
10 |
11 | #pragma mark - Private
12 |
13 | - (instancetype)init {
14 | return [self initWithBaseURL:[NSURL URLWithString:VENAPIBaseURL]];
15 | }
16 |
17 | - (instancetype)initWithBaseURL:(NSURL *)baseURL {
18 | self = [super init];
19 | if (self) {
20 | self.httpClient = [[VENHTTP alloc] initWithBaseURL:baseURL];
21 | }
22 | return self;
23 | }
24 |
25 |
26 | - (void)setAccessToken:(NSString *)accessToken {
27 | _accessToken = accessToken;
28 | [self.httpClient setAccessToken:accessToken];
29 | }
30 |
31 |
32 | + (void)setDefaultCore:(VENCore *)core {
33 | sharedInstance = core;
34 | }
35 |
36 |
37 | + (instancetype)defaultCore {
38 | return sharedInstance;
39 | }
40 |
41 | @end
42 |
--------------------------------------------------------------------------------
/VENCoreIntegrationTests/PaymentSandboxSpec.m:
--------------------------------------------------------------------------------
1 | #import "VENCore.h"
2 | #import "VENCreateTransactionRequest.h"
3 |
4 | SpecBegin(PaymentSandbox)
5 |
6 | beforeAll(^{
7 | VENCore *core = [[VENCore alloc] init];
8 | NSURL *baseURL = [NSURL URLWithString:@"https://sandbox-api.venmo.com/v1"];
9 | core.httpClient = [[VENHTTP alloc] initWithBaseURL:baseURL];
10 | [core setAccessToken:[VENTestUtilities accessToken]];
11 | [VENCore setDefaultCore:core];
12 | });
13 |
14 | describe(@"Settled Payment", ^{
15 |
16 | NSUInteger amount = 10;
17 | NSString *note = @"A message to accompany the payment.";
18 | __block VENCreateTransactionRequest *transactionService;
19 |
20 | beforeEach(^{
21 | transactionService = [[VENCreateTransactionRequest alloc] init];
22 | });
23 |
24 | it(@"should make a successful payment to a user id", ^AsyncBlock{
25 | NSString *handle = @"145434160922624933";
26 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:handle amount:amount];
27 | transactionService.note = note;
28 | [transactionService addTransactionTarget:target];
29 |
30 | [transactionService sendWithSuccess:^(NSArray *sentTransactions, VENHTTPResponse *response) {
31 | expect(sentTransactions.count).to.equal(1);
32 | VENTransaction *sentTransaction = [sentTransactions firstObject];
33 | expect(sentTransaction.status).to.equal(VENTransactionStatusSettled);
34 | done();
35 | } failure:^(NSArray *sentTransactions, VENHTTPResponse *response, NSError *error) {
36 | failure(@"Failed to return correct response.");
37 | done();
38 | }];
39 | });
40 |
41 | it(@"should make a successful payment to an email", ^AsyncBlock{
42 | NSString *handle = @"venmo@venmo.com";
43 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:handle amount:amount];
44 | transactionService.note = note;
45 | [transactionService addTransactionTarget:target];
46 |
47 | [transactionService sendWithSuccess:^(NSArray *sentTransactions, VENHTTPResponse *response) {
48 | expect(sentTransactions.count).to.equal(1);
49 | VENTransaction *sentTransaction = [sentTransactions firstObject];
50 | expect(sentTransaction.status).to.equal(VENTransactionStatusSettled);
51 | done();
52 | } failure:^(NSArray *sentTransactions, VENHTTPResponse *response, NSError *error) {
53 | failure(@"Failed to return correct response.");
54 | done();
55 | }];
56 | });
57 |
58 |
59 | it(@"should make a successful payment to a phone number", ^AsyncBlock{
60 | NSString *handle = @"15555555555";
61 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:handle amount:amount];
62 | transactionService.note = note;
63 | [transactionService addTransactionTarget:target];
64 |
65 | [transactionService sendWithSuccess:^(NSArray *sentTransactions, VENHTTPResponse *response) {
66 | expect(sentTransactions.count).to.equal(1);
67 | VENTransaction *sentTransaction = [sentTransactions firstObject];
68 | expect(sentTransaction.status).to.equal(VENTransactionStatusSettled);
69 | done();
70 | } failure:^(NSArray *sentTransactions, VENHTTPResponse *response, NSError *error) {
71 | failure(@"Failed to return correct response.");
72 | done();
73 | }];
74 | });
75 | });
76 |
77 | describe(@"Failed Payment", ^{
78 |
79 | NSUInteger amount = 20;
80 | NSString *note = @"A message to accompany the payment.";
81 | __block VENCreateTransactionRequest *transactionService;
82 |
83 | beforeEach(^{
84 | transactionService = [[VENCreateTransactionRequest alloc] init];
85 | });
86 |
87 | it(@"should make a failed payment to an email", ^AsyncBlock{
88 | NSString *handle = @"venmo@venmo.com";
89 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:handle amount:amount];
90 | transactionService.note = note;
91 | [transactionService addTransactionTarget:target];
92 |
93 | [transactionService sendWithSuccess:^(NSArray *sentTransactions, VENHTTPResponse *response) {
94 | expect(sentTransactions.count).to.equal(1);
95 | VENTransaction *sentTransaction = [sentTransactions firstObject];
96 | expect(sentTransaction.status).to.equal(VENTransactionStatusFailed);
97 | done();
98 | } failure:^(NSArray *sentTransactions, VENHTTPResponse *response, NSError *error) {
99 | failure(@"Failed to return correct response.");
100 | done();
101 | }];
102 | });
103 | });
104 |
105 | describe(@"Pending Payment", ^{
106 |
107 | NSUInteger amount = 30;
108 | NSString *note = @"A message to accompany the payment.";
109 | __block VENCreateTransactionRequest *transactionService;
110 |
111 | beforeEach(^{
112 | transactionService = [[VENCreateTransactionRequest alloc] init];
113 | });
114 |
115 | it(@"should make a pending payment to an email", ^AsyncBlock{
116 | NSString *handle = @"foo@venmo.com";
117 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:handle amount:amount];
118 | transactionService.note = note;
119 | [transactionService addTransactionTarget:target];
120 |
121 | [transactionService sendWithSuccess:^(NSArray *sentTransactions, VENHTTPResponse *response) {
122 | expect(sentTransactions.count).to.equal(1);
123 | VENTransaction *sentTransaction = [sentTransactions firstObject];
124 | expect(sentTransaction.status).to.equal(VENTransactionStatusPending);
125 | done();
126 | } failure:^(NSArray *sentTransactions, VENHTTPResponse *response, NSError *error) {
127 | failure(@"Failed to return correct response.");
128 | done();
129 | }];
130 |
131 | });
132 |
133 | it(@"should make a pending payment to a new phone", ^AsyncBlock{
134 | NSString *handle = @"5555555556";
135 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:handle amount:amount];
136 | transactionService.note = note;
137 | [transactionService addTransactionTarget:target];
138 |
139 | [transactionService sendWithSuccess:^(NSArray *sentTransactions, VENHTTPResponse *response) {
140 | expect(sentTransactions.count).to.equal(1);
141 | VENTransaction *sentTransaction = [sentTransactions firstObject];
142 | expect(sentTransaction.status).to.equal(VENTransactionStatusPending);
143 | done();
144 | } failure:^(NSArray *sentTransactions, VENHTTPResponse *response, NSError *error) {
145 | failure(@"Failed to return correct response.");
146 | done();
147 | }];
148 | });
149 | });
150 |
151 | describe(@"Settled Charge", ^{
152 |
153 | NSUInteger amount = 10;
154 | NSString *note = @"A message to accompany the payment.";
155 | __block VENCreateTransactionRequest *transactionRequest;
156 |
157 | beforeEach(^{
158 | transactionRequest = [[VENCreateTransactionRequest alloc] init];
159 | transactionRequest.transactionType = VENTransactionTypeCharge;
160 | });
161 |
162 | it(@"should make a settled charge to a trusted email", ^AsyncBlock{
163 | NSString *handle = @"venmo@venmo.com";
164 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:handle amount:amount];
165 | transactionRequest.note = note;
166 | [transactionRequest addTransactionTarget:target];
167 |
168 | [transactionRequest sendWithSuccess:^(NSArray *sentTransactions, VENHTTPResponse *response) {
169 | expect(sentTransactions.count).to.equal(1);
170 | VENTransaction *sentTransaction = [sentTransactions firstObject];
171 | expect(sentTransaction.status).to.equal(VENTransactionStatusSettled);
172 | done();
173 | } failure:^(NSArray *sentTransactions, VENHTTPResponse *response, NSError *error) {
174 | failure(@"Failed to return correct response.");
175 | done();
176 | }];
177 | });
178 | });
179 |
180 | describe(@"Pending Charge", ^{
181 |
182 | NSUInteger amount = 20;
183 | NSString *note = @"A message to accompany the payment.";
184 | __block VENCreateTransactionRequest *transactionRequest;
185 |
186 | beforeEach(^{
187 | transactionRequest = [[VENCreateTransactionRequest alloc] init];
188 | transactionRequest.transactionType = VENTransactionTypeCharge;
189 | });
190 |
191 | it(@"should make a pending charge to a non-trusted friend", ^AsyncBlock{
192 | NSString *handle = @"venmo@venmo.com";
193 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:handle amount:amount];
194 | transactionRequest.note = note;
195 | [transactionRequest addTransactionTarget:target];
196 |
197 | [transactionRequest sendWithSuccess:^(NSArray *sentTransactions, VENHTTPResponse *response) {
198 | expect(sentTransactions.count).to.equal(1);
199 | VENTransaction *sentTransaction = [sentTransactions firstObject];
200 | expect(sentTransaction.status).to.equal(VENTransactionStatusPending);
201 | done();
202 | } failure:^(NSArray *sentTransactions, VENHTTPResponse *response, NSError *error) {
203 | failure(@"Failed to return correct response.");
204 | done();
205 | }];
206 | });
207 | });
208 |
209 |
210 | SpecEnd
211 |
--------------------------------------------------------------------------------
/VENCoreIntegrationTests/VENCoreIntegrationTests-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundlePackageType
14 | BNDL
15 | CFBundleShortVersionString
16 | 1.0
17 | CFBundleSignature
18 | ????
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/VENCoreIntegrationTests/VENCoreIntegrationTests-Prefix.pch:
--------------------------------------------------------------------------------
1 | @import Foundation;
2 | @import XCTest;
3 |
4 | #define SPT_SHORTHAND
5 | #import
6 |
7 | #define EXP_SHORTHAND
8 | #import
9 |
10 | #import
11 |
12 | #define HC_SHORTHAND
13 | #import
14 |
15 | #import
16 |
17 | #import "VENTestUtilities.h"
18 |
--------------------------------------------------------------------------------
/VENCoreIntegrationTests/VENUserIntegrationSpec.m:
--------------------------------------------------------------------------------
1 | #import "VENUser.h"
2 | #import "VENCore.h"
3 |
4 | SpecBegin(VENUserIntegration)
5 |
6 | NSString *plistPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"config" ofType:@"plist"];
7 | NSDictionary *config = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
8 | NSString *accessToken = config[@"access_token"];
9 |
10 | beforeAll(^{
11 | VENCore *core = [[VENCore alloc] init];
12 | [core setAccessToken:accessToken];
13 | [VENCore setDefaultCore:core];
14 | });
15 |
16 | describe(@"Fetching a user", ^{
17 | it(@"should retrieve a user with a correct external id", ^AsyncBlock{
18 | NSString *externalId = @"1062502213353472181"; // (Ben)
19 | [VENUser fetchUserWithExternalId:externalId success:^(VENUser *user) {
20 | expect(user.externalId).to.equal(externalId);
21 | done();
22 | } failure:^(NSError *error) {
23 | failure(@"Failed to return correct response.");
24 | done();
25 | }];
26 | });
27 | });
28 |
29 | SpecEnd
30 |
--------------------------------------------------------------------------------
/VENCoreIntegrationTests/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/VENCoreUnitTests/API/errors/invalidAccessTokenError.json:
--------------------------------------------------------------------------------
1 | {"error": {"message": "You did not pass a valid OAuth access token.", "code": 261}}
2 |
--------------------------------------------------------------------------------
/VENCoreUnitTests/API/errors/invalidAmountError.json:
--------------------------------------------------------------------------------
1 | {"error": {"message": "Invalid parameter : Please pass a valid amount", "code": 104}}
--------------------------------------------------------------------------------
/VENCoreUnitTests/API/errors/invalidAudienceError.json:
--------------------------------------------------------------------------------
1 | {"error": {"message": "Invalid audience", "code": 3104}}
--------------------------------------------------------------------------------
/VENCoreUnitTests/API/payments/paymentToEmail.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "balance": 123.00,
4 | "payment": {
5 | "id": "1322585332520059421",
6 | "status": "pending",
7 | "note": "Rock Climbing!",
8 | "amount": 4.0,
9 | "action": "pay",
10 | "date_created": "2013-12-30T19:40:57.865985",
11 | "date_completed": null,
12 | "audience": "public",
13 | "target": {
14 | "type": "email",
15 | "phone": null,
16 | "email": "nonvenmouser@gmail.com",
17 | "user": null
18 | },
19 | "actor": {
20 | "username": "delavara",
21 | "first_name": "Cody",
22 | "last_name": "De La Vara",
23 | "display_name": "Cody De La Vara",
24 | "about": "So happy",
25 | "profile_picture_url": "https://venmopics.appspot.com/u/v3/s/6ecc7b37-5c4a-49df-b91e-3552f02dc397",
26 | "id": "1088551785594880949",
27 | "date_joined": "2013-02-10T21:58:05"
28 | },
29 | "fee": null,
30 | "refund": null,
31 | "medium": "api",
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/VENCoreUnitTests/API/payments/paymentToUser.json:
--------------------------------------------------------------------------------
1 | {
2 | "data": {
3 | "balance": 9.90,
4 | "payment": {
5 | "id": "1322585332520059420",
6 | "status": "settled",
7 | "note": "Rock Climbing!",
8 | "amount": 0.10,
9 | "action": "pay",
10 | "date_created": "2013-12-30T19:40:57.865985",
11 | "date_completed": "2013-12-30T19:40:57.865985",
12 | "audience": "public",
13 | "target": {
14 | "type": "user",
15 | "phone": null,
16 | "email": null,
17 | "user": {
18 | "username": "testuser",
19 | "first_name": "Test",
20 | "last_name": "User",
21 | "display_name": "Test User",
22 | "about": "Long walks on the beach, sunsets, testing",
23 | "profile_picture_url": "https://venmopics.appspot.com/u/v3/s/6ecc7b37-5c4a-49df-b91e-3552f02dc397",
24 | "id": "153136",
25 | "date_joined": "2013-02-10T21:58:05"
26 | }
27 | },
28 | "actor": {
29 | "username": "delavara",
30 | "first_name": "Cody",
31 | "last_name": "De La Vara",
32 | "display_name": "Cody De La Vara",
33 | "about": "So happy",
34 | "profile_picture_url": "https://venmopics.appspot.com/u/v3/s/6ecc7b37-5c4a-49df-b91e-3552f02dc397",
35 | "id": "1088551785594880949",
36 | "date_joined": "2013-02-10T21:58:05"
37 | },
38 | "fee": null,
39 | "refund": null,
40 | "medium": "api",
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/VENCoreUnitTests/API/users/fetchChrisUser.json:
--------------------------------------------------------------------------------
1 | {"data": {"username": "chrismaddern", "first_name": "Chris", "last_name": "Maddern", "display_name": "Chris Maddern", "is_friend": false, "friends_count": 239, "about": "Mobile@Venmo. Bugs may be my fault.", "email": null, "phone": null, "profile_picture_url": "https://venmopics.appspot.com/u/v8/s/43cd2f55-5a41-42bb-9634-8dccd9656673", "id": "1106387358711808333", "date_joined": "2013-03-07T12:34:11"}}
2 |
--------------------------------------------------------------------------------
/VENCoreUnitTests/API/users/fetchFriends.json:
--------------------------------------------------------------------------------
1 | {
2 | "pagination":{
3 | "next":"https://api.venmo.com/v1/users/1088551785594880949/friends?after=161568197181440668&limit=3"
4 | },
5 | "data":[
6 | {
7 | "username":"kortina",
8 | "about":"make a joyful sound, la da da da",
9 | "last_name":"Kortina",
10 | "display_name":"Andrew Kortina",
11 | "first_name":"Andrew",
12 | "profile_picture_url":"https://venmopics.appspot.com/u/v5/s/25f9c7a0-5d5c-4988-8737-a3278d78ae42",
13 | "id":"145434160922624167"
14 | },
15 | {
16 | "username":"iqram",
17 | "about":"Frisbee enthusiast",
18 | "last_name":"magdon-ismail - organic",
19 | "display_name":"Iqram magdon-ismail - organic",
20 | "first_name":"Iqram",
21 | "profile_picture_url":"https://venmopics.appspot.com/u/v11/s/6c3740ad-26bd-475c-9da2-7cb3fd7591da",
22 | "id":"145436736225280235"
23 | },
24 | {
25 | "username":"staub",
26 | "about":"phonewalletbaby",
27 | "last_name":"Staub",
28 | "display_name":"Andrew Staub",
29 | "first_name":"Andrew",
30 | "profile_picture_url":"https://venmopics.appspot.com/u/v1/s/0b4b85ab-a72b-4326-a887-5bf7a08ff445",
31 | "id":"152710129123328341"
32 | },
33 | {
34 | "username":"azeem",
35 | "about":"No Short Bio",
36 | "last_name":"Ansar",
37 | "display_name":"Azeem Ansar",
38 | "first_name":"Azeem",
39 | "profile_picture_url":"https://venmopics.appspot.com/u/v1/s/2b797ba9-1bd5-41b8-a5a9-f133e128f234",
40 | "id":"161568197181440668"
41 | },
42 | {
43 | "username": "great-friend",
44 | "about": "details",
45 | "display_name": "Great Friend",
46 | "first_name": "Great",
47 | "profile_picture_url": null,
48 | "id": "1106387358711808333"
49 | }
50 | ]
51 | }
--------------------------------------------------------------------------------
/VENCoreUnitTests/API/users/fetchInvalidFriends.json:
--------------------------------------------------------------------------------
1 | {"error": {"message": "Resource not found.", "code": 283}}
2 |
--------------------------------------------------------------------------------
/VENCoreUnitTests/API/users/fetchInvalidUser.json:
--------------------------------------------------------------------------------
1 | {"error": {"message": "Resource not found.", "code": 283}}
2 |
--------------------------------------------------------------------------------
/VENCoreUnitTests/Categories/NSArray+VENCoreSpec.m:
--------------------------------------------------------------------------------
1 | #import "NSArray+VENCore.h"
2 |
3 | SpecBegin(NSArrayVENCore)
4 |
5 | describe(@"stringForElement", ^{
6 | it(@"should return the array untouched if all elements are strings", ^{
7 | NSString *firstElement = @"elementOne";
8 | NSString *secondElement = @"elementTwo";
9 | NSArray *array = @[firstElement, secondElement];
10 | [array arrayByCleansingResponseArray];
11 | expect([array objectAtIndex:0]).to.equal(firstElement);
12 | expect([array objectAtIndex:1]).to.equal(secondElement);
13 | });
14 | });
15 |
16 | describe(@"cleanseResponseArray", ^{
17 | it(@"should remove null values from an array", ^{
18 | NSMutableArray *mutableArray = [@[[NSNull null], @"notNull", @"alsoNotNull"] mutableCopy];
19 | [mutableArray cleanseResponseArray];
20 | [mutableArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
21 | expect(obj).to.beKindOf([NSString class]);
22 | }];
23 | });
24 |
25 | it(@"should stringify all NSNumbers", ^{
26 | NSMutableArray *mutableArray = [[NSMutableArray alloc] init];
27 | [mutableArray addObject:@"thisIsAString"];
28 | [mutableArray addObject:@"alsoAString"];
29 | [mutableArray addObject:@3];
30 | [mutableArray addObject:@44];
31 | [mutableArray cleanseResponseArray];
32 | [mutableArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
33 | expect(obj).to.beKindOf([NSString class]);
34 | }];
35 | });
36 |
37 | it(@"should not modify NSArray memebers that dont contain NSNull/NSNumber values or NSArray members", ^{
38 | NSArray *array = @[@"Lucas", @"uses", @"Venmo"];
39 | NSDictionary *dictionary = @{@1: @"one",
40 | @2: @"two",
41 | @3: @"3"};
42 | NSMutableArray *mutableArray = [@[@{@"array": array,
43 | @"dictionary": dictionary}] mutableCopy];
44 | [mutableArray cleanseResponseArray];
45 | expect(mutableArray).to.beKindOf([NSArray class]);
46 | expect(mutableArray[0][@"array"]).to.beKindOf([NSArray class]);
47 | expect(mutableArray[0][@"dictionary"]).to.beKindOf([NSDictionary class]);
48 | });
49 |
50 | it(@"should cleanse NSDictionary and NSArray members that contain NSNull/NSNumber values", ^{
51 | NSArray *memberArray = @[@"Lucas", @"uses", @"Venmo"];
52 | NSDictionary *memberDictionary = @{@1: @"one",
53 | @2: [NSNull null],
54 | @3: @3};
55 | NSArray *array = @[memberArray, memberDictionary];
56 | NSMutableArray *mutableArray = [array mutableCopy];
57 | [mutableArray cleanseResponseArray];
58 | NSDictionary *cleansedMemberDictionary = @{@1: @"one",
59 | @3: @"3"};
60 | NSArray *cleansedArray = @[memberArray, cleansedMemberDictionary];
61 | NSMutableArray *cleansedMutableArray = [cleansedArray mutableCopy];
62 | expect(mutableArray).to.equal(cleansedMutableArray);
63 | });
64 | });
65 |
66 | SpecEnd
67 |
--------------------------------------------------------------------------------
/VENCoreUnitTests/Categories/NSDictionary+VENCoreSpec.m:
--------------------------------------------------------------------------------
1 | #import "NSDictionary+VENCore.h"
2 |
3 | SpecBegin(NSDictionaryVENCore)
4 |
5 | describe(@"objectOrNilForKey:", ^{
6 | it(@"should return the object if it exists", ^{
7 | NSString *key = @"key";
8 | NSObject *object = [NSObject new];
9 | NSDictionary *d = @{key: object};
10 | expect([d objectOrNilForKey:key]).to.equal(object);
11 | });
12 |
13 | it(@"should return nil if the key has an NSNull object associated with it", ^{
14 | NSString *key = @"key";
15 | NSObject *object = [NSNull null];
16 | NSDictionary *d = @{key: object};
17 | expect([d objectOrNilForKey:key]).to.equal(nil);
18 | });
19 |
20 | it(@"should return nil if there is no object", ^{
21 | NSDictionary *d = @{@"key": @"value"};
22 | expect([d objectOrNilForKey:@"other_key"]).to.equal(nil);
23 | });
24 | });
25 |
26 |
27 | describe(@"boolForKey", ^{
28 | it(@"should return YES if the object is non-zero", ^{
29 | NSString *key = @"key";
30 | NSDictionary *d = @{key: @(1)};
31 | expect([d boolForKey:key]).to.equal(YES);
32 |
33 | d = @{key: @(-1)};
34 | expect([d boolForKey:key]).to.equal(YES);
35 |
36 | d = @{key: @(100)};
37 | expect([d boolForKey:key]).to.equal(YES);
38 | });
39 |
40 | it(@"should return NO if the object is 0", ^{
41 | NSString *key = @"key";
42 | NSDictionary *d = @{key: @(0)};
43 | expect([d boolForKey:key]).to.equal(NO);
44 | });
45 |
46 | it(@"should return NO if the object is NSNull", ^{
47 | NSString *key = @"key";
48 | NSDictionary *d = @{key: [NSNull null]};
49 | expect([d boolForKey:key]).to.equal(NO);
50 | });
51 |
52 | it(@"should return NO if there is no object", ^{
53 | NSDictionary *d = @{@"key": @"value"};
54 | expect([d boolForKey:@"other_key"]).to.equal(NO);
55 | });
56 | });
57 |
58 |
59 | describe(@"stringForKey", ^{
60 | it(@"should return the object if it is a string", ^{
61 | NSString *key = @"key";
62 | NSString *value = @"value";
63 | NSDictionary *d = @{key: value};
64 | expect([d stringForKey:key]).to.equal(value);
65 | });
66 |
67 | it(@"should return the string representation of a positive number", ^{
68 | NSString *key = @"key";
69 | NSNumber *value = @(123.45);
70 | NSDictionary *d = @{key: value};
71 | expect([d stringForKey:key]).to.equal(@"123.45");
72 | });
73 |
74 | it(@"should return the string representation of a negative number", ^{
75 | NSString *key = @"key";
76 | NSNumber *value = @(-123.45);
77 | NSDictionary *d = @{key: value};
78 | expect([d stringForKey:key]).to.equal(@"-123.45");
79 | });
80 | });
81 |
82 | describe(@"cleanseResponseDictionary", ^{
83 | it(@"should remove null values from a dictionary", ^{
84 | NSMutableDictionary *mutableDictionary = [@{@"nullKey":[NSNull null], @"notNullKey":@"value"} mutableCopy];
85 | [mutableDictionary cleanseResponseDictionary];
86 | expect(mutableDictionary[@"nullKey"]).to.beNil();
87 | expect(mutableDictionary[@"notNullKey"]).to.equal(@"value");
88 | });
89 |
90 | it(@"should stringify all NSNumbers", ^{
91 | NSMutableDictionary *mutableDictionary = [@{@"numberKey": @(3),
92 | @"numberKey2": @(12830.123),
93 | @"stringKey": @"Hi there"} mutableCopy];
94 | [mutableDictionary cleanseResponseDictionary];
95 |
96 | [mutableDictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
97 | expect(obj).to.beKindOf([NSString class]);
98 | }];
99 | });
100 |
101 | it(@"should not modify NSDictionary members that dont contain NSNull/NSNumber values or NSArray members", ^{
102 | NSArray *array = @[@"Lucas",@"uses",@"Venmo"];
103 | NSDictionary *dictionary = @{@1: @"one",
104 | @2: @"two",
105 | @3: @"3"};
106 | NSMutableDictionary *mutableDictionary = [@{@"array": array,
107 | @"dictionary": dictionary} mutableCopy];
108 |
109 | [mutableDictionary cleanseResponseDictionary];
110 | expect(mutableDictionary[@"array"]).to.beKindOf([NSArray class]);
111 | expect(mutableDictionary[@"dictionary"]).to.beKindOf([NSDictionary class]);
112 |
113 | });
114 |
115 | it(@"should not cleanse NSDictionary members that contain NSNull values and NSNumber values", ^{
116 | NSArray *memberArray = @[@"Lucas",@"uses",@"Venmo"];
117 | NSDictionary *memberDictionary = @{@1: @"one",
118 | @2: [NSNull null],
119 | @3: @3};
120 | NSMutableDictionary *mutableDictionary = [@{@"array": memberArray,
121 | @"dictionary": memberDictionary} mutableCopy];
122 | NSDictionary *cleansedMemberDictionary = @{@1: @"one",
123 | @3: @"3"};
124 | NSMutableDictionary *cleansedMutableDictionary = [mutableDictionary mutableCopy];
125 | [cleansedMutableDictionary setObject:cleansedMemberDictionary
126 | forKey:@"dictionary"];
127 |
128 | [mutableDictionary cleanseResponseDictionary];
129 | expect(mutableDictionary).to.equal(cleansedMutableDictionary);
130 | });
131 |
132 | });
133 |
134 | SpecEnd
--------------------------------------------------------------------------------
/VENCoreUnitTests/Categories/NSError+VENCoreSpec.m:
--------------------------------------------------------------------------------
1 | #import "NSError+VENCore.h"
2 | #import "VENHTTPResponse.h"
3 | #import "VENCore.h"
4 |
5 | SpecBegin(NSErrorVENCore)
6 |
7 | describe(@"errorWithDomain:Code:description:recoverySuggestion:", ^{
8 | it(@"should return an NSError object with the correct domain and code", ^{
9 | NSError *error = [NSError errorWithDomain:VENErrorDomainHTTPResponse
10 | code:123
11 | description:@"bad error"
12 | recoverySuggestion:@"deal with it"];
13 | expect(error.code).to.equal(123);
14 | expect(error.domain).to.equal(VENErrorDomainHTTPResponse);
15 | });
16 |
17 | it(@"should return an NSError object with the user info dictionary", ^{
18 | NSString *domain = VENErrorDomainHTTPResponse;
19 | NSString *description = @"fatal error";
20 | NSString *recoverySuggestion = @"deal with it";
21 | NSError *error = [NSError errorWithDomain:domain
22 | code:123
23 | description:description
24 | recoverySuggestion:recoverySuggestion];
25 | NSDictionary *expectedUserInfo = @{NSLocalizedDescriptionKey: description,
26 | NSLocalizedRecoverySuggestionErrorKey: recoverySuggestion};
27 | expect(error.userInfo).to.equal(expectedUserInfo);
28 | });
29 | });
30 |
31 |
32 | describe(@"defaultResponseError", ^{
33 | it(@"should return an NSError object with the correct code and user info", ^{
34 | NSError *error = [NSError defaultResponseError];
35 |
36 | NSString *expectedDescription = NSLocalizedString(@"Bad response", nil);
37 | expect(error.domain).to.equal(VENErrorDomainHTTPResponse);
38 | expect(error.code).to.equal(VENErrorCodeHTTPResponseBadResponse);
39 | expect(error.localizedDescription).to.equal(expectedDescription);
40 | });
41 | });
42 |
43 |
44 | describe(@"noDefaultCoreError", ^{
45 | it(@"should return an NSError object with the correct code and user info", ^{
46 | NSError *error = [NSError noDefaultCoreError];
47 |
48 | NSString *expectedDescription = NSLocalizedString(@"No default core", nil);
49 | expect(error.domain).to.equal(VENErrorDomainCore);
50 | expect(error.code).to.equal(VENCoreErrorCodeNoDefaultCore);
51 | expect(error.localizedDescription).to.equal(expectedDescription);
52 | });
53 | });
54 | SpecEnd
--------------------------------------------------------------------------------
/VENCoreUnitTests/Categories/NSString+VENCoreSpec.m:
--------------------------------------------------------------------------------
1 | #import "NSString+VENCore.h"
2 |
3 | SpecBegin(NSStringVENCore)
4 |
5 | describe(@"isUSPhone", ^{
6 | it(@"should return YES if the testPhone is all numbers and 10 digits", ^{
7 | NSString *testPhone = @"2234567890";
8 | expect([testPhone isUSPhone]).to.equal(YES);
9 | });
10 |
11 | it(@"should return YES if the testPhone is all numbers and 11 digits", ^{
12 | NSString *testPhone = @"12234567890";
13 | expect([testPhone isUSPhone]).to.equal(YES);
14 | });
15 |
16 | it(@"should return NO if the testPhone is all numbers and > 11 digits", ^{
17 | NSString *testPhone = @"112234567890";
18 | expect([testPhone isUSPhone]).to.equal(NO);
19 | });
20 |
21 | it(@"should return NO if the testPhone is all numbers and < 10 digits", ^{
22 | NSString *testPhone = @"234567890";
23 | expect([testPhone isUSPhone]).to.equal(NO);
24 | });
25 |
26 | it(@"should return YES if the testPhone is valid and has hyphens and parentheses", ^{
27 | NSString *testPhone = @"(223)456-7890";
28 | expect([testPhone isUSPhone]).to.equal(YES);
29 | });
30 |
31 | it(@"should return NO if the testPhone is 10 digits and has letters", ^{
32 | NSString *testPhone = @"12222T67890";
33 | expect([testPhone isUSPhone]).to.equal(NO);
34 | });
35 | });
36 |
37 | describe(@"isEmail", ^{
38 | it(@"should return YES if the email has a handle@domain.tld", ^{
39 | NSString *testEmail = @"test@domain.com";
40 | expect([testEmail isEmail]).to.equal(YES);
41 | });
42 |
43 | it(@"should return NO if the email is not handle@domain.tld", ^{
44 | NSString *testEmail = @"test@domaincom";
45 | expect([testEmail isEmail]).to.equal(NO);
46 |
47 | testEmail = @"testdomain.com";
48 | expect([testEmail isEmail]).to.equal(NO);
49 | });
50 | });
51 |
52 | describe(@"isUserId", ^{
53 | it(@"should return YES if the value is all numbers and not a valid phone number", ^{
54 | NSString *userId = @"1234";
55 | expect([userId isUserId]).to.equal(YES);
56 | });
57 |
58 | it(@"should return NO if the value is all numbers and is a valid phone number", ^{
59 | NSString *userId = @"1234567890";
60 | expect([userId isUserId]).to.equal(NO);
61 | });
62 |
63 | it(@"should return NO if the value is not all numbers", ^{
64 | NSString *userId = @"1234T";
65 | expect([userId isUserId]).to.equal(NO);
66 | });
67 | });
68 |
69 | describe(@"targetType", ^{
70 | it(@"should return VENTargetTypePhone if the value is a phone number", ^{
71 | NSString *testPhone = @"(223)456-7890";
72 | expect([testPhone targetType]).to.equal(VENTargetTypePhone);
73 | });
74 |
75 | it(@"should return VENTargetTypeUserId if the value is a userID", ^{
76 | NSString *userId = @"1234";
77 | expect([userId targetType]).to.equal(VENTargetTypeUserId);
78 | });
79 |
80 | it(@"should return VENTargetTypeEmail if the value is an email address", ^{
81 | NSString *testEmail = @"test@domain.com";
82 | expect([testEmail targetType]).to.equal(VENTargetTypeEmail);
83 | });
84 |
85 | it(@"should return VENTargetTypeUnknown if the value is an email address", ^{
86 | NSString *testEmail = @"1234T";
87 | expect([testEmail targetType]).to.equal(VENTargetTypeUnknown);
88 | });
89 | });
90 |
91 | describe(@"hasContent", ^{
92 | it(@"should return YES if the value contains non-whitespace characters", ^{
93 | NSString *string = @"*";
94 | expect([string hasContent]).to.equal(YES);
95 | });
96 | it(@"should return YES if the value contains non-whitespace characters", ^{
97 | NSString *string = @" ";
98 | expect([string hasContent]).to.equal(NO);
99 | });
100 | });
101 | SpecEnd
--------------------------------------------------------------------------------
/VENCoreUnitTests/Models/Transactions/VENCreateTransactionRequestSpec.m:
--------------------------------------------------------------------------------
1 | #import "VENTransaction.h"
2 | #import "VENTestUtilities.h"
3 | #import "VENTransactionTarget.h"
4 | #import "VENUser.h"
5 | #import "VENTransactionPayloadKeys.h"
6 | #import "VENCore.h"
7 | #import "VENHTTPResponse.h"
8 | #import "VENCreateTransactionRequest.h"
9 |
10 | @interface VENCreateTransactionRequest (Private)
11 |
12 | @property (strong, nonatomic) NSMutableOrderedSet *mutableTargets;
13 |
14 | - (NSDictionary *)dictionaryWithParametersForTarget:(VENTransactionTarget *)target;
15 |
16 | - (BOOL)containsDuplicateOfTarget:(VENTransactionTarget *)target;
17 |
18 | @end
19 |
20 | SpecBegin(VENCreateTransactionRequest)
21 |
22 | describe(@"Sending Payments With Stubbed Responses", ^{
23 |
24 | __block NSDictionary *emailPaymentObject;
25 | __block NSDictionary *userPaymentObject;
26 | __block id mockVENHTTP;
27 | __block VENCore *core;
28 |
29 | void(^stubSuccessBlockEmail)(NSInvocation *) = ^(NSInvocation *invocation) {
30 | void(^successBlock)(VENHTTPResponse *);
31 | [invocation getArgument:&successBlock atIndex:4];
32 |
33 | VENHTTPResponse *response = [[VENHTTPResponse alloc] initWithStatusCode:200 responseObject:@{@"data":@{@"payment":emailPaymentObject}}];
34 | successBlock(response);
35 | };
36 |
37 | void(^stubSuccessBlockPhone)(NSInvocation *) = ^(NSInvocation *invocation) {
38 | void(^successBlock)(VENHTTPResponse *);
39 | [invocation getArgument:&successBlock atIndex:4];
40 |
41 | VENHTTPResponse *response = [[VENHTTPResponse alloc] initWithStatusCode:200 responseObject:@{@"data":@{@"payment":userPaymentObject}}];
42 | successBlock(response);
43 | };
44 |
45 | void(^stubFailureBlock)(NSInvocation *) = ^(NSInvocation *invocation) {
46 | void(^failureBlock)(VENHTTPResponse *, NSError *);
47 | [invocation getArgument:&failureBlock atIndex:5];
48 |
49 | VENHTTPResponse *response = [[VENHTTPResponse alloc] initWithStatusCode:400 responseObject:nil];
50 | id mockError = [OCMockObject mockForClass:[NSError class]];
51 | failureBlock(response, mockError);
52 | };
53 |
54 | NSDictionary *(^expectedParameters)(VENCreateTransactionRequest *transactionService, VENTransactionTarget *target) =
55 | ^(VENCreateTransactionRequest *transactionService, VENTransactionTarget *target) {
56 | NSMutableDictionary *expectedParams = [[transactionService dictionaryWithParametersForTarget:target] mutableCopy];
57 | NSDictionary *accessTokenParams = @{@"access_token" : core.accessToken};
58 | [expectedParams addEntriesFromDictionary:accessTokenParams];
59 | return expectedParams;
60 | };
61 |
62 | beforeEach(^{
63 | NSDictionary *emailPaymentResponse = [VENTestUtilities objectFromJSONResource:@"paymentToEmail"];
64 | emailPaymentObject = emailPaymentResponse[@"data"][@"payment"];
65 | NSDictionary *userPaymentResponse = [VENTestUtilities objectFromJSONResource:@"paymentToUser"];
66 | userPaymentObject = userPaymentResponse[@"data"][@"payment"];
67 | mockVENHTTP = [OCMockObject niceMockForClass:[VENHTTP class]];
68 | core = [[VENCore alloc] init];
69 | core.httpClient = mockVENHTTP;
70 | [core setAccessToken:@"123"];
71 | [VENCore setDefaultCore:core];
72 |
73 | });
74 |
75 | describe(@"sending a transaction with one target", ^{
76 | it(@"should POST to the payments endpoint and call the success block when the POST succeeds", ^{
77 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
78 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"peter@venmo.com" amount:30];
79 | [transactionService addTransactionTarget:target];
80 | transactionService.note = @"hi";
81 |
82 | NSDictionary *expectedParams = expectedParameters(transactionService, target);
83 | [[[mockVENHTTP expect] andDo:stubSuccessBlockEmail] POST:@"payments"
84 | parameters:expectedParams
85 | success:OCMOCK_ANY
86 | failure:OCMOCK_ANY];
87 |
88 | waitUntil(^(DoneCallback done) {
89 | [transactionService sendWithSuccess:^(NSArray *sentTransactions, VENHTTPResponse *response) {
90 | expect([sentTransactions count]).to.equal(1);
91 | done();
92 | } failure:^(NSArray *sentTransactions, VENHTTPResponse *response, NSError *error) {
93 | failure(@"Failed to return correct response.");
94 | done();
95 | }];
96 | });
97 | });
98 |
99 | it(@"should POST to the payments endpoint and call the failure block when the POST fails", ^{
100 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
101 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"peter@venmo.com" amount:30];
102 | [transactionService addTransactionTarget:target];
103 | transactionService.note = @"hi";
104 |
105 | NSDictionary *expectedParams = expectedParameters(transactionService, target);
106 | [[[mockVENHTTP expect] andDo:stubFailureBlock] POST:@"payments"
107 | parameters:expectedParams
108 | success:OCMOCK_ANY
109 | failure:OCMOCK_ANY];
110 |
111 | waitUntil(^(DoneCallback done) {
112 | [transactionService sendWithSuccess:^(NSArray *sentTransactions, VENHTTPResponse *response) {
113 | failure(@"Failed to return correct response.");
114 | done();
115 | } failure:^(NSArray *sentTransactions, VENHTTPResponse *response, NSError *error) {
116 | expect([sentTransactions count]).to.equal(0);
117 | expect(response).toNot.beNil();
118 | expect(error).toNot.beNil();
119 | done();
120 | }];
121 | });
122 | });
123 | });
124 |
125 | describe(@"sending a transaction with two targets", ^{
126 | it(@"should POST twice to the payments endpoint and call the success block twice when both transactions succeed", ^{
127 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
128 | transactionService.note = @"hi";
129 |
130 | VENTransactionTarget *target1 = [[VENTransactionTarget alloc] initWithHandle:@"peter@venmo.com" amount:30];
131 | [transactionService addTransactionTarget:target1];
132 |
133 | VENTransactionTarget *target2 = [[VENTransactionTarget alloc] initWithHandle:@"ben@venmo.com" amount:420];
134 | [transactionService addTransactionTarget:target2];
135 |
136 | NSDictionary *expectedParameters1 = expectedParameters(transactionService, target1);
137 | NSDictionary *expectedParameters2 = expectedParameters(transactionService, target2);
138 |
139 | [[[mockVENHTTP expect] andDo:stubSuccessBlockEmail] POST:@"payments"
140 | parameters:expectedParameters1
141 | success:OCMOCK_ANY
142 | failure:OCMOCK_ANY];
143 | [[[mockVENHTTP expect] andDo:stubSuccessBlockPhone] POST:@"payments"
144 | parameters:expectedParameters2
145 | success:OCMOCK_ANY
146 | failure:OCMOCK_ANY];
147 |
148 | waitUntil(^(DoneCallback done) {
149 | [transactionService sendWithSuccess:^(NSArray *sentTransactions, VENHTTPResponse *response) {
150 | expect([sentTransactions count]).to.equal(2);
151 | done();
152 | } failure:^(NSArray *sentTransactions, VENHTTPResponse *response, NSError *error) {
153 | failure(@"Failed to return correct response.");
154 | done();
155 | }];
156 | });
157 | });
158 |
159 | it(@"should call successBlock for successful payment and failureBlock for second payment which fails", ^{
160 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
161 | transactionService.note = @"hi";
162 |
163 | VENTransactionTarget *target1 = [[VENTransactionTarget alloc] initWithHandle:@"peter@venmo.com" amount:30];
164 | [transactionService addTransactionTarget:target1];
165 |
166 | VENTransactionTarget *target2 = [[VENTransactionTarget alloc] initWithHandle:@"ben@venmo.com" amount:420];
167 | [transactionService addTransactionTarget:target2];
168 |
169 | NSDictionary *expectedParameters1 = expectedParameters(transactionService, target1);
170 | NSDictionary *expectedParameters2 = expectedParameters(transactionService, target2);
171 |
172 | [[[mockVENHTTP expect] andDo:stubSuccessBlockEmail] POST:@"payments"
173 | parameters:expectedParameters1
174 | success:OCMOCK_ANY
175 | failure:OCMOCK_ANY];
176 |
177 | [[[mockVENHTTP expect] andDo:stubFailureBlock] POST:@"payments"
178 | parameters:expectedParameters2
179 | success:OCMOCK_ANY
180 | failure:OCMOCK_ANY];
181 |
182 | waitUntil(^(DoneCallback done) {
183 | [transactionService sendWithSuccess:^(NSArray *sentTransactions, VENHTTPResponse *response) {
184 | failure(@"Failed to return correct response.");
185 | done();
186 | } failure:^(NSArray *sentTransactions, VENHTTPResponse *response, NSError *error) {
187 | // The failure block shouldn't be called
188 | expect([sentTransactions count]).to.equal(1);
189 | done();
190 | }];
191 | });
192 | });
193 |
194 | it(@"should not initiate second payment if the first payment fails", ^{
195 |
196 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
197 | transactionService.note = @"hi";
198 |
199 | VENTransactionTarget *target1 = [[VENTransactionTarget alloc] initWithHandle:@"peter@venmo.com" amount:30];
200 | [transactionService addTransactionTarget:target1];
201 | NSDictionary *expectedParameters1 = expectedParameters(transactionService, target1);
202 |
203 | //payment to target 2 should not be sent since the first payment fails
204 | VENTransactionTarget *target2 = [[VENTransactionTarget alloc] initWithHandle:@"das@venmo.com" amount:125];
205 | [transactionService addTransactionTarget:target2];
206 |
207 | [[[mockVENHTTP expect] andDo:stubFailureBlock] POST:@"payments"
208 | parameters:expectedParameters1
209 | success:OCMOCK_ANY
210 | failure:OCMOCK_ANY];
211 |
212 | waitUntil(^(DoneCallback done) {
213 | [transactionService sendWithSuccess:^(NSArray *sentTransactions, VENHTTPResponse *response) {
214 | failure(@"Failed to return correct response.");
215 | done();
216 | } failure:^(NSArray *sentTransactions, VENHTTPResponse *response, NSError *error) {
217 | expect([sentTransactions count]).to.equal(0);
218 | expect(response).toNot.beNil();
219 | expect(error).toNot.beNil();
220 | // Make sure no POSTs are made after the first one.
221 | dispatch_after(3, dispatch_get_main_queue(), ^{
222 | done();
223 | });
224 | }];
225 | });
226 | });
227 | });
228 | });
229 |
230 |
231 | describe(@"addTarget", ^{
232 | it(@"it should add a valid target", ^{
233 | id mockTarget = [OCMockObject niceMockForClass:[VENTransactionTarget class]];
234 | [[[mockTarget stub] andReturnValue:@(YES)]isValid];
235 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
236 | BOOL addedTarget = [transactionService addTransactionTarget:mockTarget];
237 | expect(addedTarget).to.beTruthy();
238 | expect([transactionService.targets count]).to.equal(1);
239 | });
240 |
241 | it(@"it should not add a invalid target", ^{
242 | id mockTarget = [OCMockObject niceMockForClass:[VENTransactionTarget class]];
243 | [[[mockTarget stub] andReturnValue:@(NO)] isValid];
244 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
245 | BOOL addedTarget = [transactionService addTransactionTarget:mockTarget];
246 | expect(addedTarget).to.beFalsy();
247 | expect([transactionService.targets count]).to.equal(0);
248 | });
249 |
250 | it(@"it should not add duplicate targets", ^{
251 | id mockTarget = [OCMockObject niceMockForClass:[VENTransactionTarget class]];
252 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
253 | id mockTransaction = [OCMockObject partialMockForObject:transactionService];
254 | [[[mockTransaction stub] andReturnValue:@(YES)] containsDuplicateOfTarget:OCMOCK_ANY];
255 |
256 | BOOL addedTarget = [mockTransaction addTransactionTarget:mockTarget];
257 | expect(addedTarget).to.beFalsy();
258 | expect(((VENCreateTransactionRequest *)mockTransaction).targets.count).to.equal(0);
259 | });
260 | });
261 |
262 |
263 | describe(@"readyToSend", ^{
264 | it(@"should return YES if transaction has at least one target, has a note, transactionType and status is not sent.", ^{
265 | id object = [NSObject new];
266 | NSOrderedSet *orderedSet = [[NSOrderedSet alloc] initWithObject:object];
267 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
268 | id mockTransaction = [OCMockObject partialMockForObject:transactionService];
269 | [[[mockTransaction stub] andReturn:orderedSet] mutableTargets];
270 | ((VENCreateTransactionRequest *)mockTransaction).note = @"Here is 10 Bucks";
271 | ((VENCreateTransactionRequest *)mockTransaction).transactionType = VENTransactionTypePay;
272 | expect([((VENCreateTransactionRequest *)mockTransaction) readyToSend]).to.equal(YES);
273 | });
274 |
275 | it(@"should return NO if there are 0 targets", ^{
276 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
277 | transactionService.note = @"Here is 10 Bucks";
278 | transactionService.transactionType = VENTransactionTypePay;
279 | expect([transactionService readyToSend]).to.equal(NO);
280 | });
281 |
282 | it(@"should return NO if transaction has no note", ^{
283 | id object = [NSObject new];
284 | NSOrderedSet *orderedSet = [[NSOrderedSet alloc] initWithObject:object];
285 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
286 | id mockTransaction = [OCMockObject partialMockForObject:transactionService];
287 | [[[mockTransaction stub] andReturn:orderedSet] mutableTargets];
288 | ((VENCreateTransactionRequest *)mockTransaction).transactionType = VENTransactionTypePay;
289 | expect([((VENCreateTransactionRequest *)mockTransaction) readyToSend]).to.equal(NO);
290 | });
291 |
292 | it(@"should return NO if transactionType has not been set", ^{
293 | id object = [NSObject new];
294 | NSOrderedSet *orderedSet = [[NSOrderedSet alloc] initWithObject:object];
295 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
296 | id mockTransaction = [OCMockObject partialMockForObject:transactionService];
297 | [[[mockTransaction stub] andReturn:orderedSet] mutableTargets];
298 | ((VENCreateTransactionRequest *)mockTransaction).note = @"Here is 10 Bucks";
299 | expect([((VENCreateTransactionRequest *)mockTransaction) readyToSend]).to.equal(NO);
300 | });
301 |
302 | });
303 |
304 |
305 | describe(@"dictionaryWithParametersForTarget:", ^{
306 | it(@"should create a parameters dictionary for positive amounts", ^{
307 | NSString *emailAddress = @"dasmer@venmo.com";
308 | NSString *note = @"Here is two bucks";
309 | NSString *amount = @"200";
310 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:emailAddress amount:[amount integerValue]];
311 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
312 | [transactionService addTransactionTarget:target];
313 | transactionService.note = note;
314 | transactionService.transactionType = VENTransactionTypePay;
315 | transactionService.audience = VENTransactionAudienceFriends;
316 | NSDictionary *expectedPostParameters = @{@"email": emailAddress,
317 | @"note": note,
318 | @"amount" : @"2.00",
319 | @"audience" : @"friends"};
320 | NSDictionary *postParameters = [transactionService dictionaryWithParametersForTarget:target];
321 | expect(postParameters).to.equal(expectedPostParameters);
322 |
323 | });
324 |
325 | it(@"should create a parameters dictionary for negative amounts", ^{
326 | NSString *emailAddress = @"dasmer@venmo.com";
327 | NSString *note = @"I want your two bucks";
328 | NSString *amount = @"200";
329 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:emailAddress amount:[amount integerValue]];
330 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
331 | [transactionService addTransactionTarget:target];
332 | transactionService.note = note;
333 | transactionService.transactionType = VENTransactionTypeCharge;
334 | transactionService.audience = VENTransactionAudiencePrivate;
335 | NSDictionary *expectedPostParameters = @{@"email": emailAddress,
336 | @"note": note,
337 | @"amount" : @"-2.00",
338 | @"audience" : @"private"};
339 | NSDictionary *postParameters = [transactionService dictionaryWithParametersForTarget:target];
340 | expect(postParameters).to.equal(expectedPostParameters);
341 |
342 | });
343 |
344 |
345 | it(@"should return nil if target type is unknown", ^{
346 | NSString *emailAddress = @"dasmer";
347 | NSString *note = @"I want your two bucks";
348 | NSString *amount = @"200";
349 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:emailAddress amount:[amount integerValue]];
350 | NSOrderedSet *orderedSet = [[NSOrderedSet alloc] initWithObject:target];
351 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
352 | id mockTransaction = [OCMockObject partialMockForObject:transactionService];
353 | [[[mockTransaction stub] andReturn:orderedSet] mutableTargets];
354 | [transactionService addTransactionTarget:target];
355 | ((VENCreateTransactionRequest *)mockTransaction).note = note;
356 | ((VENCreateTransactionRequest *)mockTransaction).transactionType = VENTransactionTypeCharge;
357 | ((VENCreateTransactionRequest *)mockTransaction).audience = VENTransactionAudiencePrivate;
358 | NSDictionary *postParameters = [((VENCreateTransactionRequest *)mockTransaction) dictionaryWithParametersForTarget:target];
359 | expect(target.targetType).equal(VENTargetTypeUnknown);
360 | expect(postParameters).to.beNil();
361 | });
362 |
363 |
364 | it(@"should not include audience if audience is set to UserDefault", ^{
365 | NSString *emailAddress = @"btest@example.com";
366 | NSString *note = @"bla";
367 | NSString *amount = @"200";
368 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:emailAddress amount:[amount integerValue]];
369 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
370 | [transactionService addTransactionTarget:target];
371 | transactionService.note = note;
372 | transactionService.transactionType = VENTransactionTypePay;
373 | transactionService.audience = VENTransactionAudienceUserDefault;
374 | NSDictionary *expectedPostParameters = @{@"email": emailAddress,
375 | @"note": note,
376 | @"amount" : @"2.00"};
377 | NSDictionary *postParameters = [transactionService dictionaryWithParametersForTarget:target];
378 | expect(postParameters).to.equal(expectedPostParameters);
379 | });
380 |
381 |
382 | });
383 |
384 | describe(@"containsDuplicateOfTarget", ^{
385 | it(@"should return YES if the transaction already has a target with the same handle", ^{
386 | NSString *handle = @"handle";
387 | id mockTarget1 = [OCMockObject mockForClass:[VENTransactionTarget class]];
388 | [[[mockTarget1 stub] andReturn:handle] handle];
389 |
390 | id mockTarget2 = [OCMockObject mockForClass:[VENTransactionTarget class]];
391 | [[[mockTarget2 stub] andReturn:@"bla"] handle];
392 |
393 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
394 | id mockTransaction = [OCMockObject partialMockForObject:transactionService];
395 | NSMutableOrderedSet *targetSet = [[NSMutableOrderedSet alloc] initWithArray:@[mockTarget2, mockTarget1]];
396 | [[[mockTransaction stub] andReturn:targetSet] targets];
397 |
398 | id mockTargetParameter = [OCMockObject mockForClass:[VENTransactionTarget class]];
399 | [[[mockTargetParameter stub] andReturn:handle] handle];
400 | BOOL containsDuplicate = [mockTransaction containsDuplicateOfTarget:mockTargetParameter];
401 | expect(containsDuplicate).to.beTruthy();
402 | });
403 |
404 | it(@"should return NO if the transaction doesn't have any targets with the same handle", ^{
405 | id mockTarget1 = [OCMockObject mockForClass:[VENTransactionTarget class]];
406 | [[[mockTarget1 stub] andReturn:@"foo"] handle];
407 |
408 | id mockTarget2 = [OCMockObject mockForClass:[VENTransactionTarget class]];
409 | [[[mockTarget2 stub] andReturn:@"bar"] handle];
410 |
411 | VENCreateTransactionRequest *transactionService = [[VENCreateTransactionRequest alloc] init];
412 | id mockTransaction = [OCMockObject partialMockForObject:transactionService];
413 | NSMutableOrderedSet *targetSet = [[NSMutableOrderedSet alloc] initWithArray:@[mockTarget2, mockTarget1]];
414 | [[[mockTransaction stub] andReturn:targetSet] targets];
415 |
416 | id mockTargetParameter = [OCMockObject mockForClass:[VENTransactionTarget class]];
417 | [[[mockTargetParameter stub] andReturn:@"baz"] handle];
418 | BOOL containsDuplicate = [mockTransaction containsDuplicateOfTarget:mockTargetParameter];
419 | expect(containsDuplicate).to.beFalsy();
420 | });
421 | });
422 |
423 | SpecEnd
424 |
--------------------------------------------------------------------------------
/VENCoreUnitTests/Models/Transactions/VENTransactionSpec.m:
--------------------------------------------------------------------------------
1 | #import "VENTransaction.h"
2 | #import "VENTestUtilities.h"
3 | #import "VENTransactionTarget.h"
4 | #import "VENUser.h"
5 | #import "VENTransactionPayloadKeys.h"
6 | #import "VENCore.h"
7 | #import "VENHTTPResponse.h"
8 |
9 | @interface VENTransaction (Private)
10 |
11 | @property (strong, nonatomic) NSMutableOrderedSet *mutableTargets;
12 |
13 | - (BOOL)containsDuplicateOfTarget:(VENTransactionTarget *)target;
14 |
15 | @end
16 |
17 | SpecBegin(VENTransaction)
18 |
19 | describe(@"Initialization", ^{
20 |
21 | NSDictionary *paymentResponse = [VENTestUtilities objectFromJSONResource:@"paymentToEmail"];
22 | NSDictionary *paymentObject = paymentResponse[@"data"][@"payment"];
23 |
24 | it(@"should return YES to canInitWithDictionary for a valid transaction dictionary", ^{
25 | NSDictionary *validTransactionDictionary = paymentObject;
26 |
27 | expect([VENTransaction canInitWithDictionary:validTransactionDictionary]).to.beTruthy();
28 | });
29 |
30 | it(@"should return NO to canInitWithDictionary for a transaction dictionary without an ID", ^{
31 | NSMutableDictionary *invalidTransactionDictionary = [paymentObject mutableCopy];
32 | [invalidTransactionDictionary removeObjectForKey:VENTransactionIDKey];
33 |
34 | expect([VENTransaction canInitWithDictionary:invalidTransactionDictionary]).to.beFalsy();
35 | });
36 |
37 | it(@"should return NO to canInitWithDictionary for a transaction dictionary without any transaction targets", ^{
38 | NSMutableDictionary *invalidTransactionDictionary = [paymentObject mutableCopy];
39 | [invalidTransactionDictionary removeObjectForKey:VENTransactionTargetKey];
40 |
41 | expect([VENTransaction canInitWithDictionary:invalidTransactionDictionary]).to.beFalsy();
42 | });
43 |
44 | it(@"should return NO to canInitWithDictionary for a transaction dictionary without a note", ^{
45 | NSMutableDictionary *invalidTransactionDictionary = [paymentObject mutableCopy];
46 | [invalidTransactionDictionary removeObjectForKey:VENTransactionNoteKey];
47 |
48 | expect([VENTransaction canInitWithDictionary:invalidTransactionDictionary]).to.beFalsy();
49 | });
50 |
51 | it(@"should return NO to canInitWithDictionary for a transaction dictionary without an actor", ^{
52 | NSMutableDictionary *invalidTransactionDictionary = [paymentObject mutableCopy];
53 | [invalidTransactionDictionary removeObjectForKey:VENTransactionActorKey];
54 |
55 | expect([VENTransaction canInitWithDictionary:invalidTransactionDictionary]).to.beFalsy();
56 | });
57 |
58 | it(@"should return NO to canInitWithDictionary for a transaction dictionary without an amount", ^{
59 | NSMutableDictionary *invalidTransactionDictionary = [paymentObject mutableCopy];
60 | [invalidTransactionDictionary removeObjectForKey:VENTransactionAmountKey];
61 |
62 | expect([VENTransaction canInitWithDictionary:invalidTransactionDictionary]).to.beFalsy();
63 | });
64 |
65 | it(@"should return NO to canInitWithDictionary when the note is NSNull", ^{
66 | NSMutableDictionary *invalidTransactionDictionary = [paymentObject mutableCopy];
67 | invalidTransactionDictionary[VENTransactionNoteKey] = [NSNull null];
68 |
69 | expect([VENTransaction canInitWithDictionary:invalidTransactionDictionary]).to.beFalsy();
70 | });
71 |
72 | it(@"should successfully initialize a transaction from a valid transaction dictionary", ^{
73 | VENTransaction *transaction = [[VENTransaction alloc] initWithDictionary:paymentObject];
74 |
75 | expect(transaction.transactionID).to.equal(@"1322585332520059421");
76 | expect(transaction.note).to.equal(@"Rock Climbing!");
77 | expect(transaction.actor.externalId).to.equal(@"1088551785594880949");
78 | expect(transaction.transactionType).to.equal(VENTransactionTypePay);
79 | expect(transaction.status).to.equal(VENTransactionStatusPending);
80 | expect(transaction.audience).to.equal(VENTransactionAudiencePublic);
81 | expect(((VENTransactionTarget *)transaction.target).handle).to.equal(@"nonvenmouser@gmail.com");
82 | });
83 | });
84 |
85 |
86 | describe(@"Equality", ^{
87 |
88 | NSDictionary *paymentResponse = [VENTestUtilities objectFromJSONResource:@"paymentToEmail"];
89 | NSDictionary *paymentObject = paymentResponse[@"data"][@"payment"];
90 |
91 | it(@"should consider two identical transactions equal", ^{
92 | VENTransaction *transaction1 = [[VENTransaction alloc] initWithDictionary:paymentObject];
93 | VENTransaction *transaction2 = [[VENTransaction alloc] initWithDictionary:paymentObject];
94 |
95 | expect(transaction1).to.equal(transaction2);
96 | expect(transaction2).to.equal(transaction1);
97 | });
98 |
99 | it(@"should consider two transactions with different transaction targets different", ^{
100 | VENTransaction *transaction = [[VENTransaction alloc] initWithDictionary:paymentObject];
101 |
102 | NSMutableDictionary *transactionDictionary = [paymentObject mutableCopy];
103 | [transactionDictionary removeObjectForKey:VENTransactionTargetKey];
104 |
105 | VENTransactionTarget *newTarget = [[VENTransactionTarget alloc] initWithHandle:@"Ben Guo" amount:14];
106 | transactionDictionary[VENTransactionTargetKey] = [newTarget dictionaryRepresentation];
107 | VENTransaction *otherTransaction = [[VENTransaction alloc] initWithDictionary:transactionDictionary];
108 |
109 | expect(transaction).to.equal(otherTransaction);
110 | });
111 |
112 | it(@"should consider two identical transactions with empty targets equal", ^{
113 | NSMutableDictionary *transactionDictionary = [paymentObject mutableCopy];
114 | [transactionDictionary removeObjectForKey:VENTransactionTargetKey];
115 | VENTransaction *transaction = [[VENTransaction alloc] initWithDictionary:transactionDictionary];
116 | VENTransaction *otherTransaction = [[VENTransaction alloc] initWithDictionary:transactionDictionary];
117 |
118 | expect(transaction).to.equal(otherTransaction);
119 | });
120 |
121 | it(@"should consider two identical transactions but with different types inequal", ^{
122 | NSMutableDictionary *transactionDictionary = [paymentObject mutableCopy];
123 | NSMutableDictionary *otherTransactionDictionary = [transactionDictionary mutableCopy];
124 | otherTransactionDictionary[VENTransactionTypeKey] = VENTransactionTypeStrings[VENTransactionTypeCharge];
125 | VENTransaction *transaction = [[VENTransaction alloc] initWithDictionary:transactionDictionary];
126 | VENTransaction *otherTransaction = [[VENTransaction alloc] initWithDictionary:otherTransactionDictionary];
127 |
128 | expect(transaction).toNot.equal(otherTransaction);
129 | });
130 |
131 | it(@"should consider transactions with different ids inequal", ^{
132 | NSMutableDictionary *transactionDictionary = [paymentObject mutableCopy];
133 | transactionDictionary[VENTransactionIDKey] = @"frack";
134 |
135 | NSMutableDictionary *otherTransactionDictionary = [transactionDictionary mutableCopy];
136 | otherTransactionDictionary[VENTransactionIDKey] = @"frick";
137 | VENTransaction *transaction = [[VENTransaction alloc] initWithDictionary:transactionDictionary];
138 | VENTransaction *otherTransaction = [[VENTransaction alloc] initWithDictionary:otherTransactionDictionary];
139 |
140 | expect(transaction).toNot.equal(otherTransaction);
141 | });
142 |
143 | it(@"should consider transactions with different statuses EQUAL", ^{
144 | NSMutableDictionary *transactionDictionary = [paymentObject mutableCopy];
145 | transactionDictionary[VENTransactionStatusKey] = VENTransactionStatusStrings[VENTransactionStatusUnknown];
146 |
147 | NSMutableDictionary *otherTransactionDictionary = [transactionDictionary mutableCopy];
148 | otherTransactionDictionary[VENTransactionStatusKey] = VENTransactionStatusStrings[VENTransactionStatusPending];
149 |
150 | VENTransaction *transaction = [[VENTransaction alloc] initWithDictionary:transactionDictionary];
151 | VENTransaction *otherTransaction = [[VENTransaction alloc] initWithDictionary:otherTransactionDictionary];
152 |
153 | expect(transaction).to.equal(otherTransaction);
154 | });
155 |
156 | });
157 |
158 | SpecEnd
--------------------------------------------------------------------------------
/VENCoreUnitTests/Models/Transactions/VENTransactionTargetSpec.m:
--------------------------------------------------------------------------------
1 | #import "VENTransactionTarget.h"
2 | #import "VENUser.h"
3 | #import "VENTestUtilities.h"
4 | #import "VENTransactionPayloadKeys.h"
5 |
6 | @interface VENUser (Internal)
7 |
8 | @property (strong, nonatomic, readwrite) NSString *externalId;
9 |
10 | @end
11 |
12 |
13 | SpecBegin(VENTransactionTarget)
14 |
15 | void(^assertTargetsAreFieldWiseEqual)(VENTransactionTarget *, VENTransactionTarget *) = ^(VENTransactionTarget *target1, VENTransactionTarget *target2) {
16 | expect(target1.amount).to.equal(target2.amount);
17 | expect(target1.handle).to.equal(target2.handle);
18 | expect(target1.targetType).to.equal(target2.targetType);
19 | };
20 |
21 | describe(@"Initialization", ^{
22 |
23 | NSDictionary *transactionJSON = (NSDictionary *)[VENTestUtilities objectFromJSONResource:@"paymentToEmail"];
24 | NSMutableDictionary *targetDictionary = [NSMutableDictionary dictionaryWithDictionary:transactionJSON[@"data"][@"payment"][VENTransactionTargetKey]];
25 | [targetDictionary setValue:@(4.12) forKey:VENTransactionAmountKey];
26 |
27 | it(@"should return NO to canInitWithDictionary for a dictionary without an amount", ^{
28 | NSMutableDictionary *currentPayload = [targetDictionary mutableCopy];
29 | [currentPayload removeObjectForKey:VENTransactionAmountKey];
30 |
31 | expect([VENTransactionTarget canInitWithDictionary:currentPayload]).to.beFalsy();
32 | });
33 |
34 | it(@"should return NO to canInitWithDictionary for a dictionary with an empty string amount", ^{
35 | NSMutableDictionary *currentPayload = [targetDictionary mutableCopy];
36 | currentPayload[VENTransactionAmountKey] = @"";
37 |
38 | expect([VENTransactionTarget canInitWithDictionary:currentPayload]).to.beFalsy();
39 | });
40 |
41 | it(@"should return NO to canInitWithDictionary for a dictionary without a type-field pair", ^{
42 | NSMutableDictionary *currentPayload = [targetDictionary mutableCopy];
43 | [currentPayload removeObjectForKey:VENTransactionTargetTypeKey];
44 |
45 | expect([VENTransactionTarget canInitWithDictionary:currentPayload]).to.beFalsy();
46 | });
47 |
48 | it(@"should return YES to canInitWithDictionary for a valid target payload", ^{
49 | expect([VENTransactionTarget canInitWithDictionary:targetDictionary]).to.beTruthy();
50 | });
51 |
52 | it(@"should correctly initialize a VENTransactionTarget from a valid dictionary", ^{
53 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithDictionary:targetDictionary];
54 |
55 | expect(target).to.beKindOf([VENTransactionTarget class]);
56 | expect(target.amount).to.equal(412);
57 | expect(target.handle).to.equal(@"nonvenmouser@gmail.com");
58 | expect(target.targetType).to.equal(VENTargetTypeEmail);
59 | expect([target isValid]).to.equal(YES);
60 | });
61 | });
62 |
63 |
64 | describe(@"initWithHandle:amount:", ^{
65 | it(@"setting the handle to an email should set the type to email", ^{
66 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"ben@venmo.com" amount:128];
67 |
68 | expect(target.amount).to.equal(128);
69 | expect(target.handle).to.equal(@"ben@venmo.com");
70 | expect(target.targetType).to.equal(VENTargetTypeEmail);
71 | });
72 |
73 | it(@"setting the handle to a phone number should set the type to phone", ^{
74 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"2125007000" amount:10123150];
75 |
76 | expect(target.amount).to.equal(10123150);
77 | expect(target.handle).to.equal(@"2125007000");
78 | expect(target.targetType).to.equal(VENTargetTypePhone);
79 | });
80 |
81 | it(@"setting the handle to a userID should set the type to userID", ^{
82 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"1234567" amount:12];
83 |
84 | expect(target.amount).to.equal(12);
85 | expect(target.handle).to.equal(@"1234567");
86 | expect(target.targetType).to.equal(VENTargetTypeUserId);
87 | });
88 | });
89 |
90 |
91 | describe(@"isValid", ^{
92 | it(@"should return YES if the target has a valid type and nonzero amount", ^{
93 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"ben@venmo.com" amount:653445];
94 | expect([target isValid]).to.equal(YES);
95 | });
96 |
97 | it(@"should return NO if the target has type VENTargetTypeUnknown and nonzero amount", ^{
98 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"b2124" amount:8765];
99 | expect([target isValid]).to.equal(NO);
100 | });
101 |
102 | it(@"should return NO if the target has type VENTargetTypeUnknown and zero amount", ^{
103 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"b2124" amount:0];
104 | expect([target isValid]).to.equal(NO);
105 | });
106 |
107 | it(@"should return NO if the target has a valid type but zero amount", ^{
108 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"4104444444" amount:0];
109 | expect([target isValid]).to.equal(NO);
110 | });
111 |
112 | it(@"should return NO if the target has a negative amount" , ^{
113 | VENTransactionTarget *target =[[VENTransactionTarget alloc] initWithHandle:@"9177436332" amount:-20];
114 | expect([target isValid]).to.equal(NO);
115 | });
116 | });
117 |
118 |
119 | describe(@"setUser", ^{
120 | it(@"should set the target's user", ^{
121 | id mockUser = [OCMockObject niceMockForClass:[VENUser class]];
122 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"ben@venmo.com" amount:100];
123 | target.user = mockUser;
124 |
125 | expect(target.user).to.equal(mockUser);
126 | });
127 |
128 | it(@"should set the target's handle to the user's user id", ^{
129 | id mockUser = [OCMockObject mockForClass:[VENUser class]];
130 | NSString *userId = @"12345";
131 | [[[mockUser stub] andReturn:userId] externalId];
132 |
133 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"ben@venmo.com" amount:100];
134 | target.user = mockUser;
135 |
136 | expect(target.handle).to.equal(userId);
137 | expect(target.targetType).to.equal(VENTargetTypeUserId);
138 | });
139 | });
140 |
141 |
142 | describe(@"Equality", ^{
143 | it(@"should consider two VENTransactionTargets with the same handle and amount equal", ^{
144 | VENTransactionTarget *target1 = [[VENTransactionTarget alloc] initWithHandle:@"peter" amount:100];
145 | VENTransactionTarget *target2 = [[VENTransactionTarget alloc] initWithHandle:@"peter" amount:100];
146 |
147 | expect(target1).to.equal(target2);
148 | });
149 |
150 | it(@"should be transitive", ^{
151 | VENTransactionTarget *target1 = [[VENTransactionTarget alloc] initWithHandle:@"peter" amount:100];
152 | VENTransactionTarget *target2 = [[VENTransactionTarget alloc] initWithHandle:@"peter" amount:100];
153 |
154 | expect(target1).to.equal(target2);
155 | expect(target2).to.equal(target1);
156 | });
157 |
158 | it(@"should consider a VENTransactionTarget equal to itself", ^{
159 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"peter" amount:1000];
160 |
161 | expect(target).to.equal(target);
162 | });
163 |
164 | it(@"should consider a VENTransactionTarget not to be equal to something that is not a VENTransactionTarget", ^{
165 | NSString *notATarget = @"peter";
166 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"Peter" amount:100];
167 |
168 | expect(notATarget).notTo.equal(target);
169 | expect(target).notTo.equal(notATarget);
170 | });
171 |
172 | it(@"should consider two VENTransactionTargets with the same handle but different amounts different", ^{
173 | VENTransactionTarget *target1 = [[VENTransactionTarget alloc] initWithHandle:@"Peter" amount:50];
174 | VENTransactionTarget *target2 = [[VENTransactionTarget alloc] initWithHandle:@"Peter" amount:100];
175 |
176 | expect(target1).toNot.equal(target2);
177 | });
178 |
179 | it(@"should consider two VENTransactionTargets with different handles and the same amount different", ^{
180 | VENTransactionTarget *target1 = [[VENTransactionTarget alloc] initWithHandle:@"Peter" amount:100];
181 | VENTransactionTarget *target2 = [[VENTransactionTarget alloc] initWithHandle:@"Piotr" amount:100];
182 |
183 | expect(target1).toNot.equal(target2);
184 | });
185 |
186 | it(@"should consider two empty VENTransactionTargets equal", ^{
187 | VENTransactionTarget *target1 = [VENTransactionTarget new];
188 | VENTransactionTarget *target2 = [VENTransactionTarget new];
189 |
190 | expect(target2).to.equal(target1);
191 | });
192 |
193 | it(@"should consider two VENTransactionTargets with the same handle and no amount equal", ^{
194 | VENTransactionTarget *target1 = [VENTransactionTarget new];
195 | VENTransactionTarget *target2 = [VENTransactionTarget new];
196 | target1.handle = @"Peter";
197 | target2.handle = @"Peter";
198 |
199 | expect(target2).to.equal(target1);
200 | });
201 |
202 | it(@"should consider two VENTransactionTargets with the same amount and no handle equal", ^{
203 | VENTransactionTarget *target1 = [VENTransactionTarget new];
204 | VENTransactionTarget *target2 = [VENTransactionTarget new];
205 | target1.amount = 100;
206 | target2.amount = 100;
207 |
208 | expect(target1).to.equal(target2);
209 | });
210 |
211 | });
212 |
213 | describe(@"dictionaryRepresentation", ^{
214 | it(@"should correctly represent a VENTransactionTarget in dictionary form with target type phone", ^{
215 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"9177436332" amount:10];
216 | NSDictionary *dictionaryRepresentation = [target dictionaryRepresentation];
217 | NSString *targetType = dictionaryRepresentation[VENTransactionTargetTypeKey];
218 |
219 | expect(dictionaryRepresentation[VENTransactionAmountKey]).to.equal((float)target.amount/100);
220 | expect(targetType).to.equal(@"phone");
221 | expect(target.handle).to.equal(@"9177436332");
222 | });
223 |
224 | it(@"should correctly represent a VENTransactionTarget in dictionary form with target type email", ^{
225 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"peter.zakin@gmail.com" amount:10];
226 | NSDictionary *dictionaryRepresentation = [target dictionaryRepresentation];
227 | NSString *targetType = dictionaryRepresentation[VENTransactionTargetTypeKey];
228 |
229 | expect(dictionaryRepresentation[VENTransactionAmountKey]).to.equal((float)target.amount/100);
230 | expect(targetType).to.equal(@"email");
231 | expect(target.handle).to.equal(@"peter.zakin@gmail.com");
232 | });
233 |
234 | it(@"should correctly represent a VENTransactionTarget in dictionary form with target type userId", ^{
235 | VENTransactionTarget *target = [[VENTransactionTarget alloc] initWithHandle:@"1271231231239" amount:10];
236 | NSDictionary *dictionaryRepresentation = [target dictionaryRepresentation];
237 | NSString *targetType = dictionaryRepresentation[VENTransactionTargetTypeKey];
238 |
239 | expect(dictionaryRepresentation[VENTransactionAmountKey]).to.equal((float)target.amount/100);
240 | expect(targetType).to.equal(VENTransactionTargetUserKey);
241 | expect(target.handle).to.equal(@"1271231231239");
242 | });
243 |
244 | it(@"should correctly initiate an identical VENTransactionTarget from its dictionary representation", ^{
245 | VENTransactionTarget *target1 = [[VENTransactionTarget alloc] initWithHandle:@"1231231231233" amount:12635];
246 | NSDictionary *dictionaryRepresentation = [target1 dictionaryRepresentation];
247 |
248 | VENTransactionTarget *target2 = [[VENTransactionTarget alloc] initWithDictionary:dictionaryRepresentation];
249 |
250 | expect(target1).to.equal(target2);
251 | assertTargetsAreFieldWiseEqual(target1, target2);
252 | });
253 | });
254 |
255 | SpecEnd
--------------------------------------------------------------------------------
/VENCoreUnitTests/Models/Users/VENUserSpec.m:
--------------------------------------------------------------------------------
1 | #import "VENUser.h"
2 | #import "VENCore.h"
3 | #import "VENUserPayloadKeys.h"
4 | #import "VENTestUtilities.h"
5 |
6 | SpecBegin(VENUser)
7 |
8 | NSDictionary *validUserDictionary1 = @{VENUserKeyUsername: @"PeterIsAZakin",
9 | VENUserKeyInternalId: @"234234",
10 | VENUserKeyExternalId: @"JLHDSJFIOHh23ioHLH",
11 | VENUserKeyFirstName: @"Peter",
12 | VENUserKeyLastName: @"Maddern",
13 | VENUserKeyAbout: @"Happily married!"};
14 |
15 | NSDictionary *validUserDictionary2 = @{VENUserKeyUsername: @"PetefadsrIsAZakin",
16 | VENUserKeyInternalId: @"234223434",
17 | VENUserKeyExternalId: @"JLHDSfadJfsdFIOHh23ioHLH",
18 | VENUserKeyFirstName: @"Pefadster",
19 | VENUserKeyLastName: @"Mafadsddern",
20 | VENUserKeyAbout: @"Happifasdly married!"};
21 |
22 | NSDictionary *validUserDictionary3 = @{VENUserKeyUsername: @"PetefadsrIsAZakin",
23 | VENUserKeyInternalId: @"234223434",
24 | VENUserKeyExternalId: @"JLHDSJFIOHh23ioHLH",
25 | VENUserKeyFirstName: @"Pefadster",
26 | VENUserKeyLastName: @"Mafadsddern",
27 | VENUserKeyAbout: @"Happifasdly married!"};
28 |
29 | NSDictionary *invalidUserDictionary1 = @{VENUserKeyInternalId: @"234234",
30 | VENUserKeyExternalId: @"JLHDSJFIOHh23ioHLH",
31 | VENUserKeyFirstName: @"Peter",
32 | VENUserKeyLastName: @"Maddern",
33 | VENUserKeyAbout: @"Happily married!"};
34 |
35 | // This does not contain the external ID
36 | NSDictionary *invalidUserDictionary2 = @{VENUserKeyUsername: @"PeterIsAZakin",
37 | VENUserKeyFirstName: @"Peter",
38 | VENUserKeyLastName: @"Maddern",
39 | VENUserKeyAbout: @"Happily married!"};
40 |
41 |
42 | void(^assertUsersAreFieldwiseEqual)(VENUser *, VENUser *) = ^(VENUser *user1, VENUser *user2) {
43 | expect(user1.username).to.equal(user2.username);
44 | expect(user1.firstName).to.equal(user2.firstName);
45 | expect(user1.lastName).to.equal(user2.lastName);
46 | expect(user1.displayName).to.equal(user2.displayName);
47 | expect(user1.about).to.equal(user2.about);
48 | expect(user1.primaryPhone).to.equal(user2.primaryPhone);
49 | expect(user1.primaryEmail).to.equal(user2.primaryEmail);
50 | expect(user1.internalId).to.equal(user2.internalId);
51 | expect(user1.externalId).to.equal(user2.externalId);
52 | expect(user1.dateJoined).to.equal(user2.dateJoined);
53 | expect(user1.profileImageUrl).to.equal(user2.profileImageUrl);
54 | };
55 |
56 |
57 | beforeAll(^{
58 | VENCore *core = [[VENCore alloc] init];
59 | [VENCore setDefaultCore:core];
60 |
61 | [[LSNocilla sharedInstance] start];
62 | });
63 |
64 | afterAll(^{
65 | [[LSNocilla sharedInstance] stop];
66 | });
67 |
68 | afterEach(^{
69 | [[LSNocilla sharedInstance] clearStubs];
70 | });
71 |
72 |
73 | describe(@"Initialization", ^{
74 |
75 | it(@"should succesfully create an empty object from init", ^{
76 | VENUser *usr = [[VENUser alloc] init];
77 | expect(usr).toNot.beNil();
78 |
79 | expect(usr.username).to.beNil();
80 | expect(usr.firstName).to.beNil();
81 | expect(usr.lastName).to.beNil();
82 | expect(usr.profileImageUrl).to.beNil();
83 | });
84 |
85 | it(@"should return NO to canInitWithDictionary for an invalid dictionary", ^{
86 | BOOL canInit = [VENUser canInitWithDictionary:invalidUserDictionary1];
87 | expect(canInit).to.beFalsy();
88 |
89 | canInit = [VENUser canInitWithDictionary:invalidUserDictionary2];
90 | expect(canInit).to.beFalsy();
91 | });
92 |
93 | it(@"should return NO to a nil or empty dictionary", ^{
94 | BOOL canInit = [VENUser canInitWithDictionary:@{}];
95 | expect(canInit).to.beFalsy();
96 |
97 | canInit = [VENUser canInitWithDictionary:nil];
98 | expect(canInit).to.beFalsy();
99 | });
100 |
101 | it(@"should return YES to canInitWithDictionary for a valid dictionary", ^{
102 | BOOL canInit = [VENUser canInitWithDictionary:validUserDictionary1];
103 | expect(canInit).to.beTruthy();
104 | });
105 |
106 | it(@"should create a user with minimally a username and externalId for dictionaries that return YES to canInitWithDictionary",^{
107 | VENUser *user1 = [[VENUser alloc] initWithDictionary:validUserDictionary1];
108 | expect(user1.username).to.equal(@"PeterIsAZakin");
109 | expect(user1.externalId).to.equal(@"JLHDSJFIOHh23ioHLH");
110 | VENUser *user2 = [[VENUser alloc] initWithDictionary:validUserDictionary2];
111 | expect(user2.username).to.equal(@"PetefadsrIsAZakin");
112 | expect(user2.externalId).to.equal(@"JLHDSfadJfsdFIOHh23ioHLH");
113 | });
114 |
115 | });
116 |
117 |
118 | describe(@"Copying", ^{
119 |
120 | it(@"should create a valid copy of any empty object", ^{
121 | VENUser *usr = [[VENUser alloc] init];
122 |
123 | VENUser *myOtherUser = [usr copy];
124 |
125 | expect(myOtherUser).notTo.beNil();
126 | expect(myOtherUser).to.beKindOf([usr class]);
127 |
128 | });
129 |
130 | it(@"should create a valid copy of a valid user", ^{
131 | VENUser *user = [[VENUser alloc] initWithDictionary:validUserDictionary1];
132 | VENUser *newUser = [user copy];
133 |
134 | expect(user).to.equal(newUser);
135 | });
136 | });
137 |
138 |
139 | describe(@"Equality", ^{
140 |
141 | it(@"should correctly validate two equal objects", ^{
142 | VENUser *user1 = [[VENUser alloc] initWithDictionary:validUserDictionary1];
143 | VENUser *user2 = [user1 copy];
144 |
145 | expect([user1 isEqual:user2]).to.beTruthy();
146 |
147 | VENUser *user3 = [[VENUser alloc] initWithDictionary:validUserDictionary3];
148 |
149 | expect([user1 isEqual:user3]).to.beTruthy();
150 | });
151 |
152 | it(@"should not indicate that two different users are the same", ^{
153 | VENUser *user1 = [[VENUser alloc] initWithDictionary:validUserDictionary1];
154 | VENUser *user2 = [[VENUser alloc] initWithDictionary:validUserDictionary2];
155 |
156 | expect([user1 isEqual:user2]).to.beFalsy();
157 | });
158 |
159 | it(@"should behave transitively", ^{
160 | VENUser *user1 = [[VENUser alloc] initWithDictionary:validUserDictionary1];
161 | VENUser *user2 = [[VENUser alloc] initWithDictionary:validUserDictionary2];
162 |
163 | expect([user1 isEqual:user2]).to.beFalsy();
164 | expect([user2 isEqual:user1]).to.beFalsy();
165 |
166 | user1 = [[VENUser alloc] initWithDictionary:validUserDictionary1];
167 | user2 = [user1 copy];
168 |
169 | expect([user1 isEqual:user2]).to.beTruthy();
170 | expect([user2 isEqual:user1]).to.beTruthy();
171 | });
172 |
173 | it(@"should follow the rule that two users are equal ONLY if their external Ids are the same", ^{
174 | VENUser *user = [[VENUser alloc] init];
175 | VENUser *myOtherUser = [user copy];
176 |
177 | expect(user).toNot.equal(myOtherUser);
178 |
179 | VENUser *invalidUser = [[VENUser alloc] initWithDictionary:invalidUserDictionary2];
180 | VENUser *copiedInvalidUser = [invalidUser copy];
181 |
182 | expect(invalidUser).toNot.equal(copiedInvalidUser);
183 | });
184 |
185 | });
186 |
187 | describe(@"Dictionary Representation", ^{
188 | it(@"should consider dictionary representations of equal users to be equal", ^{
189 | VENUser *user1 = [[VENUser alloc] initWithDictionary:validUserDictionary1];
190 | NSDictionary *user1Dictionary = [user1 dictionaryRepresentation];
191 |
192 | VENUser *user2 = [[VENUser alloc] initWithDictionary:user1Dictionary];
193 | expect([user1 isEqual:user2]).to.beTruthy();
194 | assertUsersAreFieldwiseEqual(user1, user2);
195 |
196 | });
197 |
198 | });
199 |
200 |
201 | describe(@"Fetching a User", ^{
202 | it(@"should retrieve a pre-canned Chris user and create a valid user", ^{
203 | NSString *externalId = @"1106387358711808333";
204 |
205 | NSString *baseURLString = [VENTestUtilities baseURLStringForCore:[VENCore defaultCore]];
206 | NSString *urlToStub = [NSString stringWithFormat:@"%@/%@/%@?", baseURLString, VENAPIPathUsers, externalId];
207 |
208 | [VENTestUtilities stubNetworkGET:urlToStub withStatusCode:200 andResponseFilePath:@"fetchChrisUser"];
209 | waitUntil(^(DoneCallback done) {
210 | [VENUser fetchUserWithExternalId:externalId success:^(VENUser *user) {
211 |
212 | expect(user.externalId).to.equal(externalId);
213 | done();
214 | } failure:^(NSError *error) {
215 | failure(@"Failed to return correct response.");
216 | done();
217 | }];
218 | });
219 |
220 | });
221 |
222 | it(@"should call failure when cannot find a user with that external Id", ^{
223 | NSString *externalId = @"1106387358711808339"; //invalid external id
224 |
225 | NSString *baseURLString = [VENTestUtilities baseURLStringForCore:[VENCore defaultCore]];
226 | NSString *urlToStub = [NSString stringWithFormat:@"%@/%@/%@?", baseURLString, VENAPIPathUsers, externalId];
227 |
228 | [VENTestUtilities stubNetworkGET:urlToStub withStatusCode:400 andResponseFilePath:@"fetchInvalidUser"];
229 |
230 | waitUntil(^(DoneCallback done) {
231 | [VENUser fetchUserWithExternalId:externalId success:^(VENUser *user) {
232 | failure(@"Failed to return correct response.");
233 | done();
234 | } failure:^(NSError *error) {
235 | expect([error localizedDescription]).to.equal(@"Resource not found.");
236 | done();
237 | }];
238 | });
239 | });
240 |
241 | it(@"should call failure when not passed an external id", ^{
242 | waitUntil(^(DoneCallback done) {
243 | [VENUser fetchUserWithExternalId:nil success:^(VENUser *user) {
244 | failure(@"Failed to return correct response.");
245 | done();
246 | } failure:^(NSError *error) {
247 | expect(error).notTo.beNil();
248 | done();
249 | }];
250 | });
251 | });
252 |
253 | it(@"should call failure when passed an empty-string external id", ^{
254 | waitUntil(^(DoneCallback done) {
255 | [VENUser fetchUserWithExternalId:@"" success:^(VENUser *user) {
256 | failure(@"Failed to return correct response.");
257 | done();
258 | } failure:^(NSError *error) {
259 | expect(error).notTo.beNil();
260 | done();
261 | }];
262 | });
263 | });
264 |
265 | });
266 |
267 | describe(@"Fetching Friends", ^{
268 | it(@"should retrieve a pre-canned list of friends and create a valid array of friends", ^{
269 | NSString *externalId = @"110638735871180833";
270 | NSString *baseURLString = [VENTestUtilities baseURLStringForCore:[VENCore defaultCore]];
271 | NSString *urlToStub = [NSString stringWithFormat:@"%@/users/%@/friends?limit=1000", baseURLString, externalId];
272 | [VENTestUtilities stubNetworkGET:urlToStub withStatusCode:200 andResponseFilePath:@"fetchFriends"];
273 |
274 | waitUntil(^(DoneCallback done) {
275 | [VENUser fetchFriendsWithExternalId:externalId success:^(NSArray *friendsArray) {
276 | expect([friendsArray count]).to.equal(5);
277 | expect([friendsArray[0] class]).to.equal([VENUser class]);
278 | expect([friendsArray[2] class]).to.equal([VENUser class]);
279 | done();
280 |
281 | } failure:^(NSError *error){
282 | failure(@"Failed to return correct response.");
283 | done();
284 | }];
285 | });
286 | });
287 |
288 | it(@"should retrieve a pre-canned list of friends and deletes the NSNull key and value from the friend", ^{
289 | NSString *externalId = @"110638735871180833";
290 | NSString *baseURLString = [VENTestUtilities baseURLStringForCore:[VENCore defaultCore]];
291 | NSString *urlToStub = [NSString stringWithFormat:@"%@/users/%@/friends?limit=1000", baseURLString, externalId];
292 | [VENTestUtilities stubNetworkGET:urlToStub withStatusCode:200 andResponseFilePath:@"fetchFriends"];
293 |
294 | waitUntil(^(DoneCallback done) {
295 | [VENUser fetchFriendsWithExternalId:externalId success:^(NSArray *friendsArray) {
296 | for (id object in friendsArray) {
297 | if ([object isKindOfClass:[VENUser class]]) {
298 | VENUser *user = (VENUser *) object;
299 | if ([user.username isEqualToString:@"great-friend"]) {
300 | expect(user.profileImageUrl).to.beNil();
301 | }
302 | }
303 | }
304 | done();
305 | } failure:^(NSError *error) {
306 | failure(@"Failed to return correct response.");
307 | done();
308 | }];
309 | });
310 | });
311 |
312 | it(@"should retrieve a pre-canned list of friends and the users should be in the same order as the JSON and their values should be consistent with the JSON values", ^{
313 | NSString *externalId = @"110638735871180833";
314 | NSString *baseURLString = [VENTestUtilities baseURLStringForCore:[VENCore defaultCore]];
315 | NSString *urlToStub = [NSString stringWithFormat:@"%@/users/%@/friends?limit=1000", baseURLString, externalId];
316 | [VENTestUtilities stubNetworkGET:urlToStub withStatusCode:200 andResponseFilePath:@"fetchFriends"];
317 |
318 | waitUntil(^(DoneCallback done) {
319 | [VENUser fetchFriendsWithExternalId:externalId success:^(NSArray *friendsArray) {
320 | if ([friendsArray[0] isKindOfClass:[VENUser class]]){
321 | VENUser *friend = (VENUser *) friendsArray[0];
322 | expect(friend.username).to.equal(@"kortina");
323 | expect(friend.about).to.equal(@"make a joyful sound, la da da da");
324 | }
325 | done();
326 | } failure:^(NSError *error) {
327 | failure(@"Failed to return correct response.");
328 | done();
329 | }];
330 | });
331 | });
332 |
333 | it(@"should call failure when cannot find a user with that external Id", ^{
334 | NSString *externalId = @"1106387358711808339"; //invalid external id
335 | NSString *baseURLString = [VENTestUtilities baseURLStringForCore:[VENCore defaultCore]];
336 | NSString *urlToStub = [NSString stringWithFormat:@"%@/users/%@/friends?limit=1000", baseURLString, externalId];
337 | [VENTestUtilities stubNetworkGET:urlToStub withStatusCode:400 andResponseFilePath:@"fetchInvalidFriends"];
338 |
339 | waitUntil(^(DoneCallback done) {
340 | [VENUser fetchFriendsWithExternalId:externalId success:^(NSArray *friendsArray) {
341 | failure(@"Failed to return correct response.");
342 | done();
343 | } failure:^(NSError *error) {
344 | expect([error localizedDescription]).to.equal(@"Resource not found.");
345 | done();
346 | }];
347 | });
348 | });
349 |
350 | it(@"should call failure when not passed an external id", ^{
351 | waitUntil(^(DoneCallback done) {
352 | [VENUser fetchFriendsWithExternalId:nil success:^(NSArray *friendsArray) {
353 | failure(@"Failed to return correct response.");
354 | done();
355 | } failure:^(NSError *error) {
356 | expect(error).notTo.beNil();
357 | done();
358 | }];
359 | });
360 | });
361 |
362 | it(@"should call failure when passed an empty-string external id", ^{
363 | waitUntil(^(DoneCallback done) {
364 | [VENUser fetchFriendsWithExternalId:@"" success:^(NSArray *friendsArray) {
365 | failure(@"Failed to return correct response.");
366 | done();
367 | } failure:^(NSError *error) {
368 | expect(error).notTo.beNil();
369 | done();
370 | }];
371 | });
372 | });
373 | });
374 |
375 | SpecEnd
376 |
--------------------------------------------------------------------------------
/VENCoreUnitTests/Networking/VENHTTPResponseSpec.m:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/venmo/VENCore/b2da9791827ba851828bedfa29bb0abcdf25656f/VENCoreUnitTests/Networking/VENHTTPResponseSpec.m
--------------------------------------------------------------------------------
/VENCoreUnitTests/Networking/VENHTTPSpec.m:
--------------------------------------------------------------------------------
1 | #import "VENHTTP.h"
2 | #import "VENHTTPResponse.h"
3 | #import "VENCore.h"
4 | #import "NSString+VENCore.h"
5 |
6 | #define kVENHTTPTestProtocolScheme @"ven-http-test"
7 | #define kVENHTTPTestProtocolHost @"base.example.com"
8 | #define kVENHTTPTestProtocolBasePath @"/base/path/"
9 | #define kVENHTTPTestProtocolPort @1234
10 |
11 | @interface VENHTTPTestProtocol : NSURLProtocol
12 | @end
13 |
14 | @implementation VENHTTPTestProtocol
15 |
16 | + (BOOL)canInitWithRequest:(NSURLRequest *)request
17 | {
18 | BOOL hasCorrectScheme = [request.URL.scheme isEqualToString:kVENHTTPTestProtocolScheme];
19 | BOOL hasCorrectHost = [request.URL.host isEqualToString:kVENHTTPTestProtocolHost];
20 | BOOL hasCorrectPort = [request.URL.port isEqual:kVENHTTPTestProtocolPort];
21 | BOOL hasCorrectBasePath = [request.URL.path rangeOfString:kVENHTTPTestProtocolBasePath].location != NSNotFound;
22 |
23 | return hasCorrectScheme && hasCorrectHost && hasCorrectPort && hasCorrectBasePath;
24 | }
25 |
26 | + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
27 | return request;
28 | }
29 |
30 | - (void)startLoading {
31 | id client = self.client;
32 |
33 | NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:self.request.URL
34 | statusCode:200
35 | HTTPVersion:@"HTTP/1.1"
36 | headerFields:@{@"Content-Type": @"application/json"}];
37 |
38 | NSData *archivedRequest = [NSKeyedArchiver archivedDataWithRootObject:self.request];
39 | NSString *base64ArchivedRequest = [archivedRequest base64EncodedStringWithOptions:0];
40 |
41 | NSData *requestBodyData;
42 | if (self.request.HTTPBodyStream) {
43 | NSInputStream *inputStream = self.request.HTTPBodyStream;
44 | [inputStream open];
45 | NSMutableData *mutableBodyData = [NSMutableData data];
46 |
47 | while ([inputStream hasBytesAvailable]) {
48 | uint8_t buffer[128];
49 | NSUInteger bytesRead = [inputStream read:buffer maxLength:128];
50 | [mutableBodyData appendBytes:buffer length:bytesRead];
51 | }
52 | [inputStream close];
53 | requestBodyData = [mutableBodyData copy];
54 | } else {
55 | requestBodyData = self.request.HTTPBody;
56 | }
57 |
58 | NSDictionary *responseBody = @{ @"request": base64ArchivedRequest,
59 | @"requestBody": [[NSString alloc] initWithData:requestBodyData encoding:NSUTF8StringEncoding] };
60 |
61 | [client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
62 |
63 | [client URLProtocol:self didLoadData:[NSJSONSerialization dataWithJSONObject:responseBody options:NSJSONWritingPrettyPrinted error:NULL]];
64 |
65 | [client URLProtocolDidFinishLoading:self];
66 | }
67 |
68 | - (void)stopLoading {
69 | }
70 |
71 | + (NSURL *)testBaseURL {
72 | NSURLComponents *components = [[NSURLComponents alloc] init];
73 | components.scheme = kVENHTTPTestProtocolScheme;
74 | components.host = kVENHTTPTestProtocolHost;
75 | components.path = kVENHTTPTestProtocolBasePath;
76 | components.port = kVENHTTPTestProtocolPort;
77 | return components.URL;
78 | }
79 |
80 | + (NSURLRequest *)parseRequestFromTestResponse:(VENHTTPResponse *)response {
81 | return [NSKeyedUnarchiver unarchiveObjectWithData:[[NSData alloc] initWithBase64EncodedString:response.object[@"request"] options:0]];
82 | }
83 |
84 | + (NSString *)parseRequestBodyFromTestResponse:(VENHTTPResponse *)response {
85 | return response.object[@"requestBody"];
86 | }
87 |
88 | @end
89 |
90 |
91 | SpecBegin(VENHTTP)
92 |
93 | describe(@"performing a request", ^{
94 | __block VENHTTP *http;
95 |
96 | beforeEach(^{
97 | http = [[VENHTTP alloc] initWithBaseURL:[VENHTTPTestProtocol testBaseURL]];
98 | [http setProtocolClasses:@[[VENHTTPTestProtocol class]]];
99 | });
100 |
101 | describe(@"base URL", ^{
102 | it(@"sends requests using the specified URL scheme", ^{
103 | waitUntil(^(DoneCallback done) {
104 | [http GET:@"200.json" parameters:nil success:^(VENHTTPResponse *response) {
105 | NSURLRequest *httpRequest = [VENHTTPTestProtocol parseRequestFromTestResponse:response];
106 |
107 | expect(httpRequest.URL.scheme).to.equal(@"ven-http-test");
108 | done();
109 | } failure:^(VENHTTPResponse *response, NSError *error) {
110 | failure(@"Failed to return correct response.");
111 | }];
112 | });
113 | });
114 |
115 |
116 | it(@"sends requests to the host at the base URL", ^{
117 | waitUntil(^(DoneCallback done) {
118 | [http GET:@"200.json" parameters:nil success:^(VENHTTPResponse *response) {
119 | NSURLRequest *httpRequest = [VENHTTPTestProtocol parseRequestFromTestResponse:response];
120 | expect(httpRequest.URL.host).to.equal(@"base.example.com");
121 |
122 | done();
123 | } failure:^(VENHTTPResponse *response, NSError *error) {
124 | failure(@"Failed to return correct response.");
125 | }];
126 | });
127 | });
128 |
129 | it(@"appends the path to the base URL", ^{
130 | waitUntil(^(DoneCallback done) {
131 | [http GET:@"200.json" parameters:nil success:^(VENHTTPResponse *response) {
132 | NSURLRequest *httpRequest = [VENHTTPTestProtocol parseRequestFromTestResponse:response];
133 |
134 | expect(httpRequest.URL.path).to.equal(@"/base/path/200.json");
135 | done();
136 | } failure:^(VENHTTPResponse *response, NSError *error) {
137 | failure(@"Failed to return correct response.");
138 | }];
139 | });
140 | });
141 | });
142 |
143 | describe(@"HTTP methods", ^{
144 | it(@"sends a GET request", ^{
145 | waitUntil(^(DoneCallback done) {
146 | [http GET:@"200.json" parameters:nil success:^(VENHTTPResponse *response) {
147 | NSURLRequest *httpRequest = [VENHTTPTestProtocol parseRequestFromTestResponse:response];
148 | expect(httpRequest.URL.path).to.match(@"/200.json$");
149 | expect(httpRequest.HTTPMethod).to.equal(@"GET");
150 | expect(httpRequest.HTTPBody).to.beNil();
151 | done();
152 | } failure:^(VENHTTPResponse *response, NSError *error) {
153 | failure(@"Failed to return correct response.");
154 | }];
155 | });
156 | });
157 |
158 | it(@"sends a GET request with parameters", ^{
159 | waitUntil(^(DoneCallback done) {
160 | [http GET:@"200.json" parameters:@{@"param": @"value"} success:^(VENHTTPResponse *response) {
161 | NSURLRequest *httpRequest = [VENHTTPTestProtocol parseRequestFromTestResponse:response];
162 | expect(httpRequest.URL.path).to.match(@"/200.json$");
163 | expect(httpRequest.URL.query).to.equal(@"param=value");
164 | expect(httpRequest.HTTPMethod).to.equal(@"GET");
165 | expect(httpRequest.HTTPBody).to.beNil();
166 | done();
167 | } failure:^(VENHTTPResponse *response, NSError *error) {
168 | failure(@"Failed to return correct response.");
169 | }];
170 | });
171 | });
172 |
173 | it(@"sends a POST request", ^{
174 | waitUntil(^(DoneCallback done) {
175 | [http POST:@"200.json" parameters:@{@"param": @"value"} success:^(VENHTTPResponse *response) {
176 | NSURLRequest *httpRequest = [VENHTTPTestProtocol parseRequestFromTestResponse:response];
177 | expect(httpRequest.URL.path).to.match(@"/200.json$");
178 | expect(httpRequest.HTTPBody).to.beNil();
179 | expect(httpRequest.HTTPMethod).to.equal(@"POST");
180 | expect(httpRequest.URL.query).to.beNil();
181 | done();
182 | } failure:^(VENHTTPResponse *response, NSError *error) {
183 | failure(@"Failed to return correct response.");
184 | }];
185 | });
186 | });
187 |
188 | it(@"sends a POST request with parameters", ^{
189 | waitUntil(^(DoneCallback done) {
190 | [http POST:@"200.json" parameters:@{@"param": @"value"} success:^(VENHTTPResponse *response) {
191 | NSURLRequest *httpRequest = [VENHTTPTestProtocol parseRequestFromTestResponse:response];
192 | NSString *httpRequestBody = [VENHTTPTestProtocol parseRequestBodyFromTestResponse:response];
193 | expect(httpRequest.URL.path).to.match(@"/200.json$");
194 | expect(httpRequestBody).to.equal(@"param=value");
195 | expect(httpRequest.HTTPMethod).to.equal(@"POST");
196 | expect(httpRequest.URL.query).to.beNil();
197 | done();
198 | } failure:^(VENHTTPResponse *response, NSError *error) {
199 | failure(@"Failed to return correct response.");
200 | }];
201 | });
202 | });
203 |
204 | it(@"sends a PUT request", ^{
205 | waitUntil(^(DoneCallback done) {
206 | [http PUT:@"200.json" parameters:@{@"param": @"value"} success:^(VENHTTPResponse *response) {
207 | NSURLRequest *httpRequest = [VENHTTPTestProtocol parseRequestFromTestResponse:response];
208 | expect(httpRequest.URL.path).to.match(@"200.json$");
209 | expect(httpRequest.HTTPBody).to.beNil();
210 | expect(httpRequest.HTTPMethod).to.equal(@"PUT");
211 | expect(httpRequest.URL.query).to.beNil();
212 | done();
213 | } failure:^(VENHTTPResponse *response, NSError *error) {
214 | failure(@"Failed to return correct response.");
215 | }];
216 | });
217 | });
218 |
219 | it(@"sends a PUT request with parameters", ^{
220 | waitUntil(^(DoneCallback done) {
221 | [http PUT:@"200.json" parameters:@{@"param": @"value"} success:^(VENHTTPResponse *response) {
222 | NSURLRequest *httpRequest = [VENHTTPTestProtocol parseRequestFromTestResponse:response];
223 | NSString *httpRequestBody = [VENHTTPTestProtocol parseRequestBodyFromTestResponse:response];
224 | expect(httpRequest.URL.path).to.match(@"200.json$");
225 | expect(httpRequestBody).to.equal(@"param=value");
226 | expect(httpRequest.HTTPMethod).to.equal(@"PUT");
227 | expect(httpRequest.URL.query).to.beNil();
228 | done();
229 | } failure:^(VENHTTPResponse *response, NSError *error) {
230 | failure(@"Failed to return correct response.");
231 | }];
232 | });
233 | });
234 |
235 |
236 | it(@"sends a DELETE request", ^{
237 | waitUntil(^(DoneCallback done) {
238 | [http DELETE:@"200.json" parameters:nil success:^(VENHTTPResponse *response) {
239 | NSURLRequest *httpRequest = [VENHTTPTestProtocol parseRequestFromTestResponse:response];
240 | expect(httpRequest.URL.path).to.match(@"200.json$");
241 | expect(httpRequest.HTTPBody).to.beNil();
242 | expect(httpRequest.HTTPMethod).to.equal(@"DELETE");
243 | expect(httpRequest.URL.query).to.equal(nil);
244 | done();
245 | } failure:^(VENHTTPResponse *response, NSError *error) {
246 | failure(@"Failed to return correct response.");
247 | }];
248 | });
249 | });
250 |
251 | it(@"sends a DELETE request with parameters", ^{
252 | waitUntil(^(DoneCallback done) {
253 | [http DELETE:@"200.json" parameters:@{@"param": @"value"} success:^(VENHTTPResponse *response) {
254 | NSURLRequest *httpRequest = [VENHTTPTestProtocol parseRequestFromTestResponse:response];
255 |
256 | expect(httpRequest.URL.path).to.match(@"/200.json$");
257 | expect(httpRequest.URL.query).to.equal(@"param=value");
258 | expect(httpRequest.HTTPMethod).to.equal(@"DELETE");
259 | expect(httpRequest.HTTPBody).to.beNil();
260 | done();
261 | } failure:^(VENHTTPResponse *response, NSError *error) {
262 | failure(@"Failed to return correct response.");
263 | }];
264 | });
265 | });
266 | });
267 |
268 | describe(@"default headers", ^{
269 | it(@"include Accept", ^{
270 | waitUntil(^(DoneCallback done) {
271 | [http GET:@"200.json" parameters:nil success:^(VENHTTPResponse *response) {
272 | NSURLRequest *httpRequest = [VENHTTPTestProtocol parseRequestFromTestResponse:response];
273 | NSDictionary *requestHeaders = httpRequest.allHTTPHeaderFields;
274 | expect(requestHeaders[@"Accept"]).to.equal(@"application/json");
275 | done();
276 | } failure:^(VENHTTPResponse *response, NSError *error) {
277 | failure(@"Failed to return correct response.");
278 | }];
279 | });
280 | });
281 |
282 | it(@"include User-Agent", ^{
283 | waitUntil(^(DoneCallback done) {
284 | [http GET:@"200.json" parameters:nil success:^(VENHTTPResponse *response) {
285 | NSURLRequest *httpRequest = [VENHTTPTestProtocol parseRequestFromTestResponse:response];
286 | NSDictionary *requestHeaders = httpRequest.allHTTPHeaderFields;
287 | expect(requestHeaders[@"User-Agent"]).to.contain(@"iOS");
288 | done();
289 | } failure:^(VENHTTPResponse *response, NSError *error) {
290 | failure(@"Failed to return correct response.");
291 | }];
292 | });
293 | });
294 |
295 | it(@"include Accept-Language", ^{
296 | waitUntil(^(DoneCallback done) {
297 | [http GET:@"200.json" parameters:nil success:^(VENHTTPResponse *response) {
298 | NSURLRequest *httpRequest = [VENHTTPTestProtocol parseRequestFromTestResponse:response];
299 | NSDictionary *requestHeaders = httpRequest.allHTTPHeaderFields;
300 | expect(requestHeaders[@"Accept-Language"]).to.equal(@"en-US");
301 | done();
302 | } failure:^(VENHTTPResponse *response, NSError *error) {
303 | failure(@"Failed to return correct response.");
304 | }];
305 | });
306 | });
307 | });
308 |
309 | describe(@"parameters", ^{
310 | __block NSDictionary *parameterDictionary;
311 |
312 | beforeEach(^{
313 | parameterDictionary = @{@"stringParam": @"a value",
314 | @"numericParam": @42,
315 | @"trueBoolParam": @YES,
316 | @"falseBoolParam": @NO
317 | };
318 | });
319 |
320 | describe(@"in GET requests", ^{
321 | it(@"transmits the parameters as URL encoded query parameters", ^{
322 | waitUntil(^(DoneCallback done) {
323 | [http GET:@"200.json" parameters:parameterDictionary success:^(VENHTTPResponse *response) {
324 | NSURLRequest *httpRequest = [VENHTTPTestProtocol parseRequestFromTestResponse:response];
325 | expect(httpRequest.URL.query).to.equal(@"falseBoolParam=0&numericParam=42&stringParam=a%20value&trueBoolParam=1");
326 | done();
327 | } failure:^(VENHTTPResponse *response, NSError *error) {
328 | failure(@"Failed to return correct response.");
329 | }];
330 | });
331 | });
332 | });
333 |
334 | describe(@"in non-GET requests", ^{
335 | it(@"transmits the parameters as JSON", ^{
336 | waitUntil(^(DoneCallback done) {
337 | [http POST:@"200.json" parameters:parameterDictionary success:^(VENHTTPResponse *response) {
338 | NSURLRequest *httpRequest = [VENHTTPTestProtocol parseRequestFromTestResponse:response];
339 | NSString *httpRequestBody = [VENHTTPTestProtocol parseRequestBodyFromTestResponse:response];
340 |
341 | expect([httpRequest valueForHTTPHeaderField:@"Content-type"]).to.equal(@"application/x-www-form-urlencoded; charset=utf-8");
342 | expect(httpRequestBody).to.equal(@"falseBoolParam=0&numericParam=42&stringParam=a%20value&trueBoolParam=1");
343 |
344 | done();
345 | } failure:^(VENHTTPResponse *response, NSError *error) {
346 | failure(@"Failed to return correct response.");
347 | }];
348 | });
349 | });
350 | });
351 | });
352 | });
353 |
354 | SpecEnd
355 |
--------------------------------------------------------------------------------
/VENCoreUnitTests/Utilities/EXPMatchers+Venmo.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | EXPMatcherInterface(regexMatch, (id pattern))
4 |
5 | #define regexMatch regexMatch
6 |
--------------------------------------------------------------------------------
/VENCoreUnitTests/Utilities/EXPMatchers+Venmo.m:
--------------------------------------------------------------------------------
1 | #import "EXPMatchers+Venmo.h"
2 |
3 | EXPMatcherImplementationBegin(regexMatch, (id pattern))
4 |
5 | prerequisite(^BOOL{
6 | return [actual isKindOfClass:[NSString class]] && ([pattern isKindOfClass:[NSString class]] || [pattern isKindOfClass:[NSRegularExpression class]]);
7 | });
8 |
9 | match(^BOOL{
10 | NSRegularExpression *regex;
11 | if ([pattern isKindOfClass:[NSRegularExpression class]]) {
12 | regex = pattern;
13 | } else {
14 | NSError *error;
15 | regex = [[NSRegularExpression alloc] initWithPattern:pattern options:0 error:&error];
16 | NSAssert(error == nil, @"Error while compiling regular expression pattern \"%@\" (%@)", pattern, error);
17 | }
18 |
19 | return [regex numberOfMatchesInString:actual options:0 range:NSMakeRange(0, [actual length])] > 0;
20 | });
21 |
22 | failureMessageForTo(^NSString *{
23 | return [NSString stringWithFormat:@"expected \"%@\" to match regex pattern /%@/, but it did not", actual, pattern];
24 | });
25 |
26 | failureMessageForNotTo(^NSString *{
27 | return [NSString stringWithFormat:@"expected \"%@\" not to match regex pattern /%@/, but it did", actual, pattern];
28 | });
29 |
30 | EXPMatcherImplementationEnd
--------------------------------------------------------------------------------
/VENCoreUnitTests/Utilities/VENTestUtilities.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @class VENCore;
4 |
5 | @interface VENTestUtilities : NSObject
6 |
7 | /**
8 | * Returns the Foundation object value of the given json file.
9 | */
10 | + (id)objectFromJSONResource:(NSString *)name;
11 |
12 |
13 | /**
14 | * Returns the string value of the given json file.
15 | */
16 | + (NSString *)stringFromJSONResource:(NSString *)name;
17 |
18 |
19 | /**
20 | * Returns the base URL string of the given core instance.
21 | */
22 | + (NSString *)baseURLStringForCore:(VENCore *)core;
23 |
24 | /**
25 | * Stubs a GET of a URL with the contents of the file at the given path
26 | * @note Adds all default header parmeters
27 | */
28 | + (void)stubNetworkGET:(NSString *)path
29 | withStatusCode:(NSInteger)statusCode
30 | andResponseFilePath:(NSString *)filePath;
31 |
32 |
33 | /**
34 | * Stubs a POST of a URL with the given parameters with the contents of the file at the given path
35 | * @note Adds all default header parmeters
36 | */
37 | + (void)stubNetworkPOST:(NSString *)path
38 | forParameters:(NSDictionary *)dictionary
39 | withStatusCode:(NSInteger)statusCode
40 | andResponseFilePath:(NSString *)filePath;
41 |
42 |
43 | /**
44 | * Returns the value for the key "access_token" in the bundle's config.plist
45 | */
46 | + (NSString *)accessToken;
47 |
48 |
49 | @end
50 |
--------------------------------------------------------------------------------
/VENCoreUnitTests/Utilities/VENTestUtilities.m:
--------------------------------------------------------------------------------
1 | #import "VENTestUtilities.h"
2 | #import "VENCore.h"
3 |
4 | @implementation VENTestUtilities
5 |
6 | + (id)objectFromJSONResource:(NSString *)name {
7 | NSString *fileString = [VENTestUtilities stringFromJSONResource:name];
8 | return [NSJSONSerialization JSONObjectWithData:[fileString dataUsingEncoding:
9 | NSUTF8StringEncoding] options:0 error:nil];
10 | }
11 |
12 |
13 | + (NSString *)stringFromJSONResource:(NSString *)name {
14 | NSString *filePath = [[NSBundle bundleForClass:[self class]] pathForResource:name ofType:@"json"];
15 | NSString *fileString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding
16 | error:NULL];
17 | return fileString;
18 | }
19 |
20 |
21 | + (NSString *)baseURLStringForCore:(VENCore *)core {
22 | return [core.httpClient.baseURL absoluteString];
23 | }
24 |
25 |
26 | + (void)stubNetworkGET:(NSString *)path
27 | withStatusCode:(NSInteger)statusCode
28 | andResponseFilePath:(NSString *)filePath {
29 |
30 | NSString *responseFileContents = [self stringFromJSONResource:filePath];
31 |
32 | NSMutableDictionary *headers = [[[VENCore defaultCore].httpClient defaultHeaders] mutableCopy];
33 |
34 | stubRequest(@"GET", path).
35 | andReturn(statusCode).
36 | withHeaders(headers).
37 | withBody(responseFileContents);
38 | }
39 |
40 |
41 | + (void)stubNetworkPOST:(NSString *)path
42 | forParameters:(NSDictionary *)dictionary
43 | withStatusCode:(NSInteger)statusCode
44 | andResponseFilePath:(NSString *)filePath {
45 |
46 | NSString *responseFileContents = [self stringFromJSONResource:filePath];
47 |
48 | NSMutableDictionary *headers = [[[VENCore defaultCore].httpClient defaultHeaders] mutableCopy];
49 | headers[@"Content-Type"] = @"application/json";
50 |
51 | stubRequest(@"POST", path).
52 | andReturn(statusCode).
53 | withBody([NSKeyedArchiver archivedDataWithRootObject:dictionary]).
54 | withHeaders(headers).
55 | withBody(responseFileContents);
56 | }
57 |
58 |
59 | + (NSString *)accessToken {
60 | NSString *plistPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"config" ofType:@"plist"];
61 | NSDictionary *config = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
62 | return config[@"access_token"];
63 | }
64 |
65 | @end
66 |
--------------------------------------------------------------------------------
/VENCoreUnitTests/VENCoreSpec.m:
--------------------------------------------------------------------------------
1 | #import "VENCore.h"
2 | #import "VENHTTP.h"
3 | #import "VENUser.h"
4 |
5 | NSString *accessToken;
6 | NSDictionary *responseDictionary;
7 | NSString *responseString;
8 | NSURL *baseURL;
9 | VENCore *core;
10 | NSString *path;
11 |
12 | SpecBegin(VENCore)
13 |
14 | [Expecta setAsynchronousTestTimeout:1];
15 |
16 | describe(@"Shared Instances of VENCore should persist", ^{
17 |
18 | it(@"should set a 'defaultCore' and be retrievable", ^{
19 | VENCore *newCore = [[VENCore alloc] init];
20 | [VENCore setDefaultCore:newCore];
21 |
22 | expect([VENCore defaultCore]).to.equal(newCore);
23 | });
24 |
25 | it(@"should overwrite an existing default core when a new one is set", ^{
26 | VENCore *oldCore = [[VENCore alloc] init];
27 | [VENCore setDefaultCore:oldCore];
28 |
29 | VENCore *newCore = [[VENCore alloc] init];
30 | [VENCore setDefaultCore:newCore];
31 |
32 | expect([VENCore defaultCore]).to.equal(newCore);
33 | });
34 |
35 | it(@"should release an old core after setting a new core", ^{
36 | #pragma diagnostic push
37 | #pragma clang diagnostic ignored "-Warc-unsafe-retained-assign"
38 | __weak VENCore *oldCore = [[VENCore alloc] init];
39 | [VENCore setDefaultCore:oldCore];
40 | expect([VENCore defaultCore]).to.equal(oldCore);
41 |
42 | [VENCore setDefaultCore:nil];
43 |
44 | expect(oldCore).will.equal(nil);
45 | #pragma diagnostic pop
46 |
47 | });
48 |
49 | it(@"should clear out the defaultCore when set to nil", ^{
50 | VENCore *newCore = [[VENCore alloc] init];
51 | [VENCore setDefaultCore:newCore];
52 | [VENCore setDefaultCore:nil];
53 | expect([VENCore defaultCore]).to.equal(nil);
54 | });
55 |
56 | });
57 |
58 |
59 | SpecEnd
--------------------------------------------------------------------------------
/VENCoreUnitTests/VENCoreUnitTests-Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | ${EXECUTABLE_NAME}
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundlePackageType
14 | BNDL
15 | CFBundleShortVersionString
16 | 1.0
17 | CFBundleSignature
18 | ????
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/VENCoreUnitTests/VENCoreUnitTests-Prefix.pch:
--------------------------------------------------------------------------------
1 | @import Foundation;
2 | @import XCTest;
3 |
4 | #define SPT_SHORTHAND
5 | #import
6 |
7 | #define EXP_SHORTHAND
8 | #import
9 |
10 | #import
11 |
12 | #define HC_SHORTHAND
13 | #import
14 |
15 | #import
16 |
17 | #import "VENTestUtilities.h"
18 |
19 | #import "EXPMatchers+Venmo.h"
20 |
--------------------------------------------------------------------------------
/VENCoreUnitTests/en.lproj/InfoPlist.strings:
--------------------------------------------------------------------------------
1 | /* Localized versions of Info.plist keys */
2 |
3 |
--------------------------------------------------------------------------------
/docs/DESIGN.md:
--------------------------------------------------------------------------------
1 | ## VENCore
2 |
3 | ## Users
4 | * Venmo app / VenmoClient
5 | * VENAppSwitchSDK
6 | * VenmoSDK
7 | * Venmo
8 |
9 | ## Compatibility
10 | * Mac OS X 10.?
11 | * iOS 6+
12 | * ARC Only
13 |
14 | ## Class hierarchy
15 | ### Public interface
16 | * `VENCore` - public facing interface
17 | * Responsible for:
18 | * Providing a simple interface for interacting with the Venmo internal API
19 |
20 | ### Models
21 | * Should we have type-safe representations of Venmo resources? (YES)
22 | * If we do, should we use [Mantle](https://github.com/MantleFramework/Mantle)?
23 | * Pros:
24 | * Removes a lot of JSON<->model boilerplate for transforming values
25 | * i.e. datestamp <-> NSDate, string <-> enum
26 | * Mantle could be a transition layer between the Venmo API and Core Data
27 | * This [slide deck](http://www.slideshare.net/GuillermoGonzalez51/better-web-clients-with-mantle-and-afnetworking) is a pretty good overview and shows what using Mantle + AFNetworking could look like.
28 | * `MTLManagedObjectAdapter` seems to make going from Mantle -> Core Data pretty easy
29 | * We could turn our existing models into `MTLModel` categories that conform to ``
30 | * Cons:
31 | * Additional dependency
32 |
33 | ### Service
34 | * `VENClientAPI` – translates raw HTTP responses into domain objects
35 | * Responsible for:
36 | * API endpoint names
37 | * access tokens
38 | * default headers
39 | * response parsing
40 |
--------------------------------------------------------------------------------