FIRMessagingClientTest.m 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  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 <OCMock/OCMock.h>
  18. #import <FirebaseInstanceID/FIRInstanceID_Private.h>
  19. #import <GoogleUtilities/GULReachabilityChecker.h>
  20. #import <FirebaseInstallations/FIRInstallations.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 = [self
  116. 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 handler:^(NSError *error) {
  122. XCTAssertNil(error);
  123. }];
  124. }
  125. - (void)testsConnectWithNoNetworkError_withCachedFcmDefaults {
  126. // connection timeout interval is 1s
  127. [[[self.mockClient stub] andReturnValue:@(1)] connectionTimeoutInterval];
  128. [self addFIRMessagingPreferenceKeysToUserDefaults];
  129. [self setupFakeConnectionWithClass:[FIRMessagingFakeFailConnection class]
  130. withSetupCompletionHandler:^(FIRMessagingConnection *connection) {
  131. FIRMessagingFakeFailConnection *fakeConnection = (FIRMessagingFakeFailConnection *)connection;
  132. fakeConnection.shouldFakeSuccessLogin = NO;
  133. // should fail only once
  134. fakeConnection.failCount = 1;
  135. }];
  136. XCTestExpectation *connectExpectation = [self
  137. expectationWithDescription:@"Should retry connection if once failed"];
  138. [self.client connectWithHandler:^(NSError *error) {
  139. XCTAssertNotNil(error);
  140. XCTAssertEqual(kFIRMessagingErrorCodeNetwork, error.code);
  141. [connectExpectation fulfill];
  142. }];
  143. [self waitForExpectationsWithTimeout:10.0
  144. handler:^(NSError *error) {
  145. XCTAssertNil(error);
  146. }];
  147. }
  148. - (void)testConnectSuccessOnSecondTry_withCachedFcmDefaults {
  149. // connection timeout interval is 1s
  150. [[[self.mockClient stub] andReturnValue:@(1)] connectionTimeoutInterval];
  151. [self addFIRMessagingPreferenceKeysToUserDefaults];
  152. // the network is available
  153. [[[self.mockReachability stub]
  154. andReturnValue:@(kGULReachabilityViaWifi)] reachabilityStatus];
  155. [self setupFakeConnectionWithClass:[FIRMessagingFakeFailConnection class]
  156. withSetupCompletionHandler:^(FIRMessagingConnection *connection) {
  157. FIRMessagingFakeFailConnection *fakeConnection = (FIRMessagingFakeFailConnection *)connection;
  158. fakeConnection.shouldFakeSuccessLogin = NO;
  159. // should fail only once
  160. fakeConnection.failCount = 1;
  161. }];
  162. XCTestExpectation *connectExpectation = [self
  163. expectationWithDescription:@"Should retry connection if once failed"];
  164. [self.client connectWithHandler:^(NSError *error) {
  165. XCTAssertNil(error);
  166. [connectExpectation fulfill];
  167. }];
  168. [self waitForExpectationsWithTimeout:10.0
  169. handler:^(NSError *error) {
  170. XCTAssertNil(error);
  171. XCTAssertTrue(
  172. [self.client isConnectionActive]);
  173. }];
  174. }
  175. - (void)testDisconnectAfterConnect {
  176. // setup the connection
  177. [self addFIRMessagingPreferenceKeysToUserDefaults];
  178. // login request should be successful
  179. // Connection should not timeout because of heartbeat failure. Therefore set heartbeatTimeout
  180. // to a large value.
  181. [self setupConnectionWithFakeLoginResult:YES heartbeatTimeout:100.0];
  182. [[[self.mockClient stub] andReturnValue:@(1)] connectionTimeoutInterval];
  183. // the network is available
  184. [[[self.mockReachability stub]
  185. 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 = (FIRMessagingFakeConnection *)[self.mockClient connection];
  196. dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (0.2 * NSEC_PER_SEC));
  197. dispatch_after(time, dispatch_get_main_queue(), ^{
  198. // disconnect now
  199. [(FIRMessagingFakeConnection *)fakeConnection mockSocketDisconnect];
  200. [(FIRMessagingFakeConnection *)fakeConnection disconnectNow];
  201. });
  202. } else {
  203. XCTFail(@"Fcm should only connect at max 2 times");
  204. }
  205. };
  206. [self.mockClient connectWithHandler:handler];
  207. // reconnect after disconnect
  208. XCTAssertTrue(self.client.isConnectionActive);
  209. [self waitForExpectationsWithTimeout:10.0
  210. handler:^(NSError *error) {
  211. XCTAssertNil(error);
  212. XCTAssertNotEqual(self.client.lastDisconnectedTimestamp, 0);
  213. XCTAssertTrue(self.client.isConnectionActive);
  214. }];
  215. }
  216. #pragma mark - Private Helpers
  217. - (void)setupFakeConnectionWithClass:(Class)connectionClass
  218. withSetupCompletionHandler:(void (^)(FIRMessagingConnection *))handler {
  219. [[[self.mockClient stub] andDo:^(NSInvocation *invocation) {
  220. self.client.connection =
  221. [[connectionClass alloc] initWithAuthID:kDeviceAuthId
  222. token:kSecretToken
  223. host:[FIRMessagingFakeConnection fakeHost]
  224. port:[FIRMessagingFakeConnection fakePort]
  225. runLoop:[NSRunLoop mainRunLoop]
  226. rmq2Manager:self.mockRmqManager
  227. fcmManager:self.mockDataMessageManager];
  228. self.client.connection.delegate = self.client;
  229. handler(self.client.connection);
  230. }] setupConnection];
  231. }
  232. - (void)addFIRMessagingPreferenceKeysToUserDefaults {
  233. // `+[FIRInstallations installations]` supposed to be used on `-[FIRInstanceID start]` to get
  234. // `FIRInstallations` default instance. Need to stub it before.
  235. self.mockInstallations = OCMClassMock([FIRInstallations class]);
  236. OCMStub([self.mockInstallations installations]).andReturn(self.mockInstallations);
  237. self.mockInstanceID = OCMPartialMock([FIRInstanceID instanceIDForTests]);
  238. OCMStub([self.mockInstanceID tryToLoadValidCheckinInfo]).andReturn(YES);
  239. OCMStub([self.mockInstanceID deviceAuthID]).andReturn(kDeviceAuthId);
  240. OCMStub([self.mockInstanceID secretToken]).andReturn(kSecretToken);
  241. }
  242. @end