GULSceneDelegateSwizzlerTest.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. // Copyright 2019 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 <GoogleUtilities/GULSceneDelegateSwizzler.h>
  15. #import "GoogleUtilities/SceneDelegateSwizzler/Internal/GULSceneDelegateSwizzler_Private.h"
  16. #import <OCMock/OCMock.h>
  17. #import <XCTest/XCTest.h>
  18. #import <objc/runtime.h>
  19. /** Plist key that allows Firebase developers to disable Scene Delegate Proxying. Source of truth
  20. * is the GULAppDelegateSwizzler class.
  21. */
  22. static NSString *const kGULFirebaseSceneDelegateProxyEnabledPlistKey =
  23. @"FirebaseAppDelegateProxyEnabled";
  24. /** Plist key that allows non-Firebase developers to disable Scene Delegate Proxying. Source of
  25. * truth is the GULAppDelegateSwizzler class.
  26. */
  27. static NSString *const kGULGoogleSceneDelegateProxyEnabledPlistKey =
  28. @"GoogleUtilitiesAppDelegateProxyEnabled";
  29. #pragma mark - Scene Delegate
  30. #if UISCENE_SUPPORTED
  31. @protocol TestSceneProtocol <UISceneDelegate>
  32. @end
  33. API_AVAILABLE(ios(13.0), tvos(13.0))
  34. @interface GULTestSceneDelegate : NSObject <UISceneDelegate>
  35. @end
  36. @implementation GULTestSceneDelegate
  37. @end
  38. @interface GULSceneDelegateSwizzlerTest : XCTestCase
  39. @end
  40. @implementation GULSceneDelegateSwizzlerTest
  41. - (void)testProxySceneDelegateWithNoSceneDelegate {
  42. if (@available(iOS 13, tvOS 13, *)) {
  43. id mockSharedScene = OCMClassMock([UIScene class]);
  44. OCMStub([mockSharedScene delegate]).andReturn(nil);
  45. XCTAssertNoThrow([GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:mockSharedScene]);
  46. [mockSharedScene stopMocking];
  47. mockSharedScene = nil;
  48. }
  49. }
  50. - (void)testProxySceneDelegate {
  51. if (@available(iOS 13, tvOS 13, *)) {
  52. GULTestSceneDelegate *realSceneDelegate = [[GULTestSceneDelegate alloc] init];
  53. id mockSharedScene = OCMClassMock([UIScene class]);
  54. OCMStub([mockSharedScene delegate]).andReturn(realSceneDelegate);
  55. size_t sizeBefore = class_getInstanceSize([GULTestSceneDelegate class]);
  56. Class realSceneDelegateClassBefore = [realSceneDelegate class];
  57. [GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:mockSharedScene];
  58. XCTAssertTrue([realSceneDelegate isKindOfClass:[GULTestSceneDelegate class]]);
  59. NSString *newClassName = NSStringFromClass([realSceneDelegate class]);
  60. XCTAssertTrue([newClassName hasPrefix:@"GUL_"]);
  61. // It is no longer GULTestSceneDelegate class instance.
  62. XCTAssertFalse([realSceneDelegate isMemberOfClass:[GULTestSceneDelegate class]]);
  63. size_t sizeAfter = class_getInstanceSize([realSceneDelegate class]);
  64. // Class size must stay the same.
  65. XCTAssertEqual(sizeBefore, sizeAfter);
  66. // After being proxied, it should be able to respond to the required method selector.
  67. XCTAssertTrue([realSceneDelegate respondsToSelector:@selector(scene:openURLContexts:)]);
  68. // Make sure that the class has changed.
  69. XCTAssertNotEqualObjects([realSceneDelegate class], realSceneDelegateClassBefore);
  70. [mockSharedScene stopMocking];
  71. mockSharedScene = nil;
  72. }
  73. }
  74. - (void)testProxyProxiedSceneDelegate {
  75. if (@available(iOS 13, tvOS 13, *)) {
  76. GULTestSceneDelegate *realSceneDelegate = [[GULTestSceneDelegate alloc] init];
  77. id mockSharedScene = OCMClassMock([UIScene class]);
  78. OCMStub([mockSharedScene delegate]).andReturn(realSceneDelegate);
  79. // Proxy the scene delegate for the 1st time.
  80. [GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:mockSharedScene];
  81. Class realSceneDelegateClassBefore = [realSceneDelegate class];
  82. // Proxy the scene delegate for the 2nd time.
  83. [GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:mockSharedScene];
  84. // Make sure that the class isn't changed.
  85. XCTAssertEqualObjects([realSceneDelegate class], realSceneDelegateClassBefore);
  86. [mockSharedScene stopMocking];
  87. mockSharedScene = nil;
  88. }
  89. }
  90. - (void)testSceneOpenURLContextsIsInvokedOnInterceptors {
  91. if (@available(iOS 13, tvOS 13, *)) {
  92. NSSet *urlContexts = [NSSet set];
  93. GULTestSceneDelegate *realSceneDelegate = [[GULTestSceneDelegate alloc] init];
  94. id mockSharedScene = OCMClassMock([UIScene class]);
  95. OCMStub([mockSharedScene delegate]).andReturn(realSceneDelegate);
  96. id interceptor = OCMProtocolMock(@protocol(TestSceneProtocol));
  97. OCMExpect([interceptor scene:mockSharedScene openURLContexts:urlContexts]);
  98. id interceptor2 = OCMProtocolMock(@protocol(TestSceneProtocol));
  99. OCMExpect([interceptor2 scene:mockSharedScene openURLContexts:urlContexts]);
  100. [GULSceneDelegateSwizzler proxySceneDelegateIfNeeded:mockSharedScene];
  101. [GULSceneDelegateSwizzler registerSceneDelegateInterceptor:interceptor];
  102. [GULSceneDelegateSwizzler registerSceneDelegateInterceptor:interceptor2];
  103. [realSceneDelegate scene:mockSharedScene openURLContexts:urlContexts];
  104. OCMVerifyAll(interceptor);
  105. OCMVerifyAll(interceptor2);
  106. [mockSharedScene stopMocking];
  107. mockSharedScene = nil;
  108. }
  109. }
  110. - (void)testNotificationCenterRegister {
  111. if (@available(iOS 13, tvOS 13, *)) {
  112. [GULSceneDelegateSwizzler proxyOriginalSceneDelegate];
  113. XCTNSNotificationExpectation *expectation =
  114. [[XCTNSNotificationExpectation alloc] initWithName:UISceneWillConnectNotification];
  115. [[NSNotificationCenter defaultCenter]
  116. postNotification:[NSNotification notificationWithName:UISceneWillConnectNotification
  117. object:nil]];
  118. [self waitForExpectations:@[ expectation ] timeout:1];
  119. }
  120. }
  121. #pragma mark - Tests to test that Plist flag is honored
  122. /** Tests that scene delegate proxy is enabled when there is no Info.plist dictionary. */
  123. - (void)testAppProxyPlistFlag_NoFlag {
  124. // No keys anywhere. If there is no key, the default should be enabled.
  125. NSDictionary *mainDictionary = nil;
  126. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  127. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  128. XCTAssertTrue([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
  129. [mainBundleMock stopMocking];
  130. mainBundleMock = nil;
  131. }
  132. /** Tests that scene delegate proxy is enabled when there is neither the Firebase nor the
  133. * non-Firebase Info.plist key present.
  134. */
  135. - (void)testAppProxyPlistFlag_NoSceneDelegateProxyKey {
  136. // No scene delegate disable key. If there is no key, the default should be enabled.
  137. NSDictionary *mainDictionary = @{@"randomKey" : @"randomValue"};
  138. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  139. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  140. XCTAssertTrue([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
  141. [mainBundleMock stopMocking];
  142. mainBundleMock = nil;
  143. }
  144. /** Tests that scene delegate proxy is enabled when the Firebase plist is explicitly set to YES and
  145. * the Google flag is not present. */
  146. - (void)testAppProxyPlistFlag_FirebaseEnabled {
  147. // Set proxy enabled to YES.
  148. NSDictionary *mainDictionary = @{kGULFirebaseSceneDelegateProxyEnabledPlistKey : @(YES)};
  149. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  150. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  151. XCTAssertTrue([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
  152. [mainBundleMock stopMocking];
  153. mainBundleMock = nil;
  154. }
  155. /** Tests that scene delegate proxy is enabled when the Google plist is explicitly set to YES and
  156. * the Firebase flag is not present. */
  157. - (void)testAppProxyPlistFlag_GoogleEnabled {
  158. // Set proxy enabled to YES.
  159. NSDictionary *mainDictionary = @{kGULGoogleSceneDelegateProxyEnabledPlistKey : @(YES)};
  160. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  161. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  162. XCTAssertTrue([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
  163. [mainBundleMock stopMocking];
  164. mainBundleMock = nil;
  165. }
  166. /** Tests that the scene delegate proxy is enabled when the Firebase flag has the wrong type of
  167. * value and the Google flag is not present. */
  168. - (void)testAppProxyPlist_WrongFirebaseDisableFlagValueType {
  169. // Set proxy enabled to "NO" - a string.
  170. NSDictionary *mainDictionary = @{kGULFirebaseSceneDelegateProxyEnabledPlistKey : @"NO"};
  171. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  172. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  173. XCTAssertTrue([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
  174. [mainBundleMock stopMocking];
  175. mainBundleMock = nil;
  176. }
  177. /** Tests that the scene delegate proxy is enabled when the Google flag has the wrong type of value
  178. * and the Firebase flag is not present. */
  179. - (void)testAppProxyPlist_WrongGoogleDisableFlagValueType {
  180. // Set proxy enabled to "NO" - a string.
  181. NSDictionary *mainDictionary = @{kGULGoogleSceneDelegateProxyEnabledPlistKey : @"NO"};
  182. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  183. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  184. XCTAssertTrue([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
  185. [mainBundleMock stopMocking];
  186. mainBundleMock = nil;
  187. }
  188. /** Tests that the scene delegate proxy is disabled when the Firebase flag is set to NO and the
  189. * Google flag is not present. */
  190. - (void)testAppProxyPlist_FirebaseDisableFlag {
  191. // Set proxy enabled to NO.
  192. NSDictionary *mainDictionary = @{kGULFirebaseSceneDelegateProxyEnabledPlistKey : @(NO)};
  193. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  194. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  195. XCTAssertFalse([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
  196. [mainBundleMock stopMocking];
  197. mainBundleMock = nil;
  198. }
  199. /** Tests that the scene delegate proxy is disabled when the Google flag is set to NO and the
  200. * Firebase flag is not present. */
  201. - (void)testAppProxyPlist_GoogleDisableFlag {
  202. // Set proxy enabled to NO.
  203. NSDictionary *mainDictionary = @{kGULGoogleSceneDelegateProxyEnabledPlistKey : @(NO)};
  204. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  205. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  206. XCTAssertFalse([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
  207. [mainBundleMock stopMocking];
  208. mainBundleMock = nil;
  209. }
  210. /** Tests that the scene delegate proxy is disabled when the Google flag is set to NO and the
  211. * Firebase flag is set to YES. */
  212. - (void)testAppProxyPlist_GoogleDisableFlagFirebaseEnableFlag {
  213. // Set proxy enabled to NO.
  214. NSDictionary *mainDictionary = @{
  215. kGULGoogleSceneDelegateProxyEnabledPlistKey : @(NO),
  216. kGULFirebaseSceneDelegateProxyEnabledPlistKey : @(YES)
  217. };
  218. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  219. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  220. XCTAssertFalse([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
  221. [mainBundleMock stopMocking];
  222. mainBundleMock = nil;
  223. }
  224. /** Tests that the scene delegate proxy is disabled when the Google flag is set to NO and the
  225. * Firebase flag is set to YES. */
  226. - (void)testAppProxyPlist_FirebaseDisableFlagGoogleEnableFlag {
  227. // Set proxy enabled to NO.
  228. NSDictionary *mainDictionary = @{
  229. kGULGoogleSceneDelegateProxyEnabledPlistKey : @(YES),
  230. kGULFirebaseSceneDelegateProxyEnabledPlistKey : @(NO)
  231. };
  232. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  233. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  234. XCTAssertFalse([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
  235. [mainBundleMock stopMocking];
  236. mainBundleMock = nil;
  237. }
  238. /** Tests that the scene delegate proxy is disabled when the Google flag is set to NO and the
  239. * Firebase flag is set to NO. */
  240. - (void)testAppProxyPlist_FirebaseDisableFlagGoogleDisableFlag {
  241. // Set proxy enabled to NO.
  242. NSDictionary *mainDictionary = @{
  243. kGULGoogleSceneDelegateProxyEnabledPlistKey : @(NO),
  244. kGULFirebaseSceneDelegateProxyEnabledPlistKey : @(NO)
  245. };
  246. id mainBundleMock = OCMPartialMock([NSBundle mainBundle]);
  247. [[[mainBundleMock expect] andReturn:mainDictionary] infoDictionary];
  248. XCTAssertFalse([GULSceneDelegateSwizzler isSceneDelegateProxyEnabled]);
  249. [mainBundleMock stopMocking];
  250. mainBundleMock = nil;
  251. }
  252. @end
  253. #endif // UISCENE_SUPPORTED