GIDEMMErrorHandlerTest.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. // Copyright 2021 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 <UIKit/UIKit.h>
  15. #import <XCTest/XCTest.h>
  16. #import "GoogleSignIn/Sources/GIDEMMErrorHandler.h"
  17. #import "GoogleSignIn/Sources/GIDSignInStrings.h"
  18. #ifdef SWIFT_PACKAGE
  19. @import GoogleUtilities_MethodSwizzler;
  20. @import GoogleUtilities_SwizzlerTestHelpers;
  21. @import OCMock;
  22. #else
  23. #import <GoogleUtilities/GULSwizzler.h>
  24. #import <GoogleUtilities/GULSwizzler+Unswizzle.h>
  25. #import <OCMock/OCMock.h>
  26. #endif
  27. NS_ASSUME_NONNULL_BEGIN
  28. // Addtional methods added to UIAlertAction for testing.
  29. @interface UIAlertAction (Testing)
  30. // Returns the handler block for this alert action.
  31. - (void (^)(UIAlertAction *))actionHandler;
  32. @end
  33. @implementation UIAlertAction (Testing)
  34. - (void (^)(UIAlertAction *))actionHandler {
  35. return [self valueForKey:@"handler"];
  36. }
  37. @end
  38. // Unit test for GIDEMMErrorHandler.
  39. @interface GIDEMMErrorHandlerTest : XCTestCase
  40. @end
  41. @implementation GIDEMMErrorHandlerTest {
  42. // Whether or not the current device runs on iOS 10.
  43. BOOL _isIOS10;
  44. // Whether key window has been set.
  45. BOOL _keyWindowSet;
  46. // The view controller that has been presented, if any.
  47. UIViewController *_presentedViewController;
  48. }
  49. - (void)setUp {
  50. [super setUp];
  51. _isIOS10 = [UIDevice currentDevice].systemVersion.integerValue == 10;
  52. _keyWindowSet = NO;
  53. _presentedViewController = nil;
  54. [GULSwizzler swizzleClass:[UIWindow class]
  55. selector:@selector(makeKeyAndVisible)
  56. isClassSelector:NO
  57. withBlock:^() { _keyWindowSet = YES; }];
  58. [GULSwizzler swizzleClass:[UIViewController class]
  59. selector:@selector(presentViewController:animated:completion:)
  60. isClassSelector:NO
  61. withBlock:^(id obj, id arg1) { _presentedViewController = arg1; }];
  62. [GULSwizzler swizzleClass:[GIDSignInStrings class]
  63. selector:@selector(localizedStringForKey:text:)
  64. isClassSelector:YES
  65. withBlock:^(id obj, NSString *key, NSString *text) { return text; }];
  66. }
  67. - (void)tearDown {
  68. [GULSwizzler unswizzleClass:[UIWindow class]
  69. selector:@selector(makeKeyAndVisible)
  70. isClassSelector:NO];
  71. [GULSwizzler unswizzleClass:[UIViewController class]
  72. selector:@selector(presentViewController:animated:completion:)
  73. isClassSelector:NO];
  74. [GULSwizzler unswizzleClass:[GIDSignInStrings class]
  75. selector:@selector(localizedStringForKey:text:)
  76. isClassSelector:YES];
  77. _presentedViewController = nil;
  78. [super tearDown];
  79. }
  80. // Expects opening a particular URL string in performing an action.
  81. - (void)expectOpenURLString:(NSString *)urlString inAction:(void (^)())action {
  82. // Swizzle and mock [UIApplication sharedApplication] since it is unavailable in unit tests.
  83. id mockApplication = OCMStrictClassMock([UIApplication class]);
  84. [GULSwizzler swizzleClass:[UIApplication class]
  85. selector:@selector(sharedApplication)
  86. isClassSelector:YES
  87. withBlock:^() { return mockApplication; }];
  88. [[mockApplication expect] openURL:[NSURL URLWithString:urlString]];
  89. action();
  90. [mockApplication verify];
  91. [GULSwizzler unswizzleClass:[UIApplication class]
  92. selector:@selector(sharedApplication)
  93. isClassSelector:YES];
  94. }
  95. // Verifies that the handler doesn't handle non-exist error.
  96. - (void)testNoError {
  97. __block BOOL completionCalled = NO;
  98. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:@{ @"abc" : @123 }
  99. completion:^() {
  100. completionCalled = YES;
  101. }];
  102. XCTAssertFalse(result);
  103. XCTAssertTrue(completionCalled);
  104. XCTAssertFalse(_keyWindowSet);
  105. XCTAssertNil(_presentedViewController);
  106. }
  107. // Verifies that the handler doesn't handle non-EMM error.
  108. - (void)testNoEMMError {
  109. __block BOOL completionCalled = NO;
  110. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"invalid_token" };
  111. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  112. completion:^() {
  113. completionCalled = YES;
  114. }];
  115. XCTAssertFalse(result);
  116. XCTAssertTrue(completionCalled);
  117. XCTAssertFalse(_keyWindowSet);
  118. XCTAssertNil(_presentedViewController);
  119. }
  120. // TODO(petea): Figure out why we have a race condition for the first of these to run.
  121. #if !SWIFT_PACKAGE
  122. // Verifies that the handler handles general EMM error with user tapping 'OK'.
  123. - (void)testGeneralEMMErrorOK {
  124. __block BOOL completionCalled = NO;
  125. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"emm_something_wrong" };
  126. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  127. completion:^() {
  128. completionCalled = YES;
  129. }];
  130. if (![UIAlertController class]) {
  131. XCTAssertFalse(result);
  132. XCTAssertTrue(completionCalled);
  133. XCTAssertFalse(_keyWindowSet);
  134. XCTAssertNil(_presentedViewController);
  135. return;
  136. }
  137. XCTAssertTrue(result);
  138. XCTAssertFalse(completionCalled);
  139. XCTAssertFalse(_keyWindowSet);
  140. XCTAssertNil(_presentedViewController);
  141. // Should handle no more error while the previous one is being handled.
  142. __block BOOL secondCompletionCalled = NO;
  143. BOOL secondResult = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  144. completion:^() {
  145. secondCompletionCalled = YES;
  146. }];
  147. XCTAssertFalse(secondResult);
  148. XCTAssertTrue(secondCompletionCalled);
  149. XCTAssertFalse(_keyWindowSet);
  150. XCTAssertNil(_presentedViewController);
  151. // Wait for the code under test to be executed on the main thread.
  152. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  153. dispatch_async(dispatch_get_main_queue(), ^() {
  154. [expectation fulfill];
  155. });
  156. [self waitForExpectationsWithTimeout:1 handler:nil];
  157. XCTAssertFalse(completionCalled);
  158. XCTAssertTrue(_keyWindowSet);
  159. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  160. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  161. XCTAssertNotNil(alert.title);
  162. XCTAssertNotNil(alert.message);
  163. XCTAssertEqual(alert.actions.count, 1);
  164. // Pretend to touch the "OK" button.
  165. UIAlertAction *action = alert.actions[0];
  166. XCTAssertEqualObjects(action.title, @"OK");
  167. action.actionHandler(action);
  168. XCTAssertTrue(completionCalled);
  169. }
  170. // Verifies that the handler handles EMM screenlock required error with user tapping 'Cancel'.
  171. - (void)testScreenlockRequiredCancel {
  172. if (_isIOS10) {
  173. // The dialog is different on iOS 10.
  174. return;
  175. }
  176. __block BOOL completionCalled = NO;
  177. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"emm_passcode_required" };
  178. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  179. completion:^() {
  180. completionCalled = YES;
  181. }];
  182. if (![UIAlertController class]) {
  183. XCTAssertFalse(result);
  184. XCTAssertTrue(completionCalled);
  185. XCTAssertFalse(_keyWindowSet);
  186. XCTAssertNil(_presentedViewController);
  187. return;
  188. }
  189. XCTAssertTrue(result);
  190. XCTAssertFalse(completionCalled);
  191. XCTAssertFalse(_keyWindowSet);
  192. XCTAssertNil(_presentedViewController);
  193. // Wait for the code under test to be executed on the main thread.
  194. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  195. dispatch_async(dispatch_get_main_queue(), ^() {
  196. [expectation fulfill];
  197. });
  198. [self waitForExpectationsWithTimeout:1 handler:nil];
  199. XCTAssertFalse(completionCalled);
  200. XCTAssertTrue(_keyWindowSet);
  201. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  202. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  203. XCTAssertNotNil(alert.title);
  204. XCTAssertNotNil(alert.message);
  205. XCTAssertEqual(alert.actions.count, 2);
  206. // Pretend to touch the "Cancel" button.
  207. UIAlertAction *action = alert.actions[0];
  208. XCTAssertEqualObjects(action.title, @"Cancel");
  209. action.actionHandler(action);
  210. XCTAssertTrue(completionCalled);
  211. }
  212. // Verifies that the handler handles EMM screenlock required error with user tapping 'Settings'.
  213. - (void)testScreenlockRequiredSettings {
  214. if (_isIOS10) {
  215. // The dialog is different on iOS 10.
  216. return;
  217. }
  218. __block BOOL completionCalled = NO;
  219. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"emm_passcode_required" };
  220. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  221. completion:^() {
  222. completionCalled = YES;
  223. }];
  224. if (![UIAlertController class]) {
  225. XCTAssertFalse(result);
  226. XCTAssertTrue(completionCalled);
  227. XCTAssertFalse(_keyWindowSet);
  228. XCTAssertNil(_presentedViewController);
  229. return;
  230. }
  231. XCTAssertTrue(result);
  232. XCTAssertFalse(completionCalled);
  233. XCTAssertFalse(_keyWindowSet);
  234. XCTAssertNil(_presentedViewController);
  235. // Wait for the code under test to be executed on the main thread.
  236. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  237. dispatch_async(dispatch_get_main_queue(), ^() {
  238. [expectation fulfill];
  239. });
  240. [self waitForExpectationsWithTimeout:1 handler:nil];
  241. XCTAssertFalse(completionCalled);
  242. XCTAssertTrue(_keyWindowSet);
  243. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  244. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  245. XCTAssertNotNil(alert.title);
  246. XCTAssertNotNil(alert.message);
  247. XCTAssertEqual(alert.actions.count, 2);
  248. // Pretend to touch the "Settings" button.
  249. UIAlertAction *action = alert.actions[1];
  250. XCTAssertEqualObjects(action.title, @"Settings");
  251. [self expectOpenURLString:UIApplicationOpenSettingsURLString inAction:^() {
  252. action.actionHandler(action);
  253. }];
  254. XCTAssertTrue(completionCalled);
  255. }
  256. - (void)testScreenlockRequiredOkOnIOS10 {
  257. if (!_isIOS10) {
  258. // A more useful dialog is used for other iOS versions.
  259. return;
  260. }
  261. __block BOOL completionCalled = NO;
  262. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"emm_passcode_required" };
  263. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  264. completion:^() {
  265. completionCalled = YES;
  266. }];
  267. if (![UIAlertController class]) {
  268. XCTAssertFalse(result);
  269. XCTAssertTrue(completionCalled);
  270. XCTAssertFalse(_keyWindowSet);
  271. XCTAssertNil(_presentedViewController);
  272. return;
  273. }
  274. XCTAssertTrue(result);
  275. XCTAssertFalse(completionCalled);
  276. XCTAssertFalse(_keyWindowSet);
  277. XCTAssertNil(_presentedViewController);
  278. // Wait for the code under test to be executed on the main thread.
  279. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  280. dispatch_async(dispatch_get_main_queue(), ^() {
  281. [expectation fulfill];
  282. });
  283. [self waitForExpectationsWithTimeout:1 handler:nil];
  284. XCTAssertFalse(completionCalled);
  285. XCTAssertTrue(_keyWindowSet);
  286. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  287. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  288. XCTAssertNotNil(alert.title);
  289. XCTAssertNotNil(alert.message);
  290. XCTAssertEqual(alert.actions.count, 1);
  291. // Pretend to touch the "OK" button.
  292. UIAlertAction *action = alert.actions[0];
  293. XCTAssertEqualObjects(action.title, @"OK");
  294. action.actionHandler(action);
  295. XCTAssertTrue(completionCalled);
  296. }
  297. // Verifies that the handler handles EMM app verification required error without a URL.
  298. - (void)testAppVerificationNoURL {
  299. __block BOOL completionCalled = NO;
  300. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"emm_app_verification_required" };
  301. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  302. completion:^() {
  303. completionCalled = YES;
  304. }];
  305. if (![UIAlertController class]) {
  306. XCTAssertFalse(result);
  307. XCTAssertTrue(completionCalled);
  308. XCTAssertFalse(_keyWindowSet);
  309. XCTAssertNil(_presentedViewController);
  310. return;
  311. }
  312. XCTAssertTrue(result);
  313. XCTAssertFalse(completionCalled);
  314. XCTAssertFalse(_keyWindowSet);
  315. XCTAssertNil(_presentedViewController);
  316. // Wait for the code under test to be executed on the main thread.
  317. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  318. dispatch_async(dispatch_get_main_queue(), ^() {
  319. [expectation fulfill];
  320. });
  321. [self waitForExpectationsWithTimeout:1 handler:nil];
  322. XCTAssertFalse(completionCalled);
  323. XCTAssertTrue(_keyWindowSet);
  324. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  325. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  326. XCTAssertNotNil(alert.title);
  327. XCTAssertNotNil(alert.message);
  328. XCTAssertEqual(alert.actions.count, 1);
  329. // Pretend to touch the "OK" button.
  330. UIAlertAction *action = alert.actions[0];
  331. XCTAssertEqualObjects(action.title, @"OK");
  332. action.actionHandler(action);
  333. XCTAssertTrue(completionCalled);
  334. }
  335. // Verifies that the handler handles EMM app verification required error user tapping 'Cancel'.
  336. - (void)testAppVerificationCancel {
  337. __block BOOL completionCalled = NO;
  338. NSDictionary<NSString *, NSString *> *response =
  339. @{ @"error" : @"emm_app_verification_required: https://host.domain/path" };
  340. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  341. completion:^() {
  342. completionCalled = YES;
  343. }];
  344. if (![UIAlertController class]) {
  345. XCTAssertFalse(result);
  346. XCTAssertTrue(completionCalled);
  347. XCTAssertFalse(_keyWindowSet);
  348. XCTAssertNil(_presentedViewController);
  349. return;
  350. }
  351. XCTAssertTrue(result);
  352. XCTAssertFalse(completionCalled);
  353. XCTAssertFalse(_keyWindowSet);
  354. XCTAssertNil(_presentedViewController);
  355. // Wait for the code under test to be executed on the main thread.
  356. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  357. dispatch_async(dispatch_get_main_queue(), ^() {
  358. [expectation fulfill];
  359. });
  360. [self waitForExpectationsWithTimeout:1 handler:nil];
  361. XCTAssertFalse(completionCalled);
  362. XCTAssertTrue(_keyWindowSet);
  363. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  364. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  365. XCTAssertNotNil(alert.title);
  366. XCTAssertNotNil(alert.message);
  367. XCTAssertEqual(alert.actions.count, 2);
  368. // Pretend to touch the "Cancel" button.
  369. UIAlertAction *action = alert.actions[0];
  370. XCTAssertEqualObjects(action.title, @"Cancel");
  371. action.actionHandler(action);
  372. XCTAssertTrue(completionCalled);
  373. }
  374. // Verifies that the handler handles EMM app verification required error user tapping 'Connect'.
  375. - (void)testAppVerificationConnect {
  376. __block BOOL completionCalled = NO;
  377. NSDictionary<NSString *, NSString *> *response =
  378. @{ @"error" : @"emm_app_verification_required: https://host.domain/path" };
  379. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  380. completion:^() {
  381. completionCalled = YES;
  382. }];
  383. if (![UIAlertController class]) {
  384. XCTAssertFalse(result);
  385. XCTAssertTrue(completionCalled);
  386. XCTAssertFalse(_keyWindowSet);
  387. XCTAssertNil(_presentedViewController);
  388. return;
  389. }
  390. XCTAssertTrue(result);
  391. XCTAssertFalse(completionCalled);
  392. XCTAssertFalse(_keyWindowSet);
  393. XCTAssertNil(_presentedViewController);
  394. // Wait for the code under test to be executed on the main thread.
  395. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  396. dispatch_async(dispatch_get_main_queue(), ^() {
  397. [expectation fulfill];
  398. });
  399. [self waitForExpectationsWithTimeout:1 handler:nil];
  400. XCTAssertFalse(completionCalled);
  401. XCTAssertTrue(_keyWindowSet);
  402. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  403. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  404. XCTAssertNotNil(alert.title);
  405. XCTAssertNotNil(alert.message);
  406. XCTAssertEqual(alert.actions.count, 2);
  407. // Pretend to touch the "Connect" button.
  408. UIAlertAction *action = alert.actions[1];
  409. XCTAssertEqualObjects(action.title, @"Connect");
  410. [self expectOpenURLString:@"https://host.domain/path" inAction:^() {
  411. action.actionHandler(action);
  412. }];
  413. XCTAssertTrue(completionCalled);
  414. }
  415. // Verifies that the handler can handle sequential errors independently.
  416. - (void)testSequentialErrors {
  417. [self testGeneralEMMErrorOK];
  418. _keyWindowSet = NO;
  419. _presentedViewController = nil;
  420. [self testScreenlockRequiredCancel];
  421. }
  422. #endif
  423. @end
  424. NS_ASSUME_NONNULL_END