FIRMessagingClientTest.m 11 KB

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