Unit Test Hardening (FBSDKAppLinkResolver)

Summary:
The main goal of this diff is to remove OHHTPStubs from the FBSDKAppLinkResolver test code.

The easiest way to do that has been to extract the request building part from FBSDKAppLinkResolver to it own class.

These allows us to:

- Assert correctness over some parts of the request independently
- Inject a builder mock in FBSDKAppLinkResolver so we can control the FBSDKGraphRequest and inject the response we want, without having to mock the http layer at all.

Reviewed By: joesus

Differential Revision: D24077192

fbshipit-source-id: 54141fe1a0533bc97503e7a241387c0d0c4da5e3
This commit is contained in:
Alberto Gragera Cerrajero
2020-10-10 00:39:57 -07:00
committed by Facebook GitHub Bot
parent 6538b2700b
commit 6771546b8e
13 changed files with 588 additions and 495 deletions

View File

@@ -62,6 +62,7 @@ Pod::Spec.new do |s|
'FBSDKCoreKit/FBSDKCoreKit/*.h',
'FBSDKCoreKit/FBSDKCoreKit/AppEvents/*.h',
'FBSDKCoreKit/FBSDKCoreKit/AppLink/*.h',
'FBSDKCoreKit/FBSDKCoreKit/AppLink/Resolver/*.h',
'FBSDKCoreKit/FBSDKCoreKit/GraphAPI/*.h'
ss.private_header_files = 'FBSDKCoreKit/FBSDKCoreKit/Internal/**/*.h',
'FBSDKCoreKit/FBSDKCoreKit/AppEvents/Internal/**/*.h'

View File

@@ -68,6 +68,11 @@
0384CED1208E606B0013D404 /* FBSDKAccessTokenExpirer.m in Sources */ = {isa = PBXBuildFile; fileRef = 033429B120894D4700C94913 /* FBSDKAccessTokenExpirer.m */; };
2A3DA4161CB4C0F600339BD4 /* FBSDKAppLinkUtilityTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 2A3DA4151CB4C0F600339BD4 /* FBSDKAppLinkUtilityTests.m */; };
40853B9424C8C43300A7CB16 /* FBSDKJSONValueTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 40853B9324C8C43300A7CB16 /* FBSDKJSONValueTests.m */; };
45540D9125271A4B008E853E /* FBSDKAppLinkResolverRequestBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 45540D8F25271A4B008E853E /* FBSDKAppLinkResolverRequestBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; };
45540D9225271A4B008E853E /* FBSDKAppLinkResolverRequestBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 45540D8F25271A4B008E853E /* FBSDKAppLinkResolverRequestBuilder.h */; settings = {ATTRIBUTES = (Public, ); }; };
45540D9325271A4B008E853E /* FBSDKAppLinkResolverRequestBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45540D9025271A4B008E853E /* FBSDKAppLinkResolverRequestBuilder.m */; };
45540D9425271A4B008E853E /* FBSDKAppLinkResolverRequestBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45540D9025271A4B008E853E /* FBSDKAppLinkResolverRequestBuilder.m */; };
45540DB125271A88008E853E /* FBSDKAppLinkResolverRequestBuilderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 45540DB025271A88008E853E /* FBSDKAppLinkResolverRequestBuilderTests.m */; };
4AF47CF31F42468E00A57A67 /* FBSDKDeviceUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AF47CF11F42468D00A57A67 /* FBSDKDeviceUtilities.m */; };
4AF47CF41F42468E00A57A67 /* FBSDKDeviceUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AF47CF11F42468D00A57A67 /* FBSDKDeviceUtilities.m */; };
4AF47CF51F42468E00A57A67 /* FBSDKDeviceUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AF47CF21F42468D00A57A67 /* FBSDKDeviceUtilities.h */; };
@@ -1207,6 +1212,9 @@
033429B120894D4700C94913 /* FBSDKAccessTokenExpirer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FBSDKAccessTokenExpirer.m; sourceTree = "<group>"; };
2A3DA4151CB4C0F600339BD4 /* FBSDKAppLinkUtilityTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBSDKAppLinkUtilityTests.m; sourceTree = "<group>"; };
40853B9324C8C43300A7CB16 /* FBSDKJSONValueTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBSDKJSONValueTests.m; sourceTree = "<group>"; };
45540D8F25271A4B008E853E /* FBSDKAppLinkResolverRequestBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSDKAppLinkResolverRequestBuilder.h; sourceTree = "<group>"; };
45540D9025271A4B008E853E /* FBSDKAppLinkResolverRequestBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBSDKAppLinkResolverRequestBuilder.m; sourceTree = "<group>"; };
45540DB025271A88008E853E /* FBSDKAppLinkResolverRequestBuilderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = FBSDKAppLinkResolverRequestBuilderTests.m; path = AppLinks/FBSDKAppLinkResolverRequestBuilderTests.m; sourceTree = "<group>"; };
4AF47CF11F42468D00A57A67 /* FBSDKDeviceUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FBSDKDeviceUtilities.m; sourceTree = "<group>"; };
4AF47CF21F42468D00A57A67 /* FBSDKDeviceUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBSDKDeviceUtilities.h; sourceTree = "<group>"; };
4AF47CFE1F424A8700A57A67 /* CoreImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreImage.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS11.0.sdk/System/Library/Frameworks/CoreImage.framework; sourceTree = DEVELOPER_DIR; };
@@ -1755,6 +1763,8 @@
7E5557361A8D833100344F86 /* FBSDKAppLinkResolver.h */,
7E5557351A8D833100344F86 /* FBSDKAppLinkResolver.m */,
52963A69215992F100C7B252 /* FBSDKAppLinkResolving.h */,
45540D8F25271A4B008E853E /* FBSDKAppLinkResolverRequestBuilder.h */,
45540D9025271A4B008E853E /* FBSDKAppLinkResolverRequestBuilder.m */,
);
path = Resolver;
sourceTree = "<group>";
@@ -1860,6 +1870,7 @@
isa = PBXGroup;
children = (
7E55573A1A8D834B00344F86 /* FBSDKAppLinkResolverTests.m */,
45540DB025271A88008E853E /* FBSDKAppLinkResolverRequestBuilderTests.m */,
);
name = AppLinks;
sourceTree = "<group>";
@@ -2955,6 +2966,7 @@
81B71D6F1D19C87400933E93 /* FBSDKInternalUtility.h in Headers */,
81B71D701D19C87400933E93 /* FBSDKConstants.h in Headers */,
BFC0244A237B6B0E00A596EE /* FBSDKSuggestedEventsIndexer.h in Headers */,
45540D9225271A4B008E853E /* FBSDKAppLinkResolverRequestBuilder.h in Headers */,
81B71D711D19C87400933E93 /* FBSDKWebDialog.h in Headers */,
52963A95215992F400C7B252 /* FBSDKWebViewAppLinkResolver.h in Headers */,
81B71D721D19C87400933E93 /* FBSDKMonotonicTime.h in Headers */,
@@ -3039,6 +3051,7 @@
52963AAC2159A16E00C7B252 /* FBSDKMeasurementEvent.h in Headers */,
899C3CF81A8BF73A00EA8658 /* FBSDKProfilePictureView.h in Headers */,
891687EE1AB38C7C00F55364 /* FBSDKButton.h in Headers */,
45540D9125271A4B008E853E /* FBSDKAppLinkResolverRequestBuilder.h in Headers */,
F94310F424F8D608002441F1 /* FBSDKSKAdNetworkRule.h in Headers */,
52963A7E215992F400C7B252 /* FBSDKAppLinkResolving.h in Headers */,
894C0AF31A6F21A1009137EF /* FBSDKBridgeAPIProtocol.h in Headers */,
@@ -3868,6 +3881,7 @@
5D81B424238739E600B02B2E /* FBSDKIntegrityManager.m in Sources */,
81B71D011D19C87400933E93 /* FBSDKGraphRequestPiggybackManager.m in Sources */,
81B71D021D19C87400933E93 /* FBSDKAppEventsStateManager.m in Sources */,
45540D9425271A4B008E853E /* FBSDKAppLinkResolverRequestBuilder.m in Sources */,
81B71D031D19C87400933E93 /* FBSDKServerConfigurationManager.m in Sources */,
81B71D041D19C87400933E93 /* FBSDKGraphRequest.m in Sources */,
F9CBE51424E090590097B442 /* FBSDKSKAdNetworkReporter.m in Sources */,
@@ -3997,6 +4011,7 @@
5D81B423238739E600B02B2E /* FBSDKIntegrityManager.m in Sources */,
9DA81B2A1AA65FA200B9FE0B /* FBSDKGraphRequestPiggybackManager.m in Sources */,
9D0BC1601A8D428700BE8BA4 /* FBSDKAppEventsStateManager.m in Sources */,
45540D9325271A4B008E853E /* FBSDKAppLinkResolverRequestBuilder.m in Sources */,
89830F2C1A7805D100226ABB /* FBSDKServerConfigurationManager.m in Sources */,
9DC658961A6EE5C500B85AAF /* FBSDKGraphRequest.m in Sources */,
F9CBE51224E085CD0097B442 /* FBSDKSKAdNetworkReporter.m in Sources */,
@@ -4165,6 +4180,7 @@
F428430B246B427700CD4393 /* FBSDKServerConfigurationManagerTests.m in Sources */,
F9CEF1EB24F769F900EB0C3D /* FBSDKSKAdNetworkReporterTests.m in Sources */,
F98D1D73251297A900276B68 /* FBSDKAppEventsConfigurationManagerTests.m in Sources */,
45540DB125271A88008E853E /* FBSDKAppLinkResolverRequestBuilderTests.m in Sources */,
5DAB01F123A1831A005495FB /* FBSDKCrashObserverTests.m in Sources */,
F40B24C224732DD90059351C /* Fuzzer.swift in Sources */,
40853B9424C8C43300A7CB16 /* FBSDKJSONValueTests.m in Sources */,

