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