FIRDynamicLinksTest.m 80 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787
  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 kAsyncTestTimeout = 5.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(void) {
  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(void) {
  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_FirstTimeOpenedFromDeviceHeuristicsCodepathShouldGetWeakMatch {
  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 device heuristics 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:kAsyncTestTimeout 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:kAsyncTestTimeout 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:kAsyncTestTimeout 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:kAsyncTestTimeout 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:kAsyncTestTimeout 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:kAsyncTestTimeout 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:kAsyncTestTimeout 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, nil,
  665. nil, NO, 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, nil,
  676. nil, NO, 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, nil,
  688. nil, NO, 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, nil,
  699. nil, NO, 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:kAsyncTestTimeout 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)testDynamicLinkFromUniversalLinkURLReturnsUTMParams {
  728. NSString *expectedUtmSource = @"utm_source";
  729. NSString *expectedUtmMedium = @"utm_medium";
  730. NSString *expectedUtmCampaign = @"utm_campaign";
  731. NSString *expectedUtmTerm = @"utm_term";
  732. NSString *expectedUtmContent = @"utm_content";
  733. NSString *utmParamsString = [NSString
  734. stringWithFormat:@"utm_source=%@&utm_medium=%@&utm_campaign=%@&utm_term=%@&utm_content=%@",
  735. expectedUtmSource, expectedUtmMedium, expectedUtmCampaign, expectedUtmTerm,
  736. expectedUtmContent];
  737. NSString *urlSuffix =
  738. [NSString stringWithFormat:@"%@&%@", kEncodedComplicatedURLString, utmParamsString];
  739. NSString *urlString =
  740. [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, urlSuffix];
  741. NSURL *url = [NSURL URLWithString:urlString];
  742. [self.service setUpWithLaunchOptions:nil
  743. apiKey:kAPIKey
  744. urlScheme:kURLScheme
  745. userDefaults:self.userDefaults];
  746. XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  747. [self.service
  748. dynamicLinkFromUniversalLinkURL:url
  749. completion:^(FIRDynamicLink *_Nullable dynamicLink,
  750. NSError *_Nullable error) {
  751. XCTAssertTrue([NSThread isMainThread]);
  752. NSDictionary *utmParameters = dynamicLink.utmParametersDictionary;
  753. NSString *utmSource = [utmParameters objectForKey:@"utm_source"];
  754. XCTAssertEqualObjects(utmSource, expectedUtmSource,
  755. @"UtmSource doesn't match utm_source parameter");
  756. NSString *utmMedium = [utmParameters objectForKey:@"utm_medium"];
  757. XCTAssertEqualObjects(utmMedium, expectedUtmMedium,
  758. @"UtmMedium doesn't match utm_medium parameter");
  759. NSString *utmCampaign = [utmParameters objectForKey:@"utm_campaign"];
  760. XCTAssertEqualObjects(
  761. utmCampaign, expectedUtmCampaign,
  762. @"UtmCampaign doesn't match utm_campaign parameter");
  763. NSString *utmTerm = [utmParameters objectForKey:@"utm_term"];
  764. XCTAssertEqualObjects(utmTerm, expectedUtmTerm,
  765. @"UtmTerm doesn't match utm_term parameter");
  766. NSString *utmContent = [utmParameters objectForKey:@"utm_content"];
  767. XCTAssertEqualObjects(
  768. utmContent, expectedUtmContent,
  769. @"UtmContent doesn't match utm_content parameter");
  770. [expectation fulfill];
  771. }];
  772. [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
  773. }
  774. - (void)testDynamicLinkFromUniversalLinkURLCompletionReturnsDLMinimumVersion {
  775. NSString *expectedMinVersion = @"03-9g03hfd";
  776. NSString *urlSuffix =
  777. [NSString stringWithFormat:@"%@&imv=%@", kEncodedComplicatedURLString, expectedMinVersion];
  778. NSString *urlString =
  779. [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, urlSuffix];
  780. NSURL *url = [NSURL URLWithString:urlString];
  781. [self.service setUpWithLaunchOptions:nil
  782. apiKey:kAPIKey
  783. urlScheme:kURLScheme
  784. userDefaults:self.userDefaults];
  785. XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  786. [self.service
  787. dynamicLinkFromUniversalLinkURL:url
  788. completion:^(FIRDynamicLink *_Nullable dynamicLink,
  789. NSError *_Nullable error) {
  790. XCTAssertTrue([NSThread isMainThread]);
  791. NSString *minVersion = dynamicLink.minimumAppVersion;
  792. XCTAssertEqualObjects(expectedMinVersion, minVersion,
  793. @"Min version didn't match imv= parameter");
  794. [expectation fulfill];
  795. }];
  796. [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
  797. }
  798. - (void)testUniversalLinkWithSubdomain_DeepLink {
  799. NSString *deepLinkString = @"https://www.google.com/maps/place/Minneapolis";
  800. NSString *webPageURLString =
  801. [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, deepLinkString];
  802. NSURL *url = [NSURL URLWithString:webPageURLString];
  803. [self.service setUpWithLaunchOptions:nil
  804. apiKey:kAPIKey
  805. urlScheme:kURLScheme
  806. userDefaults:self.userDefaults];
  807. SwizzleDynamicLinkNetworkingWithMock();
  808. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:url];
  809. XCTAssertEqual(dynamicLink.matchConfidence, FIRDynamicLinkMatchConfidenceStrong);
  810. XCTAssertEqualObjects(dynamicLink.url.absoluteString, deepLinkString);
  811. UnswizzleDynamicLinkNetworking();
  812. }
  813. - (void)testUniversalLinkWithCompletionWithSubdomain_DeepLink {
  814. NSString *deepLinkString = @"https://www.google.com/maps/place/Minneapolis";
  815. NSString *webPageURLString =
  816. [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, deepLinkString];
  817. NSURL *url = [NSURL URLWithString:webPageURLString];
  818. [self.service setUpWithLaunchOptions:nil
  819. apiKey:kAPIKey
  820. urlScheme:kURLScheme
  821. userDefaults:self.userDefaults];
  822. SwizzleDynamicLinkNetworkingWithMock();
  823. XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  824. [self.service
  825. dynamicLinkFromUniversalLinkURL:url
  826. completion:^(FIRDynamicLink *_Nullable dynamicLink,
  827. NSError *_Nullable error) {
  828. XCTAssertTrue([NSThread isMainThread]);
  829. XCTAssertEqual(dynamicLink.matchConfidence,
  830. FIRDynamicLinkMatchConfidenceStrong);
  831. XCTAssertEqualObjects(dynamicLink.url.absoluteString, deepLinkString);
  832. [expectation fulfill];
  833. }];
  834. [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
  835. UnswizzleDynamicLinkNetworking();
  836. }
  837. - (void)testUniversalLinkWithSubdomain_DeepLinkWithParameters {
  838. NSString *deepLinkString = @"https://www.google.com?key1%3Dvalue1%26key2%3Dvalue2";
  839. NSString *parsedDeepLinkString = @"https://www.google.com?key1=value1&key2=value2";
  840. NSString *webPageURLString =
  841. [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, deepLinkString];
  842. NSURL *url = [NSURL URLWithString:webPageURLString];
  843. [self.service setUpWithLaunchOptions:nil
  844. apiKey:kAPIKey
  845. urlScheme:kURLScheme
  846. userDefaults:self.userDefaults];
  847. FIRDynamicLink *dynamicLink = [self.service dynamicLinkFromUniversalLinkURL:url];
  848. XCTAssertEqual(dynamicLink.matchConfidence, FIRDynamicLinkMatchConfidenceStrong);
  849. XCTAssertEqualObjects(dynamicLink.url.absoluteString, parsedDeepLinkString);
  850. }
  851. - (void)testUniversalLinkWithCompletionWithSubdomain_DeepLinkWithParameters {
  852. NSString *deepLinkString = @"https://www.google.com?key1%3Dvalue1%26key2%3Dvalue2";
  853. NSString *parsedDeepLinkString = @"https://www.google.com?key1=value1&key2=value2";
  854. NSString *webPageURLString =
  855. [NSString stringWithFormat:kStructuredUniversalLinkFmtSubdomainDeepLink, deepLinkString];
  856. NSURL *url = [NSURL URLWithString:webPageURLString];
  857. [self.service setUpWithLaunchOptions:nil
  858. apiKey:kAPIKey
  859. urlScheme:kURLScheme
  860. userDefaults:self.userDefaults];
  861. XCTestExpectation *expectation = [self expectationWithDescription:@"completion called"];
  862. [self.service dynamicLinkFromUniversalLinkURL:url
  863. completion:^(FIRDynamicLink *_Nullable dynamicLink,
  864. NSError *_Nullable error) {
  865. XCTAssertTrue([NSThread isMainThread]);
  866. XCTAssertEqual(dynamicLink.matchConfidence,
  867. FIRDynamicLinkMatchConfidenceStrong);
  868. XCTAssertEqualObjects(dynamicLink.url.absoluteString,
  869. parsedDeepLinkString);
  870. [expectation fulfill];
  871. }];
  872. [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
  873. }
  874. - (void)testResolveLinkRespectsResponseSuccessStatusCode {
  875. [self.service setUpWithLaunchOptions:nil
  876. apiKey:kAPIKey
  877. urlScheme:kURLScheme
  878. userDefaults:self.userDefaults];
  879. NSString *urlString = @"http://domain";
  880. NSURL *url = [NSURL URLWithString:urlString];
  881. void (^executeRequestBlock)(id, NSDictionary *, NSString *, FIRNetworkRequestCompletionHandler) =
  882. ^(id p1, NSDictionary *requestBody, NSString *requestURLString,
  883. FIRNetworkRequestCompletionHandler handler) {
  884. NSData *data = FIRDataWithDictionary(@{}, nil);
  885. NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url
  886. statusCode:200
  887. HTTPVersion:nil
  888. headerFields:nil];
  889. handler(data, response, nil);
  890. };
  891. SEL executeRequestSelector = @selector(executeOnePlatformRequest:forURL:completionHandler:);
  892. [GULSwizzler swizzleClass:[FIRDynamicLinkNetworking class]
  893. selector:executeRequestSelector
  894. isClassSelector:NO
  895. withBlock:executeRequestBlock];
  896. XCTestExpectation *expectation = [self expectationWithDescription:@"handler called"];
  897. [self.service resolveShortLink:url
  898. completion:^(NSURL *_Nullable url, NSError *_Nullable error) {
  899. XCTAssertNotNil(url);
  900. XCTAssertNil(error);
  901. [expectation fulfill];
  902. }];
  903. [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
  904. }
  905. - (void)testResolveLinkRespectsResponseErrorStatusCode {
  906. [self.service setUpWithLaunchOptions:nil
  907. apiKey:kAPIKey
  908. urlScheme:kURLScheme
  909. userDefaults:self.userDefaults];
  910. NSString *urlString = @"http://domain";
  911. NSURL *url = [NSURL URLWithString:urlString];
  912. NSError *expectedError = [NSError
  913. errorWithDomain:@"com.firebase.dynamicLinks"
  914. code:0
  915. userInfo:@{
  916. @"message" : [NSString stringWithFormat:@"Failed to resolve link: %@", urlString]
  917. }];
  918. void (^executeRequestBlock)(id, NSDictionary *, NSString *, FIRNetworkRequestCompletionHandler) =
  919. ^(id p1, NSDictionary *requestBody, NSString *requestURLString,
  920. FIRNetworkRequestCompletionHandler handler) {
  921. NSData *data = FIRDataWithDictionary(@{}, nil);
  922. NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url
  923. statusCode:400
  924. HTTPVersion:nil
  925. headerFields:nil];
  926. handler(data, response, nil);
  927. };
  928. SEL executeRequestSelector = @selector(executeOnePlatformRequest:forURL:completionHandler:);
  929. [GULSwizzler swizzleClass:[FIRDynamicLinkNetworking class]
  930. selector:executeRequestSelector
  931. isClassSelector:NO
  932. withBlock:executeRequestBlock];
  933. XCTestExpectation *expectation = [self expectationWithDescription:@"handler called"];
  934. [self.service resolveShortLink:url
  935. completion:^(NSURL *_Nullable url, NSError *_Nullable error) {
  936. XCTAssertNil(url);
  937. XCTAssertNotNil(error);
  938. XCTAssertEqualObjects(error, expectedError,
  939. @"Handle universal link returned unexpected error");
  940. [expectation fulfill];
  941. }];
  942. [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
  943. }
  944. - (void)testPassMatchesShortLinkFormatForDDLDomains {
  945. NSArray<NSString *> *urlStrings = @[
  946. @"https://someapp.app.goo.gl/somepath",
  947. @"https://someapp.app.goo.gl/link",
  948. @"https://someapp.app.goo.gl/somepath?link=https://somedomain",
  949. @"https://someapp.app.goo.gl/somepath?somekey=somevalue",
  950. @"https://someapp.app.goo.gl/somepath/?link=https://somedomain",
  951. @"https://someapp.app.goo.gl/somepath/?somekey=somevalue",
  952. @"https://someapp.app.google/somepath",
  953. @"https://someapp.app.google/link",
  954. @"https://someapp.app.google/somepath?link=https://somedomain",
  955. @"https://someapp.app.google/somepath?somekey=somevalue",
  956. @"https://someapp.app.google/somepath/?link=https://somedomain",
  957. @"https://someapp.app.google/somepath/?somekey=somevalue",
  958. @"https://someapp.page.link/somepath",
  959. @"https://someapp.page.link/link",
  960. @"https://someapp.page.link/somepath?link=https://somedomain",
  961. @"https://someapp.page.link/somepath?somekey=somevalue",
  962. @"https://someapp.page.link/somepath/?link=https://somedomain",
  963. @"https://someapp.page.link/somepath/?somekey=somevalue",
  964. @"http://someapp.page.link/somepath",
  965. @"http://someapp.page.link/link",
  966. @"http://someapp.page.link/somepath?link=https://somedomain",
  967. @"http://someapp.page.link/somepath?somekey=somevalue",
  968. @"http://someapp.page.link/somepath/?link=http://somedomain",
  969. @"http://someapp.page.link/somepath/?somekey=somevalue"
  970. ];
  971. for (NSString *urlString in urlStrings) {
  972. NSURL *url = [NSURL URLWithString:urlString];
  973. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  974. XCTAssertTrue(matchesShortLinkFormat,
  975. @"Non-DDL domain URL matched short link format with URL: %@", url);
  976. }
  977. }
  978. - (void)testFailMatchesShortLinkFormat {
  979. NSArray<NSString *> *urlStrings = @[
  980. @"https://someapp.app.goo.gl",
  981. @"https://someapp.app.goo.gl/",
  982. @"https://someapp.app.goo.gl?",
  983. @"https://someapp.app.goo.gl/?",
  984. @"https://someapp.app.goo.gl?somekey=somevalue",
  985. @"https://someapp.app.goo.gl/?somekey=somevalue",
  986. @"https://someapp.app.goo.gl/somepath/somepath2",
  987. @"https://someapp.app.goo.gl/somepath/somepath2?somekey=somevalue",
  988. @"https://someapp.app.goo.gl/somepath/somepath2?link=https://somedomain",
  989. @"https://someapp.app.google",
  990. @"https://someapp.app.google/",
  991. @"https://someapp.app.google?",
  992. @"https://someapp.app.google/?",
  993. @"https://someapp.app.google?somekey=somevalue",
  994. @"https://someapp.app.google/?somekey=somevalue",
  995. @"https://someapp.app.google/somepath/somepath2",
  996. @"https://someapp.app.google/somepath/somepath2?somekey=somevalue",
  997. @"https://someapp.app.google/somepath/somepath2?link=https://somedomain",
  998. @"https://someapp.page.link",
  999. @"https://someapp.page.link/",
  1000. @"https://someapp.page.link?",
  1001. @"https://someapp.page.link/?",
  1002. @"https://someapp.page.link?somekey=somevalue",
  1003. @"https://someapp.page.link/?somekey=somevalue",
  1004. @"https://someapp.page.link/somepath/somepath2",
  1005. @"https://someapp.page.link/somepath/somepath2?somekey=somevalue",
  1006. @"https://someapp.page.link/somepath/somepath2?link=https://somedomain",
  1007. @"https://www.google.com/maps/place/@1,1/My+Home/",
  1008. @"https://mydomain.com/t439gfde",
  1009. @"https://goo.gl/309dht4",
  1010. @"https://59eh.goo.gl/309dht4",
  1011. @"https://app.59eh.goo.gl/309dht4",
  1012. @"https://goo.gl/i/309dht4",
  1013. @"https://page.link/i/309dht4",
  1014. @"https://fjo3eh.goo.gl/i/309dht4",
  1015. @"https://app.fjo3eh.goo.gl/i/309dht4",
  1016. @"https://1234.page.link/link/dismiss"
  1017. ];
  1018. for (NSString *urlString in urlStrings) {
  1019. NSURL *url = [NSURL URLWithString:urlString];
  1020. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  1021. XCTAssertFalse(matchesShortLinkFormat,
  1022. @"Non-DDL domain URL matched short link format with URL: %@", url);
  1023. }
  1024. }
  1025. // Custom domain entries in plist file:
  1026. // https://google.com
  1027. // https://google.com/one
  1028. // https://a.firebase.com/mypath
  1029. - (void)testFailMatchesShortLinkFormatForCustomDomains {
  1030. NSArray<NSString *> *urlStrings = @[
  1031. @"https://google.com",
  1032. @"https://a.firebase.com",
  1033. @"https://google.com/",
  1034. @"https://google.com?",
  1035. @"https://google.com/?",
  1036. @"https://google.com?utm_campgilink=someval",
  1037. @"https://google.com?somekey=someval",
  1038. ];
  1039. for (NSString *urlString in urlStrings) {
  1040. NSURL *url = [NSURL URLWithString:urlString];
  1041. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  1042. XCTAssertFalse(matchesShortLinkFormat,
  1043. @"Non-DDL domain URL matched short link format with URL: %@", url);
  1044. }
  1045. }
  1046. // Custom domain entries in plist file:
  1047. // https://google.com
  1048. // https://google.com/one
  1049. // https://a.firebase.com/mypath
  1050. - (void)testPassMatchesShortLinkFormatForCustomDomains {
  1051. NSArray<NSString *> *urlStrings = @[
  1052. @"https://google.com/xyz", @"https://google.com/xyz/?link=https://somedomain",
  1053. @"https://google.com/xyz?link=https://somedomain",
  1054. @"https://google.com/xyz/?link=https://somedomain", @"https://google.com/one/xyz",
  1055. @"https://google.com/one/xyz?link=https://somedomain",
  1056. @"https://google.com/one/xyz/?link=https://somedomain",
  1057. @"https://google.com/one?utm_campaignlink=https://somedomain",
  1058. @"https://google.com/one/?utm_campaignlink=https://somedomain", @"https://google.com/mylink",
  1059. @"https://google.com/one/mylink", @"https://a.firebase.com/mypath/mylink"
  1060. ];
  1061. for (NSString *urlString in urlStrings) {
  1062. NSURL *url = [NSURL URLWithString:urlString];
  1063. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  1064. XCTAssertTrue(matchesShortLinkFormat,
  1065. @"Non-DDL domain URL matched short link format with URL: %@", url);
  1066. }
  1067. }
  1068. - (void)testMatchesUnversalLinkWithShortDurableLink {
  1069. NSString *urlString = @"https://sample.page.link/79g49s";
  1070. NSURL *url = [NSURL URLWithString:urlString];
  1071. BOOL matchesShort = [self.service matchesShortLinkFormat:url];
  1072. XCTAssertTrue(matchesShort, @"Short Durable Link didn't match short link");
  1073. }
  1074. - (void)testMatchesUnversalLinkWithAppInvite {
  1075. NSString *urlString = @"https://sample.page.link/i/79g49s";
  1076. NSURL *url = [NSURL URLWithString:urlString];
  1077. BOOL matchesShort = [self.service matchesShortLinkFormat:url];
  1078. XCTAssertTrue(matchesShort, @"AppInvite didn't match short link");
  1079. }
  1080. - (void)testDoesNotMatchesShortLinkFormatWithNonDDLDomains {
  1081. NSArray<NSString *> *urlStrings = @[
  1082. @"https://www.google.com/maps/place/@1,1/My+Home/", @"https://mydomain.com/t439gfde",
  1083. @"https://goo.gl/309dht4", @"https://59eh.goo.gl/309dht4", @"https://app.59eh.goo.gl/309dht4",
  1084. @"https://goo.gl/i/309dht4", @"https://page.link/i/309dht4", @"https://fjo3eh.goo.gl/i/309dht4",
  1085. @"https://app.fjo3eh.goo.gl/i/309dht4", @"https://1234.page.link/link/dismiss"
  1086. ];
  1087. for (NSString *urlString in urlStrings) {
  1088. NSURL *url = [NSURL URLWithString:urlString];
  1089. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  1090. XCTAssertFalse(matchesShortLinkFormat,
  1091. @"Non-DDL domain URL matched short link format with URL: %@", url);
  1092. }
  1093. }
  1094. - (void)testHandleUniversalLinkWithShortLink {
  1095. NSString *shortLinkString = @"https://sample.page.link/549igo";
  1096. NSString *bundleID = [NSBundle mainBundle].bundleIdentifier;
  1097. NSString *customSchemeURLString = [NSString
  1098. stringWithFormat:kStructuredLinkFmtDeepLink, bundleID, kEncodedComplicatedURLString];
  1099. XCTestExpectation *handleLinkCompletionExpectation =
  1100. [self expectationWithDescription:@"handleLink"];
  1101. XCTestExpectation *linkResolverCompletionExpectation =
  1102. [self expectationWithDescription:@"linkResolver"];
  1103. FakeShortLinkResolver *resolver =
  1104. [FakeShortLinkResolver resolverWithBlock:^NSURL *(NSURL *shortLink) {
  1105. [linkResolverCompletionExpectation fulfill];
  1106. return [NSURL URLWithString:customSchemeURLString];
  1107. }];
  1108. SwizzleDynamicLinkNetworking(resolver);
  1109. [self.service
  1110. handleUniversalLink:[NSURL URLWithString:shortLinkString]
  1111. completion:^(FIRDynamicLink *_Nonnull dynamicLink, NSError *_Nullable error) {
  1112. NSString *returnedURLString = dynamicLink.url.absoluteString;
  1113. XCTAssertEqualObjects(kDecodedComplicatedURLString, returnedURLString,
  1114. @"Handle universal link returned unexpected link");
  1115. [handleLinkCompletionExpectation fulfill];
  1116. }];
  1117. [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
  1118. UnswizzleDynamicLinkNetworking();
  1119. }
  1120. - (void)testHandleUniversalLinkWithLongLink {
  1121. NSString *longLinkString = [NSString
  1122. stringWithFormat:@"https://sample.page.link?link=%@&ibi=com.google.sample&ius=79306483",
  1123. kEncodedComplicatedURLString];
  1124. XCTestExpectation *handleLinkCompletionExpectation =
  1125. [self expectationWithDescription:@"handleLink"];
  1126. __block NSUInteger resolverInvocationsCount = 0;
  1127. // should not be used.
  1128. FakeShortLinkResolver *resolver =
  1129. [FakeShortLinkResolver resolverWithBlock:^NSURL *(NSURL *shortLink) {
  1130. resolverInvocationsCount++;
  1131. return [NSURL URLWithString:kDecodedComplicatedURLString];
  1132. }];
  1133. id handleUniversalLinkBlock = ^(FIRDynamicLink *_Nonnull dynamicLink, NSError *_Nullable error) {
  1134. [handleLinkCompletionExpectation fulfill];
  1135. NSURL *expectedResolvedLink = [NSURL URLWithString:kDecodedComplicatedURLString];
  1136. XCTAssertEqualObjects(expectedResolvedLink, dynamicLink.url,
  1137. @"Resolve short link returned unexpected link");
  1138. };
  1139. SwizzleDynamicLinkNetworking(resolver);
  1140. [self.service handleUniversalLink:[NSURL URLWithString:longLinkString]
  1141. completion:handleUniversalLinkBlock];
  1142. [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
  1143. // It is expected to call resolveLink once for logging.
  1144. XCTAssertEqual(resolverInvocationsCount, 1,
  1145. @"handleUniversalLink called resolveLink more than once");
  1146. UnswizzleDynamicLinkNetworking();
  1147. }
  1148. - (void)testHandleUniversalLinkCallsHandleUniversalLinkResolver {
  1149. XCTestExpectation *handleLinkCompletionExpectation =
  1150. [self expectationWithDescription:@"handleLink"];
  1151. void (^replacementBlock)(void) = ^{
  1152. [handleLinkCompletionExpectation fulfill];
  1153. };
  1154. SEL selectorToSwizzle = @selector(handleUniversalLink:completion:);
  1155. [GULSwizzler swizzleClass:[FIRDynamicLinks class]
  1156. selector:selectorToSwizzle
  1157. isClassSelector:NO
  1158. withBlock:replacementBlock];
  1159. NSURL *url = [NSURL URLWithString:@"https://google.com"];
  1160. [self.service
  1161. handleUniversalLink:url
  1162. completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error){
  1163. }];
  1164. [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
  1165. [GULSwizzler unswizzleClass:[FIRDynamicLinks class]
  1166. selector:selectorToSwizzle
  1167. isClassSelector:NO];
  1168. }
  1169. - (void)testHandleUniversalLinkCompletionReturnsNoForNonDDL {
  1170. NSArray<NSString *> *urlStrings = @[
  1171. @"https://www.google.com/maps/place/@1,1/My+Home/", @"https://mydomain.com/t439gfde",
  1172. @"https://goo.gl/309dht4", @"https://59eh.goo.gl/309dht4", @"https://app.59eh.goo.gl/309dht4",
  1173. @"https://goo.gl/i/309dht4", @"https://page.link/i/309dht4", @"https://fjo3eh.goo.gl/i/309dht4",
  1174. @"https://app.fjo3eh.goo.gl/i/309dht4", @"https://1234.page.link/link/dismiss"
  1175. ];
  1176. [urlStrings enumerateObjectsUsingBlock:^(NSString *_Nonnull urlString, NSUInteger idx,
  1177. BOOL *_Nonnull stop) {
  1178. NSURL *url = [NSURL URLWithString:urlString];
  1179. BOOL handled = [self.service
  1180. handleUniversalLink:url
  1181. completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
  1182. XCTAssertNil(dynamicLink, @"Non DDL returned FIRDynamicLink");
  1183. }];
  1184. XCTAssertFalse(handled, @"Non DDL Universal Link was handled");
  1185. }];
  1186. }
  1187. - (void)testHandleUniversalLinkCompletionReturnsYesForValidDDL {
  1188. [self.service setUpWithLaunchOptions:nil
  1189. apiKey:kAPIKey
  1190. urlScheme:kURLScheme
  1191. userDefaults:self.userDefaults];
  1192. NSArray<NSString *> *urlStrings = @[
  1193. @"https://some.page.link/test", @"https://some.page.link/test-test",
  1194. @"https://some.page.link/test_test", @"https://some.page.link/test_test-test",
  1195. @"https://some.app.goo.gl/test_test-test", @"https://some.app.google/test_test-test",
  1196. @"https://n8r9f.app.goo.gl/?ibi=com%2Egoogle%2EGCMTestApp%2Edev&amv=0&imv=1%2E0&link=https%3A%2F%2Fwww%2Egoogle%2Ecom",
  1197. @"https://n8r9f.app.goo.gl/?link=https%3A%2F%2Fwww%2Egoogle%2Ecom&ibi=com%2Egoogle%2EGCMTestApp%2Edev&amv=0&imv=1%2E0",
  1198. @"https://n8r9f.app.google/?ibi=com%2Egoogle%2EGCMTestApp%2Edev&amv=0&imv=1%2E0&link=https%3A%2F%2Fwww%2Egoogle%2Ecom",
  1199. @"https://n8r9f.app.google/?link=https%3A%2F%2Fwww%2Egoogle%2Ecom&ibi=com%2Egoogle%2EGCMTestApp%2Edev&amv=0&imv=1%2E0"
  1200. ];
  1201. for (NSString *urlString in urlStrings) {
  1202. NSURL *url = [NSURL URLWithString:urlString];
  1203. void (^executeRequestBlock)(id, NSDictionary *, NSString *,
  1204. FIRNetworkRequestCompletionHandler) =
  1205. ^(id p1, NSDictionary *requestBody, NSString *requestURLString,
  1206. FIRNetworkRequestCompletionHandler handler) {
  1207. NSData *data = FIRDataWithDictionary(@{}, nil);
  1208. NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url
  1209. statusCode:200
  1210. HTTPVersion:nil
  1211. headerFields:nil];
  1212. handler(data, response, nil);
  1213. };
  1214. SEL executeRequestSelector = @selector(executeOnePlatformRequest:forURL:completionHandler:);
  1215. [GULSwizzler swizzleClass:[FIRDynamicLinkNetworking class]
  1216. selector:executeRequestSelector
  1217. isClassSelector:NO
  1218. withBlock:executeRequestBlock];
  1219. XCTestExpectation *expectation = [self expectationWithDescription:@"handler called"];
  1220. BOOL handled = [self.service
  1221. handleUniversalLink:url
  1222. completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
  1223. XCTAssertNotNil(dynamicLink, @"Non DDL returned FIRDynamicLink");
  1224. [expectation fulfill];
  1225. }];
  1226. XCTAssertTrue(handled, @"Valid DDL Universal Link was not handled");
  1227. [self waitForExpectationsWithTimeout:kAsyncTestTimeout handler:nil];
  1228. }
  1229. }
  1230. - (void)test_ensureInternalMethodsNotRenamed {
  1231. // sanity check to ensure these methods has not been renamed
  1232. // we relaying on these to be the same for tests to work properly
  1233. // we are not exposing these methods in internal headers as we do not have "internal headers"
  1234. // 1P apps can import all of our headers
  1235. XCTAssertTrue([self.service
  1236. respondsToSelector:@selector
  1237. (handlePendingDynamicLinkRetrievalFailureWithErrorCode:errorDescription:underlyingError:)]);
  1238. XCTAssertTrue([self.service respondsToSelector:@selector(setRetrievingPendingDynamicLink:)]);
  1239. XCTAssertTrue([self.service respondsToSelector:@selector(dynamicLinkNetworking)]);
  1240. }
  1241. - (void)testCheckForPendingDynamicLinkReturnsImmediatelyIfAlreadyRead {
  1242. id mockService = OCMPartialMock(self.service);
  1243. [[mockService expect] handlePendingDynamicLinkRetrievalFailureWithErrorCode:-1
  1244. errorDescription:[OCMArg any]
  1245. underlyingError:[OCMArg any]];
  1246. [[mockService reject] setRetrievingPendingDynamicLink:YES];
  1247. [mockService setUpWithLaunchOptions:nil
  1248. apiKey:kAPIKey
  1249. urlScheme:nil
  1250. userDefaults:[NSUserDefaults standardUserDefaults]];
  1251. [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kFIRDLReadDeepLinkAfterInstallKey];
  1252. [mockService checkForPendingDynamicLink];
  1253. [mockService verify];
  1254. [mockService stopMocking];
  1255. }
  1256. - (void)testRetrievalProcessResultURLContainsAllParametersPassedToDynamicLinkInitializer {
  1257. NSDictionary<NSString *, NSString *> *linkParameters = @{
  1258. @"deep_link_id" : @"https://mmaksym.com/test-app1",
  1259. @"match_message" : @"Link is uniquely matched for this device.",
  1260. @"match_type" : @"unique",
  1261. @"utm_campaign" : @"Maksym M Test",
  1262. @"utm_medium" : @"test_medium",
  1263. @"utm_source" : @"test_source",
  1264. @"utm_content" : @"test_content",
  1265. @"utm_term" : @"test_term",
  1266. @"a_parameter" : @"a_value"
  1267. };
  1268. FIRDynamicLink *dynamicLink =
  1269. [[FIRDynamicLink alloc] initWithParametersDictionary:linkParameters];
  1270. FIRDLRetrievalProcessResult *result =
  1271. [[FIRDLRetrievalProcessResult alloc] initWithDynamicLink:dynamicLink
  1272. error:nil
  1273. message:nil
  1274. matchSource:nil];
  1275. NSURL *customSchemeURL = [result URLWithCustomURLScheme:@"scheme"];
  1276. XCTAssertNotNil(customSchemeURL);
  1277. // Validate URL parameters
  1278. NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:customSchemeURL
  1279. resolvingAgainstBaseURL:NO];
  1280. XCTAssertNotNil(urlComponents);
  1281. XCTAssertEqualObjects(urlComponents.scheme, @"scheme");
  1282. NSMutableDictionary<NSString *, NSString *> *notEncodedParameters = [linkParameters mutableCopy];
  1283. for (NSURLQueryItem *queryItem in urlComponents.queryItems) {
  1284. NSString *expectedValue = notEncodedParameters[queryItem.name];
  1285. XCTAssertNotNil(expectedValue, @"Extra parameter encoded: %@ = %@", queryItem.name,
  1286. queryItem.value);
  1287. XCTAssertEqualObjects(queryItem.value, expectedValue);
  1288. [notEncodedParameters removeObjectForKey:queryItem.name];
  1289. }
  1290. XCTAssertEqual(notEncodedParameters.count, 0, @"The parameters must have been encoded: %@",
  1291. notEncodedParameters);
  1292. }
  1293. - (void)test_multipleRequestsToRetrievePendingDeepLinkShouldNotCrash {
  1294. id mockService = OCMPartialMock(self.service);
  1295. [[mockService expect] handlePendingDynamicLinkRetrievalFailureWithErrorCode:-1
  1296. errorDescription:[OCMArg any]
  1297. underlyingError:[OCMArg any]];
  1298. // swizzle method to prevent actual retrieval, this will ensure that first pending link
  1299. // retrieval will stuck and second will fail with error
  1300. id (^replacementBlock)(void) = (id) ^ { return nil; };
  1301. SEL selectorToSwizzle = @selector(automaticRetrievalProcess);
  1302. [GULSwizzler swizzleClass:[FIRDLRetrievalProcessFactory class]
  1303. selector:selectorToSwizzle
  1304. isClassSelector:NO
  1305. withBlock:replacementBlock];
  1306. [mockService setUpWithLaunchOptions:nil
  1307. apiKey:kAPIKey
  1308. urlScheme:kURLScheme
  1309. userDefaults:self.userDefaults];
  1310. [mockService checkForPendingDynamicLink];
  1311. // we should not crash here
  1312. [mockService checkForPendingDynamicLink];
  1313. [mockService verify];
  1314. [mockService stopMocking];
  1315. [GULSwizzler unswizzleClass:[FIRDLRetrievalProcessFactory class]
  1316. selector:selectorToSwizzle
  1317. isClassSelector:NO];
  1318. }
  1319. - (void)test_retrievePendingDeepLinkShouldSetkFIRDLOpenURLKeyRegardlessOfFailures {
  1320. [self.service setUpWithLaunchOptions:nil
  1321. apiKey:kAPIKey
  1322. urlScheme:nil
  1323. userDefaults:[NSUserDefaults standardUserDefaults]];
  1324. FIRDynamicLinks<FIRDLRetrievalProcessDelegate> *delegate =
  1325. (FIRDynamicLinks<FIRDLRetrievalProcessDelegate> *)self.service;
  1326. // Error Result to pass
  1327. FIRDLRetrievalProcessResult *result = [[FIRDLRetrievalProcessResult alloc]
  1328. initWithDynamicLink:nil
  1329. error:[NSError errorWithDomain:@"unknown domain" code:500 userInfo:nil]
  1330. message:nil
  1331. matchSource:nil];
  1332. FIRDLDefaultRetrievalProcessV2 *defaultRetrievalProcess = [FIRDLDefaultRetrievalProcessV2 alloc];
  1333. [delegate retrievalProcess:defaultRetrievalProcess completedWithResult:result];
  1334. NSString *kFIRDLOpenURLKey = @"com.google.appinvite.openURL";
  1335. XCTAssertEqual([[NSUserDefaults standardUserDefaults] boolForKey:kFIRDLOpenURLKey], YES,
  1336. @"kFIRDLOpenURL key should be set regardless of failures");
  1337. }
  1338. - (void)test_passRetrievedDynamicLinkToApplicationDelegatesProperly {
  1339. // Creating ApplicationDelegate partial mock object.
  1340. id applicationDelegate = OCMPartialMock([UIApplication sharedApplication].delegate);
  1341. // Creating FIRDynamicLinks partial mock object.
  1342. id firebaseDynamicLinks = OCMPartialMock(self.service);
  1343. // Stubbing Application delegate to return YES when application:openURL:options method is called.
  1344. // Not sure why this is required as we are not concerned about its return, but without this, the
  1345. // test will throw NSInvalidArgumentException with message "unrecognized selector sent to
  1346. // instance".
  1347. OCMStub([applicationDelegate application:[OCMArg any] openURL:[OCMArg any] options:[OCMArg any]])
  1348. .andReturn(YES);
  1349. // Stubbing firebase dynamiclinks instance to return YES when isOpenUrlMethodPresentInAppDelegate
  1350. // is called.
  1351. OCMStub([firebaseDynamicLinks isOpenUrlMethodPresentInAppDelegate:[OCMArg any]]).andReturn(YES);
  1352. // Executing the function with a URL.
  1353. NSURL *url = [NSURL URLWithString:@"http://www.google.com"];
  1354. [firebaseDynamicLinks passRetrievedDynamicLinkToApplication:url];
  1355. // Verifying the application:openURL:options method is called in AppDelegate.
  1356. OCMVerify([applicationDelegate application:[OCMArg any] openURL:url options:[OCMArg any]]);
  1357. }
  1358. #pragma mark - Self-diagnose tests
  1359. - (void)testSelfDiagnoseWithNilCompletion {
  1360. [FIRDynamicLinks performDiagnosticsWithCompletion:nil];
  1361. }
  1362. - (void)testSelfDiagnoseCompletionCalled {
  1363. XCTestExpectation *expectation =
  1364. [self expectationWithDescription:@"Self diagnose completion block must be called"];
  1365. [FIRDynamicLinks
  1366. performDiagnosticsWithCompletion:^(NSString *_Nonnull diagnosticOutput, BOOL hasErrors) {
  1367. XCTAssert(diagnosticOutput.length > 0, @"Diagnostic expected to provide output");
  1368. [expectation fulfill];
  1369. }];
  1370. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  1371. }
  1372. #pragma mark - Custom domain tests
  1373. - (void)testValidCustomDomainNames {
  1374. // Entries in plist file:
  1375. // https://google.com
  1376. // https://google.com/one
  1377. // https://a.firebase.com/mypath
  1378. NSArray<NSString *> *urlStrings = @[
  1379. @"https://google.com/mylink", // Short FDL starting with 'https://google.com'
  1380. @"https://google.com/one", // Short FDL starting with 'https://google.com'
  1381. @"https://google.com/one/", // Short FDL starting with 'https://google.com'
  1382. @"https://google.com/one?", // Short FDL starting with 'https://google.com'
  1383. @"https://google.com/one/mylink", // Short FDL starting with 'https://google.com/one'
  1384. @"https://a.firebase.com/mypath/mylink", // Short FDL starting https://a.firebase.com/mypath
  1385. @"https://google.com/somepath?link=https://somedomain",
  1386. @"https://google.com/somepath/?link=https://somedomain",
  1387. @"https://google.com/somepath/somepath2?link=https://somedomain",
  1388. @"https://google.com/somepath/somepath2/?link=https://somedomain",
  1389. @"https://google.com/somepath?utm_campgilink=someval"
  1390. ];
  1391. NSArray<NSString *> *longFDLURLStrings = @[
  1392. @"https://a.firebase.com/mypath/?link=https://abcd&test=1", // Long FDL starting with
  1393. // https://a.firebase.com/mypath
  1394. @"https://google.com?link=http://abcd", // Long FDL starting with 'https://google.com'
  1395. @"https://google.com/?link=http://abcd", // Long FDL starting with 'https://google.com'
  1396. @"https://google.com?link=https://somedomain&some=qry", // Long FDL with link param as another
  1397. // argument.
  1398. @"https://google.com/?link=https://somedomain&some=qry", // Long FDL with link param as another
  1399. // argument.
  1400. @"https://google.com?some=qry&link=https://somedomain", // Long FDL with link param as second
  1401. // argument.
  1402. @"https://google.com/?some=qry&link=https://somedomain", // Long FDL with link param as second
  1403. // argument
  1404. @"https://google.com/?a=b&c=d&link=https://somedomain&y=z", // Long FDL with link param as
  1405. // middle argument argument
  1406. @"https://google.com?some=qry&link=https%3A%2F%2Fsomedomain", // Long FDL with Url encoded link
  1407. // param
  1408. ];
  1409. for (NSString *urlString in urlStrings) {
  1410. NSURL *url = [NSURL URLWithString:urlString];
  1411. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  1412. XCTAssertTrue(matchesShortLinkFormat, @"URL did not validate as short link: %@", url);
  1413. }
  1414. for (NSString *urlString in longFDLURLStrings) {
  1415. NSURL *url = [NSURL URLWithString:urlString];
  1416. BOOL matchesLongLinkFormat = [self.service canParseUniversalLinkURL:url];
  1417. XCTAssertTrue(matchesLongLinkFormat, @"URL did not validate as long link: %@", url);
  1418. }
  1419. }
  1420. - (void)testInvalidCustomDomainNames {
  1421. // Entries in plist file:
  1422. // https://google.com
  1423. // https://google.com/one
  1424. // https://a.firebase.com/mypath
  1425. NSArray<NSString *> *urlStrings = @[
  1426. @"google.com", // Valid domain. No scheme.
  1427. @"https://google.com", // Valid domain. No path after domainURIPrefix.
  1428. @"https://google.com/", // Valid domain. No path after domainURIPrefix.
  1429. @"https://google.co.in/mylink", // No matching domainURIPrefix.
  1430. @"https://google.com/?some=qry", // Valid domain with no path and link param
  1431. @"https://google.com/?some=qry&link=bla", // Valid domain with no path and no valid link param
  1432. @"https://firebase.com/mypath", // No matching domainURIPrefix: Invalid (sub)domain.
  1433. @"https://b.firebase.com/mypath", // No matching domainURIPrefix: Invalid subdomain.
  1434. @"https://a.firebase.com/mypathabc", // No matching domainURIPrefix: Invalid subdomain.
  1435. @"mydomain.com", // https scheme not specified for domainURIPrefix.
  1436. @"http://mydomain", // Domain not in plist. No path after domainURIPrefix.
  1437. @"https://somecustom.com?", @"https://somecustom.com/?",
  1438. @"https://somecustom.com?somekey=someval",
  1439. @"https://google.com?some=qry&somelink=https%3A%2F%2Fsomedomain", // Having somelink param
  1440. // instead of link param to
  1441. // confuse validation.
  1442. @"https://a.firebase.com/mypaths?some=qry&link=https%3A%2F%2Fsomedomain", // Additional 's' in
  1443. // path param
  1444. @"https://a.firebase.com/mypath/?some=qry#other=b&link=https://somedomain", // link param comes
  1445. // in fragmentation
  1446. @"https://a.firebase.com/mypath/?some=qry#other=b&link=https%3A%2F%2Fsomedomain", // link param
  1447. // which is
  1448. // url
  1449. // encoded
  1450. // and comes
  1451. // in
  1452. // fragmentation.
  1453. @"https://google.com?link=https1://abcd", // link query param is not a valid http link
  1454. ];
  1455. for (NSString *urlString in urlStrings) {
  1456. NSURL *url = [NSURL URLWithString:urlString];
  1457. BOOL matchesShortLinkFormat = [self.service canParseUniversalLinkURL:url];
  1458. XCTAssertFalse(matchesShortLinkFormat,
  1459. @"Non-DDL domain URL matched short link format with URL: %@", url);
  1460. }
  1461. }
  1462. #pragma mark - Private Helpers
  1463. - (void)removeAllFIRApps {
  1464. NSDictionary *apps = [FIRApp allApps];
  1465. for (FIRApp *app in apps.allValues) {
  1466. [app deleteApp:^(BOOL success) {
  1467. if (!success) {
  1468. NSLog(@"Error deleting FIRApp before tests - config tests may fail.");
  1469. }
  1470. }];
  1471. }
  1472. }
  1473. #pragma clang pop
  1474. @end