FIRAppDistributionTests.m 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  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-another-code-hash",
  77. @"displayVersion" : @"1.0.0",
  78. @"buildVersion" : @"111",
  79. @"releaseNotes" : @"This is a release",
  80. @"downloadUrl" : @"http://faketyfakefake.download"
  81. },
  82. @{
  83. @"latest" : @YES,
  84. @"codeHash" : _mockCodeHash,
  85. @"displayVersion" : _mockDisplayVersion,
  86. @"buildVersion" : _mockBuildVersion,
  87. @"releaseNotes" : @"This is a release too",
  88. @"downloadUrl" : @"http://faketyfakefake.download"
  89. }
  90. ];
  91. }
  92. - (void)tearDown {
  93. [super tearDown];
  94. [[GULUserDefaults standardUserDefaults] removeObjectForKey:@"FIRFADSignInState"];
  95. [_mockFIRAppClass stopMocking];
  96. [_mockFIRFADApiService stopMocking];
  97. [_mockFIRAppDistributionUIService stopMocking];
  98. [_mockFIRInstallations stopMocking];
  99. [_mockInstallationToken stopMocking];
  100. [_mockMachO stopMocking];
  101. }
  102. - (void)mockInstallationIdCompletion:(NSString *_Nullable)identifier
  103. error:(NSError *_Nullable)error {
  104. [OCMStub([_mockFIRInstallations installationIDWithCompletion:OCMOCK_ANY])
  105. andDo:^(NSInvocation *invocation) {
  106. __unsafe_unretained void (^handler)(NSString *identifier, NSError *_Nullable error);
  107. [invocation getArgument:&handler atIndex:2];
  108. handler(identifier, error);
  109. }];
  110. }
  111. - (void)verifyInstallationIdCompletion {
  112. OCMVerify([_mockFIRInstallations installationIDWithCompletion:OCMOCK_ANY]);
  113. }
  114. - (void)mockUIServiceRegistrationCompletion:(NSError *_Nullable)error {
  115. [OCMStub([_mockFIRAppDistributionUIService appDistributionRegistrationFlow:OCMOCK_ANY
  116. withCompletion:OCMOCK_ANY])
  117. andDo:^(NSInvocation *invocation) {
  118. __unsafe_unretained void (^handler)(NSError *_Nullable error);
  119. [invocation getArgument:&handler atIndex:3];
  120. handler(error);
  121. }];
  122. }
  123. - (void)verifyRegistrationCompletion {
  124. OCMVerify([_mockFIRAppDistributionUIService appDistributionRegistrationFlow:OCMOCK_ANY
  125. withCompletion:OCMOCK_ANY]);
  126. }
  127. - (void)rejectRegistrationCompletion {
  128. OCMReject([_mockFIRAppDistributionUIService appDistributionRegistrationFlow:OCMOCK_ANY
  129. withCompletion:OCMOCK_ANY]);
  130. }
  131. - (void)mockUIServiceShowUICompletion:(BOOL)continued {
  132. [OCMStub([_mockFIRAppDistributionUIService showUIAlertWithCompletion:OCMOCK_ANY])
  133. andDo:^(NSInvocation *invocation) {
  134. __unsafe_unretained void (^handler)(BOOL continued);
  135. [invocation getArgument:&handler atIndex:2];
  136. handler(continued);
  137. }];
  138. }
  139. - (void)verifyShowUICompletion {
  140. OCMVerify([_mockFIRAppDistributionUIService showUIAlertWithCompletion:OCMOCK_ANY]);
  141. }
  142. - (void)rejectShowUICompletion {
  143. OCMReject([_mockFIRAppDistributionUIService showUIAlertWithCompletion:OCMOCK_ANY]);
  144. }
  145. - (void)mockFetchReleasesCompletion:(NSArray *)releases error:(NSError *)error {
  146. [OCMStub([_mockFIRFADApiService fetchReleasesWithCompletion:OCMOCK_ANY])
  147. andDo:^(NSInvocation *invocation) {
  148. __unsafe_unretained void (^handler)(NSArray *releases, NSError *_Nullable error);
  149. [invocation getArgument:&handler atIndex:2];
  150. handler(releases, error);
  151. }];
  152. }
  153. - (void)verifyFetchReleasesCompletion {
  154. OCMVerify([_mockFIRFADApiService fetchReleasesWithCompletion:[OCMArg any]]);
  155. }
  156. - (void)rejectFetchReleasesCompletion {
  157. OCMReject([_mockFIRFADApiService fetchReleasesWithCompletion:[OCMArg any]]);
  158. }
  159. - (void)testInitWithApp {
  160. XCTAssertNotNil([self appDistribution]);
  161. }
  162. - (void)testSignInWithCompletionPersistSignInStateSuccess {
  163. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  164. [self mockUIServiceRegistrationCompletion:nil];
  165. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  166. XCTestExpectation *expectation =
  167. [self expectationWithDescription:@"Persist sign in state succeeds."];
  168. [[self appDistribution] signInTesterWithCompletion:^(NSError *_Nullable error) {
  169. XCTAssertNil(error);
  170. [expectation fulfill];
  171. }];
  172. [self waitForExpectations:@[ expectation ] timeout:5.0];
  173. XCTAssertTrue([[self appDistribution] isTesterSignedIn]);
  174. [self verifyInstallationIdCompletion];
  175. [self verifyRegistrationCompletion];
  176. [self verifyFetchReleasesCompletion];
  177. }
  178. - (void)testSignInWithCompletionInstallationIDNotFoundFailure {
  179. NSError *mockError =
  180. [NSError errorWithDomain:@"this.is.fake"
  181. code:3
  182. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  183. [self mockInstallationIdCompletion:_mockInstallationId error:mockError];
  184. [self mockUIServiceRegistrationCompletion:nil];
  185. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  186. XCTestExpectation *expectation =
  187. [self expectationWithDescription:@"Persist sign in state fails."];
  188. [[self appDistribution] signInTesterWithCompletion:^(NSError *_Nullable error) {
  189. XCTAssertNotNil(error);
  190. XCTAssertEqual([error code], FIRAppDistributionErrorUnknown);
  191. [expectation fulfill];
  192. }];
  193. [self waitForExpectations:@[ expectation ] timeout:5.0];
  194. XCTAssertFalse([[self appDistribution] isTesterSignedIn]);
  195. [self verifyInstallationIdCompletion];
  196. [self rejectRegistrationCompletion];
  197. [self rejectFetchReleasesCompletion];
  198. }
  199. - (void)testSignInWithCompletionDelegateFailureDoesNotPersist {
  200. NSError *mockError =
  201. [NSError errorWithDomain:@"fake.app.delegate.domain"
  202. code:4
  203. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  204. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  205. [self mockUIServiceRegistrationCompletion:mockError];
  206. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  207. XCTestExpectation *expectation =
  208. [self expectationWithDescription:
  209. @"Persist sign in state fails when the delegate recieves a failure."];
  210. [[self appDistribution] signInTesterWithCompletion:^(NSError *_Nullable error) {
  211. XCTAssertNotNil(error);
  212. XCTAssertEqual([error code], 4);
  213. [expectation fulfill];
  214. }];
  215. [self waitForExpectations:@[ expectation ] timeout:5.0];
  216. XCTAssertFalse([[self appDistribution] isTesterSignedIn]);
  217. [self verifyInstallationIdCompletion];
  218. [self verifyRegistrationCompletion];
  219. [self rejectFetchReleasesCompletion];
  220. }
  221. - (void)testSignInWithCompletionFetchReleasesFailureDoesNotPersist {
  222. NSError *mockError =
  223. [NSError errorWithDomain:kFIRFADApiErrorDomain
  224. code:FIRFADApiErrorUnauthenticated
  225. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  226. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  227. [self mockUIServiceRegistrationCompletion:nil];
  228. [self mockFetchReleasesCompletion:_mockReleases error:mockError];
  229. XCTestExpectation *expectation = [self
  230. expectationWithDescription:@"Persist sign in state fails when we fail to fetch releases."];
  231. [[self appDistribution] signInTesterWithCompletion:^(NSError *_Nullable error) {
  232. XCTAssertNotNil(error);
  233. XCTAssertEqual([error code], FIRAppDistributionErrorAuthenticationFailure);
  234. XCTAssertEqual([error domain], FIRAppDistributionErrorDomain);
  235. [expectation fulfill];
  236. }];
  237. [self waitForExpectations:@[ expectation ] timeout:5.0];
  238. XCTAssertFalse([[self appDistribution] isTesterSignedIn]);
  239. [self verifyInstallationIdCompletion];
  240. [self verifyRegistrationCompletion];
  241. [self verifyFetchReleasesCompletion];
  242. }
  243. - (void)testSignOutSuccess {
  244. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  245. [self mockUIServiceRegistrationCompletion:nil];
  246. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  247. XCTestExpectation *expectation =
  248. [self expectationWithDescription:@"Persist sign out state succeeds."];
  249. [[self appDistribution] signInTesterWithCompletion:^(NSError *_Nullable error) {
  250. XCTAssertTrue([[self appDistribution] isTesterSignedIn]);
  251. XCTAssertNil(error);
  252. [expectation fulfill];
  253. }];
  254. [self waitForExpectations:@[ expectation ] timeout:5.0];
  255. [[self appDistribution] signOutTester];
  256. XCTAssertFalse([[self appDistribution] isTesterSignedIn]);
  257. [self verifyInstallationIdCompletion];
  258. [self verifyRegistrationCompletion];
  259. [self verifyFetchReleasesCompletion];
  260. }
  261. - (void)testFetchNewLatestReleaseSuccess {
  262. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  263. OCMStub([_mockMachO codeHash]).andReturn(@"this-is-old");
  264. XCTestExpectation *expectation =
  265. [self expectationWithDescription:@"Fetch latest release succeeds."];
  266. [[self appDistribution] fetchNewLatestRelease:^(FIRAppDistributionRelease *_Nullable release,
  267. NSError *_Nullable error) {
  268. XCTAssertNotNil(release);
  269. XCTAssertNil(error);
  270. [expectation fulfill];
  271. }];
  272. [self waitForExpectations:@[ expectation ] timeout:5.0];
  273. [self verifyFetchReleasesCompletion];
  274. }
  275. - (void)testFetchNewLatestReleaseNoNewRelease {
  276. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  277. OCMStub([_mockMachO codeHash]).andReturn(_mockCodeHash);
  278. OCMStub([[self appDistribution] getAppVersion]).andReturn(_mockDisplayVersion);
  279. OCMStub([[self appDistribution] getAppBuild]).andReturn(_mockBuildVersion);
  280. XCTestExpectation *expectation =
  281. [self expectationWithDescription:@"Fetch latest release with no new release succeeds."];
  282. [[self appDistribution] fetchNewLatestRelease:^(FIRAppDistributionRelease *_Nullable release,
  283. NSError *_Nullable error) {
  284. XCTAssertNil(release);
  285. XCTAssertNil(error);
  286. [expectation fulfill];
  287. }];
  288. [self waitForExpectations:@[ expectation ] timeout:5.0];
  289. [self verifyFetchReleasesCompletion];
  290. }
  291. - (void)testFetchNewLatestReleaseFailure {
  292. NSError *mockError =
  293. [NSError errorWithDomain:kFIRFADApiErrorDomain
  294. code:FIRFADApiErrorTimeout
  295. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  296. [self mockFetchReleasesCompletion:nil error:mockError];
  297. OCMStub([_mockMachO codeHash]).andReturn(@"this-is-old");
  298. XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch latest release fails."];
  299. [[self appDistribution] fetchNewLatestRelease:^(FIRAppDistributionRelease *_Nullable release,
  300. NSError *_Nullable error) {
  301. XCTAssertNil(release);
  302. XCTAssertNotNil(error);
  303. XCTAssertEqual([error code], FIRAppDistributionErrorNetworkFailure);
  304. XCTAssertEqual([error domain], FIRAppDistributionErrorDomain);
  305. [expectation fulfill];
  306. }];
  307. [self waitForExpectations:@[ expectation ] timeout:5.0];
  308. [self verifyFetchReleasesCompletion];
  309. OCMReject([_mockMachO codeHash]);
  310. }
  311. - (void)testFetchNewLatestReleaseUnauthenticatedFailure {
  312. NSError *mockError =
  313. [NSError errorWithDomain:kFIRFADApiErrorDomain
  314. code:FIRFADApiErrorUnauthenticated
  315. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  316. [self mockFetchReleasesCompletion:nil error:mockError];
  317. OCMStub([_mockMachO codeHash]).andReturn(@"this-is-old");
  318. [[GULUserDefaults standardUserDefaults] setBool:YES forKey:@"FIRFADSignInState"];
  319. XCTAssertTrue([[self appDistribution] isTesterSignedIn]);
  320. XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch latest release fails."];
  321. [[self appDistribution] fetchNewLatestRelease:^(FIRAppDistributionRelease *_Nullable release,
  322. NSError *_Nullable error) {
  323. XCTAssertNil(release);
  324. XCTAssertNotNil(error);
  325. XCTAssertEqual([error code], FIRAppDistributionErrorAuthenticationFailure);
  326. XCTAssertEqual([error domain], FIRAppDistributionErrorDomain);
  327. XCTAssertFalse([[self appDistribution] isTesterSignedIn]);
  328. [expectation fulfill];
  329. }];
  330. [self waitForExpectations:@[ expectation ] timeout:5.0];
  331. [self verifyFetchReleasesCompletion];
  332. OCMReject([_mockMachO codeHash]);
  333. }
  334. - (void)testFetchNewLatestReleaseUnauthorizedFailure {
  335. NSError *mockError =
  336. [NSError errorWithDomain:kFIRFADApiErrorDomain
  337. code:FIRFADApiErrorUnauthorized
  338. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  339. [self mockFetchReleasesCompletion:nil error:mockError];
  340. OCMStub([_mockMachO codeHash]).andReturn(@"this-is-old");
  341. [[GULUserDefaults standardUserDefaults] setBool:YES forKey:@"FIRFADSignInState"];
  342. XCTAssertTrue([[self appDistribution] isTesterSignedIn]);
  343. XCTestExpectation *expectation = [self expectationWithDescription:@"Fetch latest release fails."];
  344. [[self appDistribution] fetchNewLatestRelease:^(FIRAppDistributionRelease *_Nullable release,
  345. NSError *_Nullable error) {
  346. XCTAssertNil(release);
  347. XCTAssertNotNil(error);
  348. XCTAssertEqual([error code], FIRAppDistributionErrorAuthenticationFailure);
  349. XCTAssertEqual([error domain], FIRAppDistributionErrorDomain);
  350. XCTAssertFalse([[self appDistribution] isTesterSignedIn]);
  351. [expectation fulfill];
  352. }];
  353. [self waitForExpectations:@[ expectation ] timeout:5.0];
  354. [self verifyFetchReleasesCompletion];
  355. OCMReject([_mockMachO codeHash]);
  356. }
  357. - (void)testCheckForUpdateWithCompletionTesterSignedIn {
  358. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  359. [self mockUIServiceRegistrationCompletion:nil];
  360. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  361. [self mockUIServiceShowUICompletion:NO];
  362. // Sign in the tester
  363. XCTestExpectation *expectation =
  364. [self expectationWithDescription:@"Persist sign in state succeeds."];
  365. [[self appDistribution] signInTesterWithCompletion:^(NSError *_Nullable error) {
  366. XCTAssertNil(error);
  367. [expectation fulfill];
  368. }];
  369. [self waitForExpectations:@[ expectation ] timeout:5.0];
  370. XCTAssertTrue([[self appDistribution] isTesterSignedIn]);
  371. // Should Call check for update without calling the UIService
  372. XCTestExpectation *checkForUpdateExpectation =
  373. [self expectationWithDescription:@"Check for update does not prompt user"];
  374. [[self appDistribution]
  375. checkForUpdateWithCompletion:^(FIRAppDistributionRelease *_Nullable release,
  376. NSError *_Nullable error) {
  377. XCTAssertNil(error);
  378. XCTAssertNotNil(release);
  379. [checkForUpdateExpectation fulfill];
  380. }];
  381. [self waitForExpectations:@[ checkForUpdateExpectation ] timeout:5.0];
  382. [self rejectShowUICompletion];
  383. }
  384. - (void)testCheckForUpdateWithCompletionDifferentCodeHashClicksYesSuccess {
  385. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  386. [self mockUIServiceRegistrationCompletion:nil];
  387. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  388. [self mockUIServiceShowUICompletion:YES];
  389. OCMStub([[self appDistribution] getAppVersion]).andReturn(_mockDisplayVersion);
  390. OCMStub([[self appDistribution] getAppBuild]).andReturn(_mockBuildVersion);
  391. OCMStub([_mockMachO codeHash]).andReturn(@"this-is-old");
  392. XCTestExpectation *checkForUpdateExpectation =
  393. [self expectationWithDescription:@"Check for update does prompt user"];
  394. [[self appDistribution]
  395. checkForUpdateWithCompletion:^(FIRAppDistributionRelease *_Nullable release,
  396. NSError *_Nullable error) {
  397. XCTAssertNil(error);
  398. XCTAssertNotNil(release);
  399. [checkForUpdateExpectation fulfill];
  400. }];
  401. [self waitForExpectations:@[ checkForUpdateExpectation ] timeout:5.0];
  402. [self verifyShowUICompletion];
  403. OCMVerify([_mockMachO codeHash]);
  404. }
  405. - (void)testCheckForUpdateWithCompletionDifferentVersionAndBuildClicksYesSuccess {
  406. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  407. [self mockUIServiceRegistrationCompletion:nil];
  408. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  409. [self mockUIServiceShowUICompletion:YES];
  410. OCMStub([[self appDistribution] getAppVersion]).andReturn(@"different-version");
  411. OCMStub([[self appDistribution] getAppBuild]).andReturn(@"different-build");
  412. OCMReject([_mockMachO codeHash]);
  413. XCTestExpectation *checkForUpdateExpectation =
  414. [self expectationWithDescription:@"Check for update does prompt user"];
  415. [[self appDistribution]
  416. checkForUpdateWithCompletion:^(FIRAppDistributionRelease *_Nullable release,
  417. NSError *_Nullable error) {
  418. XCTAssertNil(error);
  419. XCTAssertNotNil(release);
  420. [checkForUpdateExpectation fulfill];
  421. }];
  422. [self waitForExpectations:@[ checkForUpdateExpectation ] timeout:5.0];
  423. [self verifyShowUICompletion];
  424. }
  425. - (void)testCheckForUpdateWithCompletionDifferentVersionClicksYesSuccess {
  426. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  427. [self mockUIServiceRegistrationCompletion:nil];
  428. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  429. [self mockUIServiceShowUICompletion:YES];
  430. OCMStub([[self appDistribution] getAppVersion]).andReturn(@"different-version");
  431. OCMStub([[self appDistribution] getAppBuild]).andReturn(_mockBuildVersion);
  432. OCMReject([_mockMachO codeHash]);
  433. XCTestExpectation *checkForUpdateExpectation =
  434. [self expectationWithDescription:@"Check for update does prompt user"];
  435. [[self appDistribution]
  436. checkForUpdateWithCompletion:^(FIRAppDistributionRelease *_Nullable release,
  437. NSError *_Nullable error) {
  438. XCTAssertNil(error);
  439. XCTAssertNotNil(release);
  440. [checkForUpdateExpectation fulfill];
  441. }];
  442. [self waitForExpectations:@[ checkForUpdateExpectation ] timeout:5.0];
  443. [self verifyShowUICompletion];
  444. }
  445. - (void)testCheckForUpdateWithCompletionDifferentBuildClicksYesSuccess {
  446. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  447. [self mockUIServiceRegistrationCompletion:nil];
  448. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  449. [self mockUIServiceShowUICompletion:YES];
  450. OCMStub([[self appDistribution] getAppVersion]).andReturn(_mockDisplayVersion);
  451. OCMStub([[self appDistribution] getAppBuild]).andReturn(@"different-build");
  452. OCMReject([_mockMachO codeHash]);
  453. XCTestExpectation *checkForUpdateExpectation =
  454. [self expectationWithDescription:@"Check for update does prompt user"];
  455. [[self appDistribution]
  456. checkForUpdateWithCompletion:^(FIRAppDistributionRelease *_Nullable release,
  457. NSError *_Nullable error) {
  458. XCTAssertNil(error);
  459. XCTAssertNotNil(release);
  460. [checkForUpdateExpectation fulfill];
  461. }];
  462. [self waitForExpectations:@[ checkForUpdateExpectation ] timeout:5.0];
  463. [self verifyShowUICompletion];
  464. }
  465. - (void)testCheckForUpdateWithCompletionClicksYesFailure {
  466. NSError *mockError =
  467. [NSError errorWithDomain:@"this.is.fake"
  468. code:3
  469. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  470. [self mockInstallationIdCompletion:_mockInstallationId error:mockError];
  471. [self mockUIServiceRegistrationCompletion:nil];
  472. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  473. [self mockUIServiceShowUICompletion:YES];
  474. XCTestExpectation *checkForUpdateExpectation =
  475. [self expectationWithDescription:@"Check for update does prompt user"];
  476. [[self appDistribution]
  477. checkForUpdateWithCompletion:^(FIRAppDistributionRelease *_Nullable release,
  478. NSError *_Nullable error) {
  479. XCTAssertNotNil(error);
  480. XCTAssertNil(release);
  481. [checkForUpdateExpectation fulfill];
  482. }];
  483. [self waitForExpectations:@[ checkForUpdateExpectation ] timeout:5.0];
  484. [self verifyShowUICompletion];
  485. }
  486. - (void)testCheckForUpdateWithCompletionClicksNo {
  487. [self mockInstallationIdCompletion:_mockInstallationId error:nil];
  488. [self mockUIServiceRegistrationCompletion:nil];
  489. [self mockFetchReleasesCompletion:_mockReleases error:nil];
  490. [self mockUIServiceShowUICompletion:NO];
  491. XCTestExpectation *checkForUpdateExpectation =
  492. [self expectationWithDescription:@"Check for update does prompt user"];
  493. [[self appDistribution]
  494. checkForUpdateWithCompletion:^(FIRAppDistributionRelease *_Nullable release,
  495. NSError *_Nullable error) {
  496. XCTAssertNotNil(error);
  497. XCTAssertEqual([error code], FIRAppDistributionErrorAuthenticationCancelled);
  498. XCTAssertNil(release);
  499. [checkForUpdateExpectation fulfill];
  500. }];
  501. [self waitForExpectations:@[ checkForUpdateExpectation ] timeout:5.0];
  502. [self verifyShowUICompletion];
  503. }
  504. - (void)testHandleFetchReleasesErrorTimeout {
  505. NSError *mockError =
  506. [NSError errorWithDomain:kFIRFADApiErrorDomain
  507. code:FIRFADApiErrorTimeout
  508. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  509. NSError *handledError = [[self appDistribution] mapFetchReleasesError:mockError];
  510. XCTAssertNotNil(handledError);
  511. XCTAssertEqual([handledError code], FIRAppDistributionErrorNetworkFailure);
  512. XCTAssertEqual([handledError domain], FIRAppDistributionErrorDomain);
  513. }
  514. - (void)testHandleFetchReleasesErrorUnauthenticated {
  515. NSError *mockError =
  516. [NSError errorWithDomain:kFIRFADApiErrorDomain
  517. code:FIRFADApiErrorUnauthenticated
  518. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  519. NSError *handledError = [[self appDistribution] mapFetchReleasesError:mockError];
  520. XCTAssertNotNil(handledError);
  521. XCTAssertEqual([handledError code], FIRAppDistributionErrorAuthenticationFailure);
  522. XCTAssertEqual([handledError domain], FIRAppDistributionErrorDomain);
  523. }
  524. - (void)testHandleFetchReleasesErrorUnauthorized {
  525. NSError *mockError =
  526. [NSError errorWithDomain:kFIRFADApiErrorDomain
  527. code:FIRFADApiErrorUnauthorized
  528. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  529. NSError *handledError = [[self appDistribution] mapFetchReleasesError:mockError];
  530. XCTAssertNotNil(handledError);
  531. XCTAssertEqual([handledError code], FIRAppDistributionErrorAuthenticationFailure);
  532. XCTAssertEqual([handledError domain], FIRAppDistributionErrorDomain);
  533. }
  534. - (void)testHandleFetchReleasesErrorTokenGenerationFailure {
  535. NSError *mockError =
  536. [NSError errorWithDomain:kFIRFADApiErrorDomain
  537. code:FIRFADApiTokenGenerationFailure
  538. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  539. NSError *handledError = [[self appDistribution] mapFetchReleasesError:mockError];
  540. XCTAssertNotNil(handledError);
  541. XCTAssertEqual([handledError code], FIRAppDistributionErrorAuthenticationFailure);
  542. XCTAssertEqual([handledError domain], FIRAppDistributionErrorDomain);
  543. }
  544. - (void)testHandleFetchReleasesErrorInstallationIdentifierFailure {
  545. NSError *mockError =
  546. [NSError errorWithDomain:kFIRFADApiErrorDomain
  547. code:FIRFADApiInstallationIdentifierError
  548. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  549. NSError *handledError = [[self appDistribution] mapFetchReleasesError:mockError];
  550. XCTAssertNotNil(handledError);
  551. XCTAssertEqual([handledError code], FIRAppDistributionErrorAuthenticationFailure);
  552. XCTAssertEqual([handledError domain], FIRAppDistributionErrorDomain);
  553. }
  554. - (void)testHandleFetchReleasesErrorNotFound {
  555. NSError *mockError =
  556. [NSError errorWithDomain:kFIRFADApiErrorDomain
  557. code:FIRFADApiErrorNotFound
  558. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  559. NSError *handledError = [[self appDistribution] mapFetchReleasesError:mockError];
  560. XCTAssertNotNil(handledError);
  561. XCTAssertEqual([handledError code], FIRAppDistributionErrorAuthenticationFailure);
  562. XCTAssertEqual([handledError domain], FIRAppDistributionErrorDomain);
  563. }
  564. - (void)testHandleFetchReleasesErrorApiDomainErrorUnknown {
  565. NSError *mockError =
  566. [NSError errorWithDomain:kFIRFADApiErrorDomain
  567. code:209
  568. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  569. NSError *handledError = [[self appDistribution] mapFetchReleasesError:mockError];
  570. XCTAssertNotNil(handledError);
  571. XCTAssertEqual([handledError code], FIRAppDistributionErrorUnknown);
  572. XCTAssertEqual([handledError domain], FIRAppDistributionErrorDomain);
  573. }
  574. - (void)testHandleFetchReleasesErrorUnknownDomainError {
  575. NSError *mockError =
  576. [NSError errorWithDomain:@"this.is.not.an.api.failure"
  577. code:4
  578. userInfo:@{NSLocalizedDescriptionKey : @"This is unfortunate."}];
  579. NSError *handledError = [[self appDistribution] mapFetchReleasesError:mockError];
  580. XCTAssertNotNil(handledError);
  581. XCTAssertEqual([handledError code], FIRAppDistributionErrorUnknown);
  582. XCTAssertEqual([handledError domain], FIRAppDistributionErrorDomain);
  583. }
  584. @end