GIDEMMErrorHandlerTest.m 17 KB

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