FIRMessagingClientTest.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. #import <OCMock/OCMock.h>
  17. #import <XCTest/XCTest.h>
  18. #import <FirebaseInstallations/FIRInstallations.h>
  19. #import <FirebaseInstanceID/FIRInstanceID_Private.h>
  20. #import <GoogleUtilities/GULReachabilityChecker.h>
  21. #import "Example/Messaging/Tests/FIRMessagingFakeConnection.h"
  22. #import "Firebase/Messaging/FIRMessagingClient.h"
  23. #import "Firebase/Messaging/FIRMessagingConnection.h"
  24. #import "Firebase/Messaging/FIRMessagingDataMessageManager.h"
  25. #import "Firebase/Messaging/FIRMessagingRmqManager.h"
  26. #import "Firebase/Messaging/FIRMessagingSecureSocket.h"
  27. #import "Firebase/Messaging/FIRMessagingUtilities.h"
  28. #import "Firebase/Messaging/NSError+FIRMessaging.h"
  29. #import "Firebase/Messaging/Protos/GtalkCore.pbobjc.h"
  30. static NSString *const kDeviceAuthId = @"123456";
  31. static NSString *const kSecretToken = @"56789";
  32. @interface FIRInstanceID (exposedForTests)
  33. + (FIRInstanceID *)instanceIDForTests;
  34. @end
  35. @interface FIRMessagingClient () <FIRMessagingConnectionDelegate>
  36. @property(nonatomic, readwrite, strong) FIRMessagingConnection *connection;
  37. @property(nonatomic, readwrite, assign) int64_t lastConnectedTimestamp;
  38. @property(nonatomic, readwrite, assign) int64_t lastDisconnectedTimestamp;
  39. @property(nonatomic, readwrite, assign) NSUInteger subscribeRetryCount;
  40. @property(nonatomic, readwrite, assign) NSUInteger connectRetryCount;
  41. - (NSTimeInterval)connectionTimeoutInterval;
  42. - (void)setupConnection;
  43. @end
  44. @interface FIRMessagingConnection () <FIRMessagingSecureSocketDelegate>
  45. @property(nonatomic, readwrite, strong) FIRMessagingSecureSocket *socket;
  46. - (void)setupConnectionSocket;
  47. - (void)connectToSocket:(FIRMessagingSecureSocket *)socket;
  48. - (NSTimeInterval)connectionTimeoutInterval;
  49. - (void)sendHeartbeatPing;
  50. @end
  51. @interface FIRMessagingSecureSocket ()
  52. @property(nonatomic, readwrite, assign) FIRMessagingSecureSocketState state;
  53. @end
  54. @interface FIRMessagingClientTest : XCTestCase
  55. @property(nonatomic, readwrite, strong) FIRMessagingClient *client;
  56. @property(nonatomic, readwrite, strong) id mockClient;
  57. @property(nonatomic, readwrite, strong) id mockReachability;
  58. @property(nonatomic, readwrite, strong) id mockRmqManager;
  59. @property(nonatomic, readwrite, strong) id mockClientDelegate;
  60. @property(nonatomic, readwrite, strong) id mockDataMessageManager;
  61. @property(nonatomic, readwrite, strong) id mockInstanceID;
  62. @property(nonatomic, readwrite, strong) id mockInstallations;
  63. // argument callback blocks
  64. @property(nonatomic, readwrite, copy) FIRMessagingConnectCompletionHandler connectCompletion;
  65. @property(nonatomic, readwrite, copy) FIRMessagingTopicOperationCompletion subscribeCompletion;
  66. @end
  67. @implementation FIRMessagingClientTest
  68. - (void)setUp {
  69. [super setUp];
  70. _mockClientDelegate = OCMStrictProtocolMock(@protocol(FIRMessagingClientDelegate));
  71. _mockReachability = OCMClassMock([GULReachabilityChecker class]);
  72. _mockRmqManager = OCMClassMock([FIRMessagingRmqManager class]);
  73. _client = [[FIRMessagingClient alloc] initWithDelegate:_mockClientDelegate
  74. reachability:_mockReachability
  75. rmq2Manager:_mockRmqManager];
  76. _mockClient = OCMPartialMock(_client);
  77. _mockDataMessageManager = OCMClassMock([FIRMessagingDataMessageManager class]);
  78. [_mockClient setDataMessageManager:_mockDataMessageManager];
  79. }
  80. - (void)tearDown {
  81. // remove all handlers
  82. [self tearDownMocksAndHandlers];
  83. // Mock all sockets to disconnect in a nice way
  84. [[[(id)self.client.connection.socket stub] andDo:^(NSInvocation *invocation) {
  85. self.client.connection.socket.state = kFIRMessagingSecureSocketClosed;
  86. }] disconnect];
  87. [self.client teardown];
  88. [super tearDown];
  89. }
  90. - (void)tearDownMocksAndHandlers {
  91. self.connectCompletion = nil;
  92. self.subscribeCompletion = nil;
  93. self.mockInstanceID = nil;
  94. self.mockInstallations = nil;
  95. }
  96. - (void)setupConnectionWithFakeLoginResult:(BOOL)loginResult
  97. heartbeatTimeout:(NSTimeInterval)heartbeatTimeout {
  98. [self setupFakeConnectionWithClass:[FIRMessagingFakeConnection class]
  99. withSetupCompletionHandler:^(FIRMessagingConnection *connection) {
  100. FIRMessagingFakeConnection *fakeConnection = (FIRMessagingFakeConnection *)connection;
  101. fakeConnection.shouldFakeSuccessLogin = loginResult;
  102. fakeConnection.fakeConnectionTimeout = heartbeatTimeout;
  103. }];
  104. }
  105. - (void)testSetupConnection {
  106. XCTAssertNil(self.client.connection);
  107. [self.client setupConnection];
  108. XCTAssertNotNil(self.client.connection);
  109. XCTAssertNotNil(self.client.connection.delegate);
  110. }
  111. - (void)testConnectSuccess_withCachedFcmDefaults {
  112. [self addFIRMessagingPreferenceKeysToUserDefaults];
  113. // login request should be successful
  114. [self setupConnectionWithFakeLoginResult:YES heartbeatTimeout:1.0];
  115. XCTestExpectation *setupConnection =
  116. [self expectationWithDescription:@"Fcm should successfully setup a connection"];
  117. [self.client connectWithHandler:^(NSError *error) {
  118. XCTAssertNil(error);
  119. [setupConnection fulfill];
  120. }];
  121. [self waitForExpectationsWithTimeout:1.0
  122. handler:^(NSError *error) {
  123. XCTAssertNil(error);
  124. }];
  125. }
  126. - (void)testsConnectWithNoNetworkError_withCachedFcmDefaults {
  127. // connection timeout interval is 1s
  128. [[[self.mockClient stub] andReturnValue:@(1)] connectionTimeoutInterval];
  129. [self addFIRMessagingPreferenceKeysToUserDefaults];
  130. [self setupFakeConnectionWithClass:[FIRMessagingFakeFailConnection class]
  131. withSetupCompletionHandler:^(FIRMessagingConnection *connection) {
  132. FIRMessagingFakeFailConnection *fakeConnection =
  133. (FIRMessagingFakeFailConnection *)connection;
  134. fakeConnection.shouldFakeSuccessLogin = NO;
  135. // should fail only once
  136. fakeConnection.failCount = 1;
  137. }];
  138. XCTestExpectation *connectExpectation =
  139. [self expectationWithDescription:@"Should retry connection if once failed"];
  140. [self.client connectWithHandler:^(NSError *error) {
  141. XCTAssertNotNil(error);
  142. XCTAssertEqual(kFIRMessagingErrorCodeNetwork, error.code);
  143. [connectExpectation fulfill];
  144. }];
  145. [self waitForExpectationsWithTimeout:10.0
  146. handler:^(NSError *error) {
  147. XCTAssertNil(error);
  148. }];
  149. }
  150. - (void)testConnectSuccessOnSecondTry_withCachedFcmDefaults {
  151. // connection timeout interval is 1s
  152. [[[self.mockClient stub] andReturnValue:@(1)] connectionTimeoutInterval];
  153. [self addFIRMessagingPreferenceKeysToUserDefaults];
  154. // the network is available
  155. [[[self.mockReachability stub] andReturnValue:@(kGULReachabilityViaWifi)] reachabilityStatus];
  156. [self setupFakeConnectionWithClass:[FIRMessagingFakeFailConnection class]
  157. withSetupCompletionHandler:^(FIRMessagingConnection *connection) {
  158. FIRMessagingFakeFailConnection *fakeConnection =
  159. (FIRMessagingFakeFailConnection *)connection;
  160. fakeConnection.shouldFakeSuccessLogin = NO;
  161. // should fail only once
  162. fakeConnection.failCount = 1;
  163. }];
  164. XCTestExpectation *connectExpectation =
  165. [self expectationWithDescription:@"Should retry connection if once failed"];
  166. [self.client connectWithHandler:^(NSError *error) {
  167. XCTAssertNil(error);
  168. [connectExpectation fulfill];
  169. }];
  170. [self waitForExpectationsWithTimeout:10.0
  171. handler:^(NSError *error) {
  172. XCTAssertNil(error);
  173. XCTAssertTrue([self.client isConnectionActive]);
  174. }];
  175. }
  176. - (void)testDisconnectAfterConnect {
  177. // setup the connection
  178. [self addFIRMessagingPreferenceKeysToUserDefaults];
  179. // login request should be successful
  180. // Connection should not timeout because of heartbeat failure. Therefore set heartbeatTimeout
  181. // to a large value.
  182. [self setupConnectionWithFakeLoginResult:YES heartbeatTimeout:100.0];
  183. [[[self.mockClient stub] andReturnValue:@(1)] connectionTimeoutInterval];
  184. // the network is available
  185. [[[self.mockReachability stub] andReturnValue:@(kGULReachabilityViaWifi)] reachabilityStatus];
  186. XCTestExpectation *setupConnection =
  187. [self expectationWithDescription:@"Fcm should successfully setup a connection"];
  188. __block int timesConnected = 0;
  189. FIRMessagingConnectCompletionHandler handler = ^(NSError *error) {
  190. XCTAssertNil(error);
  191. timesConnected++;
  192. if (timesConnected == 1) {
  193. [setupConnection fulfill];
  194. // disconnect the connection after some time
  195. FIRMessagingFakeConnection *fakeConnection =
  196. (FIRMessagingFakeConnection *)[self.mockClient connection];
  197. dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (0.2 * NSEC_PER_SEC));
  198. dispatch_after(time, dispatch_get_main_queue(), ^{
  199. // disconnect now
  200. [(FIRMessagingFakeConnection *)fakeConnection mockSocketDisconnect];
  201. [(FIRMessagingFakeConnection *)fakeConnection disconnectNow];
  202. });
  203. } else {
  204. XCTFail(@"Fcm should only connect at max 2 times");
  205. }
  206. };
  207. [self.mockClient connectWithHandler:handler];
  208. // reconnect after disconnect
  209. XCTAssertTrue(self.client.isConnectionActive);
  210. [self waitForExpectationsWithTimeout:10.0
  211. handler:^(NSError *error) {
  212. XCTAssertNil(error);
  213. XCTAssertNotEqual(self.client.lastDisconnectedTimestamp, 0);
  214. XCTAssertTrue(self.client.isConnectionActive);
  215. }];
  216. }
  217. #pragma mark - Private Helpers
  218. - (void)setupFakeConnectionWithClass:(Class)connectionClass
  219. withSetupCompletionHandler:(void (^)(FIRMessagingConnection *))handler {
  220. [[[self.mockClient stub] andDo:^(NSInvocation *invocation) {
  221. self.client.connection =
  222. [[connectionClass alloc] initWithAuthID:kDeviceAuthId
  223. token:kSecretToken
  224. host:[FIRMessagingFakeConnection fakeHost]
  225. port:[FIRMessagingFakeConnection fakePort]
  226. runLoop:[NSRunLoop mainRunLoop]
  227. rmq2Manager:self.mockRmqManager
  228. fcmManager:self.mockDataMessageManager];
  229. self.client.connection.delegate = self.client;
  230. handler(self.client.connection);
  231. }] setupConnection];
  232. }
  233. - (void)addFIRMessagingPreferenceKeysToUserDefaults {
  234. // `+[FIRInstallations installations]` supposed to be used on `-[FIRInstanceID start]` to get
  235. // `FIRInstallations` default instance. Need to stub it before.
  236. self.mockInstallations = OCMClassMock([FIRInstallations class]);
  237. OCMStub([self.mockInstallations installations]).andReturn(self.mockInstallations);
  238. self.mockInstanceID = OCMPartialMock([FIRInstanceID instanceIDForTests]);
  239. OCMStub([self.mockInstanceID tryToLoadValidCheckinInfo]).andReturn(YES);
  240. OCMStub([self.mockInstanceID deviceAuthID]).andReturn(kDeviceAuthId);
  241. OCMStub([self.mockInstanceID secretToken]).andReturn(kSecretToken);
  242. }
  243. @end