View File

@@ -26,6 +26,7 @@
#import "FBSDKAccessToken.h"
#import "FBSDKAppLink.h"
#import "FBSDKAppLinkResolverRequestBuilder.h"
#import "FBSDKAppLinkTarget.h"
#import "FBSDKGraphRequest+Internal.h"
#import "FBSDKGraphRequestConnection.h"
@@ -48,6 +49,7 @@ static NSString *const kAppLinksKey = @"app_links";
@property (nonatomic, strong) NSMutableDictionary<NSURL *, FBSDKAppLink *> *cachedFBSDKAppLinks;
@property (nonatomic, assign) UIUserInterfaceIdiom userInterfaceIdiom;
@property (nonatomic, strong) FBSDKAppLinkResolverRequestBuilder *requestBuilder;
@end
@implementation FBSDKAppLinkResolver
@@ -62,6 +64,27 @@ static NSString *const kAppLinksKey = @"app_links";
if (self = [super init]) {
self.cachedFBSDKAppLinks = [NSMutableDictionary dictionary];
self.userInterfaceIdiom = userInterfaceIdiom;
self.requestBuilder = [FBSDKAppLinkResolverRequestBuilder new];
}
return self;
}
- (instancetype)initWithUserInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom andRequestBuilder:(FBSDKAppLinkResolverRequestBuilder *)builder
{
if (self = [super init]) {
self.cachedFBSDKAppLinks = [NSMutableDictionary dictionary];
self.userInterfaceIdiom = userInterfaceIdiom;
self.requestBuilder = builder;
}
return self;
}
- (instancetype)initWithRequestBuilder:(FBSDKAppLinkResolverRequestBuilder *)builder
{
if (self = [super init]) {
self.cachedFBSDKAppLinks = [NSMutableDictionary dictionary];
self.userInterfaceIdiom = UI_USER_INTERFACE_IDIOM();
self.requestBuilder = builder;
}
return self;
}
@@ -79,9 +102,9 @@ static NSString *const kAppLinksKey = @"app_links";
[FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorDeveloperErrors
logEntry:@"A user access token or clientToken is required to use FBAppLinkResolver"];
}
NSMutableDictionary<NSURL *, FBSDKAppLink *> *appLinks = [NSMutableDictionary dictionary];
NSMutableArray<NSURL *> *toFind = [NSMutableArray array];
NSMutableArray<NSString *> *toFindStrings = [NSMutableArray array];
@synchronized(self.cachedFBSDKAppLinks) {
for (NSURL *url in urls) {
@@ -89,86 +112,68 @@ static NSString *const kAppLinksKey = @"app_links";
[FBSDKTypeUtility dictionary:appLinks setObject:self.cachedFBSDKAppLinks[url] forKey:url];
} else {
[FBSDKTypeUtility array:toFind addObject:url];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSString *toFindString = [url.absoluteString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
#pragma clang diagnostic pop
if (toFindString) {
[FBSDKTypeUtility array:toFindStrings addObject:toFindString];
}
}
}
}
if (toFind.count == 0) {
// All of the URLs have already been found.
handler(_cachedFBSDKAppLinks, nil);
handler(appLinks, nil);
return;
}
NSMutableArray<NSString *> *fields = [NSMutableArray arrayWithObject:kIOSKey];
NSString *idiomSpecificField = nil;
switch (self.userInterfaceIdiom) {
case UIUserInterfaceIdiomPad:
idiomSpecificField = kIPadKey;
break;
case UIUserInterfaceIdiomPhone:
idiomSpecificField = kIPhoneKey;
break;
default:
break;
}
if (idiomSpecificField) {
[FBSDKTypeUtility array:fields addObject:idiomSpecificField];
}
NSString *path = [NSString stringWithFormat:@"?fields=%@.fields(%@)&ids=%@",
kAppLinksKey,
[fields componentsJoinedByString:@","],
[toFindStrings componentsJoinedByString:@","]];
FBSDKGraphRequest *request = [[FBSDKGraphRequest alloc] initWithGraphPath:path
parameters:nil
flags:FBSDKGraphRequestFlagDoNotInvalidateTokenOnError | FBSDKGraphRequestFlagDisableErrorRecovery];
FBSDKGraphRequest *request = [self.requestBuilder requestForURLs:urls];
[request startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
if (error) {
handler(@{}, error);
return;
}
for (NSURL *url in toFind) {
id nestedObject = result[url.absoluteString][kAppLinksKey];
NSMutableArray *rawTargets = [NSMutableArray array];
if (idiomSpecificField) {
[rawTargets addObjectsFromArray:nestedObject[idiomSpecificField]];
}
[rawTargets addObjectsFromArray:nestedObject[kIOSKey]];
FBSDKAppLink *link = [self buildAppLinkForURL:url inResults:result];
NSMutableArray<FBSDKAppLinkTarget *> *targets = [NSMutableArray arrayWithCapacity:rawTargets.count];
for (id rawTarget in rawTargets) {
[FBSDKTypeUtility array:targets addObject:[FBSDKAppLinkTarget appLinkTargetWithURL:[NSURL URLWithString:rawTarget[kURLKey]]
appStoreId:rawTarget[kIOSAppStoreIdKey]
appName:rawTarget[kIOSAppNameKey]]];
}
id webTarget = nestedObject[kWebKey];
NSString *webFallbackString = webTarget[kURLKey];
NSURL *fallbackUrl = webFallbackString ? [NSURL URLWithString:webFallbackString] : url;
NSNumber *shouldFallback = webTarget[kShouldFallbackKey];
if (shouldFallback != nil && !shouldFallback.boolValue) {
fallbackUrl = nil;
}
FBSDKAppLink *link = [FBSDKAppLink appLinkWithSourceURL:url
targets:targets
webURL:fallbackUrl];
@synchronized(self.cachedFBSDKAppLinks) {
[FBSDKTypeUtility dictionary:self.cachedFBSDKAppLinks setObject:link forKey:url];
}
[FBSDKTypeUtility dictionary:appLinks setObject:link forKey:url];
}
handler(appLinks, nil);
}];
}
- (FBSDKAppLink *)buildAppLinkForURL:(NSURL *)url inResults:(id)result
{
NSString *idiomSpecificField = [self.requestBuilder getIdiomSpecificField];
id nestedObject = result[url.absoluteString][kAppLinksKey];
NSMutableArray *rawTargets = [NSMutableArray array];
if (idiomSpecificField) {
[rawTargets addObjectsFromArray:nestedObject[idiomSpecificField]];
}
[rawTargets addObjectsFromArray:nestedObject[kIOSKey]];
NSMutableArray<FBSDKAppLinkTarget *> *targets = [NSMutableArray arrayWithCapacity:rawTargets.count];
for (id rawTarget in rawTargets) {
[FBSDKTypeUtility array:targets addObject:[FBSDKAppLinkTarget appLinkTargetWithURL:[NSURL URLWithString:rawTarget[kURLKey]]
appStoreId:rawTarget[kIOSAppStoreIdKey]
appName:rawTarget[kIOSAppNameKey]]];
}
id webTarget = nestedObject[kWebKey];
NSString *webFallbackString = webTarget[kURLKey];
NSURL *fallbackUrl = webFallbackString ? [NSURL URLWithString:webFallbackString] : url;
NSNumber *shouldFallback = webTarget[kShouldFallbackKey];
if (shouldFallback != nil && !shouldFallback.boolValue) {
fallbackUrl = nil;
}
return [FBSDKAppLink appLinkWithSourceURL:url
targets:targets
webURL:fallbackUrl];
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ (instancetype)resolver

View File

@@ -0,0 +1,49 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import <Foundation/Foundation.h>
#import "FBSDKAppLinkResolving.h"
#import "FBSDKGraphRequest.h"
NS_ASSUME_NONNULL_BEGIN
/**
Class responsible for generating the appropriate FBSDKGraphRequest for a given set of urls
*/
NS_SWIFT_NAME(AppLinkResolverRequestBuilder)
@interface FBSDKAppLinkResolverRequestBuilder : NSObject
/**
Generates the FBSDKGraphRequest
@param urls The URLs to build the requests for
*/
- (FBSDKGraphRequest* _Nonnull)requestForURLs:(NSArray<NSURL *> * _Nonnull)urls
NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extension");
- (NSString* _Nullable)getIdiomSpecificField
NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extension");
@end
NS_ASSUME_NONNULL_END
#endif

View File

@@ -0,0 +1,125 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to
// use, copy, modify, and distribute this software in source code or binary form
// for use in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#import "TargetConditionals.h"
#if !TARGET_OS_TV
#import "FBSDKAppLinkResolverRequestBuilder.h"
#import <UIKit/UIKit.h>
#import "FBSDKCoreKit+Internal.h"
#import "FBSDKGraphRequest+Internal.h"
static NSString *const kIOSKey = @"ios";
static NSString *const kIPhoneKey = @"iphone";
static NSString *const kIPadKey = @"ipad";
static NSString *const kAppLinksKey = @"app_links";
@interface FBSDKAppLinkResolverRequestBuilder ()
@property (nonatomic, assign) UIUserInterfaceIdiom userInterfaceIdiom;
@end
@implementation FBSDKAppLinkResolverRequestBuilder
- (instancetype)initWithUserInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom
{
if (self = [super init]) {
self.userInterfaceIdiom = userInterfaceIdiom;
}
return self;
}
- (instancetype)init
{
if ((self = [super init])) {
_userInterfaceIdiom = UI_USER_INTERFACE_IDIOM();
}
return self;
}
- (FBSDKGraphRequest *)requestForURLs:(NSArray<NSURL *> *)urls
{
NSArray<NSString *> *fields = [self getUISpecificFields];
NSArray<NSString *> *encodedURLs = [self getEncodedURLs:urls];
NSString *path =
[NSString stringWithFormat:@"?fields=%@.fields(%@)&ids=%@",
kAppLinksKey,
[fields componentsJoinedByString:@","],
[encodedURLs componentsJoinedByString:@","]];
return [[FBSDKGraphRequest alloc]
initWithGraphPath:path
parameters:nil
flags:FBSDKGraphRequestFlagDoNotInvalidateTokenOnError
| FBSDKGraphRequestFlagDisableErrorRecovery];
}
- (NSString *_Nullable)getIdiomSpecificField
{
NSString *idiomSpecificField = nil;
switch (self.userInterfaceIdiom) {
case UIUserInterfaceIdiomPad:
idiomSpecificField = kIPadKey;
break;
case UIUserInterfaceIdiomPhone:
idiomSpecificField = kIPhoneKey;
break;
default:
break;
}
return idiomSpecificField;
}
- (NSArray<NSString *> *)getUISpecificFields
{
NSMutableArray<NSString *> *fields = [NSMutableArray arrayWithObject:kIOSKey];
NSString *idiomSpecificField = [self getIdiomSpecificField];
if (idiomSpecificField) {
[FBSDKTypeUtility array:fields addObject:idiomSpecificField];
}
return fields;
}
- (NSArray<NSString *> *)getEncodedURLs:(NSArray<NSURL *> *)urls
{
NSMutableArray<NSString *> *encodedURLs = [NSMutableArray array];
for (NSURL *url in urls) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
NSString *encodedURL = [url.absoluteString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
#pragma clang diagnostic pop
if (encodedURL) {
[FBSDKTypeUtility array:encodedURLs addObject:encodedURL];
}
}
return encodedURLs;
}
@end
#endif

View File

@@ -43,6 +43,7 @@
#import <FBSDKCoreKit/FBSDKAppLink.h>
#import <FBSDKCoreKit/FBSDKAppLinkNavigation.h>
#import <FBSDKCoreKit/FBSDKAppLinkResolver.h>
#import <FBSDKCoreKit/FBSDKAppLinkResolverRequestBuilder.h>
#import <FBSDKCoreKit/FBSDKAppLinkResolving.h>
#import <FBSDKCoreKit/FBSDKAppLinkReturnToRefererController.h>
#import <FBSDKCoreKit/FBSDKAppLinkReturnToRefererView.h>
@@ -79,6 +80,7 @@
#import "FBSDKAppLink.h"
#import "FBSDKAppLinkNavigation.h"
#import "FBSDKAppLinkResolver.h"
#import "FBSDKAppLinkResolverRequestBuilder.h"
#import "FBSDKAppLinkResolving.h"
#import "FBSDKAppLinkReturnToRefererController.h"
#import "FBSDKAppLinkReturnToRefererView.h"

View File

@@ -1 +1 @@
../AppLink/FBSDKAppLinkResolver.h
../AppLink/Resolver/FBSDKAppLinkResolver.h

View File

@@ -0,0 +1 @@
../AppLink/Resolver/FBSDKAppLinkResolverRequestBuilder.h

View File

@@ -1 +1 @@
../AppLink/FBSDKAppLinkResolving.h
../AppLink/Resolver/FBSDKAppLinkResolving.h

View File

@@ -0,0 +1,58 @@
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
//
// You are hereby granted a non-exclusive, worldwide, royalty-free license to use,
// copy, modify, and distribute this software in source code or binary form for use
// in connection with the web services and APIs provided by Facebook.
//
// As with any software that integrates with the Facebook platform, your use of
// this software is subject to the Facebook Developer Principles and Policies
// [http://developers.facebook.com/policy/]. This copyright notice shall be
// included in all copies or substantial portions of the software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#import <XCTest/XCTest.h>
#import <FBSDKCoreKit/FBSDKCoreKit.h>
#import "FBSDKAppLinkResolverRequestBuilder.h"
#import "FBSDKTestCase.h"
@interface FBSDKAppLinkResolverRequestBuilder (FBSDKAppLinkResolverTests)
- (instancetype)initWithUserInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom;
@end
@interface FBSDKAppLinkResolverRequestBuilderTests : FBSDKTestCase
@end
@implementation FBSDKAppLinkResolverRequestBuilderTests
#pragma mark - test cases
- (void)testAsksForPhoneDataOnPhone
{
FBSDKAppLinkResolverRequestBuilder *builder = [[FBSDKAppLinkResolverRequestBuilder alloc] initWithUserInterfaceIdiom:UIUserInterfaceIdiomPhone];
FBSDKGraphRequest *request = [builder requestForURLs:@[]];
BOOL askedForPhone = [request.parameters[@"fields"] rangeOfString:@"iphone"].location != NSNotFound;
XCTAssertTrue(askedForPhone);
}
- (void)testAsksForPadDataOnPad
{
FBSDKAppLinkResolverRequestBuilder *builder = [[FBSDKAppLinkResolverRequestBuilder alloc] initWithUserInterfaceIdiom:UIUserInterfaceIdiomPad];
FBSDKGraphRequest *request = [builder requestForURLs:@[]];
BOOL askedForPad = [request.parameters[@"fields"] rangeOfString:@"iphone"].location != NSNotFound;
XCTAssertTrue(askedForPad);
}
@end

View File

@@ -20,19 +20,15 @@
#import <XCTest/XCTest.h>
#import <FBSDKCoreKit/FBSDKCoreKit.h>
#import <OHHTTPStubs/OHHTTPStubs.h>
#import "FBSDKAppLinkResolver.h"
#import "FBSDKCoreKitTestUtility.h"
#import "FBSDKInternalUtility.h"
#import "FBSDKSettings.h"
#import "FBSDKTestCase.h"
static NSString *const kAppLinkURLString = @"http://example.com/1234567890";
static NSString *const kAppLinkURL2String = @"http://example.com/0987654321";
static NSString *const kAppLinksKey = @"app_links";
typedef void (^HTTPStubCallback)(NSURLRequest *request);
typedef _Nullable id (^StringURLBlock)(NSString *urlString);
static NSString *const kIphoneKey = @"iphone";
static NSString *const kIpadKey = @"ipad";
@interface NSURL (FBSDKAppLinkResolverTests)
@@ -43,147 +39,87 @@ typedef _Nullable id (^StringURLBlock)(NSString *urlString);
@interface FBSDKAppLinkResolver (FBSDKAppLinkResolverTests)
- (instancetype)initWithUserInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom;
- (instancetype)initWithRequestBuilder:(FBSDKAppLinkResolverRequestBuilder *)builder;
- (instancetype)initWithUserInterfaceIdiom:(UIUserInterfaceIdiom)userInterfaceIdiom andRequestBuilder:(FBSDKAppLinkResolverRequestBuilder *)builder;
@end
@interface FBSDKAppLinkResolverTests : XCTestCase
@interface FBSDKAppLinkResolverTests : FBSDKTestCase
@end
@implementation FBSDKAppLinkResolverTests
#pragma mark - Mock requests
- (void)mockAppLinkRequestWithResult:(id)result
{
id _mockNSBundle;
[self mockAppLinkRequestWithResult:result error:nil idiomSpecificField:kIphoneKey];
}
#pragma mark - HTTP stubbing helpers
- (void)stubAllResponsesWithResult:(id)result
- (void)mockAppLinkRequestWithError
{
[self stubAllResponsesWithResult:result statusCode:200];
[self mockAppLinkRequestWithResult:@{@"error" : @{}}
error:[[NSError alloc]
initWithDomain:FBSDKErrorDomain
code:-1
userInfo:nil]
idiomSpecificField:kIphoneKey];
}
- (void)stubAllResponsesWithResult:(id)result
statusCode:(int)statusCode
- (void)mockAppLinkRequestWithResult:(id)result idiomSpecificField:(NSString *)field
{
[self stubAllResponsesWithResult:result statusCode:statusCode callback:nil];
[self mockAppLinkRequestWithResult:result error:nil idiomSpecificField:field];
}
- (void)stubAllResponsesWithResult:(id)result
statusCode:(int)statusCode
callback:(HTTPStubCallback)callback
- (void)mockAppLinkRequestWithResult:(id)result error:(NSError *)error idiomSpecificField:(NSString *)field
{
return [self stubMatchingRequestsWithResponses:@{@"" : result}
statusCode:statusCode
callback:callback];
[self mockAppLinkRequestWithResult:result error:error idiomSpecificField:field andDo:nil];
}
- (void)stubMatchingRequestsWithResponses:(NSDictionary<NSString *, id> *)requestsAndResponses
statusCode:(int)statusCode
callback:(HTTPStubCallback)callback
- (void)mockAppLinkRequestWithResult:(id)result error:(NSError *)error idiomSpecificField:(NSString *)field andDo:(void (^_Nullable)(NSInvocation *))block
{
StringURLBlock matchingKey = ^id (NSString *urlString) {
for (NSString *substring in requestsAndResponses.allKeys) {
// The first @"" always matches
if (substring.length == 0
|| [urlString rangeOfString:substring].location != NSNotFound) {
return substring;
}
}
return nil;
};
[self stubGraphRequestWithResult:result error:error connection:nil];
[self stubAppLinkResolverRequestBuilderWithIdiomSpecificField:field];
[OHHTTPStubs stubRequestsPassingTest:^BOOL (NSURLRequest *request) {
if (callback) {
callback(request);
}
return matchingKey(request.URL.absoluteString) != nil;
} withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) {
id result = requestsAndResponses[matchingKey(request.URL.absoluteString)];
NSData *data = [[FBSDKBasicUtility JSONStringForObject:result
error:NULL
invalidObjectHandler:NULL] dataUsingEncoding:NSUTF8StringEncoding];
return [OHHTTPStubsResponse responseWithData:data
statusCode:statusCode
headers:nil];
}];
OCMStub([self.appLinkResolverRequestBuilderMock requestForURLs:[OCMArg any]]).andReturn(self.graphRequestMock).andDo(block);
}
#pragma mark - test cases
- (void)setUp
{
_mockNSBundle = [FBSDKCoreKitTestUtility mainBundleMock];
}
/*
- (void)testAsksForPhoneDataOnPhone
{
XCTestExpectation *expectation = [self expectationWithDescription:@"asksForPhoneDataOnPhone"];
[FBSDKAccessToken setCurrentAccessToken:nil];
[FBSDKSettings setClientToken:@"clienttoken"];
__block BOOL askedForPhone = NO;
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
NSDictionary<NSString *, NSString *> *queryParameters =
[FBSDKInternalUtility dictionaryFromFBURL:request.URL];
askedForPhone = [queryParameters[@"fields"] rangeOfString:@"iphone"].location != NSNotFound;
return YES;
} withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) {
return [OHHTTPStubsResponse responseWithData:[NSData data]
statusCode:200
headers:nil];
}];
FBSDKAppLinkResolver *resolver = [[FBSDKAppLinkResolver alloc] initWithUserInterfaceIdiom:UIUserInterfaceIdiomPhone];
[resolver
appLinkFromURL:[NSURL URLWithString:kAppLinkURLString]
handler:^(FBSDKAppLink * _Nullable appLink, NSError * _Nullable error) {
XCTAssertTrue(askedForPhone);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
XCTAssertNil(error);
}];
[FBSDKSettings setClientToken:nil];
}
*/
/*
- (void)testUsesPhoneDataOnPhone
{
XCTestExpectation *expectation = [self expectationWithDescription:@"usesPhoneDataOnPhone"];
[FBSDKAccessToken setCurrentAccessToken:nil];
[FBSDKSettings setClientToken:@"clienttoken"];
[self stubClientTokenWith:@"clienttoken"];
[self stubAllResponsesWithResult:@{
kAppLinkURLString : @{
kAppLinksKey: @{
@"iphone": @[
@{
@"app_name": @"Example",
@"app_store_id": @"456",
@"url": @"example://things/1234567890"
}
],
@"ios": @[
@{
@"app_name": @"Example",
@"app_store_id": @"123",
@"url": @"example://things/1234567890"
}
],
},
@"id": kAppLinkURLString
}
}];
id result = @{
kAppLinkURLString : @{
kAppLinksKey : @{
kIphoneKey : @[
@{
@"app_name" : @"Example",
@"app_store_id" : @"456",
@"url" : @"example://things/1234567890"
}
],
@"ios" : @[
@{
@"app_name" : @"Example",
@"app_store_id" : @"123",
@"url" : @"example://things/1234567890"
}
],
},
@"id" : kAppLinkURLString
}
};
[self mockAppLinkRequestWithResult:result idiomSpecificField:kIphoneKey];
FBSDKAppLinkResolver *resolver = [[FBSDKAppLinkResolver alloc] initWithUserInterfaceIdiom:UIUserInterfaceIdiomPhone andRequestBuilder:self.appLinkResolverRequestBuilderMock];
FBSDKAppLinkResolver *resolver = [[FBSDKAppLinkResolver alloc] initWithUserInterfaceIdiom:UIUserInterfaceIdiomPhone];
[resolver
appLinkFromURL:[NSURL URLWithString:kAppLinkURLString]
handler:^(FBSDKAppLink * _Nullable link, NSError * _Nullable error) {
handler:^(FBSDKAppLink *_Nullable link, NSError *_Nullable error) {
XCTAssertNotNil(link);
XCTAssertEqual(link.sourceURL.absoluteString, kAppLinkURLString);
XCTAssertEqualObjects([link.targets[0] appStoreId], @"456");
@@ -192,82 +128,45 @@ typedef _Nullable id (^StringURLBlock)(NSString *urlString);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
[self waitForExpectationsWithTimeout:2 handler:^(NSError *_Nullable error) {
XCTAssertNil(error);
}];
[FBSDKSettings setClientToken:nil];
}
*/
/*
- (void)testAsksForPadDataOnPad
{
XCTestExpectation *expectation = [self expectationWithDescription:@"asksForPadDataOnPad"];
[FBSDKAccessToken setCurrentAccessToken:nil];
[FBSDKSettings setClientToken:@"clienttoken"];
__block BOOL askedForPad = NO;
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
NSDictionary<NSString *, NSString *> *queryParameters =
[FBSDKInternalUtility dictionaryFromFBURL:request.URL];
// do an "OR" because we only need to verify we asked for it once (in cases where unrelated network requests
// were resetting the flag incorrectly back to NO).
askedForPad |= [queryParameters[@"fields"] rangeOfString:@"ipad"].location != NSNotFound;
return YES;
} withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) {
return [OHHTTPStubsResponse responseWithData:[NSData data]
statusCode:200
headers:nil];
}];
FBSDKAppLinkResolver *resolver = [[FBSDKAppLinkResolver alloc] initWithUserInterfaceIdiom:UIUserInterfaceIdiomPad];
[resolver
appLinkFromURL:[NSURL URLWithString:kAppLinkURLString]
handler:^(FBSDKAppLink * _Nullable link, NSError * _Nullable error) {
XCTAssertTrue(askedForPad);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
XCTAssertNil(error);
}];
[FBSDKSettings setClientToken:nil];
}
*/
/*
- (void)testUsesPadDataOnPad
{
XCTestExpectation *expectation = [self expectationWithDescription:@"usesPadDataOnPad"];
[FBSDKAccessToken setCurrentAccessToken:nil];
[FBSDKSettings setClientToken:@"clienttoken"];
[self stubClientTokenWith:@"clienttoken"];
[self stubAllResponsesWithResult:@{
kAppLinkURLString : @{
kAppLinksKey: @{
@"ipad": @[
@{
@"app_name": @"Example",
@"app_store_id": @"456",
@"url": @"example://things/1234567890"
}
],
@"ios": @[
@{
@"app_name": @"Example",
@"app_store_id": @"123",
@"url": @"example://things/1234567890"
}
],
},
@"id": kAppLinkURLString
}
}];
id result = @{
kAppLinkURLString : @{
kAppLinksKey : @{
@"ipad" : @[
@{
@"app_name" : @"Example",
@"app_store_id" : @"456",
@"url" : @"example://things/1234567890"
}
],
@"ios" : @[
@{
@"app_name" : @"Example",
@"app_store_id" : @"123",
@"url" : @"example://things/1234567890"
}
],
},
@"id" : kAppLinkURLString
}
};
[self mockAppLinkRequestWithResult:result idiomSpecificField:kIpadKey];
FBSDKAppLinkResolver *resolver = [[FBSDKAppLinkResolver alloc] initWithUserInterfaceIdiom:UIUserInterfaceIdiomPad andRequestBuilder:self.appLinkResolverRequestBuilderMock];
FBSDKAppLinkResolver *resolver = [[FBSDKAppLinkResolver alloc] initWithUserInterfaceIdiom:UIUserInterfaceIdiomPad];
[resolver
appLinkFromURL:[NSURL URLWithString:kAppLinkURLString]
handler:^(FBSDKAppLink * _Nullable link, NSError * _Nullable error) {
handler:^(FBSDKAppLink *_Nullable link, NSError *_Nullable error) {
XCTAssertNotNil(link);
XCTAssertEqual(link.sourceURL.absoluteString, kAppLinkURLString);
XCTAssertEqualObjects([link.targets[0] appStoreId], @"456");
@@ -276,76 +175,73 @@ typedef _Nullable id (^StringURLBlock)(NSString *urlString);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
[self waitForExpectationsWithTimeout:2 handler:^(NSError *_Nullable error) {
XCTAssertNil(error);
}];
[FBSDKSettings setClientToken:nil];
}
- (void)testIgnoresAndroidData
{
XCTestExpectation *expectation = [self expectationWithDescription:@"ignoresAndroidData"];
[FBSDKAccessToken setCurrentAccessToken:nil];
[FBSDKSettings setClientToken:@"clienttoken"];
[self stubClientTokenWith:@"clienttoken"];
// We are not asking for it, but just make sure we ignore any non-iOS-platform data we get, to be safe.
[self stubAllResponsesWithResult:@{
kAppLinkURLString : @{
kAppLinksKey: @{
@"android": @[
@{
@"app_name": @"Example",
@"package": @"com.example.app",
@"url": @"example://things/1234567890"
}
],
@"ios": @[
@{
@"app_name": @"Example",
@"app_store_id": @"123",
@"url": @"example://things/1234567890"
}
],
},
@"id": kAppLinkURLString
}
}];
id result = @{
kAppLinkURLString : @{
kAppLinksKey : @{
@"android" : @[
@{
@"app_name" : @"Example",
@"package" : @"com.example.app",
@"url" : @"example://things/1234567890"
}
],
@"ios" : @[
@{
@"app_name" : @"Example",
@"app_store_id" : @"123",
@"url" : @"example://things/1234567890"
}
],
},
@"id" : kAppLinkURLString
}
};
[self mockAppLinkRequestWithResult:result];
FBSDKAppLinkResolver *resolver = [[FBSDKAppLinkResolver alloc] initWithRequestBuilder:self.appLinkResolverRequestBuilderMock];
FBSDKAppLinkResolver *resolver = [FBSDKAppLinkResolver resolver];
[resolver
appLinkFromURL:[NSURL URLWithString:kAppLinkURLString]
handler:^(FBSDKAppLink * _Nullable link, NSError * _Nullable error) {
handler:^(FBSDKAppLink *_Nullable link, NSError *_Nullable error) {
XCTAssertNotNil(link);
XCTAssertEqualObjects([link.targets[0] appStoreId], @"123");
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
[self waitForExpectationsWithTimeout:2 handler:^(NSError *_Nullable error) {
XCTAssertNil(error);
}];
[FBSDKSettings setClientToken:nil];
}
*/
/*
- (void)testHandlesNoTargets
{
XCTestExpectation *expectation = [self expectationWithDescription:@"handlesNoTargets"];
[FBSDKAccessToken setCurrentAccessToken:nil];
[FBSDKSettings setClientToken:@"clienttoken"];
[self stubClientTokenWith:@"clienttoken"];
[self stubAllResponsesWithResult:@{
kAppLinkURLString : @{
@"id": kAppLinkURLString
}
}];
id result = @{
kAppLinkURLString : @{
@"id" : kAppLinkURLString
}
};
[self mockAppLinkRequestWithResult:result];
FBSDKAppLinkResolver *resolver = [[FBSDKAppLinkResolver alloc] initWithRequestBuilder:self.appLinkResolverRequestBuilderMock];
FBSDKAppLinkResolver *resolver = [FBSDKAppLinkResolver resolver];
[resolver
appLinkFromURL:[NSURL URLWithString:kAppLinkURLString]
handler:^(FBSDKAppLink * _Nullable link, NSError * _Nullable error) {
handler:^(FBSDKAppLink *_Nullable link, NSError *_Nullable error) {
XCTAssertNotNil(link);
XCTAssertEqual(link.sourceURL.absoluteString, kAppLinkURLString);
XCTAssertEqual(link.targets.count, 0);
@@ -353,33 +249,31 @@ typedef _Nullable id (^StringURLBlock)(NSString *urlString);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
[self waitForExpectationsWithTimeout:2 handler:^(NSError *_Nullable error) {
XCTAssertNil(error);
}];
[FBSDKSettings setClientToken:nil];
}
*/
/*
- (void)testHandlesMultipleURLs
{
XCTestExpectation *expectation = [self expectationWithDescription:@"handlesMultipleURLs"];
[FBSDKAccessToken setCurrentAccessToken:nil];
[FBSDKSettings setClientToken:@"clienttoken"];
[self stubClientTokenWith:@"clienttoken"];
[self stubAllResponsesWithResult:@{
kAppLinkURLString : @{
@"id": kAppLinkURLString
},
kAppLinkURL2String : @{
@"id": kAppLinkURL2String
}
}];
id result = @{
kAppLinkURLString : @{
@"id" : kAppLinkURLString
},
kAppLinkURL2String : @{
@"id" : kAppLinkURL2String
}
};
[self mockAppLinkRequestWithResult:result];
FBSDKAppLinkResolver *resolver = [[FBSDKAppLinkResolver alloc] initWithRequestBuilder:self.appLinkResolverRequestBuilderMock];
FBSDKAppLinkResolver *resolver = [FBSDKAppLinkResolver resolver];
[resolver
appLinksFromURLs:@[[NSURL URLWithString:kAppLinkURLString], [NSURL URLWithString:kAppLinkURL2String]]
handler:^(NSDictionary<NSURL *,FBSDKAppLink *> * _Nonnull links, NSError * _Nullable error) {
handler:^(NSDictionary<NSURL *, FBSDKAppLink *> *_Nonnull links, NSError *_Nullable error) {
XCTAssertNotNil(links);
XCTAssertEqual(links.count, 2);
XCTAssertEqual([links[[NSURL URLWithString:kAppLinkURLString]] sourceURL].absoluteString, kAppLinkURLString);
@@ -388,311 +282,204 @@ typedef _Nullable id (^StringURLBlock)(NSString *urlString);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
[self waitForExpectationsWithTimeout:2 handler:^(NSError *_Nullable error) {
XCTAssertNil(error);
}];
[FBSDKSettings setClientToken:nil];
}
*/
/*
- (void)testSetsFallbackIfNotSpecified
{
XCTestExpectation *expectation = [self expectationWithDescription:@"setsFallbackIfNotSpecified"];
[FBSDKAccessToken setCurrentAccessToken:nil];
[FBSDKSettings setClientToken:@"clienttoken"];
[self stubClientTokenWith:@"clienttoken"];
[self stubAllResponsesWithResult:@{
kAppLinkURLString : @{
@"id": kAppLinkURLString
}
}];
id result = @{
kAppLinkURLString : @{
@"id" : kAppLinkURLString
}
};
[self mockAppLinkRequestWithResult:result];
FBSDKAppLinkResolver *resolver = [[FBSDKAppLinkResolver alloc] initWithRequestBuilder:self.appLinkResolverRequestBuilderMock];
FBSDKAppLinkResolver *resolver = [FBSDKAppLinkResolver resolver];
[resolver
appLinkFromURL:[NSURL URLWithString:kAppLinkURLString]
handler:^(FBSDKAppLink * _Nullable link, NSError * _Nullable error) {
handler:^(FBSDKAppLink *_Nullable link, NSError *_Nullable error) {
XCTAssertNotNil(link);
XCTAssertEqual(link.webURL.absoluteString, kAppLinkURLString);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
[self waitForExpectationsWithTimeout:2 handler:^(NSError *_Nullable error) {
XCTAssertNil(error);
}];
[FBSDKSettings setClientToken:nil];
}
*/
/*
- (void)testSetsFallbackIfSpecified
{
XCTestExpectation *expectation = [self expectationWithDescription:@"setsFallbackIfSpecified"];
[self stubClientTokenWith:@"clienttoken"];
[FBSDKAccessToken setCurrentAccessToken:nil];
[FBSDKSettings setClientToken:@"clienttoken"];
id result = @{
kAppLinkURLString : @{
kAppLinksKey : @{
@"web" : @{
@"url" : @"http://www.example.com/somethingelse",
@"should_fallback" : @"true"
}
},
@"id" : kAppLinkURLString
}
};
[self stubAllResponsesWithResult:@{
kAppLinkURLString : @{
kAppLinksKey : @{
@"web": @{
@"url" : @"http://www.example.com/somethingelse",
@"should_fallback": @"true"
}
},
@"id": kAppLinkURLString
}
}];
[self mockAppLinkRequestWithResult:result];
FBSDKAppLinkResolver *resolver = [[FBSDKAppLinkResolver alloc] initWithRequestBuilder:self.appLinkResolverRequestBuilderMock];
FBSDKAppLinkResolver *resolver = [FBSDKAppLinkResolver resolver];
[resolver
appLinkFromURL:[NSURL URLWithString:kAppLinkURLString]
handler:^(FBSDKAppLink * _Nullable link, NSError * _Nullable error) {
handler:^(FBSDKAppLink *_Nullable link, NSError *_Nullable error) {
XCTAssertNotNil(link);
XCTAssertEqualObjects(link.webURL.absoluteString, @"http://www.example.com/somethingelse");
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
[self waitForExpectationsWithTimeout:2 handler:^(NSError *_Nullable error) {
XCTAssertNil(error);
}];
[FBSDKSettings setClientToken:nil];
}
*/
/*
- (void)testUsesSourceAsFallbackIfSpecified
{
XCTestExpectation *expectation = [self expectationWithDescription:@"usesSourceAsFallbackIfSpecified"];
[self stubClientTokenWith:@"clienttoken"];
[FBSDKAccessToken setCurrentAccessToken:nil];
[FBSDKSettings setClientToken:@"clienttoken"];
id result = @{
kAppLinkURLString : @{
kAppLinksKey : @{
@"web" : @{
@"should_fallback" : @"true"
}
},
@"id" : kAppLinkURLString,
}
};
[self stubAllResponsesWithResult:@{
kAppLinkURLString : @{
kAppLinksKey: @{
@"web": @{
@"should_fallback": @"true"
}
},
@"id": kAppLinkURLString,
}
}];
[self mockAppLinkRequestWithResult:result];
FBSDKAppLinkResolver *resolver = [[FBSDKAppLinkResolver alloc] initWithRequestBuilder:self.appLinkResolverRequestBuilderMock];
FBSDKAppLinkResolver *resolver = [FBSDKAppLinkResolver resolver];
[resolver
appLinkFromURL:[NSURL URLWithString:kAppLinkURLString]
handler:^(FBSDKAppLink * _Nullable link, NSError * _Nullable error) {
handler:^(FBSDKAppLink *_Nullable link, NSError *_Nullable error) {
XCTAssertNotNil(link);
XCTAssertEqual(link.webURL.absoluteString, kAppLinkURLString);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
[self waitForExpectationsWithTimeout:2 handler:^(NSError *_Nullable error) {
XCTAssertNil(error);
}];
[FBSDKSettings setClientToken:nil];
}
*/
/*
- (void)testSetsNoFallbackIfSpecified
{
XCTestExpectation *expectation = [self expectationWithDescription:@"setsNoFallbackIfSpecified"];
[self stubClientTokenWith:@"clienttoken"];
[FBSDKAccessToken setCurrentAccessToken:nil];
[FBSDKSettings setClientToken:@"clienttoken"];
id result = @{
kAppLinkURLString : @{
kAppLinksKey : @{
@"web" : @{
@"url" : @"http://www.example.com/somethingelse",
@"should_fallback" : @"false"
}
},
@"id" : kAppLinkURLString
}
};
[self stubAllResponsesWithResult:@{
kAppLinkURLString : @{
kAppLinksKey : @{
@"web": @{
@"url" : @"http://www.example.com/somethingelse",
@"should_fallback": @"false"
}
},
@"id": kAppLinkURLString
}
}];
[self mockAppLinkRequestWithResult:result];
FBSDKAppLinkResolver *resolver = [[FBSDKAppLinkResolver alloc] initWithRequestBuilder:self.appLinkResolverRequestBuilderMock];
FBSDKAppLinkResolver *resolver = [FBSDKAppLinkResolver resolver];
[resolver
appLinkFromURL:[NSURL URLWithString:kAppLinkURLString]
handler:^(FBSDKAppLink * _Nullable link, NSError * _Nullable error) {
handler:^(FBSDKAppLink *_Nullable link, NSError *_Nullable error) {
XCTAssertNotNil(link);
XCTAssertNil(link.webURL.absoluteString);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
[self waitForExpectationsWithTimeout:2 handler:^(NSError *_Nullable error) {
XCTAssertNil(error);
}];
[FBSDKSettings setClientToken:nil];
}
*/
/*
- (void)testHandlesError
{
XCTestExpectation *expectation = [self expectationWithDescription:@"handlesError"];
[self stubClientTokenWith:@"clienttoken"];
[FBSDKAccessToken setCurrentAccessToken:nil];
[FBSDKSettings setClientToken:@"clienttoken"];
[self mockAppLinkRequestWithError];
FBSDKAppLinkResolver *resolver = [[FBSDKAppLinkResolver alloc] initWithRequestBuilder:self.appLinkResolverRequestBuilderMock];
// We are not asking for it, but just make sure we ignore any non-iOS-platform data we get, to be safe.
[self stubAllResponsesWithResult:@{
@"error" : @{}
}
statusCode:404];
FBSDKAppLinkResolver *resolver = [FBSDKAppLinkResolver resolver];
[resolver
appLinkFromURL:[NSURL URLWithString:kAppLinkURLString]
handler:^(FBSDKAppLink * _Nullable link, NSError * _Nullable error) {
handler:^(FBSDKAppLink *_Nullable link, NSError *_Nullable error) {
XCTAssertNil(link);
XCTAssertNotNil(error);
[expectation fulfill];
}];
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
[self waitForExpectationsWithTimeout:2 handler:^(NSError *_Nullable error) {
XCTAssertNil(error);
}];
[FBSDKSettings setClientToken:nil];
}
*/
/*
- (void)testResultsAreCachedAndCacheIsUsed
{
XCTestExpectation *expectation = [self expectationWithDescription:@"handlesCache"];
[self stubClientTokenWith:@"clienttoken"];
[FBSDKAccessToken setCurrentAccessToken:nil];
[FBSDKSettings setClientToken:@"clienttoken"];
id result = @{
kAppLinkURLString : @{
kAppLinksKey : @{
@"iphone" : @[
@{
@"app_name" : @"Example",
@"app_store_id" : @"456",
@"url" : @"example://things/1234567890"
}
],
},
@"id" : kAppLinkURLString
}
};
__block NSUInteger callCount = 0;
// We can change the approach and do OCMVerify(exactly(1), [_mockRequestBuilder requestForURLs]);
// in the inner block of the double call instead of using this counter.
__block int callCount = 0;
[self mockAppLinkRequestWithResult:result error:nil idiomSpecificField:kIphoneKey andDo:^(NSInvocation *invocation) {
callCount++;
}];
[self stubAllResponsesWithResult:@{
kAppLinkURLString : @{
kAppLinksKey : @{
@"iphone": @[
@{
@"app_name": @"Example",
@"app_store_id": @"456",
@"url": @"example://things/1234567890"
}
],
},
@"id": kAppLinkURLString
}
}
statusCode:200
callback:^(NSURLRequest *request) {
++callCount;
}];
FBSDKAppLinkResolver *resolver = [[FBSDKAppLinkResolver alloc] initWithRequestBuilder:self.appLinkResolverRequestBuilderMock];
FBSDKAppLinkResolver *resolver = [FBSDKAppLinkResolver resolver];
[resolver
appLinkFromURL:[NSURL URLWithString:kAppLinkURLString]
handler:^(FBSDKAppLink * _Nullable link1, NSError * _Nullable error1) {
// Note: callCount is not necessarily 1, as the callback may be called multiple times during processing of the request.
NSUInteger expectedCallCount = callCount;
handler:^(FBSDKAppLink *_Nullable link1, NSError *_Nullable error1) {
[resolver
appLinkFromURL:[NSURL URLWithString:kAppLinkURLString]
handler:^(FBSDKAppLink * _Nullable link2, NSError * _Nullable error2) {
XCTAssertEqual(callCount, expectedCallCount);
handler:^(FBSDKAppLink *_Nullable link2, NSError *_Nullable error2) {
XCTAssertEqual(callCount, 1);
[expectation fulfill];
}];
}];
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
[self waitForExpectationsWithTimeout:2 handler:^(NSError *_Nullable error) {
XCTAssertNil(error);
}];
[FBSDKSettings setClientToken:nil];
}
*/
/*
- (void)testMixOfCachedAndUncached
{
XCTestExpectation *expectation = [self expectationWithDescription:@"mixOfCachedAndUncached"];
[FBSDKAccessToken setCurrentAccessToken:nil];
[FBSDKSettings setClientToken:@"clienttoken"];
__block NSMutableDictionary<NSString *, NSNumber *> *callCounts =
[NSMutableDictionary dictionary];
[self stubMatchingRequestsWithResponses:@{
@"1234567890" : @{
kAppLinkURLString : @{
kAppLinksKey : @{
@"iphone": @[
@{
@"app_name": @"Example",
@"app_store_id": @"456",
@"url": @"example://things/1234567890"
}
],
},
@"id": kAppLinkURLString
}
},
@"0987654321" : @{
kAppLinkURL2String : @{
kAppLinksKey : @{
@"iphone": @[
@{
@"app_name": @"Example",
@"app_store_id": @"456",
@"url": @"example://things/1234567890"
}
],
},
@"id": kAppLinkURL2String
}
}
}
statusCode:200
callback:^(NSURLRequest *request) {
NSUInteger callCount = callCounts[request.URL.absoluteString].unsignedIntegerValue;
++callCount;
callCounts[request.URL.absoluteString] = @(callCount);
}];
FBSDKAppLinkResolver *resolver = [FBSDKAppLinkResolver resolver];
// Prime the cache with kAppLinkURL
[resolver
appLinkFromURL:[NSURL URLWithString:kAppLinkURLString]
handler:^(FBSDKAppLink * _Nullable appLink, NSError * _Nullable error1) {
XCTAssertEqual(callCounts.count, 1);
// Note: callCount is not necessarily 1, as the callback may be called multiple times during processing of the request.
NSString *firstCallKey = callCounts.allKeys[0];
NSUInteger expectedCallCount = callCounts[firstCallKey].unsignedIntegerValue;
// Now request them both; we expect the call count for kAppLinkURL to be unchanged.
[resolver
appLinksFromURLs:@[[NSURL URLWithString:kAppLinkURLString], [NSURL URLWithString:kAppLinkURL2String]]
handler:^(NSDictionary<NSURL *,FBSDKAppLink *> * _Nonnull links, NSError * _Nullable error2) {
XCTAssertEqual(callCounts.count, 2);
XCTAssertEqual([callCounts[firstCallKey] unsignedIntegerValue], expectedCallCount);
XCTAssertEqual(links.count, 2);
[expectation fulfill];
}];
}];
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
XCTAssertNil(error);
}];
[FBSDKSettings setClientToken:nil];
}
*/
@end

