FIRDynamicLinksTest.m 77 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730
  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, 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: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",
  900. @"https://someapp.app.goo.gl/link",
  901. @"https://someapp.app.goo.gl/somepath?link=https://somedomain",
  902. @"https://someapp.app.goo.gl/somepath?somekey=somevalue",
  903. @"https://someapp.app.goo.gl/somepath/?link=https://somedomain",
  904. @"https://someapp.app.goo.gl/somepath/?somekey=somevalue",
  905. @"https://someapp.app.google/somepath",
  906. @"https://someapp.app.google/link",
  907. @"https://someapp.app.google/somepath?link=https://somedomain",
  908. @"https://someapp.app.google/somepath?somekey=somevalue",
  909. @"https://someapp.app.google/somepath/?link=https://somedomain",
  910. @"https://someapp.app.google/somepath/?somekey=somevalue",
  911. @"https://someapp.page.link/somepath",
  912. @"https://someapp.page.link/link",
  913. @"https://someapp.page.link/somepath?link=https://somedomain",
  914. @"https://someapp.page.link/somepath?somekey=somevalue",
  915. @"https://someapp.page.link/somepath/?link=https://somedomain",
  916. @"https://someapp.page.link/somepath/?somekey=somevalue",
  917. @"http://someapp.page.link/somepath",
  918. @"http://someapp.page.link/link",
  919. @"http://someapp.page.link/somepath?link=https://somedomain",
  920. @"http://someapp.page.link/somepath?somekey=somevalue",
  921. @"http://someapp.page.link/somepath/?link=http://somedomain",
  922. @"http://someapp.page.link/somepath/?somekey=somevalue"
  923. ];
  924. for (NSString *urlString in urlStrings) {
  925. NSURL *url = [NSURL URLWithString:urlString];
  926. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  927. XCTAssertTrue(matchesShortLinkFormat,
  928. @"Non-DDL domain URL matched short link format with URL: %@", url);
  929. }
  930. }
  931. - (void)testFailMatchesShortLinkFormat {
  932. NSArray<NSString *> *urlStrings = @[
  933. @"https://someapp.app.goo.gl",
  934. @"https://someapp.app.goo.gl/",
  935. @"https://someapp.app.goo.gl?",
  936. @"https://someapp.app.goo.gl/?",
  937. @"https://someapp.app.goo.gl?somekey=somevalue",
  938. @"https://someapp.app.goo.gl/?somekey=somevalue",
  939. @"https://someapp.app.goo.gl/somepath/somepath2",
  940. @"https://someapp.app.goo.gl/somepath/somepath2?somekey=somevalue",
  941. @"https://someapp.app.goo.gl/somepath/somepath2?link=https://somedomain",
  942. @"https://someapp.app.google",
  943. @"https://someapp.app.google/",
  944. @"https://someapp.app.google?",
  945. @"https://someapp.app.google/?",
  946. @"https://someapp.app.google?somekey=somevalue",
  947. @"https://someapp.app.google/?somekey=somevalue",
  948. @"https://someapp.app.google/somepath/somepath2",
  949. @"https://someapp.app.google/somepath/somepath2?somekey=somevalue",
  950. @"https://someapp.app.google/somepath/somepath2?link=https://somedomain",
  951. @"https://someapp.page.link",
  952. @"https://someapp.page.link/",
  953. @"https://someapp.page.link?",
  954. @"https://someapp.page.link/?",
  955. @"https://someapp.page.link?somekey=somevalue",
  956. @"https://someapp.page.link/?somekey=somevalue",
  957. @"https://someapp.page.link/somepath/somepath2",
  958. @"https://someapp.page.link/somepath/somepath2?somekey=somevalue",
  959. @"https://someapp.page.link/somepath/somepath2?link=https://somedomain",
  960. @"https://www.google.com/maps/place/@1,1/My+Home/",
  961. @"https://mydomain.com/t439gfde",
  962. @"https://goo.gl/309dht4",
  963. @"https://59eh.goo.gl/309dht4",
  964. @"https://app.59eh.goo.gl/309dht4",
  965. @"https://goo.gl/i/309dht4",
  966. @"https://page.link/i/309dht4",
  967. @"https://fjo3eh.goo.gl/i/309dht4",
  968. @"https://app.fjo3eh.goo.gl/i/309dht4",
  969. @"https://1234.page.link/link/dismiss"
  970. ];
  971. for (NSString *urlString in urlStrings) {
  972. NSURL *url = [NSURL URLWithString:urlString];
  973. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  974. XCTAssertFalse(matchesShortLinkFormat,
  975. @"Non-DDL domain URL matched short link format with URL: %@", url);
  976. }
  977. }
  978. // Custom domain entries in plist file:
  979. // https://google.com
  980. // https://google.com/one
  981. // https://a.firebase.com/mypath
  982. - (void)testFailMatchesShortLinkFormatForCustomDomains {
  983. NSArray<NSString *> *urlStrings = @[
  984. @"https://google.com",
  985. @"https://a.firebase.com",
  986. @"https://google.com/",
  987. @"https://google.com?",
  988. @"https://google.com/?",
  989. @"https://google.com?utm_campgilink=someval",
  990. @"https://google.com?somekey=someval",
  991. ];
  992. for (NSString *urlString in urlStrings) {
  993. NSURL *url = [NSURL URLWithString:urlString];
  994. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  995. XCTAssertFalse(matchesShortLinkFormat,
  996. @"Non-DDL domain URL matched short link format with URL: %@", url);
  997. }
  998. }
  999. // Custom domain entries in plist file:
  1000. // https://google.com
  1001. // https://google.com/one
  1002. // https://a.firebase.com/mypath
  1003. - (void)testPassMatchesShortLinkFormatForCustomDomains {
  1004. NSArray<NSString *> *urlStrings = @[
  1005. @"https://google.com/xyz", @"https://google.com/xyz/?link=https://somedomain",
  1006. @"https://google.com/xyz?link=https://somedomain",
  1007. @"https://google.com/xyz/?link=https://somedomain", @"https://google.com/one/xyz",
  1008. @"https://google.com/one/xyz?link=https://somedomain",
  1009. @"https://google.com/one/xyz/?link=https://somedomain",
  1010. @"https://google.com/one?utm_campaignlink=https://somedomain",
  1011. @"https://google.com/one/?utm_campaignlink=https://somedomain", @"https://google.com/mylink",
  1012. @"https://google.com/one/mylink", @"https://a.firebase.com/mypath/mylink"
  1013. ];
  1014. for (NSString *urlString in urlStrings) {
  1015. NSURL *url = [NSURL URLWithString:urlString];
  1016. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  1017. XCTAssertTrue(matchesShortLinkFormat,
  1018. @"Non-DDL domain URL matched short link format with URL: %@", url);
  1019. }
  1020. }
  1021. - (void)testMatchesUnversalLinkWithShortDurableLink {
  1022. NSString *urlString = @"https://sample.page.link/79g49s";
  1023. NSURL *url = [NSURL URLWithString:urlString];
  1024. BOOL matchesShort = [self.service matchesShortLinkFormat:url];
  1025. XCTAssertTrue(matchesShort, @"Short Durable Link didn't match short link");
  1026. }
  1027. - (void)testMatchesUnversalLinkWithAppInvite {
  1028. NSString *urlString = @"https://sample.page.link/i/79g49s";
  1029. NSURL *url = [NSURL URLWithString:urlString];
  1030. BOOL matchesShort = [self.service matchesShortLinkFormat:url];
  1031. XCTAssertTrue(matchesShort, @"AppInvite didn't match short link");
  1032. }
  1033. - (void)testDoesNotMatchesShortLinkFormatWithNonDDLDomains {
  1034. NSArray<NSString *> *urlStrings = @[
  1035. @"https://www.google.com/maps/place/@1,1/My+Home/", @"https://mydomain.com/t439gfde",
  1036. @"https://goo.gl/309dht4", @"https://59eh.goo.gl/309dht4", @"https://app.59eh.goo.gl/309dht4",
  1037. @"https://goo.gl/i/309dht4", @"https://page.link/i/309dht4", @"https://fjo3eh.goo.gl/i/309dht4",
  1038. @"https://app.fjo3eh.goo.gl/i/309dht4", @"https://1234.page.link/link/dismiss"
  1039. ];
  1040. for (NSString *urlString in urlStrings) {
  1041. NSURL *url = [NSURL URLWithString:urlString];
  1042. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  1043. XCTAssertFalse(matchesShortLinkFormat,
  1044. @"Non-DDL domain URL matched short link format with URL: %@", url);
  1045. }
  1046. }
  1047. - (void)testHandleUniversalLinkWithShortLink {
  1048. NSString *shortLinkString = @"https://sample.page.link/549igo";
  1049. NSString *bundleID = [NSBundle mainBundle].bundleIdentifier;
  1050. NSString *customSchemeURLString = [NSString
  1051. stringWithFormat:kStructuredLinkFmtDeepLink, bundleID, kEncodedComplicatedURLString];
  1052. XCTestExpectation *handleLinkCompletionExpectation =
  1053. [self expectationWithDescription:@"handleLink"];
  1054. XCTestExpectation *linkResolverCompletionExpectation =
  1055. [self expectationWithDescription:@"linkResolver"];
  1056. FakeShortLinkResolver *resolver =
  1057. [FakeShortLinkResolver resolverWithBlock:^NSURL *(NSURL *shortLink) {
  1058. [linkResolverCompletionExpectation fulfill];
  1059. return [NSURL URLWithString:customSchemeURLString];
  1060. }];
  1061. SwizzleDynamicLinkNetworking(resolver);
  1062. [self.service
  1063. handleUniversalLink:[NSURL URLWithString:shortLinkString]
  1064. completion:^(FIRDynamicLink *_Nonnull dynamicLink, NSError *_Nullable error) {
  1065. NSString *returnedURLString = dynamicLink.url.absoluteString;
  1066. XCTAssertEqualObjects(kDecodedComplicatedURLString, returnedURLString,
  1067. @"Handle universal link returned unexpected link");
  1068. [handleLinkCompletionExpectation fulfill];
  1069. }];
  1070. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  1071. UnswizzleDynamicLinkNetworking();
  1072. }
  1073. - (void)testHandleUniversalLinkWithLongLink {
  1074. NSString *longLinkString = [NSString
  1075. stringWithFormat:@"https://sample.page.link?link=%@&ibi=com.google.sample&ius=79306483",
  1076. kEncodedComplicatedURLString];
  1077. XCTestExpectation *handleLinkCompletionExpectation =
  1078. [self expectationWithDescription:@"handleLink"];
  1079. __block NSUInteger resolverInvocationsCount = 0;
  1080. // should not be used.
  1081. FakeShortLinkResolver *resolver =
  1082. [FakeShortLinkResolver resolverWithBlock:^NSURL *(NSURL *shortLink) {
  1083. resolverInvocationsCount++;
  1084. return [NSURL URLWithString:kDecodedComplicatedURLString];
  1085. }];
  1086. id handleUniversalLinkBlock = ^(FIRDynamicLink *_Nonnull dynamicLink, NSError *_Nullable error) {
  1087. [handleLinkCompletionExpectation fulfill];
  1088. NSURL *expectedResolvedLink = [NSURL URLWithString:kDecodedComplicatedURLString];
  1089. XCTAssertEqualObjects(expectedResolvedLink, dynamicLink.url,
  1090. @"Resolve short link returned unexpected link");
  1091. };
  1092. SwizzleDynamicLinkNetworking(resolver);
  1093. [self.service handleUniversalLink:[NSURL URLWithString:longLinkString]
  1094. completion:handleUniversalLinkBlock];
  1095. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  1096. // It is expected to call resolveLink once for logging.
  1097. XCTAssertEqual(resolverInvocationsCount, 1,
  1098. @"handleUniversalLink called resolveLink more than once");
  1099. UnswizzleDynamicLinkNetworking();
  1100. }
  1101. - (void)testHandleUniversalLinkCallsHandleUniversalLinkResolver {
  1102. XCTestExpectation *handleLinkCompletionExpectation =
  1103. [self expectationWithDescription:@"handleLink"];
  1104. void (^replacementBlock)(void) = ^{
  1105. [handleLinkCompletionExpectation fulfill];
  1106. };
  1107. SEL selectorToSwizzle = @selector(handleUniversalLink:completion:);
  1108. [GULSwizzler swizzleClass:[FIRDynamicLinks class]
  1109. selector:selectorToSwizzle
  1110. isClassSelector:NO
  1111. withBlock:replacementBlock];
  1112. NSURL *url = [NSURL URLWithString:@"https://google.com"];
  1113. [self.service
  1114. handleUniversalLink:url
  1115. completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error){
  1116. }];
  1117. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  1118. [GULSwizzler unswizzleClass:[FIRDynamicLinks class]
  1119. selector:selectorToSwizzle
  1120. isClassSelector:NO];
  1121. }
  1122. - (void)testHandleUniversalLinkCompletionReturnsNoForNonDDL {
  1123. NSArray<NSString *> *urlStrings = @[
  1124. @"https://www.google.com/maps/place/@1,1/My+Home/", @"https://mydomain.com/t439gfde",
  1125. @"https://goo.gl/309dht4", @"https://59eh.goo.gl/309dht4", @"https://app.59eh.goo.gl/309dht4",
  1126. @"https://goo.gl/i/309dht4", @"https://page.link/i/309dht4", @"https://fjo3eh.goo.gl/i/309dht4",
  1127. @"https://app.fjo3eh.goo.gl/i/309dht4", @"https://1234.page.link/link/dismiss"
  1128. ];
  1129. [urlStrings enumerateObjectsUsingBlock:^(NSString *_Nonnull urlString, NSUInteger idx,
  1130. BOOL *_Nonnull stop) {
  1131. NSURL *url = [NSURL URLWithString:urlString];
  1132. BOOL handled = [self.service
  1133. handleUniversalLink:url
  1134. completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
  1135. XCTAssertNil(dynamicLink, @"Non DDL returned FIRDynamicLink");
  1136. }];
  1137. XCTAssertFalse(handled, @"Non DDL Universal Link was handled");
  1138. }];
  1139. }
  1140. - (void)testHandleUniversalLinkCompletionReturnsYesForValidDDL {
  1141. [self.service setUpWithLaunchOptions:nil
  1142. apiKey:kAPIKey
  1143. urlScheme:kURLScheme
  1144. userDefaults:self.userDefaults];
  1145. NSArray<NSString *> *urlStrings = @[
  1146. @"https://some.page.link/test", @"https://some.page.link/test-test",
  1147. @"https://some.page.link/test_test", @"https://some.page.link/test_test-test",
  1148. @"https://some.app.goo.gl/test_test-test", @"https://some.app.google/test_test-test",
  1149. @"https://n8r9f.app.goo.gl/?ibi=com%2Egoogle%2EGCMTestApp%2Edev&amv=0&imv=1%2E0&link=https%3A%2F%2Fwww%2Egoogle%2Ecom",
  1150. @"https://n8r9f.app.goo.gl/?link=https%3A%2F%2Fwww%2Egoogle%2Ecom&ibi=com%2Egoogle%2EGCMTestApp%2Edev&amv=0&imv=1%2E0",
  1151. @"https://n8r9f.app.google/?ibi=com%2Egoogle%2EGCMTestApp%2Edev&amv=0&imv=1%2E0&link=https%3A%2F%2Fwww%2Egoogle%2Ecom",
  1152. @"https://n8r9f.app.google/?link=https%3A%2F%2Fwww%2Egoogle%2Ecom&ibi=com%2Egoogle%2EGCMTestApp%2Edev&amv=0&imv=1%2E0"
  1153. ];
  1154. for (NSString *urlString in urlStrings) {
  1155. NSURL *url = [NSURL URLWithString:urlString];
  1156. void (^executeRequestBlock)(id, NSDictionary *, NSString *,
  1157. FIRNetworkRequestCompletionHandler) =
  1158. ^(id p1, NSDictionary *requestBody, NSString *requestURLString,
  1159. FIRNetworkRequestCompletionHandler handler) {
  1160. NSData *data = FIRDataWithDictionary(@{}, nil);
  1161. NSHTTPURLResponse *response = [[NSHTTPURLResponse alloc] initWithURL:url
  1162. statusCode:200
  1163. HTTPVersion:nil
  1164. headerFields:nil];
  1165. handler(data, response, nil);
  1166. };
  1167. SEL executeRequestSelector = @selector(executeOnePlatformRequest:forURL:completionHandler:);
  1168. [GULSwizzler swizzleClass:[FIRDynamicLinkNetworking class]
  1169. selector:executeRequestSelector
  1170. isClassSelector:NO
  1171. withBlock:executeRequestBlock];
  1172. XCTestExpectation *expectation = [self expectationWithDescription:@"handler called"];
  1173. BOOL handled = [self.service
  1174. handleUniversalLink:url
  1175. completion:^(FIRDynamicLink *_Nullable dynamicLink, NSError *_Nullable error) {
  1176. XCTAssertNotNil(dynamicLink, @"Non DDL returned FIRDynamicLink");
  1177. [expectation fulfill];
  1178. }];
  1179. XCTAssertTrue(handled, @"Valid DDL Universal Link was not handled");
  1180. [self waitForExpectationsWithTimeout:kAsyncTestTimout handler:nil];
  1181. }
  1182. }
  1183. - (void)test_ensureInternalMethodsNotRenamed {
  1184. // sanity check to ensure these methods has not been renamed
  1185. // we relaying on these to be the same for tests to work properly
  1186. // we are not exposing these methods in internal headers as we do not have "internal headers"
  1187. // 1P apps can import all of our headers
  1188. XCTAssertTrue([self.service
  1189. respondsToSelector:@selector
  1190. (handlePendingDynamicLinkRetrievalFailureWithErrorCode:errorDescription:underlyingError:)]);
  1191. XCTAssertTrue([self.service respondsToSelector:@selector(setRetrievingPendingDynamicLink:)]);
  1192. XCTAssertTrue([self.service respondsToSelector:@selector(dynamicLinkNetworking)]);
  1193. }
  1194. - (void)testCheckForPendingDynamicLinkReturnsImmediatelyIfAlreadyRead {
  1195. id mockService = OCMPartialMock(self.service);
  1196. [[mockService expect] handlePendingDynamicLinkRetrievalFailureWithErrorCode:-1
  1197. errorDescription:[OCMArg any]
  1198. underlyingError:[OCMArg any]];
  1199. [[mockService reject] setRetrievingPendingDynamicLink:YES];
  1200. [mockService setUpWithLaunchOptions:nil
  1201. apiKey:kAPIKey
  1202. urlScheme:nil
  1203. userDefaults:[NSUserDefaults standardUserDefaults]];
  1204. [[NSUserDefaults standardUserDefaults] setBool:YES forKey:kFIRDLReadDeepLinkAfterInstallKey];
  1205. [mockService checkForPendingDynamicLink];
  1206. [mockService verify];
  1207. [mockService stopMocking];
  1208. }
  1209. - (void)testRetrievalProcessResultURLContainsAllParametersPassedToDynamicLinkInitializer {
  1210. NSDictionary<NSString *, NSString *> *linkParameters = @{
  1211. @"deep_link_id" : @"https://mmaksym.com/test-app1",
  1212. @"match_message" : @"Link is uniquely matched for this device.",
  1213. @"match_type" : @"unique",
  1214. @"utm_campaign" : @"Maksym M Test",
  1215. @"utm_medium" : @"test_medium",
  1216. @"utm_source" : @"test_source",
  1217. @"utm_content" : @"test_content",
  1218. @"utm_term" : @"test_term",
  1219. @"a_parameter" : @"a_value"
  1220. };
  1221. FIRDynamicLink *dynamicLink =
  1222. [[FIRDynamicLink alloc] initWithParametersDictionary:linkParameters];
  1223. FIRDLRetrievalProcessResult *result =
  1224. [[FIRDLRetrievalProcessResult alloc] initWithDynamicLink:dynamicLink
  1225. error:nil
  1226. message:nil
  1227. matchSource:nil];
  1228. NSURL *customSchemeURL = [result URLWithCustomURLScheme:@"scheme"];
  1229. XCTAssertNotNil(customSchemeURL);
  1230. // Validate URL parameters
  1231. NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:customSchemeURL
  1232. resolvingAgainstBaseURL:NO];
  1233. XCTAssertNotNil(urlComponents);
  1234. XCTAssertEqualObjects(urlComponents.scheme, @"scheme");
  1235. NSMutableDictionary<NSString *, NSString *> *notEncodedParameters = [linkParameters mutableCopy];
  1236. for (NSURLQueryItem *queryItem in urlComponents.queryItems) {
  1237. NSString *expectedValue = notEncodedParameters[queryItem.name];
  1238. XCTAssertNotNil(expectedValue, @"Extra parameter encoded: %@ = %@", queryItem.name,
  1239. queryItem.value);
  1240. XCTAssertEqualObjects(queryItem.value, expectedValue);
  1241. [notEncodedParameters removeObjectForKey:queryItem.name];
  1242. }
  1243. XCTAssertEqual(notEncodedParameters.count, 0, @"The parameters must have been encoded: %@",
  1244. notEncodedParameters);
  1245. }
  1246. - (void)test_multipleRequestsToRetrievePendingDeepLinkShouldNotCrash {
  1247. id mockService = OCMPartialMock(self.service);
  1248. [[mockService expect] handlePendingDynamicLinkRetrievalFailureWithErrorCode:-1
  1249. errorDescription:[OCMArg any]
  1250. underlyingError:[OCMArg any]];
  1251. // swizzle method to prevent actual retrieval, this will ensure that first pending link
  1252. // retrieval will stuck and second will fail with error
  1253. id (^replacementBlock)(void) = (id) ^ { return nil; };
  1254. SEL selectorToSwizzle = @selector(automaticRetrievalProcess);
  1255. [GULSwizzler swizzleClass:[FIRDLRetrievalProcessFactory class]
  1256. selector:selectorToSwizzle
  1257. isClassSelector:NO
  1258. withBlock:replacementBlock];
  1259. [mockService setUpWithLaunchOptions:nil
  1260. apiKey:kAPIKey
  1261. urlScheme:kURLScheme
  1262. userDefaults:self.userDefaults];
  1263. [mockService checkForPendingDynamicLink];
  1264. // we should not crash here
  1265. [mockService checkForPendingDynamicLink];
  1266. [mockService verify];
  1267. [mockService stopMocking];
  1268. [GULSwizzler unswizzleClass:[FIRDLRetrievalProcessFactory class]
  1269. selector:selectorToSwizzle
  1270. isClassSelector:NO];
  1271. }
  1272. - (void)test_retrievePendingDeepLinkShouldSetkFIRDLOpenURLKeyRegardlessOfFailures {
  1273. [self.service setUpWithLaunchOptions:nil
  1274. apiKey:kAPIKey
  1275. urlScheme:nil
  1276. userDefaults:[NSUserDefaults standardUserDefaults]];
  1277. FIRDynamicLinks<FIRDLRetrievalProcessDelegate> *delegate =
  1278. (FIRDynamicLinks<FIRDLRetrievalProcessDelegate> *)self.service;
  1279. // Error Result to pass
  1280. FIRDLRetrievalProcessResult *result = [[FIRDLRetrievalProcessResult alloc]
  1281. initWithDynamicLink:nil
  1282. error:[NSError errorWithDomain:@"unknown domain" code:500 userInfo:nil]
  1283. message:nil
  1284. matchSource:nil];
  1285. FIRDLDefaultRetrievalProcessV2 *defaultRetrievalProcess = [FIRDLDefaultRetrievalProcessV2 alloc];
  1286. [delegate retrievalProcess:defaultRetrievalProcess completedWithResult:result];
  1287. NSString *kFIRDLOpenURLKey = @"com.google.appinvite.openURL";
  1288. XCTAssertEqual([[NSUserDefaults standardUserDefaults] boolForKey:kFIRDLOpenURLKey], YES,
  1289. @"kFIRDLOpenURL key should be set regardless of failures");
  1290. }
  1291. - (void)test_passRetrievedDynamicLinkToApplicationDelegatesProperly {
  1292. // Creating ApplicationDelegate partial mock object.
  1293. id applicationDelegate = OCMPartialMock([UIApplication sharedApplication].delegate);
  1294. // Creating FIRDynamicLinks partial mock object.
  1295. id firebaseDynamicLinks = OCMPartialMock(self.service);
  1296. // Stubbing Application delegate to return YES when application:openURL:options method is called.
  1297. // Not sure why this is required as we are not concerned about its return, but without this, the
  1298. // test will throw NSInvalidArgumentException with message "unrecognized selector sent to
  1299. // instance".
  1300. OCMStub([applicationDelegate application:[OCMArg any] openURL:[OCMArg any] options:[OCMArg any]])
  1301. .andReturn(YES);
  1302. // Stubbing firebase dynamiclinks instance to return YES when isOpenUrlMethodPresentInAppDelegate
  1303. // is called.
  1304. OCMStub([firebaseDynamicLinks isOpenUrlMethodPresentInAppDelegate:[OCMArg any]]).andReturn(YES);
  1305. // Executing the function with a URL.
  1306. NSURL *url = [NSURL URLWithString:@"http://www.google.com"];
  1307. [firebaseDynamicLinks passRetrievedDynamicLinkToApplication:url];
  1308. // Verifying the application:openURL:options method is called in AppDelegate.
  1309. OCMVerify([applicationDelegate application:[OCMArg any] openURL:url options:[OCMArg any]]);
  1310. }
  1311. #pragma mark - Self-diagnose tests
  1312. - (void)testSelfDiagnoseWithNilCompletion {
  1313. [FIRDynamicLinks performDiagnosticsWithCompletion:nil];
  1314. }
  1315. - (void)testSelfDiagnoseCompletionCalled {
  1316. XCTestExpectation *expectation =
  1317. [self expectationWithDescription:@"Self diagnose completion block must be called"];
  1318. [FIRDynamicLinks
  1319. performDiagnosticsWithCompletion:^(NSString *_Nonnull diagnosticOutput, BOOL hasErrors) {
  1320. XCTAssert(diagnosticOutput.length > 0, @"Diagnostic expected to provide output");
  1321. [expectation fulfill];
  1322. }];
  1323. [self waitForExpectationsWithTimeout:2.0 handler:nil];
  1324. }
  1325. #pragma mark - Custom domain tests
  1326. - (void)testValidCustomDomainNames {
  1327. // Entries in plist file:
  1328. // https://google.com
  1329. // https://google.com/one
  1330. // https://a.firebase.com/mypath
  1331. NSArray<NSString *> *urlStrings = @[
  1332. @"https://google.com/mylink", // Short FDL starting with 'https://google.com'
  1333. @"https://google.com/one", // Short FDL starting with 'https://google.com'
  1334. @"https://google.com/one/", // Short FDL starting with 'https://google.com'
  1335. @"https://google.com/one?", // Short FDL starting with 'https://google.com'
  1336. @"https://google.com/one/mylink", // Short FDL starting with 'https://google.com/one'
  1337. @"https://a.firebase.com/mypath/mylink", // Short FDL starting https://a.firebase.com/mypath
  1338. @"https://google.com/somepath?link=https://somedomain",
  1339. @"https://google.com/somepath/?link=https://somedomain",
  1340. @"https://google.com/somepath/somepath2?link=https://somedomain",
  1341. @"https://google.com/somepath/somepath2/?link=https://somedomain",
  1342. @"https://google.com/somepath?utm_campgilink=someval"
  1343. ];
  1344. NSArray<NSString *> *longFDLURLStrings = @[
  1345. @"https://a.firebase.com/mypath/?link=https://abcd&test=1", // Long FDL starting with
  1346. // https://a.firebase.com/mypath
  1347. @"https://google.com?link=http://abcd", // Long FDL starting with 'https://google.com'
  1348. @"https://google.com/?link=http://abcd", // Long FDL starting with 'https://google.com'
  1349. @"https://google.com?link=https://somedomain&some=qry", // Long FDL with link param as another
  1350. // argument.
  1351. @"https://google.com/?link=https://somedomain&some=qry", // Long FDL with link param as another
  1352. // argument.
  1353. @"https://google.com?some=qry&link=https://somedomain", // Long FDL with link param as second
  1354. // argument.
  1355. @"https://google.com/?some=qry&link=https://somedomain", // Long FDL with link param as second
  1356. // argument
  1357. @"https://google.com/?a=b&c=d&link=https://somedomain&y=z", // Long FDL with link param as
  1358. // middle argument argument
  1359. @"https://google.com?some=qry&link=https%3A%2F%2Fsomedomain", // Long FDL with Url encoded link
  1360. // param
  1361. ];
  1362. for (NSString *urlString in urlStrings) {
  1363. NSURL *url = [NSURL URLWithString:urlString];
  1364. BOOL matchesShortLinkFormat = [self.service matchesShortLinkFormat:url];
  1365. XCTAssertTrue(matchesShortLinkFormat, @"URL did not validate as short link: %@", url);
  1366. }
  1367. for (NSString *urlString in longFDLURLStrings) {
  1368. NSURL *url = [NSURL URLWithString:urlString];
  1369. BOOL matchesLongLinkFormat = [self.service canParseUniversalLinkURL:url];
  1370. XCTAssertTrue(matchesLongLinkFormat, @"URL did not validate as long link: %@", url);
  1371. }
  1372. }
  1373. - (void)testInvalidCustomDomainNames {
  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. @"google.com", // Valid domain. No scheme.
  1380. @"https://google.com", // Valid domain. No path after domainURIPrefix.
  1381. @"https://google.com/", // Valid domain. No path after domainURIPrefix.
  1382. @"https://google.co.in/mylink", // No matching domainURIPrefix.
  1383. @"https://google.com/?some=qry", // Valid domain with no path and link param
  1384. @"https://google.com/?some=qry&link=bla", // Valid domain with no path and no valid link param
  1385. @"https://firebase.com/mypath", // No matching domainURIPrefix: Invalid (sub)domain.
  1386. @"https://b.firebase.com/mypath", // No matching domainURIPrefix: Invalid subdomain.
  1387. @"https://a.firebase.com/mypathabc", // No matching domainURIPrefix: Invalid subdomain.
  1388. @"mydomain.com", // https scheme not specified for domainURIPrefix.
  1389. @"http://mydomain", // Domain not in plist. No path after domainURIPrefix.
  1390. @"https://somecustom.com?", @"https://somecustom.com/?",
  1391. @"https://somecustom.com?somekey=someval",
  1392. @"https://google.com?some=qry&somelink=https%3A%2F%2Fsomedomain", // Having somelink param
  1393. // instead of link param to
  1394. // confuse validation.
  1395. @"https://a.firebase.com/mypaths?some=qry&link=https%3A%2F%2Fsomedomain", // Additional 's' in
  1396. // path param
  1397. @"https://a.firebase.com/mypath/?some=qry#other=b&link=https://somedomain", // link param comes
  1398. // in fragmentation
  1399. @"https://a.firebase.com/mypath/?some=qry#other=b&link=https%3A%2F%2Fsomedomain", // link param
  1400. // which is
  1401. // url
  1402. // encoded
  1403. // and comes
  1404. // in
  1405. // fragmentation.
  1406. @"https://google.com?link=https1://abcd", // link query param is not a valid http link
  1407. ];
  1408. for (NSString *urlString in urlStrings) {
  1409. NSURL *url = [NSURL URLWithString:urlString];
  1410. BOOL matchesShortLinkFormat = [self.service canParseUniversalLinkURL:url];
  1411. XCTAssertFalse(matchesShortLinkFormat,
  1412. @"Non-DDL domain URL matched short link format with URL: %@", url);
  1413. }
  1414. }
  1415. #pragma mark - Private Helpers
  1416. - (void)removeAllFIRApps {
  1417. NSDictionary *apps = [FIRApp allApps];
  1418. for (FIRApp *app in apps.allValues) {
  1419. [app deleteApp:^(BOOL succes) {
  1420. if (!succes) {
  1421. NSLog(@"Error deleting FIRApp before tests - config tests may fail.");
  1422. }
  1423. }];
  1424. }
  1425. }
  1426. #pragma clang pop
  1427. @end