GIDEMMErrorHandlerTest.m 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  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. UIWindow *fakeKeyWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  57. [GULSwizzler swizzleClass:[GIDEMMErrorHandler class]
  58. selector:@selector(keyWindow)
  59. isClassSelector:NO
  60. withBlock:^() { return fakeKeyWindow; }];
  61. [GULSwizzler swizzleClass:[UIWindow class]
  62. selector:@selector(makeKeyAndVisible)
  63. isClassSelector:NO
  64. withBlock:^() { self->_keyWindowSet = YES; }];
  65. [GULSwizzler swizzleClass:[UIViewController class]
  66. selector:@selector(presentViewController:animated:completion:)
  67. isClassSelector:NO
  68. withBlock:^(id obj, id arg1) { self->_presentedViewController = arg1; }];
  69. [GULSwizzler swizzleClass:[GIDSignInStrings class]
  70. selector:@selector(localizedStringForKey:text:)
  71. isClassSelector:YES
  72. withBlock:^(id obj, NSString *key, NSString *text) { return text; }];
  73. }
  74. - (void)tearDown {
  75. [GULSwizzler unswizzleClass:[GIDEMMErrorHandler class]
  76. selector:@selector(keyWindow)
  77. isClassSelector:NO];
  78. [GULSwizzler unswizzleClass:[UIWindow class]
  79. selector:@selector(makeKeyAndVisible)
  80. isClassSelector:NO];
  81. [GULSwizzler unswizzleClass:[UIViewController class]
  82. selector:@selector(presentViewController:animated:completion:)
  83. isClassSelector:NO];
  84. [GULSwizzler unswizzleClass:[GIDSignInStrings class]
  85. selector:@selector(localizedStringForKey:text:)
  86. isClassSelector:YES];
  87. _presentedViewController = nil;
  88. [super tearDown];
  89. }
  90. // Expects opening a particular URL string in performing an action.
  91. - (void)expectOpenURLString:(NSString *)urlString inAction:(void (^)(void))action {
  92. // Swizzle and mock [UIApplication sharedApplication] since it is unavailable in unit tests.
  93. id mockApplication = OCMStrictClassMock([UIApplication class]);
  94. [GULSwizzler swizzleClass:[UIApplication class]
  95. selector:@selector(sharedApplication)
  96. isClassSelector:YES
  97. withBlock:^() { return mockApplication; }];
  98. [[mockApplication expect] openURL:[NSURL URLWithString:urlString] options:@{} completionHandler:nil];
  99. action();
  100. [mockApplication verify];
  101. [GULSwizzler unswizzleClass:[UIApplication class]
  102. selector:@selector(sharedApplication)
  103. isClassSelector:YES];
  104. }
  105. // Verifies that the handler doesn't handle non-exist error.
  106. - (void)testNoError {
  107. __block BOOL completionCalled = NO;
  108. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:@{ @"abc" : @123 }
  109. completion:^() {
  110. completionCalled = YES;
  111. }];
  112. XCTAssertFalse(result);
  113. XCTAssertTrue(completionCalled);
  114. XCTAssertFalse(_keyWindowSet);
  115. XCTAssertNil(_presentedViewController);
  116. }
  117. // Verifies that the handler doesn't handle non-EMM error.
  118. - (void)testNoEMMError {
  119. __block BOOL completionCalled = NO;
  120. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"invalid_token" };
  121. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  122. completion:^() {
  123. completionCalled = YES;
  124. }];
  125. XCTAssertFalse(result);
  126. XCTAssertTrue(completionCalled);
  127. XCTAssertFalse(_keyWindowSet);
  128. XCTAssertNil(_presentedViewController);
  129. }
  130. // Verifies that the handler handles general EMM error with user tapping 'OK'.
  131. - (void)testGeneralEMMErrorOK {
  132. __block BOOL completionCalled = NO;
  133. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"emm_something_wrong" };
  134. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  135. completion:^() {
  136. completionCalled = YES;
  137. }];
  138. if (![UIAlertController class]) {
  139. XCTAssertFalse(result);
  140. XCTAssertTrue(completionCalled);
  141. XCTAssertFalse(_keyWindowSet);
  142. XCTAssertNil(_presentedViewController);
  143. return;
  144. }
  145. XCTAssertTrue(result);
  146. XCTAssertFalse(completionCalled);
  147. XCTAssertFalse(_keyWindowSet);
  148. XCTAssertNil(_presentedViewController);
  149. // Should handle no more error while the previous one is being handled.
  150. __block BOOL secondCompletionCalled = NO;
  151. BOOL secondResult = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  152. completion:^() {
  153. secondCompletionCalled = YES;
  154. }];
  155. XCTAssertFalse(secondResult);
  156. XCTAssertTrue(secondCompletionCalled);
  157. XCTAssertFalse(_keyWindowSet);
  158. XCTAssertNil(_presentedViewController);
  159. // Wait for the code under test to be executed on the main thread.
  160. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  161. dispatch_async(dispatch_get_main_queue(), ^() {
  162. [expectation fulfill];
  163. });
  164. [self waitForExpectationsWithTimeout:1 handler:nil];
  165. XCTAssertFalse(completionCalled);
  166. XCTAssertTrue(_keyWindowSet);
  167. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  168. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  169. XCTAssertNotNil(alert.title);
  170. XCTAssertNotNil(alert.message);
  171. XCTAssertEqual(alert.actions.count, 1);
  172. // Pretend to touch the "OK" button.
  173. UIAlertAction *action = alert.actions[0];
  174. XCTAssertEqualObjects(action.title, @"OK");
  175. action.actionHandler(action);
  176. XCTAssertTrue(completionCalled);
  177. }
  178. // Verifies that the handler handles EMM screenlock required error with user tapping 'Cancel'.
  179. - (void)testScreenlockRequiredCancel {
  180. if (_isIOS10) {
  181. // The dialog is different on iOS 10.
  182. return;
  183. }
  184. __block BOOL completionCalled = NO;
  185. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"emm_passcode_required" };
  186. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  187. completion:^() {
  188. completionCalled = YES;
  189. }];
  190. if (![UIAlertController class]) {
  191. XCTAssertFalse(result);
  192. XCTAssertTrue(completionCalled);
  193. XCTAssertFalse(_keyWindowSet);
  194. XCTAssertNil(_presentedViewController);
  195. return;
  196. }
  197. XCTAssertTrue(result);
  198. XCTAssertFalse(completionCalled);
  199. XCTAssertFalse(_keyWindowSet);
  200. XCTAssertNil(_presentedViewController);
  201. // Wait for the code under test to be executed on the main thread.
  202. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  203. dispatch_async(dispatch_get_main_queue(), ^() {
  204. [expectation fulfill];
  205. });
  206. [self waitForExpectationsWithTimeout:1 handler:nil];
  207. XCTAssertFalse(completionCalled);
  208. XCTAssertTrue(_keyWindowSet);
  209. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  210. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  211. XCTAssertNotNil(alert.title);
  212. XCTAssertNotNil(alert.message);
  213. XCTAssertEqual(alert.actions.count, 2);
  214. // Pretend to touch the "Cancel" button.
  215. UIAlertAction *action = alert.actions[0];
  216. XCTAssertEqualObjects(action.title, @"Cancel");
  217. action.actionHandler(action);
  218. XCTAssertTrue(completionCalled);
  219. }
  220. // Verifies that the handler handles EMM screenlock required error with user tapping 'Settings'.
  221. - (void)testScreenlockRequiredSettings {
  222. if (_isIOS10) {
  223. // The dialog is different on iOS 10.
  224. return;
  225. }
  226. __block BOOL completionCalled = NO;
  227. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"emm_passcode_required" };
  228. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  229. completion:^() {
  230. completionCalled = YES;
  231. }];
  232. if (![UIAlertController class]) {
  233. XCTAssertFalse(result);
  234. XCTAssertTrue(completionCalled);
  235. XCTAssertFalse(_keyWindowSet);
  236. XCTAssertNil(_presentedViewController);
  237. return;
  238. }
  239. XCTAssertTrue(result);
  240. XCTAssertFalse(completionCalled);
  241. XCTAssertFalse(_keyWindowSet);
  242. XCTAssertNil(_presentedViewController);
  243. // Wait for the code under test to be executed on the main thread.
  244. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  245. dispatch_async(dispatch_get_main_queue(), ^() {
  246. [expectation fulfill];
  247. });
  248. [self waitForExpectationsWithTimeout:1 handler:nil];
  249. XCTAssertFalse(completionCalled);
  250. XCTAssertTrue(_keyWindowSet);
  251. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  252. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  253. XCTAssertNotNil(alert.title);
  254. XCTAssertNotNil(alert.message);
  255. XCTAssertEqual(alert.actions.count, 2);
  256. // Pretend to touch the "Settings" button.
  257. UIAlertAction *action = alert.actions[1];
  258. XCTAssertEqualObjects(action.title, @"Settings");
  259. [self expectOpenURLString:UIApplicationOpenSettingsURLString inAction:^() {
  260. action.actionHandler(action);
  261. }];
  262. XCTAssertTrue(completionCalled);
  263. }
  264. - (void)testScreenlockRequiredOkOnIOS10 {
  265. if (!_isIOS10) {
  266. // A more useful dialog is used for other iOS versions.
  267. return;
  268. }
  269. __block BOOL completionCalled = NO;
  270. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"emm_passcode_required" };
  271. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  272. completion:^() {
  273. completionCalled = YES;
  274. }];
  275. if (![UIAlertController class]) {
  276. XCTAssertFalse(result);
  277. XCTAssertTrue(completionCalled);
  278. XCTAssertFalse(_keyWindowSet);
  279. XCTAssertNil(_presentedViewController);
  280. return;
  281. }
  282. XCTAssertTrue(result);
  283. XCTAssertFalse(completionCalled);
  284. XCTAssertFalse(_keyWindowSet);
  285. XCTAssertNil(_presentedViewController);
  286. // Wait for the code under test to be executed on the main thread.
  287. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  288. dispatch_async(dispatch_get_main_queue(), ^() {
  289. [expectation fulfill];
  290. });
  291. [self waitForExpectationsWithTimeout:1 handler:nil];
  292. XCTAssertFalse(completionCalled);
  293. XCTAssertTrue(_keyWindowSet);
  294. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  295. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  296. XCTAssertNotNil(alert.title);
  297. XCTAssertNotNil(alert.message);
  298. XCTAssertEqual(alert.actions.count, 1);
  299. // Pretend to touch the "OK" button.
  300. UIAlertAction *action = alert.actions[0];
  301. XCTAssertEqualObjects(action.title, @"OK");
  302. action.actionHandler(action);
  303. XCTAssertTrue(completionCalled);
  304. }
  305. // Verifies that the handler handles EMM app verification required error without a URL.
  306. - (void)testAppVerificationNoURL {
  307. __block BOOL completionCalled = NO;
  308. NSDictionary<NSString *, NSString *> *response = @{ @"error" : @"emm_app_verification_required" };
  309. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  310. completion:^() {
  311. completionCalled = YES;
  312. }];
  313. if (![UIAlertController class]) {
  314. XCTAssertFalse(result);
  315. XCTAssertTrue(completionCalled);
  316. XCTAssertFalse(_keyWindowSet);
  317. XCTAssertNil(_presentedViewController);
  318. return;
  319. }
  320. XCTAssertTrue(result);
  321. XCTAssertFalse(completionCalled);
  322. XCTAssertFalse(_keyWindowSet);
  323. XCTAssertNil(_presentedViewController);
  324. // Wait for the code under test to be executed on the main thread.
  325. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  326. dispatch_async(dispatch_get_main_queue(), ^() {
  327. [expectation fulfill];
  328. });
  329. [self waitForExpectationsWithTimeout:1 handler:nil];
  330. XCTAssertFalse(completionCalled);
  331. XCTAssertTrue(_keyWindowSet);
  332. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  333. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  334. XCTAssertNotNil(alert.title);
  335. XCTAssertNotNil(alert.message);
  336. XCTAssertEqual(alert.actions.count, 1);
  337. // Pretend to touch the "OK" button.
  338. UIAlertAction *action = alert.actions[0];
  339. XCTAssertEqualObjects(action.title, @"OK");
  340. action.actionHandler(action);
  341. XCTAssertTrue(completionCalled);
  342. }
  343. // Verifies that the handler handles EMM app verification required error user tapping 'Cancel'.
  344. - (void)testAppVerificationCancel {
  345. __block BOOL completionCalled = NO;
  346. NSDictionary<NSString *, NSString *> *response =
  347. @{ @"error" : @"emm_app_verification_required: https://host.domain/path" };
  348. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  349. completion:^() {
  350. completionCalled = YES;
  351. }];
  352. if (![UIAlertController class]) {
  353. XCTAssertFalse(result);
  354. XCTAssertTrue(completionCalled);
  355. XCTAssertFalse(_keyWindowSet);
  356. XCTAssertNil(_presentedViewController);
  357. return;
  358. }
  359. XCTAssertTrue(result);
  360. XCTAssertFalse(completionCalled);
  361. XCTAssertFalse(_keyWindowSet);
  362. XCTAssertNil(_presentedViewController);
  363. // Wait for the code under test to be executed on the main thread.
  364. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  365. dispatch_async(dispatch_get_main_queue(), ^() {
  366. [expectation fulfill];
  367. });
  368. [self waitForExpectationsWithTimeout:1 handler:nil];
  369. XCTAssertFalse(completionCalled);
  370. XCTAssertTrue(_keyWindowSet);
  371. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  372. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  373. XCTAssertNotNil(alert.title);
  374. XCTAssertNotNil(alert.message);
  375. XCTAssertEqual(alert.actions.count, 2);
  376. // Pretend to touch the "Cancel" button.
  377. UIAlertAction *action = alert.actions[0];
  378. XCTAssertEqualObjects(action.title, @"Cancel");
  379. action.actionHandler(action);
  380. XCTAssertTrue(completionCalled);
  381. }
  382. // Verifies that the handler handles EMM app verification required error user tapping 'Connect'.
  383. - (void)testAppVerificationConnect {
  384. __block BOOL completionCalled = NO;
  385. NSDictionary<NSString *, NSString *> *response =
  386. @{ @"error" : @"emm_app_verification_required: https://host.domain/path" };
  387. BOOL result = [[GIDEMMErrorHandler sharedInstance] handleErrorFromResponse:response
  388. completion:^() {
  389. completionCalled = YES;
  390. }];
  391. if (![UIAlertController class]) {
  392. XCTAssertFalse(result);
  393. XCTAssertTrue(completionCalled);
  394. XCTAssertFalse(_keyWindowSet);
  395. XCTAssertNil(_presentedViewController);
  396. return;
  397. }
  398. XCTAssertTrue(result);
  399. XCTAssertFalse(completionCalled);
  400. XCTAssertFalse(_keyWindowSet);
  401. XCTAssertNil(_presentedViewController);
  402. // Wait for the code under test to be executed on the main thread.
  403. XCTestExpectation *expectation = [self expectationWithDescription:@"wait for main thread"];
  404. dispatch_async(dispatch_get_main_queue(), ^() {
  405. [expectation fulfill];
  406. });
  407. [self waitForExpectationsWithTimeout:1 handler:nil];
  408. XCTAssertFalse(completionCalled);
  409. XCTAssertTrue(_keyWindowSet);
  410. XCTAssertTrue([_presentedViewController isKindOfClass:[UIAlertController class]]);
  411. UIAlertController *alert = (UIAlertController *)_presentedViewController;
  412. XCTAssertNotNil(alert.title);
  413. XCTAssertNotNil(alert.message);
  414. XCTAssertEqual(alert.actions.count, 2);
  415. // Pretend to touch the "Connect" button.
  416. UIAlertAction *action = alert.actions[1];
  417. XCTAssertEqualObjects(action.title, @"Connect");
  418. [self expectOpenURLString:@"https://host.domain/path" inAction:^() {
  419. action.actionHandler(action);
  420. }];
  421. XCTAssertTrue(completionCalled);
  422. }
  423. // Verifies that the handler can handle sequential errors independently.
  424. - (void)testSequentialErrors {
  425. [self testGeneralEMMErrorOK];
  426. _keyWindowSet = NO;
  427. _presentedViewController = nil;
  428. [self testScreenlockRequiredCancel];
  429. }
  430. // Temporarily disable testKeyWindow for Xcode 12 and under due to unexplained failure.
  431. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000
  432. // Verifies that the `keyWindow` internal method works on all OS versions as expected.
  433. - (void)testKeyWindow {
  434. // The original method has been swizzled in `setUp` so get its original implementation to test.
  435. typedef id (*KeyWindowSignature)(id, SEL);
  436. KeyWindowSignature keyWindowFunction = (KeyWindowSignature)
  437. [GULSwizzler originalImplementationForClass:[GIDEMMErrorHandler class]
  438. selector:@selector(keyWindow)
  439. isClassSelector:NO];
  440. UIWindow *mockKeyWindow = OCMClassMock([UIWindow class]);
  441. OCMStub(mockKeyWindow.isKeyWindow).andReturn(YES);
  442. UIApplication *mockApplication = OCMClassMock([UIApplication class]);
  443. [GULSwizzler swizzleClass:[UIApplication class]
  444. selector:@selector(sharedApplication)
  445. isClassSelector:YES
  446. withBlock:^{ return mockApplication; }];
  447. #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000
  448. if (@available(iOS 15, *)) {
  449. UIWindowScene *mockWindowScene = OCMClassMock([UIWindowScene class]);
  450. OCMStub(mockApplication.connectedScenes).andReturn(@[mockWindowScene]);
  451. OCMStub(mockWindowScene.activationState).andReturn(UISceneActivationStateForegroundActive);
  452. OCMStub(mockWindowScene.keyWindow).andReturn(mockKeyWindow);
  453. } else
  454. #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000
  455. {
  456. #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_15_0
  457. if (@available(iOS 13, *)) {
  458. OCMStub(mockApplication.windows).andReturn(@[mockKeyWindow]);
  459. } else {
  460. #if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_13_0
  461. OCMStub(mockApplication.keyWindow).andReturn(mockKeyWindow);
  462. #endif // __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_13_0
  463. }
  464. #endif // __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_15_0
  465. }
  466. UIWindow *keyWindow =
  467. keyWindowFunction([GIDEMMErrorHandler sharedInstance], @selector(keyWindow));
  468. XCTAssertEqual(keyWindow, mockKeyWindow);
  469. [GULSwizzler unswizzleClass:[UIApplication class]
  470. selector:@selector(sharedApplication)
  471. isClassSelector:YES];
  472. }
  473. #endif // __IPHONE_OS_VERSION_MAX_ALLOWED >= 150000
  474. @end
  475. NS_ASSUME_NONNULL_END
  476. #endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST