FIRDynamicLinksTest.m 76 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707
  1. /*
  2. * Copyright 2018 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <XCTest/XCTest.h>
  17. #import <GoogleUtilities/GULSwizzler.h>
  18. #import <GoogleUtilities/GULSwizzler+Unswizzle.h>
  19. #import <OCMock/OCMock.h>
  20. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  21. #import "FirebaseDynamicLinks/Sources/FIRDLDefaultRetrievalProcessV2.h"
  22. #import "FirebaseDynamicLinks/Sources/FIRDLRetrievalProcessFactory.h"
  23. #import "FirebaseDynamicLinks/Sources/FIRDLRetrievalProcessResult+Private.h"
  24. #import "FirebaseDynamicLinks/Sources/FIRDynamicLink+Private.h"
  25. #import "FirebaseDynamicLinks/Sources/FIRDynamicLinkNetworking+Private.h"
  26. #import "FirebaseDynamicLinks/Sources/FIRDynamicLinks+FirstParty.h"
  27. #import "FirebaseDynamicLinks/Sources/FIRDynamicLinks+Private.h"
  28. #import "FirebaseDynamicLinks/Sources/Utilities/FDLUtilities.h"
  29. #import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
  30. static NSString *const kAPIKey = @"myAPIKey";
  31. static NSString *const kStructuredLinkFmtFreeform = @"%@://google/link/%@";
  32. static NSString *const kStructuredLinkFmtDeepLink = @"%@://google/link/?deep_link_id=%@";
  33. static NSString *const kStructuredLinkFmtInvitation = @"%@://google/link/?invitation_id=%@";
  34. static NSString *const kStructuredLinkFmtInvitationWeak =
  35. @"%@://google/link/?invitation_id=%@&match_type=weak";
  36. static NSString *const kStructuredLinkFmtDeepLinkAndInvitation =
  37. @"%@://google/link/?deep_link_id=%@&invitation_id=%@";
  38. static NSString *const kStructuredUniversalLinkFmtFreeForm = @"https://goo.gl/app/sample%@";
  39. static NSString *const kStructuredUniversalLinkFmtDeepLink =
  40. @"https://goo.gl/app/sample?link=%@&isi=585027354";
  41. static NSString *const kStructuredUniversalLinkFmtSubdomain = @"https://sample.page.link%@";
  42. static NSString *const kStructuredUniversalLinkFmtSubdomainDeepLink =
  43. @"https://sample.page.link?link=%@&isi=585027354";
  44. static NSString *const kURLScheme = @"gindeeplinkurl";
  45. static const NSTimeInterval kAsyncTestTimout = 1.0;
  46. /**
  47. * This string was generated by percent-encoding the Tactile URL for the Tokyo American Club in
  48. * Tokyo, and then replacing a '%2B' with a '+' to verify that the '+' does not cause our parsing to
  49. * fail and double-encoding one value to verify that only one decoding pass is run.
  50. */
  51. NSString *kEncodedComplicatedURLString =
  52. @"https%3A%2F%2Fwww.google.com%252Fmaps%2Fplace%2FTokyo+Am"
  53. @"erican%2BClub%2F%4035.658578%2C139.741588%2C3a%2C75y%2C90t%2Fdata%3D%213m8%211e2%213m6%211s42"
  54. @"66698%212e1%213e10%216s%252F%252Fstorage.googleapis.com%252Fstatic.panoramio.com%252Fphotos%2"
  55. @"52Fmedium%252F4266698.jpg%217i640%218i480%214m2%213m1%211s0x0000000000000000%3A0x1b8b8130c791"
  56. @"48e1%216m1%211e1";
  57. /** This string was generated by percent-decoding kEncodedComplicatedURLString. */
  58. NSString *kDecodedComplicatedURLString =
  59. @"https://www.google.com%2Fmaps/place/Tokyo+American+Club/"
  60. @"@35.658578,139.741588,3a,75y,90t/data=!3m8!1e2!3m6!1s4266698!2e1!3e10!6s%2F%2Fstorage.googlea"
  61. @"pis.com%2Fstatic.panoramio.com%2Fphotos%2Fmedium%2F4266698.jpg!7i640!8i480!4m2!3m1!1s0x000000"
  62. @"0000000000:0x1b8b8130c79148e1!6m1!1e1";
  63. static void *kOpenURLHandlerKey = &kOpenURLHandlerKey;
  64. typedef NSURL * (^FakeShortLinkResolverHandler)(NSURL *shortLink);
  65. @interface FIRDynamicLinks (FIRApp)
  66. - (void)configureDynamicLinks:(FIRApp *)app;
  67. - (BOOL)setUpWithLaunchOptions:(nullable NSDictionary *)launchOptions
  68. apiKey:(NSString *)apiKey
  69. urlScheme:(nullable NSString *)urlScheme
  70. userDefaults:(nullable NSUserDefaults *)userDefaults;
  71. - (BOOL)canParseUniversalLinkURL:(nullable NSURL *)url;
  72. - (void)passRetrievedDynamicLinkToApplication:(NSURL *)url;
  73. - (BOOL)isOpenUrlMethodPresentInAppDelegate:(id<UIApplicationDelegate>)applicationDelegate;
  74. @end
  75. @interface FakeShortLinkResolver : FIRDynamicLinkNetworking
  76. + (instancetype)resolverWithBlock:(FakeShortLinkResolverHandler)resolverHandler;
  77. @end
  78. @implementation FakeShortLinkResolver {
  79. FakeShortLinkResolverHandler _resolverHandler;
  80. }
  81. + (instancetype)resolverWithBlock:(FakeShortLinkResolverHandler)resolverHandler {
  82. // The parameters don't matter since they aren't validated or used here.
  83. FakeShortLinkResolver *resolver = [[self alloc] initWithAPIKey:@"" URLScheme:@""];
  84. resolver->_resolverHandler = [resolverHandler copy];
  85. return resolver;
  86. }
  87. - (void)resolveShortLink:(NSURL *)url
  88. FDLSDKVersion:(NSString *)FDLSDKVersion
  89. completion:(FIRDynamicLinkResolverHandler)completion {
  90. if (_resolverHandler && completion) {
  91. NSURL *resolvedLink = _resolverHandler(url);
  92. completion(resolvedLink, nil);
  93. }
  94. }
  95. @end
  96. // dummy protocol to prevent compile warning
  97. @protocol DummyProtocol <NSObject>
  98. @property(atomic, assign) BOOL retrievingPendingDynamicLink;
  99. @property(nonatomic, readonly) FIRDynamicLinkNetworking *dynamicLinkNetworking;
  100. - (void)handlePendingDynamicLinkRetrievalFailureWithErrorCode:(NSInteger)errorCode
  101. errorDescription:(NSString *)errorDescription
  102. underlyingError:(nullable NSError *)underlyingError;
  103. @end
  104. // Swizzle DynamicLinks.dynamicLinkNetworking property to return fake resolver.
  105. static void SwizzleDynamicLinkNetworking(id linkResolver) {
  106. id (^dynamicLinkNetworkingBlock)(void) = ^id(void) {
  107. return linkResolver;
  108. };
  109. [GULSwizzler swizzleClass:[FIRDynamicLinks class]
  110. selector:@selector(dynamicLinkNetworking)
  111. isClassSelector:NO
  112. withBlock:dynamicLinkNetworkingBlock];
  113. }
  114. static void SwizzleDynamicLinkNetworkingWithMock() {
  115. id linkResolver = OCMPartialMock([[FIRDynamicLinkNetworking alloc] initWithAPIKey:kAPIKey
  116. URLScheme:kURLScheme]);
  117. [[linkResolver stub] resolveShortLink:OCMOCK_ANY FDLSDKVersion:@"1.0.0" completion:OCMOCK_ANY];
  118. SwizzleDynamicLinkNetworking(linkResolver);
  119. }
  120. static void UnswizzleDynamicLinkNetworking() {
  121. [GULSwizzler unswizzleClass:[FIRDynamicLinks class]
  122. selector:@selector(dynamicLinkNetworking)
  123. isClassSelector:NO];
  124. }
  125. @interface FIRDynamicLinksTest : XCTestCase {
  126. id _bundleMock;
  127. }
  128. // An instance of |GINDurableDeepLinkService| used for testing.
  129. @property(nonatomic, strong) FIRDynamicLinks *service;
  130. // An instance of |NSUserDefaults| that have all default values removed.
  131. @property(nonatomic, strong) NSUserDefaults *userDefaults;
  132. // FIRAnalytics mock. Necessary because we don't call [FIRAPP configure].
  133. @property(nonatomic, strong) id analytics;
  134. @end
  135. @implementation FIRDynamicLinksTest
  136. // Disable deprecated warning for internal methods.
  137. #pragma clang diagnostic push
  138. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  139. #pragma mark - Test lifecycle
  140. static NSString *const kInfoPlistCustomDomainsKey = @"FirebaseDynamicLinksCustomDomains";
  141. - (void)setUp {
  142. [super setUp];
  143. // Mock the mainBundle infoDictionary with version from DL-Info.plist for custom domain testing.
  144. NSBundle *bundle = [NSBundle bundleForClass:[self class]];
  145. NSString *filePath = [bundle pathForResource:@"DL-Info" ofType:@"plist"];
  146. _bundleMock = OCMPartialMock([NSBundle mainBundle]);
  147. OCMStub([_bundleMock infoDictionary])
  148. .andReturn([NSDictionary dictionaryWithContentsOfFile:filePath]);
  149. if (![FIRApp isDefaultAppConfigured]) {
  150. XCTAssertNoThrow([FIRApp configureWithOptions:[self appOptions]]);
  151. }
  152. self.service = [[FIRDynamicLinks alloc] init];
  153. self.userDefaults = [[NSUserDefaults alloc] init];
  154. [self.userDefaults removePersistentDomainForName:[[NSBundle mainBundle] bundleIdentifier]];
  155. self.analytics = OCMProtocolMock(@protocol(FIRAnalyticsInterop));
  156. }
  157. - (void)tearDown {
  158. self.service = nil;
  159. self.userDefaults = nil;
  160. [self.analytics stopMocking];
  161. self.analytics = nil;
  162. [_bundleMock stopMocking];
  163. _bundleMock = nil;
  164. [super tearDown];
  165. }
  166. - (FIROptions *)appOptions {
  167. // TODO: Evaluate if we want to hardcode things here instead.
  168. FIROptions *options = [[FIROptions alloc] initWithGoogleAppID:@"1:123:ios:123abc"
  169. GCMSenderID:@"correct_gcm_sender_id"];
  170. options.APIKey = @"correct_api_key";
  171. options.projectID = @"abc-xyz-123";
  172. return options;
  173. }
  174. #pragma mark - Set Up.
  175. - (void)testURLScheme_NoApiKey {
  176. #pragma clang diagnostic push
  177. #pragma clang diagnostic ignored "-Wnonnull"
  178. BOOL setUpSucceed = [self.service setUpWithLaunchOptions:nil
  179. apiKey:nil
  180. urlScheme:nil
  181. userDefaults:nil];
  182. #pragma clang diagnostic pop
  183. XCTAssertFalse(setUpSucceed, @"Should fail when apiKey is nil.");
  184. }
  185. - (void)testURLScheme_MinimumParameters {
  186. BOOL setUpSucceed = [self.service setUpWithLaunchOptions:nil
  187. apiKey:kAPIKey
  188. urlScheme:nil
  189. userDefaults:nil];
  190. XCTAssertTrue(setUpSucceed, @"Should not fail when apiKey is set.");
  191. }
  192. - (void)testFactoryMethodReturnsProperClassObject {
  193. id service = [FIRDynamicLinks dynamicLinks];
  194. XCTAssertNotNil(service, @"Factory method returned nil");
  195. XCTAssertEqualObjects([service class], [FIRDynamicLinks class],
  196. @"Factory returned incorrect class object");
  197. }
  198. - (void)testURLScheme_LaunchOptionsWithCustomSchemeURL {
  199. NSString *deepLinkString =
  200. [NSString stringWithFormat:kStructuredLinkFmtDeepLink,
  201. [[NSBundle mainBundle] bundleIdentifier], @"abc123"];
  202. NSDictionary *launchOptions =
  203. @{UIApplicationLaunchOptionsURLKey : [NSURL URLWithString:deepLinkString]};
  204. [self.userDefaults setBool:NO forKey:kFIRDLReadDeepLinkAfterInstallKey];
  205. [self.service setUpWithLaunchOptions:launchOptions
  206. apiKey:kAPIKey
  207. urlScheme:nil
  208. userDefaults:self.userDefaults];
  209. XCTAssertTrue([self.userDefaults boolForKey:kFIRDLReadDeepLinkAfterInstallKey]);
  210. }
  211. - (void)testURLScheme_LaunchOptionsWithUniversalLinkURL {
  212. NSString *deepLinkString =
  213. [NSString stringWithFormat:kStructuredUniversalLinkFmtDeepLink, @"abc123"];
  214. NSDictionary *launchOptions =
  215. @{UIApplicationLaunchOptionsURLKey : [NSURL URLWithString:deepLinkString]};
  216. [self.userDefaults setBool:NO forKey:kFIRDLReadDeepLinkAfterInstallKey];
  217. [self.service setUpWithLaunchOptions:launchOptions
  218. apiKey:kAPIKey
  219. urlScheme:nil
  220. userDefaults:self.userDefaults];
  221. XCTAssertTrue([self.userDefaults boolForKey:kFIRDLReadDeepLinkAfterInstallKey]);
  222. }
  223. - (void)testURLScheme_LaunchOptionsWithInvalidURLWillNotResetUserDefaultsFlag {
  224. NSDictionary *launchOptions =
  225. @{UIApplicationLaunchOptionsURLKey : [NSURL URLWithString:@"https://www.google.com"]};
  226. [self.userDefaults setBool:YES forKey:kFIRDLReadDeepLinkAfterInstallKey];
  227. [self.service setUpWithLaunchOptions:launchOptions
  228. apiKey:kAPIKey
  229. urlScheme:nil
  230. userDefaults:self.userDefaults];
  231. XCTAssertTrue([self.userDefaults boolForKey:kFIRDLReadDeepLinkAfterInstallKey]);
  232. }
  233. - (void)testURLScheme_Nil {
  234. BOOL setUpSucceed = [self.service setUpWithLaunchOptions:nil
  235. apiKey:kAPIKey
  236. urlScheme:nil
  237. userDefaults:nil];
  238. XCTAssertTrue(setUpSucceed);
  239. XCTAssertEqualObjects(self.service.URLScheme, [NSBundle mainBundle].bundleIdentifier);
  240. }
  241. - (void)testURLScheme_EmptyString {
  242. BOOL setUpSucceed = [self.service setUpWithLaunchOptions:nil
  243. apiKey:kAPIKey
  244. urlScheme:@""
  245. userDefaults:nil];
  246. XCTAssertTrue(setUpSucceed);
  247. XCTAssertEqualObjects(self.service.URLScheme, [NSBundle mainBundle].bundleIdentifier);
  248. }
  249. - (void)testURLScheme_NonNil {
  250. [self.service setUpWithLaunchOptions:nil
  251. apiKey:kAPIKey
  252. urlScheme:kURLScheme
  253. userDefaults:self.userDefaults];
  254. XCTAssertEqualObjects(self.service.URLScheme, kURLScheme,
  255. @"URLScheme should be the same as in argument");
  256. }
  257. // TODO (b/63079414) Re-enable this
  258. - (void)DISABLED_testConfigNamedFIRApp {
  259. [self removeAllFIRApps];
  260. id deepLinkServicePartialMock = OCMPartialMock([FIRDynamicLinks dynamicLinks]);
  261. [[deepLinkServicePartialMock reject] configureDynamicLinks:[OCMArg any]];
  262. [FIRApp configureWithName:@"NonDefaultName" options:[FIROptions defaultOptions]];
  263. [deepLinkServicePartialMock stopMocking];
  264. }
  265. // TODO (b/37855379) re-enable the test
  266. - (void)DISABLED_testConfigForFIRApp {
  267. [self removeAllFIRApps];
  268. id deepLinkServicePartialMock = OCMPartialMock([FIRDynamicLinks dynamicLinks]);
  269. [FIRApp configure];
  270. OCMVerify([deepLinkServicePartialMock configureDynamicLinks:[OCMArg any]]);
  271. [deepLinkServicePartialMock stopMocking];
  272. }
  273. #pragma mark - dynamicLinkFromCustomSchemeURL
  274. - (void)testCustomScheme_NoDeepLink {
  275. NSString *urlString =
  276. [NSString stringWithFormat:kStructuredLinkFmtFreeform, kURLScheme, @"abc123xyz"];
  277. NSURL *url = [NSURL URLWithString:urlString];
  278. [self.service setUpWithLaunchOptions:nil
  279. apiKey:kAPIKey
  280. urlScheme:kURLScheme
  281. userDefaults:self.userDefaults];
  282. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  283. XCTAssertNil(dynamicLink, @"invite should be nil since there is no parameter.");
  284. }
  285. - (void)testCustomScheme_DeepLinkOnly {
  286. NSString *deepLinkString = @"https://developers.google.com/products/";
  287. NSString *urlString =
  288. [NSString stringWithFormat:kStructuredLinkFmtDeepLink, kURLScheme, deepLinkString];
  289. NSURL *url = [NSURL URLWithString:urlString];
  290. [self.service setUpWithLaunchOptions:nil
  291. apiKey:kAPIKey
  292. urlScheme:kURLScheme
  293. userDefaults:self.userDefaults];
  294. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  295. XCTAssertEqualObjects(dynamicLink.url.absoluteString, deepLinkString);
  296. XCTAssertNil(dynamicLink.inviteId);
  297. }
  298. - (void)testCustomScheme_InvitationOnly {
  299. NSString *invitationId = @"213920940217491274389172947";
  300. NSString *urlString =
  301. [NSString stringWithFormat:kStructuredLinkFmtInvitation, kURLScheme, invitationId];
  302. NSURL *url = [NSURL URLWithString:urlString];
  303. [self.service setUpWithLaunchOptions:nil
  304. apiKey:kAPIKey
  305. urlScheme:kURLScheme
  306. userDefaults:self.userDefaults];
  307. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  308. XCTAssertNil(dynamicLink.url);
  309. XCTAssertEqualObjects(dynamicLink.inviteId, invitationId);
  310. }
  311. - (void)testCustomScheme_DeepLinkAndInvitation {
  312. NSString *deepLinkString = @"https://developers.google.com/products/";
  313. NSString *invitationId = @"21392094021749127-4389172947";
  314. NSString *urlString = [NSString stringWithFormat:kStructuredLinkFmtDeepLinkAndInvitation,
  315. [[NSBundle mainBundle] bundleIdentifier],
  316. deepLinkString, invitationId];
  317. NSURL *url = [NSURL URLWithString:urlString];
  318. [self.service setUpWithLaunchOptions:nil
  319. apiKey:kAPIKey
  320. urlScheme:kURLScheme
  321. userDefaults:self.userDefaults];
  322. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  323. XCTAssertEqualObjects(dynamicLink.url.absoluteString, deepLinkString);
  324. XCTAssertEqualObjects(dynamicLink.inviteId, invitationId);
  325. }
  326. - (void)testCustomScheme_FirstTimeOpenedWithCustomSchemeShouldGetStrongMatch {
  327. NSString *invitationId = @"21392094021749127-4389172947";
  328. NSString *urlString =
  329. [NSString stringWithFormat:kStructuredLinkFmtInvitation, kURLScheme, invitationId];
  330. NSURL *url = [NSURL URLWithString:urlString];
  331. [self.service setUpWithLaunchOptions:nil
  332. apiKey:kAPIKey
  333. urlScheme:kURLScheme
  334. userDefaults:self.userDefaults];
  335. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  336. XCTAssertEqual(dynamicLink.matchConfidence, FIRDynamicLinkMatchConfidenceStrong,
  337. @"matchConfidence should be strong when app is first opened with custom scheme.");
  338. XCTAssertNil(dynamicLink.url);
  339. XCTAssertEqualObjects(dynamicLink.inviteId, invitationId);
  340. }
  341. - (void)testCustomScheme_FirstTimeOpenedFromFingerprintCodepathShouldGetWeakMatch {
  342. NSString *invitationId = @"21392094021749127-4389172947";
  343. NSString *urlString =
  344. [NSString stringWithFormat:kStructuredLinkFmtInvitationWeak, kURLScheme, invitationId];
  345. NSURL *url = [NSURL URLWithString:urlString];
  346. [self.service setUpWithLaunchOptions:nil
  347. apiKey:kAPIKey
  348. urlScheme:kURLScheme
  349. userDefaults:self.userDefaults];
  350. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  351. XCTAssertEqual(
  352. dynamicLink.matchConfidence, FIRDynamicLinkMatchConfidenceWeak,
  353. @"matchConfidence should be weak when app is first opened from fingerprint codepath.");
  354. XCTAssertNil(dynamicLink.url);
  355. XCTAssertEqualObjects(dynamicLink.inviteId, invitationId);
  356. }
  357. - (void)testCustomScheme_StrongMatch {
  358. NSString *invitationId = @"21392094021749127-4389172947";
  359. NSString *urlString =
  360. [NSString stringWithFormat:kStructuredLinkFmtInvitation, kURLScheme, invitationId];
  361. NSURL *url = [NSURL URLWithString:urlString];
  362. // Simulate opening the app.
  363. [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kFIRDLReadDeepLinkAfterInstallKey];
  364. [self.service setUpWithLaunchOptions:nil
  365. apiKey:kAPIKey
  366. urlScheme:kURLScheme
  367. userDefaults:self.userDefaults];
  368. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  369. XCTAssertEqual(dynamicLink.matchConfidence, FIRDynamicLinkMatchConfidenceStrong,
  370. @"matchConfidence should be strong when opening an url after app is installed.");
  371. XCTAssertNil(dynamicLink.url);
  372. XCTAssertEqualObjects(dynamicLink.inviteId, invitationId);
  373. }
  374. - (void)testLinkParamWithPlus {
  375. [self.service setUpWithLaunchOptions:nil
  376. apiKey:kAPIKey
  377. urlScheme:kURLScheme
  378. userDefaults:self.userDefaults];
  379. NSString *durableDeepLinkString =
  380. [NSString stringWithFormat:@"gindeeplinkurl://google/link?deep_link_id=%@",
  381. kEncodedComplicatedURLString];
  382. NSURL *durabledeepLinkURL = [NSURL URLWithString:durableDeepLinkString];
  383. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:durabledeepLinkURL];
  384. NSString *deepLinkURLString = dynamicLink.url.absoluteString;
  385. XCTAssertEqualObjects(kDecodedComplicatedURLString, deepLinkURLString,
  386. @"ddl url parameter and deep link url should be the same");
  387. }
  388. #pragma mark - dynamicLinkFromUniversalLinkURL
  389. - (void)testUniversalLink_NoDeepLink {
  390. NSString *webPageURLString =
  391. [NSString stringWithFormat:kStructuredUniversalLinkFmtFreeForm, @"/abc123"];
  392. NSURL *url = [NSURL URLWithString:webPageURLString];
  393. NSUserActivity *activity =
  394. [[NSUserActivity alloc] initWithActivityType:NSUserActivityTypeBrowsingWeb];
  395. activity.webpageURL = [NSURL URLWithString:webPageURLString];
  396. [self.service setUpWithLaunchOptions:nil
  397. apiKey:kAPIKey
  398. urlScheme:kURLScheme
  399. userDefaults:self.userDefaults];
  400. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  401. XCTAssertNil(dynamicLink, @"invite should be nil since there is no parameter.");
  402. }
  403. // Custom domain entries in plist file:
  404. // https://google.com
  405. // https://google.com/one
  406. // https://a.firebase.com/mypath
  407. - (void)testDynamicLinkFromUniversalLinkURLWithCustomDomainLink {
  408. self.service = [[FIRDynamicLinks alloc] init];
  409. NSString *durableDeepLinkString = @"https://a.firebase.com/mypath/?link=http://abcd";
  410. NSURL *durabledeepLinkURL = [NSURL URLWithString:durableDeepLinkString];
  411. SwizzleDynamicLinkNetworkingWithMock();
  412. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:durabledeepLinkURL];
  413. XCTAssertNotNil(dynamicLink);
  414. NSString *deepLinkURLString = dynamicLink.url.absoluteString;
  415. XCTAssertEqualObjects(@"http://abcd", deepLinkURLString,
  416. @"ddl url parameter and deep link url should be the same");
  417. UnswizzleDynamicLinkNetworking();
  418. }
  419. - (void)testDynamicLinkFromUniversalLinkURLCompletionWithCustomDomainLink {
  420. self.service = [[FIRDynamicLinks alloc] init];
  421. NSString *durableDeepLinkString = @"https://a.firebase.com/mypath/?link=http://abcd";
  422. NSURL *durabledeepLinkURL = [NSURL URLWithString:durableDeepLinkString];
  423. SwizzleDynamicLinkNetworkingWithMock();
  424. XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  425. [self.service
  426. dynamicLinkFromUniversalLinkURL:durabledeepLinkURL
  427. completion:^(FIRDynamicLink *_Nullable dynamicLink,
  428. NSError *_Nullable error) {
  429. XCTAssertTrue([NSThread isMainThread]);
  430. XCTAssertNotNil(dynamicLink);
  431. NSString *deepLinkURLString = dynamicLink.url.absoluteString;
  432. XCTAssertEqualObjects(
  433. @"http://abcd", deepLinkURLString,
  434. @"ddl url parameter and deep link url should be the same");
  435. [expectation fulfill];
  436. }];
  437. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  438. UnswizzleDynamicLinkNetworking();
  439. }
  440. - (void)testDynamicLinkFromUniversalLinkURLWithSpecialCharacters {
  441. NSString *durableDeepLinkString =
  442. [NSString stringWithFormat:@"https://xyz.page.link/?link=%@", kEncodedComplicatedURLString];
  443. NSURL *durabledeepLinkURL = [NSURL URLWithString:durableDeepLinkString];
  444. SwizzleDynamicLinkNetworkingWithMock();
  445. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:durabledeepLinkURL];
  446. NSString *deepLinkURLString = dynamicLink.url.absoluteString;
  447. XCTAssertEqualObjects(kDecodedComplicatedURLString, deepLinkURLString,
  448. @"ddl url parameter and deep link url should be the same");
  449. UnswizzleDynamicLinkNetworking();
  450. }
  451. - (void)testDynamicLinkFromUniversalLinkURLCompletionWithSpecialCharacters {
  452. NSString *durableDeepLinkString =
  453. [NSString stringWithFormat:@"https://xyz.page.link/?link=%@", kEncodedComplicatedURLString];
  454. NSURL *durabledeepLinkURL = [NSURL URLWithString:durableDeepLinkString];
  455. SwizzleDynamicLinkNetworkingWithMock();
  456. XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  457. [self.service
  458. dynamicLinkFromUniversalLinkURL:durabledeepLinkURL
  459. completion:^(FIRDynamicLink *_Nullable dynamicLink,
  460. NSError *_Nullable error) {
  461. XCTAssertTrue([NSThread isMainThread]);
  462. NSString *deepLinkURLString = dynamicLink.url.absoluteString;
  463. XCTAssertEqualObjects(
  464. kDecodedComplicatedURLString, deepLinkURLString,
  465. @"ddl url parameter and deep link url should be the same");
  466. [expectation fulfill];
  467. }];
  468. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  469. UnswizzleDynamicLinkNetworking();
  470. }
  471. - (void)testDynamicLinkFromUniversalLinkURLWithEncodedCharacters {
  472. NSString *durableDeepLinkString =
  473. [NSString stringWithFormat:@"https://xyz.page.link/?link=%@", kEncodedComplicatedURLString];
  474. NSURL *durabledeepLinkURL = [NSURL URLWithString:durableDeepLinkString];
  475. SwizzleDynamicLinkNetworkingWithMock();
  476. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:durabledeepLinkURL];
  477. NSString *deepLinkURLString = dynamicLink.url.absoluteString;
  478. XCTAssertEqualObjects(kDecodedComplicatedURLString, deepLinkURLString,
  479. @"ddl url parameter and deep link url should be the same");
  480. UnswizzleDynamicLinkNetworking();
  481. }
  482. - (void)testDynamicLinkFromUniversalLinkURLCompletionWithEncodedCharacters {
  483. NSString *durableDeepLinkString =
  484. [NSString stringWithFormat:@"https://xyz.page.link/?link=%@", kEncodedComplicatedURLString];
  485. NSURL *durabledeepLinkURL = [NSURL URLWithString:durableDeepLinkString];
  486. SwizzleDynamicLinkNetworkingWithMock();
  487. XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  488. [self.service
  489. dynamicLinkFromUniversalLinkURL:durabledeepLinkURL
  490. completion:^(FIRDynamicLink *_Nullable dynamicLink,
  491. NSError *_Nullable error) {
  492. XCTAssertTrue([NSThread isMainThread]);
  493. NSString *deepLinkURLString = dynamicLink.url.absoluteString;
  494. XCTAssertEqualObjects(
  495. kDecodedComplicatedURLString, deepLinkURLString,
  496. @"ddl url parameter and deep link url should be the same");
  497. [expectation fulfill];
  498. }];
  499. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  500. UnswizzleDynamicLinkNetworking();
  501. }
  502. - (void)testUniversalLink_DeepLink {
  503. NSString *deepLinkString = @"https://www.google.com/maps/place/Minneapolis";
  504. NSString *webPageURLString =
  505. [NSString stringWithFormat:kStructuredUniversalLinkFmtDeepLink, deepLinkString];
  506. NSURL *url = [NSURL URLWithString:webPageURLString];
  507. [self.service setUpWithLaunchOptions:nil
  508. apiKey:kAPIKey
  509. urlScheme:kURLScheme
  510. userDefaults:self.userDefaults];
  511. SwizzleDynamicLinkNetworkingWithMock();
  512. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:url];
  513. XCTAssertEqual(dynamicLink.matchConfidence, FIRDynamicLinkMatchConfidenceStrong);
  514. XCTAssertEqualObjects(dynamicLink.url.absoluteString, deepLinkString);
  515. UnswizzleDynamicLinkNetworking();
  516. }
  517. - (void)testUniversalLinkWithCompletion_DeepLink {
  518. NSString *deepLinkString = @"https://www.google.com/maps/place/Minneapolis";
  519. NSString *webPageURLString =
  520. [NSString stringWithFormat:kStructuredUniversalLinkFmtDeepLink, deepLinkString];
  521. NSURL *url = [NSURL URLWithString:webPageURLString];
  522. [self.service setUpWithLaunchOptions:nil
  523. apiKey:kAPIKey
  524. urlScheme:kURLScheme
  525. userDefaults:self.userDefaults];
  526. SwizzleDynamicLinkNetworkingWithMock();
  527. XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  528. [self.service
  529. dynamicLinkFromUniversalLinkURL:url
  530. completion:^(FIRDynamicLink *_Nullable dynamicLink,
  531. NSError *_Nullable error) {
  532. XCTAssertTrue([NSThread isMainThread]);
  533. XCTAssertEqual(dynamicLink.matchConfidence,
  534. FIRDynamicLinkMatchConfidenceStrong);
  535. XCTAssertEqualObjects(dynamicLink.url.absoluteString, deepLinkString);
  536. [expectation fulfill];
  537. }];
  538. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  539. UnswizzleDynamicLinkNetworking();
  540. }
  541. - (void)testUniversalLink_DeepLinkWithParameters {
  542. NSString *deepLinkString = @"https://www.google.com?key1%3Dvalue1%26key2%3Dvalue2";
  543. NSString *parsedDeepLinkString = @"https://www.google.com?key1=value1&key2=value2";
  544. NSString *webPageURLString =
  545. [NSString stringWithFormat:kStructuredUniversalLinkFmtDeepLink, deepLinkString];
  546. NSURL *url = [NSURL URLWithString:webPageURLString];
  547. [self.service setUpWithLaunchOptions:nil
  548. apiKey:kAPIKey
  549. urlScheme:kURLScheme
  550. userDefaults:self.userDefaults];
  551. SwizzleDynamicLinkNetworkingWithMock();
  552. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:url];
  553. XCTAssertEqual(dynamicLink.matchConfidence, FIRDynamicLinkMatchConfidenceStrong);
  554. XCTAssertEqualObjects(dynamicLink.url.absoluteString, parsedDeepLinkString);
  555. UnswizzleDynamicLinkNetworking();
  556. }
  557. - (void)testUniversalLinkWithCompletion_DeepLinkWithParameters {
  558. NSString *deepLinkString = @"https://www.google.com?key1%3Dvalue1%26key2%3Dvalue2";
  559. NSString *parsedDeepLinkString = @"https://www.google.com?key1=value1&key2=value2";
  560. NSString *webPageURLString =
  561. [NSString stringWithFormat:kStructuredUniversalLinkFmtDeepLink, deepLinkString];
  562. NSURL *url = [NSURL URLWithString:webPageURLString];
  563. [self.service setUpWithLaunchOptions:nil
  564. apiKey:kAPIKey
  565. urlScheme:kURLScheme
  566. userDefaults:self.userDefaults];
  567. SwizzleDynamicLinkNetworkingWithMock();
  568. XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  569. [self.service dynamicLinkFromUniversalLinkURL:url
  570. completion:^(FIRDynamicLink *_Nullable dynamicLink,
  571. NSError *_Nullable error) {
  572. XCTAssertTrue([NSThread isMainThread]);
  573. XCTAssertEqual(dynamicLink.matchConfidence,
  574. FIRDynamicLinkMatchConfidenceStrong);
  575. XCTAssertEqualObjects(dynamicLink.url.absoluteString,
  576. parsedDeepLinkString);
  577. [expectation fulfill];
  578. }];
  579. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  580. UnswizzleDynamicLinkNetworking();
  581. }
  582. - (void)testResolveLinkReturnsDLWithNilMinAppVersionWhenNotPresent {
  583. [self.service setUpWithLaunchOptions:nil
  584. apiKey:kAPIKey
  585. urlScheme:kURLScheme
  586. userDefaults:self.userDefaults];
  587. NSString *urlString = @"http://reinl.page.link/t4ionvr";
  588. NSURL *url = [NSURL URLWithString:urlString];
  589. void (^executeRequestBlock)(id, NSDictionary *, NSString *, FIRNetworkRequestCompletionHandler) =
  590. ^(id p1, NSDictionary *requestBody, NSString *requestURLString,
  591. FIRNetworkRequestCompletionHandler handler) {
  592. NSDictionary *dictionary = @{kFDLResolvedLinkDeepLinkURLKey : kEncodedComplicatedURLString};
  593. NSData *data = FIRDataWithDictionary(dictionary, nil);
  594. handler(data, nil, nil);
  595. };
  596. SEL executeRequestSelector = @selector(executeOnePlatformRequest:forURL:completionHandler:);
  597. [GULSwizzler swizzleClass:[FIRDynamicLinkNetworking class]
  598. selector:executeRequestSelector
  599. isClassSelector:NO
  600. withBlock:executeRequestBlock];
  601. XCTestExpectation *expectation = [self expectationWithDescription:@"handler called"];
  602. [self.service
  603. handleUniversalLink:url
  604. completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
  605. XCTAssertNil(dynamicLink.minimumAppVersion,
  606. @"Min app version not nil when not present.");
  607. [expectation fulfill];
  608. }];
  609. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  610. }
  611. - (void)testResolveLinkReturnsDLWithMinAppVersionWhenPresent {
  612. NSString *expectedMinVersion = @"8g5u3e";
  613. [self.service setUpWithLaunchOptions:nil
  614. apiKey:kAPIKey
  615. urlScheme:kURLScheme
  616. userDefaults:self.userDefaults];
  617. NSString *urlString = @"http://reinl.page.link/t4ionvr";
  618. NSURL *url = [NSURL URLWithString:urlString];
  619. void (^executeRequestBlock)(id, NSDictionary *, NSString *, FIRNetworkRequestCompletionHandler) =
  620. ^(id p1, NSDictionary *requestBody, NSString *requestURLString,
  621. FIRNetworkRequestCompletionHandler handler) {
  622. NSDictionary *dictionary = @{
  623. kFDLResolvedLinkDeepLinkURLKey : kEncodedComplicatedURLString,
  624. kFDLResolvedLinkMinAppVersionKey : expectedMinVersion,
  625. };
  626. NSData *data = FIRDataWithDictionary(dictionary, nil);
  627. NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url
  628. statusCode:200
  629. HTTPVersion:nil
  630. headerFields:nil];
  631. handler(data, response, nil);
  632. };
  633. SEL executeRequestSelector = @selector(executeOnePlatformRequest:forURL:completionHandler:);
  634. [GULSwizzler swizzleClass:[FIRDynamicLinkNetworking class]
  635. selector:executeRequestSelector
  636. isClassSelector:NO
  637. withBlock:executeRequestBlock];
  638. XCTestExpectation *expectation = [self expectationWithDescription:@"handler called"];
  639. [self.service
  640. handleUniversalLink:url
  641. completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
  642. XCTAssertEqualObjects(expectedMinVersion, dynamicLink.minimumAppVersion,
  643. @"min app version did not match imv parameter.");
  644. [expectation fulfill];
  645. }];
  646. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  647. }
  648. - (void)testUniversalLinkWithSubdomain_NoDeepLink {
  649. NSString *webPageURLString =
  650. [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomain, @"/abc123"];
  651. NSURL *url = [NSURL URLWithString:webPageURLString];
  652. [self.service setUpWithLaunchOptions:nil
  653. apiKey:kAPIKey
  654. urlScheme:kURLScheme
  655. userDefaults:self.userDefaults];
  656. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  657. XCTAssertNil(dynamicLink, @"deepLink should be nil since there is no parameter.");
  658. }
  659. - (void)testDynamicLinkFromCustomSchemeURLReturnsDLWithNilMinimumVersion {
  660. [self.service setUpWithLaunchOptions:nil
  661. apiKey:kAPIKey
  662. urlScheme:kURLScheme
  663. userDefaults:self.userDefaults];
  664. NSURL *url = FIRDLDeepLinkURLWithInviteID(nil, kEncodedComplicatedURLString, nil, nil, nil, NO,
  665. nil, nil, kURLScheme, nil);
  666. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  667. XCTAssertNil(dynamicLink.minimumAppVersion, @"Min app version was not nil when not set.");
  668. }
  669. - (void)testDynamicLinkFromCustomSchemeURLReturnsDLMinimumVersion {
  670. NSString *expectedMinVersion = @"03-9g03hfd";
  671. [self.service setUpWithLaunchOptions:nil
  672. apiKey:kAPIKey
  673. urlScheme:kURLScheme
  674. userDefaults:self.userDefaults];
  675. NSURL *url = FIRDLDeepLinkURLWithInviteID(nil, kEncodedComplicatedURLString, nil, nil, nil, NO,
  676. nil, expectedMinVersion, kURLScheme, nil);
  677. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromCustomSchemeURL:url];
  678. NSString *minVersion = dynamicLink.minimumAppVersion;
  679. XCTAssertEqualObjects(expectedMinVersion, minVersion,
  680. @"Min version didn't match the min app version parameter");
  681. }
  682. - (void)testDynamicLinkFromUniversalLinkURLReturnsDLWithNilMinimumVersion {
  683. [self.service setUpWithLaunchOptions:nil
  684. apiKey:kAPIKey
  685. urlScheme:kURLScheme
  686. userDefaults:self.userDefaults];
  687. NSURL *url = FIRDLDeepLinkURLWithInviteID(nil, kEncodedComplicatedURLString, nil, nil, nil, NO,
  688. nil, nil, kURLScheme, nil);
  689. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:url];
  690. NSString *minVersion = dynamicLink.minimumAppVersion;
  691. XCTAssertNil(minVersion, @"Min app version was not nil when not set.");
  692. }
  693. - (void)testDynamicLinkFromUniversalLinkURLCompletionReturnsDLWithNilMinimumVersion {
  694. [self.service setUpWithLaunchOptions:nil
  695. apiKey:kAPIKey
  696. urlScheme:kURLScheme
  697. userDefaults:self.userDefaults];
  698. NSURL *url = FIRDLDeepLinkURLWithInviteID(nil, kEncodedComplicatedURLString, nil, nil, nil, NO,
  699. nil, nil, kURLScheme, nil);
  700. XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  701. [self.service
  702. dynamicLinkFromUniversalLinkURL:url
  703. completion:^(FIRDynamicLink *_Nullable dynamicLink,
  704. NSError *_Nullable error) {
  705. XCTAssertTrue([NSThread isMainThread]);
  706. NSString *minVersion = dynamicLink.minimumAppVersion;
  707. XCTAssertNil(minVersion, @"Min app version was not nil when not set.");
  708. [expectation fulfill];
  709. }];
  710. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  711. }
  712. - (void)testDynamicLinkFromUniversalLinkURLReturnsDLMinimumVersion {
  713. NSString *expectedMinVersion = @"03-9g03hfd";
  714. NSString *urlSuffix =
  715. [NSString stringWithFormat:@"%@&imv=%@", kEncodedComplicatedURLString, expectedMinVersion];
  716. NSString *urlString =
  717. [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, urlSuffix];
  718. NSURL *url = [NSURL URLWithString:urlString];
  719. [self.service setUpWithLaunchOptions:nil
  720. apiKey:kAPIKey
  721. urlScheme:kURLScheme
  722. userDefaults:self.userDefaults];
  723. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:url];
  724. NSString *minVersion = dynamicLink.minimumAppVersion;
  725. XCTAssertEqualObjects(expectedMinVersion, minVersion, @"Min version didn't match imv= parameter");
  726. }
  727. - (void)testDynamicLinkFromUniversalLinkURLCompletionReturnsDLMinimumVersion {
  728. NSString *expectedMinVersion = @"03-9g03hfd";
  729. NSString *urlSuffix =
  730. [NSString stringWithFormat:@"%@&imv=%@", kEncodedComplicatedURLString, expectedMinVersion];
  731. NSString *urlString =
  732. [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, urlSuffix];
  733. NSURL *url = [NSURL URLWithString:urlString];
  734. [self.service setUpWithLaunchOptions:nil
  735. apiKey:kAPIKey
  736. urlScheme:kURLScheme
  737. userDefaults:self.userDefaults];
  738. XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  739. [self.service
  740. dynamicLinkFromUniversalLinkURL:url
  741. completion:^(FIRDynamicLink *_Nullable dynamicLink,
  742. NSError *_Nullable error) {
  743. XCTAssertTrue([NSThread isMainThread]);
  744. NSString *minVersion = dynamicLink.minimumAppVersion;
  745. XCTAssertEqualObjects(expectedMinVersion, minVersion,
  746. @"Min version didn't match imv= parameter");
  747. [expectation fulfill];
  748. }];
  749. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  750. }
  751. - (void)testUniversalLinkWithSubdomain_DeepLink {
  752. NSString *deepLinkString = @"https://www.google.com/maps/place/Minneapolis";
  753. NSString *webPageURLString =
  754. [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, deepLinkString];
  755. NSURL *url = [NSURL URLWithString:webPageURLString];
  756. [self.service setUpWithLaunchOptions:nil
  757. apiKey:kAPIKey
  758. urlScheme:kURLScheme
  759. userDefaults:self.userDefaults];
  760. SwizzleDynamicLinkNetworkingWithMock();
  761. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:url];
  762. XCTAssertEqual(dynamicLink.matchConfidence, FIRDynamicLinkMatchConfidenceStrong);
  763. XCTAssertEqualObjects(dynamicLink.url.absoluteString, deepLinkString);
  764. UnswizzleDynamicLinkNetworking();
  765. }
  766. - (void)testUniversalLinkWithCompletionWithSubdomain_DeepLink {
  767. NSString *deepLinkString = @"https://www.google.com/maps/place/Minneapolis";
  768. NSString *webPageURLString =
  769. [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, deepLinkString];
  770. NSURL *url = [NSURL URLWithString:webPageURLString];
  771. [self.service setUpWithLaunchOptions:nil
  772. apiKey:kAPIKey
  773. urlScheme:kURLScheme
  774. userDefaults:self.userDefaults];
  775. SwizzleDynamicLinkNetworkingWithMock();
  776. XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  777. [self.service
  778. dynamicLinkFromUniversalLinkURL:url
  779. completion:^(FIRDynamicLink *_Nullable dynamicLink,
  780. NSError *_Nullable error) {
  781. XCTAssertTrue([NSThread isMainThread]);
  782. XCTAssertEqual(dynamicLink.matchConfidence,
  783. FIRDynamicLinkMatchConfidenceStrong);
  784. XCTAssertEqualObjects(dynamicLink.url.absoluteString, deepLinkString);
  785. [expectation fulfill];
  786. }];
  787. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  788. UnswizzleDynamicLinkNetworking();
  789. }
  790. - (void)testUniversalLinkWithSubdomain_DeepLinkWithParameters {
  791. NSString *deepLinkString = @"https://www.google.com?key1%3Dvalue1%26key2%3Dvalue2";
  792. NSString *parsedDeepLinkString = @"https://www.google.com?key1=value1&key2=value2";
  793. NSString *webPageURLString =
  794. [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, deepLinkString];
  795. NSURL *url = [NSURL URLWithString:webPageURLString];
  796. [self.service setUpWithLaunchOptions:nil
  797. apiKey:kAPIKey
  798. urlScheme:kURLScheme
  799. userDefaults:self.userDefaults];
  800. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:url];
  801. XCTAssertEqual(dynamicLink.matchConfidence, FIRDynamicLinkMatchConfidenceStrong);
  802. XCTAssertEqualObjects(dynamicLink.url.absoluteString, parsedDeepLinkString);
  803. }
  804. - (void)testUniversalLinkWithCompletionWithSubdomain_DeepLinkWithParameters {
  805. NSString *deepLinkString = @"https://www.google.com?key1%3Dvalue1%26key2%3Dvalue2";
  806. NSString *parsedDeepLinkString = @"https://www.google.com?key1=value1&key2=value2";
  807. NSString *webPageURLString =
  808. [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, deepLinkString];
  809. NSURL *url = [NSURL URLWithString:webPageURLString];
  810. [self.service setUpWithLaunchOptions:nil
  811. apiKey:kAPIKey
  812. urlScheme:kURLScheme
  813. userDefaults:self.userDefaults];
  814. XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  815. [self.service dynamicLinkFromUniversalLinkURL:url
  816. completion:^(FIRDynamicLink *_Nullable dynamicLink,
  817. NSError *_Nullable error) {
  818. XCTAssertTrue([NSThread isMainThread]);
  819. XCTAssertEqual(dynamicLink.matchConfidence,
  820. FIRDynamicLinkMatchConfidenceStrong);
  821. XCTAssertEqualObjects(dynamicLink.url.absoluteString,
  822. parsedDeepLinkString);
  823. [expectation fulfill];
  824. }];
  825. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  826. }
  827. - (void)testResolveLinkRespectsResponseSuccessStatusCode {
  828. [self.service setUpWithLaunchOptions:nil
  829. apiKey:kAPIKey
  830. urlScheme:kURLScheme
  831. userDefaults:self.userDefaults];
  832. NSString *urlString = @"http://domain";
  833. NSURL *url = [NSURL URLWithString:urlString];
  834. void (^executeRequestBlock)(id, NSDictionary *, NSString *, FIRNetworkRequestCompletionHandler) =
  835. ^(id p1, NSDictionary *requestBody, NSString *requestURLString,
  836. FIRNetworkRequestCompletionHandler handler) {
  837. NSData *data = FIRDataWithDictionary(@{}, nil);
  838. NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url
  839. statusCode:200
  840. HTTPVersion:nil
  841. headerFields:nil];
  842. handler(data, response, nil);
  843. };
  844. SEL executeRequestSelector = @selector(executeOnePlatformRequest:forURL:completionHandler:);
  845. [GULSwizzler swizzleClass:[FIRDynamicLinkNetworking class]
  846. selector:executeRequestSelector
  847. isClassSelector:NO
  848. withBlock:executeRequestBlock];
  849. XCTestExpectation *expectation = [self expectationWithDescription:@"handler called"];
  850. [self.service resolveShortLink:url
  851. completion:^(NSURL *_Nullable url, NSError *_Nullable error) {
  852. XCTAssertNotNil(url);
  853. XCTAssertNil(error);
  854. [expectation fulfill];
  855. }];
  856. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  857. }
  858. - (void)testResolveLinkRespectsResponseErrorStatusCode {
  859. [self.service setUpWithLaunchOptions:nil
  860. apiKey:kAPIKey
  861. urlScheme:kURLScheme
  862. userDefaults:self.userDefaults];
  863. NSString *urlString = @"http://domain";
  864. NSURL *url = [NSURL URLWithString:urlString];
  865. NSError *expectedError = [NSError
  866. errorWithDomain:@"com.firebase.dynamicLinks"
  867. code:0
  868. userInfo:@{
  869. @"message" : [NSString stringWithFormat:@"Failed to resolve link: %@", urlString]
  870. }];
  871. void (^executeRequestBlock)(id, NSDictionary *, NSString *, FIRNetworkRequestCompletionHandler) =
  872. ^(id p1, NSDictionary *requestBody, NSString *requestURLString,
  873. FIRNetworkRequestCompletionHandler handler) {
  874. NSData *data = FIRDataWithDictionary(@{}, nil);
  875. NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url
  876. statusCode:400
  877. HTTPVersion:nil
  878. headerFields:nil];
  879. handler(data, response, nil);
  880. };
  881. SEL executeRequestSelector = @selector(executeOnePlatformRequest:forURL:completionHandler:);
  882. [GULSwizzler swizzleClass:[FIRDynamicLinkNetworking class]
  883. selector:executeRequestSelector
  884. isClassSelector:NO
  885. withBlock:executeRequestBlock];
  886. XCTestExpectation *expectation = [self expectationWithDescription:@"handler called"];
  887. [self.service resolveShortLink:url
  888. completion:^(NSURL *_Nullable url, NSError *_Nullable error) {
  889. XCTAssertNil(url);
  890. XCTAssertNotNil(error);
  891. XCTAssertEqualObjects(error, expectedError,
  892. @"Handle universal link returned unexpected error");
  893. [expectation fulfill];
  894. }];
  895. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  896. }
  897. - (void)testPassMatchesShortLinkFormatForDDLDomains {
  898. NSArray<NSString *> *urlStrings = @[
  899. @"https://someapp.app.goo.gl/somepath", @"https://someapp.app.goo.gl/link",
  900. @"https://someapp.app.goo.gl/somepath?link=https://somedomain",
  901. @"https://someapp.app.goo.gl/somepath?somekey=somevalue",
  902. @"https://someapp.app.goo.gl/somepath/?link=https://somedomain",
  903. @"https://someapp.app.goo.gl/somepath/?somekey=somevalue",
  904. @"https://someapp.page.link/somepath", @"https://someapp.page.link/link",
  905. @"https://someapp.page.link/somepath?link=https://somedomain",
  906. @"https://someapp.page.link/somepath?somekey=somevalue",
  907. @"https://someapp.page.link/somepath/?link=https://somedomain",
  908. @"https://someapp.page.link/somepath/?somekey=somevalue", @"http://someapp.page.link/somepath",
  909. @"http://someapp.page.link/link", @"http://someapp.page.link/somepath?link=https://somedomain",
  910. @"http://someapp.page.link/somepath?somekey=somevalue",
  911. @"http://someapp.page.link/somepath/?link=http://somedomain",
  912. @"http://someapp.page.link/somepath/?somekey=somevalue"
  913. ];
  914. for (NSString *urlString in urlStrings) {
  915. NSURL *url = [NSURL URLWithString:urlString];
  916. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  917. XCTAssertTrue(matchesShortLinkFormat,
  918. @"Non-DDL domain URL matched short link format with URL: %@", url);
  919. }
  920. }
  921. - (void)testFailMatchesShortLinkFormat {
  922. NSArray<NSString *> *urlStrings = @[
  923. @"https://someapp.app.goo.gl",
  924. @"https://someapp.app.goo.gl/",
  925. @"https://someapp.app.goo.gl?",
  926. @"https://someapp.app.goo.gl/?",
  927. @"https://someapp.app.goo.gl?somekey=somevalue",
  928. @"https://someapp.app.goo.gl/?somekey=somevalue",
  929. @"https://someapp.app.goo.gl/somepath/somepath2",
  930. @"https://someapp.app.goo.gl/somepath/somepath2?somekey=somevalue",
  931. @"https://someapp.app.goo.gl/somepath/somepath2?link=https://somedomain",
  932. @"https://someapp.page.link",
  933. @"https://someapp.page.link/",
  934. @"https://someapp.page.link?",
  935. @"https://someapp.page.link/?",
  936. @"https://someapp.page.link?somekey=somevalue",
  937. @"https://someapp.page.link/?somekey=somevalue",
  938. @"https://someapp.page.link/somepath/somepath2",
  939. @"https://someapp.page.link/somepath/somepath2?somekey=somevalue",
  940. @"https://someapp.page.link/somepath/somepath2?link=https://somedomain",
  941. @"https://www.google.com/maps/place/@1,1/My+Home/",
  942. @"https://mydomain.com/t439gfde",
  943. @"https://goo.gl/309dht4",
  944. @"https://59eh.goo.gl/309dht4",
  945. @"https://app.59eh.goo.gl/309dht4",
  946. @"https://goo.gl/i/309dht4",
  947. @"https://page.link/i/309dht4",
  948. @"https://fjo3eh.goo.gl/i/309dht4",
  949. @"https://app.fjo3eh.goo.gl/i/309dht4",
  950. @"https://1234.page.link/link/dismiss"
  951. ];
  952. for (NSString *urlString in urlStrings) {
  953. NSURL *url = [NSURL URLWithString:urlString];
  954. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  955. XCTAssertFalse(matchesShortLinkFormat,
  956. @"Non-DDL domain URL matched short link format with URL: %@", url);
  957. }
  958. }
  959. // Custom domain entries in plist file:
  960. // https://google.com
  961. // https://google.com/one
  962. // https://a.firebase.com/mypath
  963. - (void)testFailMatchesShortLinkFormatForCustomDomains {
  964. NSArray<NSString *> *urlStrings = @[
  965. @"https://google.com",
  966. @"https://a.firebase.com",
  967. @"https://google.com/",
  968. @"https://google.com?",
  969. @"https://google.com/?",
  970. @"https://google.com?utm_campgilink=someval",
  971. @"https://google.com?somekey=someval",
  972. ];
  973. for (NSString *urlString in urlStrings) {
  974. NSURL *url = [NSURL URLWithString:urlString];
  975. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  976. XCTAssertFalse(matchesShortLinkFormat,
  977. @"Non-DDL domain URL matched short link format with URL: %@", url);
  978. }
  979. }
  980. // Custom domain entries in plist file:
  981. // https://google.com
  982. // https://google.com/one
  983. // https://a.firebase.com/mypath
  984. - (void)testPassMatchesShortLinkFormatForCustomDomains {
  985. NSArray<NSString *> *urlStrings = @[
  986. @"https://google.com/xyz", @"https://google.com/xyz/?link=https://somedomain",
  987. @"https://google.com/xyz?link=https://somedomain",
  988. @"https://google.com/xyz/?link=https://somedomain", @"https://google.com/one/xyz",
  989. @"https://google.com/one/xyz?link=https://somedomain",
  990. @"https://google.com/one/xyz/?link=https://somedomain",
  991. @"https://google.com/one?utm_campaignlink=https://somedomain",
  992. @"https://google.com/one/?utm_campaignlink=https://somedomain", @"https://google.com/mylink",
  993. @"https://google.com/one/mylink", @"https://a.firebase.com/mypath/mylink"
  994. ];
  995. for (NSString *urlString in urlStrings) {
  996. NSURL *url = [NSURL URLWithString:urlString];
  997. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  998. XCTAssertTrue(matchesShortLinkFormat,
  999. @"Non-DDL domain URL matched short link format with URL: %@", url);
  1000. }
  1001. }
  1002. - (void)testMatchesUnversalLinkWithShortDurableLink {
  1003. NSString *urlString = @"https://sample.page.link/79g49s";
  1004. NSURL *url = [NSURL URLWithString:urlString];
  1005. BOOL matchesShort = [self.service matchesShortLinkFormat:url];
  1006. XCTAssertTrue(matchesShort, @"Short Durable Link didn't match short link");
  1007. }
  1008. - (void)testMatchesUnversalLinkWithAppInvite {
  1009. NSString *urlString = @"https://sample.page.link/i/79g49s";
  1010. NSURL *url = [NSURL URLWithString:urlString];
  1011. BOOL matchesShort = [self.service matchesShortLinkFormat:url];
  1012. XCTAssertTrue(matchesShort, @"AppInvite didn't match short link");
  1013. }
  1014. - (void)testDoesNotMatchesShortLinkFormatWithNonDDLDomains {
  1015. NSArray<NSString *> *urlStrings = @[
  1016. @"https://www.google.com/maps/place/@1,1/My+Home/", @"https://mydomain.com/t439gfde",
  1017. @"https://goo.gl/309dht4", @"https://59eh.goo.gl/309dht4", @"https://app.59eh.goo.gl/309dht4",
  1018. @"https://goo.gl/i/309dht4", @"https://page.link/i/309dht4", @"https://fjo3eh.goo.gl/i/309dht4",
  1019. @"https://app.fjo3eh.goo.gl/i/309dht4", @"https://1234.page.link/link/dismiss"
  1020. ];
  1021. for (NSString *urlString in urlStrings) {
  1022. NSURL *url = [NSURL URLWithString:urlString];
  1023. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  1024. XCTAssertFalse(matchesShortLinkFormat,
  1025. @"Non-DDL domain URL matched short link format with URL: %@", url);
  1026. }
  1027. }
  1028. - (void)testHandleUniversalLinkWithShortLink {
  1029. NSString *shortLinkString = @"https://sample.page.link/549igo";
  1030. NSString *bundleID = [NSBundle mainBundle].bundleIdentifier;
  1031. NSString *customSchemeURLString = [NSString
  1032. stringWithFormat:kStructuredLinkFmtDeepLink, bundleID, kEncodedComplicatedURLString];
  1033. XCTestExpectation *handleLinkCompletionExpectation =
  1034. [self expectationWithDescription:@"handleLink"];
  1035. XCTestExpectation *linkResolverCompletionExpectation =
  1036. [self expectationWithDescription:@"linkResolver"];
  1037. FakeShortLinkResolver *resolver =
  1038. [FakeShortLinkResolver resolverWithBlock:^NSURL *(NSURL *shortLink) {
  1039. [linkResolverCompletionExpectation fulfill];
  1040. return [NSURL URLWithString:customSchemeURLString];
  1041. }];
  1042. SwizzleDynamicLinkNetworking(resolver);
  1043. [self.service
  1044. handleUniversalLink:[NSURL URLWithString:shortLinkString]
  1045. completion:^(FIRDynamicLink *_Nonnull dynamicLink, NSError *_Nullable error) {
  1046. NSString *returnedURLString = dynamicLink.url.absoluteString;
  1047. XCTAssertEqualObjects(kDecodedComplicatedURLString, returnedURLString,
  1048. @"Handle universal link returned unexpected link");
  1049. [handleLinkCompletionExpectation fulfill];
  1050. }];
  1051. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  1052. UnswizzleDynamicLinkNetworking();
  1053. }
  1054. - (void)testHandleUniversalLinkWithLongLink {
  1055. NSString *longLinkString = [NSString
  1056. stringWithFormat:@"https://sample.page.link?link=%@&ibi=com.google.sample&ius=79306483",
  1057. kEncodedComplicatedURLString];
  1058. XCTestExpectation *handleLinkCompletionExpectation =
  1059. [self expectationWithDescription:@"handleLink"];
  1060. __block NSUInteger resolverInvocationsCount = 0;
  1061. // should not be used.
  1062. FakeShortLinkResolver *resolver =
  1063. [FakeShortLinkResolver resolverWithBlock:^NSURL *(NSURL *shortLink) {
  1064. resolverInvocationsCount++;
  1065. return [NSURL URLWithString:kDecodedComplicatedURLString];
  1066. }];
  1067. id handleUniversalLinkBlock = ^(FIRDynamicLink *_Nonnull dynamicLink, NSError *_Nullable error) {
  1068. [handleLinkCompletionExpectation fulfill];
  1069. NSURL *expectedResolvedLink = [NSURL URLWithString:kDecodedComplicatedURLString];
  1070. XCTAssertEqualObjects(expectedResolvedLink, dynamicLink.url,
  1071. @"Resolve short link returned unexpected link");
  1072. };
  1073. SwizzleDynamicLinkNetworking(resolver);
  1074. [self.service handleUniversalLink:[NSURL URLWithString:longLinkString]
  1075. completion:handleUniversalLinkBlock];
  1076. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  1077. // It is expected to call resolveLink once for logging.
  1078. XCTAssertEqual(resolverInvocationsCount, 1,
  1079. @"handleUniversalLink called resolveLink more than once");
  1080. UnswizzleDynamicLinkNetworking();
  1081. }
  1082. - (void)testHandleUniversalLinkCallsHandleUniversalLinkResolver {
  1083. XCTestExpectation *handleLinkCompletionExpectation =
  1084. [self expectationWithDescription:@"handleLink"];
  1085. void (^replacementBlock)(void) = ^{
  1086. [handleLinkCompletionExpectation fulfill];
  1087. };
  1088. SEL selectorToSwizzle = @selector(handleUniversalLink:completion:);
  1089. [GULSwizzler swizzleClass:[FIRDynamicLinks class]
  1090. selector:selectorToSwizzle
  1091. isClassSelector:NO
  1092. withBlock:replacementBlock];
  1093. NSURL *url = [NSURL URLWithString:@"https://google.com"];
  1094. [self.service
  1095. handleUniversalLink:url
  1096. completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error){
  1097. }];
  1098. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  1099. [GULSwizzler unswizzleClass:[FIRDynamicLinks class]
  1100. selector:selectorToSwizzle
  1101. isClassSelector:NO];
  1102. }
  1103. - (void)testHandleUniversalLinkCompletionReturnsNoForNonDDL {
  1104. NSArray<NSString *> *urlStrings = @[
  1105. @"https://www.google.com/maps/place/@1,1/My+Home/", @"https://mydomain.com/t439gfde",
  1106. @"https://goo.gl/309dht4", @"https://59eh.goo.gl/309dht4", @"https://app.59eh.goo.gl/309dht4",
  1107. @"https://goo.gl/i/309dht4", @"https://page.link/i/309dht4", @"https://fjo3eh.goo.gl/i/309dht4",
  1108. @"https://app.fjo3eh.goo.gl/i/309dht4", @"https://1234.page.link/link/dismiss"
  1109. ];
  1110. [urlStrings enumerateObjectsUsingBlock:^(NSString *_Nonnull urlString, NSUInteger idx,
  1111. BOOL *_Nonnull stop) {
  1112. NSURL *url = [NSURL URLWithString:urlString];
  1113. BOOL handled = [self.service
  1114. handleUniversalLink:url
  1115. completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
  1116. XCTAssertNil(dynamicLink, @"Non DDL returned FIRDynamicLink");
  1117. }];
  1118. XCTAssertFalse(handled, @"Non DDL Universal Link was handled");
  1119. }];
  1120. }
  1121. - (void)testHandleUniversalLinkCompletionReturnsYesForValidDDL {
  1122. [self.service setUpWithLaunchOptions:nil
  1123. apiKey:kAPIKey
  1124. urlScheme:kURLScheme
  1125. userDefaults:self.userDefaults];
  1126. NSArray<NSString *> *urlStrings = @[
  1127. @"https://some.page.link/test", @"https://some.page.link/test-test",
  1128. @"https://some.page.link/test_test", @"https://some.page.link/test_test-test",
  1129. @"https://some.app.goo.gl/test_test-test",
  1130. @"https://n8r9f.app.goo.gl/?ibi=com%2Egoogle%2EGCMTestApp%2Edev&amv=0&imv=1%2E0&link=https%3A%2F%2Fwww%2Egoogle%2Ecom",
  1131. @"https://n8r9f.app.goo.gl/?link=https%3A%2F%2Fwww%2Egoogle%2Ecom&ibi=com%2Egoogle%2EGCMTestApp%2Edev&amv=0&imv=1%2E0"
  1132. ];
  1133. for (NSString *urlString in urlStrings) {
  1134. NSURL *url = [NSURL URLWithString:urlString];
  1135. void (^executeRequestBlock)(id, NSDictionary *, NSString *,
  1136. FIRNetworkRequestCompletionHandler) =
  1137. ^(id p1, NSDictionary *requestBody, NSString *requestURLString,
  1138. FIRNetworkRequestCompletionHandler handler) {
  1139. NSData *data = FIRDataWithDictionary(@{}, nil);
  1140. NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url
  1141. statusCode:200
  1142. HTTPVersion:nil
  1143. headerFields:nil];
  1144. handler(data, response, nil);
  1145. };
  1146. SEL executeRequestSelector = @selector(executeOnePlatformRequest:forURL:completionHandler:);
  1147. [GULSwizzler swizzleClass:[FIRDynamicLinkNetworking class]
  1148. selector:executeRequestSelector
  1149. isClassSelector:NO
  1150. withBlock:executeRequestBlock];
  1151. XCTestExpectation *expectation = [self expectationWithDescription:@"handler called"];
  1152. BOOL handled = [self.service
  1153. handleUniversalLink:url
  1154. completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
  1155. XCTAssertNotNil(dynamicLink, @"Non DDL returned FIRDynamicLink");
  1156. [expectation fulfill];
  1157. }];
  1158. XCTAssertTrue(handled, @"Valid DDL Universal Link was not handled");
  1159. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  1160. }
  1161. }
  1162. - (void)test_ensureInternalMethodsNotRenamed {
  1163. // sanity check to ensure these methods has not been renamed
  1164. // we relaying on these to be the same for tests to work properly
  1165. // we are not exposing these methods in internal headers as we do not have "internal headers"
  1166. // 1P apps can import all of our headers
  1167. XCTAssertTrue([self.service
  1168. respondsToSelector:@selector
  1169. (handlePendingDynamicLinkRetrievalFailureWithErrorCode:errorDescription:underlyingError:)]);
  1170. XCTAssertTrue([self.service respondsToSelector:@selector(setRetrievingPendingDynamicLink:)]);
  1171. XCTAssertTrue([self.service respondsToSelector:@selector(dynamicLinkNetworking)]);
  1172. }
  1173. - (void)testCheckForPendingDynamicLinkReturnsImmediatelyIfAlreadyRead {
  1174. id mockService = OCMPartialMock(self.service);
  1175. [[mockService expect] handlePendingDynamicLinkRetrievalFailureWithErrorCode:-1
  1176. errorDescription:[OCMArg any]
  1177. underlyingError:[OCMArg any]];
  1178. [[mockService reject] setRetrievingPendingDynamicLink:YES];
  1179. [mockService setUpWithLaunchOptions:nil
  1180. apiKey:kAPIKey
  1181. urlScheme:nil
  1182. userDefaults:[NSUserDefaults standardUserDefaults]];
  1183. [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kFIRDLReadDeepLinkAfterInstallKey];
  1184. [mockService checkForPendingDynamicLink];
  1185. [mockService verify];
  1186. [mockService stopMocking];
  1187. }
  1188. - (void)testRetrievalProcessResultURLContainsAllParametersPassedToDynamicLinkInitializer {
  1189. NSDictionary<NSString *, NSString *> *linkParameters = @{
  1190. @"deep_link_id" : @"https://mmaksym.com/test-app1",
  1191. @"match_message" : @"Link is uniquely matched for this device.",
  1192. @"match_type" : @"unique",
  1193. @"utm_campaign" : @"Maksym M Test",
  1194. @"utm_medium" : @"test_medium",
  1195. @"utm_source" : @"test_source",
  1196. @"a_parameter" : @"a_value"
  1197. };
  1198. FIRDynamicLink *dynamicLink =
  1199. [[FIRDynamicLink alloc] initWithParametersDictionary:linkParameters];
  1200. FIRDLRetrievalProcessResult *result =
  1201. [[FIRDLRetrievalProcessResult alloc] initWithDynamicLink:dynamicLink
  1202. error:nil
  1203. message:nil
  1204. matchSource:nil];
  1205. NSURL *customSchemeURL = [result URLWithCustomURLScheme:@"scheme"];
  1206. XCTAssertNotNil(customSchemeURL);
  1207. // Validate URL parameters
  1208. NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:customSchemeURL
  1209. resolvingAgainstBaseURL:NO];
  1210. XCTAssertNotNil(urlComponents);
  1211. XCTAssertEqualObjects(urlComponents.scheme, @"scheme");
  1212. NSMutableDictionary<NSString *, NSString *> *notEncodedParameters = [linkParameters mutableCopy];
  1213. for (NSURLQueryItem *queryItem in urlComponents.queryItems) {
  1214. NSString *expectedValue = notEncodedParameters[queryItem.name];
  1215. XCTAssertNotNil(expectedValue, @"Extra parameter encoded: %@ = %@", queryItem.name,
  1216. queryItem.value);
  1217. XCTAssertEqualObjects(queryItem.value, expectedValue);
  1218. [notEncodedParameters removeObjectForKey:queryItem.name];
  1219. }
  1220. XCTAssertEqual(notEncodedParameters.count, 0, @"The parameters must have been encoded: %@",
  1221. notEncodedParameters);
  1222. }
  1223. - (void)test_multipleRequestsToRetrievePendingDeepLinkShouldNotCrash {
  1224. id mockService = OCMPartialMock(self.service);
  1225. [[mockService expect] handlePendingDynamicLinkRetrievalFailureWithErrorCode:-1
  1226. errorDescription:[OCMArg any]
  1227. underlyingError:[OCMArg any]];
  1228. // swizzle method to prevent actual retrieval, this will ensure that first pending link
  1229. // retrieval will stuck and second will fail with error
  1230. id (^replacementBlock)(void) = (id) ^ { return nil; };
  1231. SEL selectorToSwizzle = @selector(automaticRetrievalProcess);
  1232. [GULSwizzler swizzleClass:[FIRDLRetrievalProcessFactory class]
  1233. selector:selectorToSwizzle
  1234. isClassSelector:NO
  1235. withBlock:replacementBlock];
  1236. [mockService setUpWithLaunchOptions:nil
  1237. apiKey:kAPIKey
  1238. urlScheme:kURLScheme
  1239. userDefaults:self.userDefaults];
  1240. [mockService checkForPendingDynamicLink];
  1241. // we should not crash here
  1242. [mockService checkForPendingDynamicLink];
  1243. [mockService verify];
  1244. [mockService stopMocking];
  1245. [GULSwizzler unswizzleClass:[FIRDLRetrievalProcessFactory class]
  1246. selector:selectorToSwizzle
  1247. isClassSelector:NO];
  1248. }
  1249. - (void)test_retrievePendingDeepLinkShouldSetkFIRDLOpenURLKeyRegardlessOfFailures {
  1250. [self.service setUpWithLaunchOptions:nil
  1251. apiKey:kAPIKey
  1252. urlScheme:nil
  1253. userDefaults:[NSUserDefaults standardUserDefaults]];
  1254. FIRDynamicLinks<FIRDLRetrievalProcessDelegate> *delegate =
  1255. (FIRDynamicLinks<FIRDLRetrievalProcessDelegate> *)self.service;
  1256. // Error Result to pass
  1257. FIRDLRetrievalProcessResult *result = [[FIRDLRetrievalProcessResult alloc]
  1258. initWithDynamicLink:nil
  1259. error:[NSError errorWithDomain:@"unknown domain" code:500 userInfo:nil]
  1260. message:nil
  1261. matchSource:nil];
  1262. FIRDLDefaultRetrievalProcessV2 *defaultRetrievalProcess = [FIRDLDefaultRetrievalProcessV2 alloc];
  1263. [delegate retrievalProcess:defaultRetrievalProcess completedWithResult:result];
  1264. NSString *kFIRDLOpenURLKey = @"com.google.appinvite.openURL";
  1265. XCTAssertEqual([[NSUserDefaults standardUserDefaults] boolForKey:kFIRDLOpenURLKey], YES,
  1266. @"kFIRDLOpenURL key should be set regardless of failures");
  1267. }
  1268. - (void)test_passRetrievedDynamicLinkToApplicationDelegatesProperly {
  1269. // Creating ApplicationDelegate partial mock object.
  1270. id applicationDelegate = OCMPartialMock([UIApplication sharedApplication].delegate);
  1271. // Creating FIRDynamicLinks partial mock object.
  1272. id firebaseDynamicLinks = OCMPartialMock(self.service);
  1273. // Stubbing Application delegate to return YES when application:openURL:options method is called.
  1274. // Not sure why this is required as we are not concerned about its return, but without this, the
  1275. // test will throw NSInvalidArgumentException with message "unrecognized selector sent to
  1276. // instance".
  1277. OCMStub([applicationDelegate application:[OCMArg any] openURL:[OCMArg any] options:[OCMArg any]])
  1278. .andReturn(YES);
  1279. // Stubbing firebase dynamiclinks instance to return YES when isOpenUrlMethodPresentInAppDelegate
  1280. // is called.
  1281. OCMStub([firebaseDynamicLinks isOpenUrlMethodPresentInAppDelegate:[OCMArg any]]).andReturn(YES);
  1282. // Executing the function with a URL.
  1283. NSURL *url = [NSURL URLWithString:@"http://www.google.com"];
  1284. [firebaseDynamicLinks passRetrievedDynamicLinkToApplication:url];
  1285. // Verifying the application:openURL:options method is called in AppDelegate.
  1286. OCMVerify([applicationDelegate application:[OCMArg any] openURL:url options:[OCMArg any]]);
  1287. }
  1288. #pragma mark - Self-diagnose tests
  1289. - (void)testSelfDiagnoseWithNilCompletion {
  1290. [FIRDynamicLinks performDiagnosticsWithCompletion:nil];
  1291. }
  1292. - (void)testSelfDiagnoseCompletionCalled {
  1293. XCTestExpectation *expectation =
  1294. [self expectationWithDescription:@"Self diagnose completion block must be called"];
  1295. [FIRDynamicLinks
  1296. performDiagnosticsWithCompletion:^(NSString *_Nonnull diagnosticOutput, BOOL hasErrors) {
  1297. XCTAssert(diagnosticOutput.length > 0, @"Diagnostic expected to provide output");
  1298. [expectation fulfill];
  1299. }];
  1300. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  1301. }
  1302. #pragma mark - Custom domain tests
  1303. - (void)testValidCustomDomainNames {
  1304. // Entries in plist file:
  1305. // https://google.com
  1306. // https://google.com/one
  1307. // https://a.firebase.com/mypath
  1308. NSArray<NSString *> *urlStrings = @[
  1309. @"https://google.com/mylink", // Short FDL starting with 'https://google.com'
  1310. @"https://google.com/one", // Short FDL starting with 'https://google.com'
  1311. @"https://google.com/one/", // Short FDL starting with 'https://google.com'
  1312. @"https://google.com/one?", // Short FDL starting with 'https://google.com'
  1313. @"https://google.com/one/mylink", // Short FDL starting with 'https://google.com/one'
  1314. @"https://a.firebase.com/mypath/mylink", // Short FDL starting https://a.firebase.com/mypath
  1315. @"https://google.com/somepath?link=https://somedomain",
  1316. @"https://google.com/somepath/?link=https://somedomain",
  1317. @"https://google.com/somepath/somepath2?link=https://somedomain",
  1318. @"https://google.com/somepath/somepath2/?link=https://somedomain",
  1319. @"https://google.com/somepath?utm_campgilink=someval"
  1320. ];
  1321. NSArray<NSString *> *longFDLURLStrings = @[
  1322. @"https://a.firebase.com/mypath/?link=https://abcd&test=1", // Long FDL starting with
  1323. // https://a.firebase.com/mypath
  1324. @"https://google.com?link=http://abcd", // Long FDL starting with 'https://google.com'
  1325. @"https://google.com/?link=http://abcd", // Long FDL starting with 'https://google.com'
  1326. @"https://google.com?link=https://somedomain&some=qry", // Long FDL with link param as another
  1327. // argument.
  1328. @"https://google.com/?link=https://somedomain&some=qry", // Long FDL with link param as another
  1329. // argument.
  1330. @"https://google.com?some=qry&link=https://somedomain", // Long FDL with link param as second
  1331. // argument.
  1332. @"https://google.com/?some=qry&link=https://somedomain", // Long FDL with link param as second
  1333. // argument
  1334. @"https://google.com/?a=b&c=d&link=https://somedomain&y=z", // Long FDL with link param as
  1335. // middle argument argument
  1336. @"https://google.com?some=qry&link=https%3A%2F%2Fsomedomain", // Long FDL with Url encoded link
  1337. // param
  1338. ];
  1339. for (NSString *urlString in urlStrings) {
  1340. NSURL *url = [NSURL URLWithString:urlString];
  1341. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  1342. XCTAssertTrue(matchesShortLinkFormat, @"URL did not validate as short link: %@", url);
  1343. }
  1344. for (NSString *urlString in longFDLURLStrings) {
  1345. NSURL *url = [NSURL URLWithString:urlString];
  1346. BOOL matchesLongLinkFormat = [self.service canParseUniversalLinkURL:url];
  1347. XCTAssertTrue(matchesLongLinkFormat, @"URL did not validate as long link: %@", url);
  1348. }
  1349. }
  1350. - (void)testInvalidCustomDomainNames {
  1351. // Entries in plist file:
  1352. // https://google.com
  1353. // https://google.com/one
  1354. // https://a.firebase.com/mypath
  1355. NSArray<NSString *> *urlStrings = @[
  1356. @"google.com", // Valid domain. No scheme.
  1357. @"https://google.com", // Valid domain. No path after domainURIPrefix.
  1358. @"https://google.com/", // Valid domain. No path after domainURIPrefix.
  1359. @"https://google.co.in/mylink", // No matching domainURIPrefix.
  1360. @"https://google.com/?some=qry", // Valid domain with no path and link param
  1361. @"https://google.com/?some=qry&link=bla", // Valid domain with no path and no valid link param
  1362. @"https://firebase.com/mypath", // No matching domainURIPrefix: Invalid (sub)domain.
  1363. @"https://b.firebase.com/mypath", // No matching domainURIPrefix: Invalid subdomain.
  1364. @"https://a.firebase.com/mypathabc", // No matching domainURIPrefix: Invalid subdomain.
  1365. @"mydomain.com", // https scheme not specified for domainURIPrefix.
  1366. @"http://mydomain", // Domain not in plist. No path after domainURIPrefix.
  1367. @"https://somecustom.com?", @"https://somecustom.com/?",
  1368. @"https://somecustom.com?somekey=someval",
  1369. @"https://google.com?some=qry&somelink=https%3A%2F%2Fsomedomain", // Having somelink param
  1370. // instead of link param to
  1371. // confuse validation.
  1372. @"https://a.firebase.com/mypaths?some=qry&link=https%3A%2F%2Fsomedomain", // Additional 's' in
  1373. // path param
  1374. @"https://a.firebase.com/mypath/?some=qry#other=b&link=https://somedomain", // link param comes
  1375. // in fragmentation
  1376. @"https://a.firebase.com/mypath/?some=qry#other=b&link=https%3A%2F%2Fsomedomain", // link param
  1377. // which is
  1378. // url
  1379. // encoded
  1380. // and comes
  1381. // in
  1382. // fragmentation.
  1383. @"https://google.com?link=https1://abcd", // link query param is not a valid http link
  1384. ];
  1385. for (NSString *urlString in urlStrings) {
  1386. NSURL *url = [NSURL URLWithString:urlString];
  1387. BOOL matchesShortLinkFormat = [self.service canParseUniversalLinkURL:url];
  1388. XCTAssertFalse(matchesShortLinkFormat,
  1389. @"Non-DDL domain URL matched short link format with URL: %@", url);
  1390. }
  1391. }
  1392. #pragma mark - Private Helpers
  1393. - (void)removeAllFIRApps {
  1394. NSDictionary *apps = [FIRApp allApps];
  1395. for (FIRApp *app in apps.allValues) {
  1396. [app deleteApp:^(BOOL succes) {
  1397. if (!succes) {
  1398. NSLog(@"Error deleting FIRApp before tests - config tests may fail.");
  1399. }
  1400. }];
  1401. }
  1402. }
  1403. #pragma clang pop
  1404. @end