FIRMessagingClientTest.m 11 KB

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