FIRAppDistributionTests.m 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. // Copyright 2020 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import <Foundation/Foundation.h>
  15. #import <OCMock/OCMock.h>
  16. #import <XCTest/XCTest.h>
  17. #import <GoogleUtilities/GULAppDelegateSwizzler.h>
  18. #import <GoogleUtilities/GULUserDefaults.h>
  19. #import "FirebaseAppDistribution/Sources/FIRAppDistributionMachO.h"
  20. #import "FirebaseAppDistribution/Sources/FIRAppDistributionUIService.h"
  21. #import "FirebaseAppDistribution/Sources/FIRFADApiService.h"
  22. #import "FirebaseAppDistribution/Sources/Private/FIRAppDistribution.h"
  23. #import "FirebaseCore/Extension/FirebaseCoreInternal.h"
  24. #import "FirebaseInstallations/Source/Library/Private/FirebaseInstallationsInternal.h"
  25. @interface FIRAppDistributionTests : XCTestCase
  26. @property(nonatomic, strong) FIRAppDistribution *appDistribution;
  27. @end
  28. @interface FIRAppDistribution (PrivateUnitTesting)
  29. - (instancetype)initWithApp:(FIRApp *)app appInfo:(NSDictionary *)appInfo;
  30. - (void)fetchNewLatestRelease:(void (^)(FIRAppDistributionRelease *_Nullable release,
  31. NSError *_Nullable error))completion;
  32. - (NSError *)mapFetchReleasesError:(NSError *)error;
  33. - (NSString *)getAppVersion;
  34. - (NSString *)getAppBuild;
  35. @end
  36. @implementation FIRAppDistributionTests {
  37. id _mockFIRAppClass;
  38. id _mockFIRFADApiService;
  39. id _mockFIRAppDistributionUIService;
  40. id _mockFIRInstallations;
  41. id _mockInstallationToken;
  42. id _mockMachO;
  43. NSString *_mockAuthToken;
  44. NSString *_mockInstallationId;
  45. NSArray *_mockReleases;
  46. NSString *_mockCodeHash;
  47. NSString *_mockDisplayVersion;
  48. NSString *_mockBuildVersion;
  49. }
  50. - (void)setUp {
  51. [super setUp];
  52. _mockAuthToken = @"this-is-an-auth-token";
  53. _mockCodeHash = @"this-is-a-fake-code-hash";
  54. _mockDisplayVersion = @"mock-display-version";
  55. _mockBuildVersion = @"mock-build-version";
  56. _mockFIRAppClass = OCMClassMock([FIRApp class]);
  57. _mockFIRFADApiService = OCMClassMock([FIRFADApiService class]);
  58. _mockFIRAppDistributionUIService = OCMPartialMock([FIRAppDistributionUIService sharedInstance]);
  59. _mockFIRInstallations = OCMClassMock([FIRInstallations class]);
  60. _mockInstallationToken = OCMClassMock([FIRInstallationsAuthTokenResult class]);
  61. _mockMachO = OCMClassMock([FIRAppDistributionMachO class]);
  62. id mockBundle = OCMClassMock([NSBundle class]);
  63. OCMStub([_mockFIRAppClass defaultApp]).andReturn(_mockFIRAppClass);
  64. OCMStub([_mockFIRAppDistributionUIService initializeUIState]);
  65. OCMStub([_mockFIRInstallations installations]).andReturn(_mockFIRInstallations);
  66. OCMStub([_mockInstallationToken authToken]).andReturn(_mockAuthToken);
  67. OCMStub([_mockMachO alloc]).andReturn(_mockMachO);
  68. OCMStub([_mockMachO initWithPath:OCMOCK_ANY]).andReturn(_mockMachO);
  69. OCMStub([mockBundle mainBundle]).andReturn(mockBundle);
  70. OCMStub([mockBundle executablePath]).andReturn(@"this-is-a-fake-executablePath");
  71. NSDictionary<NSString *, NSString *> *dict = [[NSDictionary<NSString *, NSString *> alloc] init];
  72. self.appDistribution = [[FIRAppDistribution alloc] initWithApp:_mockFIRAppClass appInfo:dict];
  73. _mockInstallationId = @"this-id-is-fake-ccccc";
  74. _mockReleases = @[
  75. @{
  76. @"codeHash" : @"this-is-the-first-code-hash",
  77. @"displayVersion" : @"1.0.0",
  78. @"buildVersion" : @"110",
  79. @"downloadUrl" : @"http://faketyfakefake.download"
  80. },
  81. @{
  82. @"codeHash" : @"this-is-another-code-hash",
  83. @"displayVersion" : @"1.0.0",
  84. @"buildVersion" : @"111",
  85. @"releaseNotes" : @"This is a release",
  86. @"downloadUrl" : @"http://faketyfakefake.download"
  87. },
  88. @{
  89. @"latest" : @YES,
  90. @"codeHash" : _mockCodeHash,
  91. @"displayVersion" : _mockDisplayVersion,
  92. @"buildVersion" : _mockBuildVersion,
  93. @"releaseNotes" : @"This is a release too",
  94. @"downloadUrl" : @"http://faketyfakefake.download"
  95. }
  96. ];
  97. }
  98. - (void)tearDown {
  99. [super tearDown];
  100. [[GULUserDefaults standardUserDefaults] removeObjectForKey:@"FIRFADSignInState"];
  101. [_mockFIRAppClass stopMocking];
  102. [_mockFIRFADApiService stopMocking];
  103. [_mockFIRAppDistributionUIService stopMocking];
  104. [_mockFIRInstallations stopMocking];
  105. [_mockInstallationToken stopMocking];
  106. [_mockMachO stopMocking];
  107. }
  108. - (void)mockInstallationIdCompletion:(NSString *_Nullable)identifier
  109. error:(NSError *_Nullable)error {
  110. [OCMStub([_mockFIRInstallations installationIDWithCompletion:OCMOCK_ANY])
  111. andDo:^(NSInvocation *invocation) {
  112. __unsafe_unretained void (^handler)(NSString *identifier, NSError *_Nullable error);
  113. [invocation getArgument:&handler atIndex:2];
  114. handler(identifier, error);
  115. }];
  116. }
  117. - (void)verifyInstallationIdCompletion {
  118. OCMVerify([_mockFIRInstallations installationIDWithCompletion:OCMOCK_ANY]);
  119. }
  120. - (void)mockUIServiceRegistrationCompletion:(NSError *_Nullable)error {
  121. [OCMStub([_mockFIRAppDistributionUIService appDistributionRegistrationFlow:OCMOCK_ANY
  122. withCompletion:OCMOCK_ANY])
  123. andDo:^(NSInvocation *invocation) {
  124. __unsafe_unretained void (^handler)(NSError *_Nullable error);
  125. [invocation getArgument:&handler atIndex:3];
  126. handler(error);
  127. }];
  128. }
  129. - (void)verifyRegistrationCompletion {
  130. OCMVerify([_mockFIRAppDistributionUIService appDistributionRegistrationFlow:OCMOCK_ANY
  131. withCompletion:OCMOCK_ANY]);
  132. }
  133. - (void)rejectRegistrationCompletion {
  134. OCMReject([_mockFIRAppDistributionUIService appDistributionRegistrationFlow:OCMOCK_ANY
  135. withCompletion:OCMOCK_ANY]);
  136. }
  137. - (void)mockUIServiceShowUICompletion:(BOOL)continued {
  138. [OCMStub([_mockFIRAppDistributionUIService showUIAlertWithCompletion:OCMOCK_ANY])
  139. andDo:^(NSInvocation *invocation) {
  140. __unsafe_unretained void (^handler)(BOOL continued);
  141. [invocation getArgument:&handler atIndex:2];
  142. handler(continued);
  143. }];
  144. }
  145. - (void)verifyShowUICompletion {
  146. OCMVerify([_mockFIRAppDistributionUIService showUIAlertWithCompletion:OCMOCK_ANY]);
  147. }
  148. - (void)rejectShowUICompletion {
  149. OCMReject([_mockFIRAppDistributionUIService showUIAlertWithCompletion:OCMOCK_ANY]);
  150. }
  151. - (void)mockFetchReleasesCompletion:(NSArray *)releases error:(NSError *)error {
  152. [OCMStub([_mockFIRFADApiService fetchReleasesWithCompletion:OCMOCK_ANY])
  153. andDo:^(NSInvocation *invocation) {
  154. __unsafe_unretained void (^handler)(NSArray *releases, NSError *_Nullable error);
  155. [invocation getArgument:&handler atIndex:2];
  156. handler(releases, error);
  157. }];
  158. }
  159. - (void)verifyFetchReleasesCompletion {
  160. OCMVerify([_mockFIRFADApiService fetchReleasesWithCompletion:[OCMArg any]]);
  161. }
  162. - (void)rejectFetchReleasesCompletion {
  163. OCMReject([_mockFIRFADApiService fetchReleasesWithCompletion:[OCMArg any]]);
  164. }
  165. - (void)testInitWithApp {
  166. XCTAssertNotNil([self appDistribution]);
  167. }
  168. - (void)testSignInWithCompletionPersistSignInStateSuccess {
  169. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  170. [self mockUIServiceRegistrationCompletion:nil];
  171. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  172. XCTestExpectation *expectation =
  173. [self expectationWithDescription:@"Persist sign in state succeeds."];
  174. [[self appDistribution] signInTesterWithCompletion:^(NSError *_Nullable error) {
  175. XCTAssertNil(error);
  176. [expectation fulfill];
  177. }];
  178. [self waitForExpectations:@[ expectation ] timeout:5.0];
  179. XCTAssertTrue([[self appDistribution] isTesterSignedIn]);
  180. [self verifyInstallationIdCompletion];
  181. [self verifyRegistrationCompletion];
  182. [self verifyFetchReleasesCompletion];
  183. }
  184. - (void)testSignInWithCompletionInstallationIDNotFoundFailure {
  185. NSError *mockError =
  186. [NSError errorWithDomain:@"this.is.fake"
  187. code:3
  188. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  189. [self mockInstallationIdCompletion:_mockInstallationId error:mockError];
  190. [self mockUIServiceRegistrationCompletion:nil];
  191. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  192. XCTestExpectation *expectation =
  193. [self expectationWithDescription:@"Persist sign in state fails."];
  194. [[self appDistribution] signInTesterWithCompletion:^(NSError *_Nullable error) {
  195. XCTAssertNotNil(error);
  196. XCTAssertEqual([error code], FIRAppDistributionErrorUnknown);
  197. [expectation fulfill];
  198. }];
  199. [self waitForExpectations:@[ expectation ] timeout:5.0];
  200. XCTAssertFalse([[self appDistribution] isTesterSignedIn]);
  201. [self verifyInstallationIdCompletion];
  202. [self rejectRegistrationCompletion];
  203. [self rejectFetchReleasesCompletion];
  204. }
  205. - (void)testSignInWithCompletionDelegateFailureDoesNotPersist {
  206. NSError *mockError =
  207. [NSError errorWithDomain:@"fake.app.delegate.domain"
  208. code:4
  209. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  210. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  211. [self mockUIServiceRegistrationCompletion:mockError];
  212. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  213. XCTestExpectation *expectation =
  214. [self expectationWithDescription:
  215. @"Persist sign in state fails when the delegate receives a failure."];
  216. [[self appDistribution] signInTesterWithCompletion:^(NSError *_Nullable error) {
  217. XCTAssertNotNil(error);
  218. XCTAssertEqual([error code], 4);
  219. [expectation fulfill];
  220. }];
  221. [self waitForExpectations:@[ expectation ] timeout:5.0];
  222. XCTAssertFalse([[self appDistribution] isTesterSignedIn]);
  223. [self verifyInstallationIdCompletion];
  224. [self verifyRegistrationCompletion];
  225. [self rejectFetchReleasesCompletion];
  226. }
  227. - (void)testSignInWithCompletionFetchReleasesFailureDoesNotPersist {
  228. NSError *mockError =
  229. [NSError errorWithDomain:kFIRFADApiErrorDomain
  230. code:FIRFADApiErrorUnauthenticated
  231. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  232. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  233. [self mockUIServiceRegistrationCompletion:nil];
  234. [self mockFetchReleasesCompletion:_mockReleases error:mockError];
  235. XCTestExpectation *expectation = [self
  236. expectationWithDescription:@"Persist sign in state fails when we fail to fetch releases."];
  237. [[self appDistribution] signInTesterWithCompletion:^(NSError *_Nullable error) {
  238. XCTAssertNotNil(error);
  239. XCTAssertEqual([error code], FIRAppDistributionErrorAuthenticationFailure);
  240. XCTAssertEqual([error domain], FIRAppDistributionErrorDomain);
  241. [expectation fulfill];
  242. }];
  243. [self waitForExpectations:@[ expectation ] timeout:5.0];
  244. XCTAssertFalse([[self appDistribution] isTesterSignedIn]);
  245. [self verifyInstallationIdCompletion];
  246. [self verifyRegistrationCompletion];
  247. [self verifyFetchReleasesCompletion];
  248. }
  249. - (void)testSignOutSuccess {
  250. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  251. [self mockUIServiceRegistrationCompletion:nil];
  252. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  253. XCTestExpectation *expectation =
  254. [self expectationWithDescription:@"Persist sign out state succeeds."];
  255. [[self appDistribution] signInTesterWithCompletion:^(NSError *_Nullable error) {
  256. XCTAssertTrue([[self appDistribution] isTesterSignedIn]);
  257. XCTAssertNil(error);
  258. [expectation fulfill];
  259. }];
  260. [self waitForExpectations:@[ expectation ] timeout:5.0];
  261. [[self appDistribution] signOutTester];
  262. XCTAssertFalse([[self appDistribution] isTesterSignedIn]);
  263. [self verifyInstallationIdCompletion];
  264. [self verifyRegistrationCompletion];
  265. [self verifyFetchReleasesCompletion];
  266. }
  267. - (void)testFetchNewLatestReleaseSuccess {
  268. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  269. OCMStub([_mockMachO codeHash]).andReturn(@"this-is-old");
  270. XCTestExpectation *expectation =
  271. [self expectationWithDescription:@"Fetch latest release succeeds."];
  272. [[self appDistribution] fetchNewLatestRelease:^(FIRAppDistributionRelease *_Nullable release,
  273. NSError *_Nullable error) {
  274. XCTAssertNotNil(release);
  275. XCTAssertNil(error);
  276. [expectation fulfill];
  277. }];
  278. [self waitForExpectations:@[ expectation ] timeout:5.0];
  279. [self verifyFetchReleasesCompletion];
  280. }
  281. - (void)testFetchNewLatestReleaseNoNewRelease {
  282. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  283. OCMStub([_mockMachO codeHash]).andReturn(_mockCodeHash);
  284. OCMStub([[self appDistribution] getAppVersion]).andReturn(_mockDisplayVersion);
  285. OCMStub([[self appDistribution] getAppBuild]).andReturn(_mockBuildVersion);
  286. XCTestExpectation *expectation =
  287. [self expectationWithDescription:@"Fetch latest release with no new release succeeds."];
  288. [[self appDistribution] fetchNewLatestRelease:^(FIRAppDistributionRelease *_Nullable release,
  289. NSError *_Nullable error) {
  290. XCTAssertNil(release);
  291. XCTAssertNil(error);
  292. [expectation fulfill];
  293. }];
  294. [self waitForExpectations:@[ expectation ] timeout:5.0];
  295. [self verifyFetchReleasesCompletion];
  296. }
  297. - (void)testFetchNewLatestReleaseFailure {
  298. NSError *mockError =
  299. [NSError errorWithDomain:kFIRFADApiErrorDomain
  300. code:FIRFADApiErrorTimeout
  301. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  302. [self mockFetchReleasesCompletion:nil error:mockError];
  303. OCMStub([_mockMachO codeHash]).andReturn(@"this-is-old");
  304. XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch latest release fails."];
  305. [[self appDistribution] fetchNewLatestRelease:^(FIRAppDistributionRelease *_Nullable release,
  306. NSError *_Nullable error) {
  307. XCTAssertNil(release);
  308. XCTAssertNotNil(error);
  309. XCTAssertEqual([error code], FIRAppDistributionErrorNetworkFailure);
  310. XCTAssertEqual([error domain], FIRAppDistributionErrorDomain);
  311. [expectation fulfill];
  312. }];
  313. [self waitForExpectations:@[ expectation ] timeout:5.0];
  314. [self verifyFetchReleasesCompletion];
  315. OCMReject([_mockMachO codeHash]);
  316. }
  317. - (void)testFetchNewLatestReleaseUnauthenticatedFailure {
  318. NSError *mockError =
  319. [NSError errorWithDomain:kFIRFADApiErrorDomain
  320. code:FIRFADApiErrorUnauthenticated
  321. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  322. [self mockFetchReleasesCompletion:nil error:mockError];
  323. OCMStub([_mockMachO codeHash]).andReturn(@"this-is-old");
  324. [[GULUserDefaults standardUserDefaults] setBool:YES forKey:@"FIRFADSignInState"];
  325. XCTAssertTrue([[self appDistribution] isTesterSignedIn]);
  326. XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch latest release fails."];
  327. [[self appDistribution] fetchNewLatestRelease:^(FIRAppDistributionRelease *_Nullable release,
  328. NSError *_Nullable error) {
  329. XCTAssertNil(release);
  330. XCTAssertNotNil(error);
  331. XCTAssertEqual([error code], FIRAppDistributionErrorAuthenticationFailure);
  332. XCTAssertEqual([error domain], FIRAppDistributionErrorDomain);
  333. XCTAssertFalse([[self appDistribution] isTesterSignedIn]);
  334. [expectation fulfill];
  335. }];
  336. [self waitForExpectations:@[ expectation ] timeout:5.0];
  337. [self verifyFetchReleasesCompletion];
  338. OCMReject([_mockMachO codeHash]);
  339. }
  340. - (void)testFetchNewLatestReleaseUnauthorizedFailure {
  341. NSError *mockError =
  342. [NSError errorWithDomain:kFIRFADApiErrorDomain
  343. code:FIRFADApiErrorUnauthorized
  344. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  345. [self mockFetchReleasesCompletion:nil error:mockError];
  346. OCMStub([_mockMachO codeHash]).andReturn(@"this-is-old");
  347. [[GULUserDefaults standardUserDefaults] setBool:YES forKey:@"FIRFADSignInState"];
  348. XCTAssertTrue([[self appDistribution] isTesterSignedIn]);
  349. XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch latest release fails."];
  350. [[self appDistribution] fetchNewLatestRelease:^(FIRAppDistributionRelease *_Nullable release,
  351. NSError *_Nullable error) {
  352. XCTAssertNil(release);
  353. XCTAssertNotNil(error);
  354. XCTAssertEqual([error code], FIRAppDistributionErrorAuthenticationFailure);
  355. XCTAssertEqual([error domain], FIRAppDistributionErrorDomain);
  356. XCTAssertFalse([[self appDistribution] isTesterSignedIn]);
  357. [expectation fulfill];
  358. }];
  359. [self waitForExpectations:@[ expectation ] timeout:5.0];
  360. [self verifyFetchReleasesCompletion];
  361. OCMReject([_mockMachO codeHash]);
  362. }
  363. - (void)testCheckForUpdateWithCompletionTesterSignedIn {
  364. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  365. [self mockUIServiceRegistrationCompletion:nil];
  366. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  367. [self mockUIServiceShowUICompletion:NO];
  368. // Sign in the tester
  369. XCTestExpectation *expectation =
  370. [self expectationWithDescription:@"Persist sign in state succeeds."];
  371. [[self appDistribution] signInTesterWithCompletion:^(NSError *_Nullable error) {
  372. XCTAssertNil(error);
  373. [expectation fulfill];
  374. }];
  375. [self waitForExpectations:@[ expectation ] timeout:5.0];
  376. XCTAssertTrue([[self appDistribution] isTesterSignedIn]);
  377. // Should Call check for update without calling the UIService
  378. XCTestExpectation *checkForUpdateExpectation =
  379. [self expectationWithDescription:@"Check for update does not prompt user"];
  380. [[self appDistribution]
  381. checkForUpdateWithCompletion:^(FIRAppDistributionRelease *_Nullable release,
  382. NSError *_Nullable error) {
  383. XCTAssertNil(error);
  384. XCTAssertNotNil(release);
  385. [checkForUpdateExpectation fulfill];
  386. }];
  387. [self waitForExpectations:@[ checkForUpdateExpectation ] timeout:5.0];
  388. [self rejectShowUICompletion];
  389. }
  390. - (void)testCheckForUpdateWithCompletionDifferentCodeHashClicksYesSuccess {
  391. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  392. [self mockUIServiceRegistrationCompletion:nil];
  393. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  394. [self mockUIServiceShowUICompletion:YES];
  395. OCMStub([[self appDistribution] getAppVersion]).andReturn(_mockDisplayVersion);
  396. OCMStub([[self appDistribution] getAppBuild]).andReturn(_mockBuildVersion);
  397. OCMStub([_mockMachO codeHash]).andReturn(@"this-is-old");
  398. XCTestExpectation *checkForUpdateExpectation =
  399. [self expectationWithDescription:@"Check for update does prompt user"];
  400. [[self appDistribution]
  401. checkForUpdateWithCompletion:^(FIRAppDistributionRelease *_Nullable release,
  402. NSError *_Nullable error) {
  403. XCTAssertNil(error);
  404. XCTAssertNotNil(release);
  405. [checkForUpdateExpectation fulfill];
  406. }];
  407. [self waitForExpectations:@[ checkForUpdateExpectation ] timeout:5.0];
  408. [self verifyShowUICompletion];
  409. OCMVerify([_mockMachO codeHash]);
  410. }
  411. - (void)testCheckForUpdateWithCompletionDifferentVersionAndBuildClicksYesSuccess {
  412. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  413. [self mockUIServiceRegistrationCompletion:nil];
  414. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  415. [self mockUIServiceShowUICompletion:YES];
  416. OCMStub([[self appDistribution] getAppVersion]).andReturn(@"different-version");
  417. OCMStub([[self appDistribution] getAppBuild]).andReturn(@"different-build");
  418. OCMReject([_mockMachO codeHash]);
  419. XCTestExpectation *checkForUpdateExpectation =
  420. [self expectationWithDescription:@"Check for update does prompt user"];
  421. [[self appDistribution]
  422. checkForUpdateWithCompletion:^(FIRAppDistributionRelease *_Nullable release,
  423. NSError *_Nullable error) {
  424. XCTAssertNil(error);
  425. XCTAssertNotNil(release);
  426. [checkForUpdateExpectation fulfill];
  427. }];
  428. [self waitForExpectations:@[ checkForUpdateExpectation ] timeout:5.0];
  429. [self verifyShowUICompletion];
  430. }
  431. - (void)testCheckForUpdateWithCompletionDifferentVersionClicksYesSuccess {
  432. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  433. [self mockUIServiceRegistrationCompletion:nil];
  434. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  435. [self mockUIServiceShowUICompletion:YES];
  436. OCMStub([[self appDistribution] getAppVersion]).andReturn(@"different-version");
  437. OCMStub([[self appDistribution] getAppBuild]).andReturn(_mockBuildVersion);
  438. OCMReject([_mockMachO codeHash]);
  439. XCTestExpectation *checkForUpdateExpectation =
  440. [self expectationWithDescription:@"Check for update does prompt user"];
  441. [[self appDistribution]
  442. checkForUpdateWithCompletion:^(FIRAppDistributionRelease *_Nullable release,
  443. NSError *_Nullable error) {
  444. XCTAssertNil(error);
  445. XCTAssertNotNil(release);
  446. [checkForUpdateExpectation fulfill];
  447. }];
  448. [self waitForExpectations:@[ checkForUpdateExpectation ] timeout:5.0];
  449. [self verifyShowUICompletion];
  450. }
  451. - (void)testCheckForUpdateWithCompletionDifferentBuildClicksYesSuccess {
  452. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  453. [self mockUIServiceRegistrationCompletion:nil];
  454. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  455. [self mockUIServiceShowUICompletion:YES];
  456. OCMStub([[self appDistribution] getAppVersion]).andReturn(_mockDisplayVersion);
  457. OCMStub([[self appDistribution] getAppBuild]).andReturn(@"different-build");
  458. OCMReject([_mockMachO codeHash]);
  459. XCTestExpectation *checkForUpdateExpectation =
  460. [self expectationWithDescription:@"Check for update does prompt user"];
  461. [[self appDistribution]
  462. checkForUpdateWithCompletion:^(FIRAppDistributionRelease *_Nullable release,
  463. NSError *_Nullable error) {
  464. XCTAssertNil(error);
  465. XCTAssertNotNil(release);
  466. [checkForUpdateExpectation fulfill];
  467. }];
  468. [self waitForExpectations:@[ checkForUpdateExpectation ] timeout:5.0];
  469. [self verifyShowUICompletion];
  470. }
  471. - (void)testCheckForUpdateWithCompletionClicksYesFailure {
  472. NSError *mockError =
  473. [NSError errorWithDomain:@"this.is.fake"
  474. code:3
  475. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  476. [self mockInstallationIdCompletion:_mockInstallationId error:mockError];
  477. [self mockUIServiceRegistrationCompletion:nil];
  478. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  479. [self mockUIServiceShowUICompletion:YES];
  480. XCTestExpectation *checkForUpdateExpectation =
  481. [self expectationWithDescription:@"Check for update does prompt user"];
  482. [[self appDistribution]
  483. checkForUpdateWithCompletion:^(FIRAppDistributionRelease *_Nullable release,
  484. NSError *_Nullable error) {
  485. XCTAssertNotNil(error);
  486. XCTAssertNil(release);
  487. [checkForUpdateExpectation fulfill];
  488. }];
  489. [self waitForExpectations:@[ checkForUpdateExpectation ] timeout:5.0];
  490. [self verifyShowUICompletion];
  491. }
  492. - (void)testCheckForUpdateWithCompletionClicksNo {
  493. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  494. [self mockUIServiceRegistrationCompletion:nil];
  495. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  496. [self mockUIServiceShowUICompletion:NO];
  497. XCTestExpectation *checkForUpdateExpectation =
  498. [self expectationWithDescription:@"Check for update does prompt user"];
  499. [[self appDistribution]
  500. checkForUpdateWithCompletion:^(FIRAppDistributionRelease *_Nullable release,
  501. NSError *_Nullable error) {
  502. XCTAssertNotNil(error);
  503. XCTAssertEqual([error code], FIRAppDistributionErrorAuthenticationCancelled);
  504. XCTAssertNil(release);
  505. [checkForUpdateExpectation fulfill];
  506. }];
  507. [self waitForExpectations:@[ checkForUpdateExpectation ] timeout:5.0];
  508. [self verifyShowUICompletion];
  509. }
  510. - (void)testHandleFetchReleasesErrorTimeout {
  511. NSError *mockError =
  512. [NSError errorWithDomain:kFIRFADApiErrorDomain
  513. code:FIRFADApiErrorTimeout
  514. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  515. NSError *handledError = [[self appDistribution] mapFetchReleasesError:mockError];
  516. XCTAssertNotNil(handledError);
  517. XCTAssertEqual([handledError code], FIRAppDistributionErrorNetworkFailure);
  518. XCTAssertEqual([handledError domain], FIRAppDistributionErrorDomain);
  519. }
  520. - (void)testHandleFetchReleasesErrorUnauthenticated {
  521. NSError *mockError =
  522. [NSError errorWithDomain:kFIRFADApiErrorDomain
  523. code:FIRFADApiErrorUnauthenticated
  524. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  525. NSError *handledError = [[self appDistribution] mapFetchReleasesError:mockError];
  526. XCTAssertNotNil(handledError);
  527. XCTAssertEqual([handledError code], FIRAppDistributionErrorAuthenticationFailure);
  528. XCTAssertEqual([handledError domain], FIRAppDistributionErrorDomain);
  529. }
  530. - (void)testHandleFetchReleasesErrorUnauthorized {
  531. NSError *mockError =
  532. [NSError errorWithDomain:kFIRFADApiErrorDomain
  533. code:FIRFADApiErrorUnauthorized
  534. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  535. NSError *handledError = [[self appDistribution] mapFetchReleasesError:mockError];
  536. XCTAssertNotNil(handledError);
  537. XCTAssertEqual([handledError code], FIRAppDistributionErrorAuthenticationFailure);
  538. XCTAssertEqual([handledError domain], FIRAppDistributionErrorDomain);
  539. }
  540. - (void)testHandleFetchReleasesErrorTokenGenerationFailure {
  541. NSError *mockError =
  542. [NSError errorWithDomain:kFIRFADApiErrorDomain
  543. code:FIRFADApiTokenGenerationFailure
  544. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  545. NSError *handledError = [[self appDistribution] mapFetchReleasesError:mockError];
  546. XCTAssertNotNil(handledError);
  547. XCTAssertEqual([handledError code], FIRAppDistributionErrorAuthenticationFailure);
  548. XCTAssertEqual([handledError domain], FIRAppDistributionErrorDomain);
  549. }
  550. - (void)testHandleFetchReleasesErrorInstallationIdentifierFailure {
  551. NSError *mockError =
  552. [NSError errorWithDomain:kFIRFADApiErrorDomain
  553. code:FIRFADApiInstallationIdentifierError
  554. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  555. NSError *handledError = [[self appDistribution] mapFetchReleasesError:mockError];
  556. XCTAssertNotNil(handledError);
  557. XCTAssertEqual([handledError code], FIRAppDistributionErrorAuthenticationFailure);
  558. XCTAssertEqual([handledError domain], FIRAppDistributionErrorDomain);
  559. }
  560. - (void)testHandleFetchReleasesErrorNotFound {
  561. NSError *mockError =
  562. [NSError errorWithDomain:kFIRFADApiErrorDomain
  563. code:FIRFADApiErrorNotFound
  564. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  565. NSError *handledError = [[self appDistribution] mapFetchReleasesError:mockError];
  566. XCTAssertNotNil(handledError);
  567. XCTAssertEqual([handledError code], FIRAppDistributionErrorAuthenticationFailure);
  568. XCTAssertEqual([handledError domain], FIRAppDistributionErrorDomain);
  569. }
  570. - (void)testHandleFetchReleasesErrorApiDomainErrorUnknown {
  571. NSError *mockError =
  572. [NSError errorWithDomain:kFIRFADApiErrorDomain
  573. code:209
  574. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  575. NSError *handledError = [[self appDistribution] mapFetchReleasesError:mockError];
  576. XCTAssertNotNil(handledError);
  577. XCTAssertEqual([handledError code], FIRAppDistributionErrorUnknown);
  578. XCTAssertEqual([handledError domain], FIRAppDistributionErrorDomain);
  579. }
  580. - (void)testHandleFetchReleasesErrorUnknownDomainError {
  581. NSError *mockError =
  582. [NSError errorWithDomain:@"this.is.not.an.api.failure"
  583. code:4
  584. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  585. NSError *handledError = [[self appDistribution] mapFetchReleasesError:mockError];
  586. XCTAssertNotNil(handledError);
  587. XCTAssertEqual([handledError code], FIRAppDistributionErrorUnknown);
  588. XCTAssertEqual([handledError domain], FIRAppDistributionErrorDomain);
  589. }
  590. @end