FIRMessagingClientTest.m 12 KB

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