View File

@@ -67,6 +67,9 @@ Also, to get a better understanding of mocking, please read the documentation at
/// Used for sharing a `FBSDKAppEventsUtility` class mock between tests
@property (nullable, nonatomic, assign) id appEventsUtilityClassMock;
/// Used for sharing an `FBSDKAppLinkResolverRequestBuilder` class mock between tests
@property (nullable, assign) id appLinkResolverRequestBuilderMock;
/// Used for sharing an `FBSDKApplicationDelegate` class mock between tests
@property (nullable, assign) id fbApplicationDelegateClassMock;
@@ -76,6 +79,9 @@ Also, to get a better understanding of mocking, please read the documentation at
/// Used for sharing an `FBSDKGatekeeperManager` class mock between tests
@property (nullable, assign) id gatekeeperManagerClassMock;
/// Used for sharing an `FBSDKGraphRequest` class mock between tests
@property (nullable, assign) id graphRequestMock;
/// Used for sharing an `NSBundle` class mock between tests
@property (nullable, assign) id nsBundleClassMock;
@@ -199,6 +205,12 @@ Also, to get a better understanding of mocking, please read the documentation at
/// Stubs `FBSDKAppEventsUtility.tokenStringToUseFor:` and returns the provided string
- (void)stubAppEventsUtilityTokenStringToUseForTokenWith:(NSString *)tokenString;
/// Stubs `FBSDKGraphRequest.startWithCompletionHandler:` and returns the provided result, error and connection
- (void)stubGraphRequestWithResult:(id)result error:(nullable NSError *)error connection:(nullable FBSDKGraphRequestConnection *)connection;
/// Stubs `FBSDKGraphRequest.startWithCompletionHandler:` and returns the provided result, error and connection
- (void)stubAppLinkResolverRequestBuilderWithIdiomSpecificField:(nullable NSString *)field;
@end
NS_ASSUME_NONNULL_END

View File

@@ -73,6 +73,8 @@ typedef void (^FBSDKSKAdNetworkReporterBlock)(void);
[self setUpTimeSpendDataMock];
[self setUpInternalUtilityMock];
[self setUpAdNetworkReporterMock];
[self setUpAppLinkResolverRequestBuilderMock];
[self setUpGraphRequestMock];
}
- (void)tearDown
@@ -132,6 +134,12 @@ typedef void (^FBSDKSKAdNetworkReporterBlock)(void);
[_adNetworkReporterClassMock stopMocking];
_adNetworkReporterClassMock = nil;
[_appLinkResolverRequestBuilderMock stopMocking];
_appLinkResolverRequestBuilderMock = nil;
[_graphRequestMock stopMocking];
_graphRequestMock = nil;
}
- (void)setUpSettingsMock
@@ -225,6 +233,16 @@ typedef void (^FBSDKSKAdNetworkReporterBlock)(void);
self.adNetworkReporterClassMock = OCMClassMock(FBSDKSKAdNetworkReporter.class);
}
- (void)setUpAppLinkResolverRequestBuilderMock
{
_appLinkResolverRequestBuilderMock = OCMStrictClassMock(FBSDKAppLinkResolverRequestBuilder.class);
}
- (void)setUpGraphRequestMock
{
_graphRequestMock = OCMStrictClassMock(FBSDKGraphRequest.class);
}
#pragma mark - Public Methods
- (void)stubAppID:(NSString *)appID
@@ -383,4 +401,23 @@ typedef void (^FBSDKSKAdNetworkReporterBlock)(void);
OCMStub(ClassMethod([_serverConfigurationManagerClassMock loadServerConfigurationWithCompletionBlock:OCMArg.isNil]));
}
- (void)stubGraphRequestWithResult:(id)result error:(nullable NSError *)error connection:(nullable FBSDKGraphRequestConnection *)connection
{
OCMStub([_graphRequestMock startWithCompletionHandler:([OCMArg invokeBlockWithArgs:[self nsNullIfNil:connection], [self nsNullIfNil:result], [self nsNullIfNil:error], nil])]);
}
- (void)stubAppLinkResolverRequestBuilderWithIdiomSpecificField:(nullable NSString *)field
{
OCMStub([_appLinkResolverRequestBuilderMock getIdiomSpecificField]).andReturn(field);
}
- (id)nsNullIfNil:(id)nilValue
{
id converted = nilValue;
if (!nilValue) {
converted = [NSNull null];
}
return converted;
}
@end