FIRMessagingConnectionTest.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  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 "Protos/GtalkCore.pbobjc.h"
  19. #import <GoogleToolboxForMac/GTMDefines.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. static NSString *const kDeviceAuthId = @"123456";
  28. static NSString *const kSecretToken = @"56789";
  29. // used to verify if we are sending in the right proto or not.
  30. // set it to negative value to disable this check
  31. static FIRMessagingProtoTag currentProtoSendTag;
  32. @interface FIRMessagingSecureSocket ()
  33. @property(nonatomic, readwrite, assign) FIRMessagingSecureSocketState state;
  34. @end
  35. @interface FIRMessagingSecureSocket (test_FIRMessagingConnection)
  36. - (void)_successconnectToHost:(NSString *)host
  37. port:(NSUInteger)port
  38. onRunLoop:(NSRunLoop *)runLoop;
  39. - (void)_fakeSuccessfulSocketConnect;
  40. @end
  41. @implementation FIRMessagingSecureSocket (test_FIRMessagingConnection)
  42. - (void)_successconnectToHost:(NSString *)host
  43. port:(NSUInteger)port
  44. onRunLoop:(NSRunLoop *)runLoop {
  45. // created ports, opened streams
  46. // invoke callback async
  47. [self _fakeSuccessfulSocketConnect];
  48. }
  49. - (void)_fakeSuccessfulSocketConnect {
  50. self.state = kFIRMessagingSecureSocketOpen;
  51. [self.delegate secureSocketDidConnect:self];
  52. }
  53. @end
  54. // make sure these are defined in FIRMessagingConnection
  55. @interface FIRMessagingConnection () <FIRMessagingSecureSocketDelegate>
  56. @property(nonatomic, readwrite, assign) int64_t lastLoginServerTimestamp;
  57. @property(nonatomic, readwrite, assign) int lastStreamIdAcked;
  58. @property(nonatomic, readwrite, assign) int inStreamId;
  59. @property(nonatomic, readwrite, assign) int outStreamId;
  60. @property(nonatomic, readwrite, strong) FIRMessagingSecureSocket *socket;
  61. @property(nonatomic, readwrite, strong) NSMutableArray *unackedS2dIds;
  62. @property(nonatomic, readwrite, strong) NSMutableDictionary *ackedS2dMap;
  63. @property(nonatomic, readwrite, strong) NSMutableArray *d2sInfos;
  64. - (void)setupConnectionSocket;
  65. - (void)connectToSocket:(FIRMessagingSecureSocket *)socket;
  66. - (NSTimeInterval)connectionTimeoutInterval;
  67. - (void)sendHeartbeatPing;
  68. @end
  69. @interface FIRMessagingConnectionTest : XCTestCase
  70. @property(nonatomic, readwrite, assign) BOOL didSuccessfullySendData;
  71. @property(nonatomic, readwrite, strong) NSUserDefaults *userDefaults;
  72. @property(nonatomic, readwrite, strong) FIRMessagingConnection *fakeConnection;
  73. @property(nonatomic, readwrite, strong) id mockClient;
  74. @property(nonatomic, readwrite, strong) id mockConnection;
  75. @property(nonatomic, readwrite, strong) id mockRmq;
  76. @property(nonatomic, readwrite, strong) id mockDataMessageManager;
  77. @end
  78. @implementation FIRMessagingConnectionTest
  79. - (void)setUp {
  80. [super setUp];
  81. _userDefaults = [[NSUserDefaults alloc] init];
  82. _mockRmq = OCMClassMock([FIRMessagingRmqManager class]);
  83. _mockDataMessageManager = OCMClassMock([FIRMessagingDataMessageManager class]);
  84. // fake connection is only used to simulate the socket behavior
  85. _fakeConnection = [[FIRMessagingFakeConnection alloc] initWithAuthID:kDeviceAuthId
  86. token:kSecretToken
  87. host:[FIRMessagingFakeConnection fakeHost]
  88. port:[FIRMessagingFakeConnection fakePort]
  89. runLoop:[NSRunLoop currentRunLoop]
  90. rmq2Manager:_mockRmq
  91. fcmManager:_mockDataMessageManager];
  92. _mockClient = OCMClassMock([FIRMessagingClient class]);
  93. _fakeConnection.delegate = _mockClient;
  94. _mockConnection = OCMPartialMock(_fakeConnection);
  95. _didSuccessfullySendData = NO;
  96. }
  97. - (void)tearDown {
  98. [self.fakeConnection teardown];
  99. [super tearDown];
  100. }
  101. - (void)testInitialConnectionNotConnected {
  102. XCTAssertEqual(kFIRMessagingConnectionNotConnected, [self.fakeConnection state]);
  103. }
  104. - (void)testSuccessfulSocketConnection {
  105. [self.fakeConnection signIn];
  106. // should be connected now
  107. XCTAssertEqual(kFIRMessagingConnectionConnected, self.fakeConnection.state);
  108. XCTAssertEqual(0, self.fakeConnection.lastStreamIdAcked);
  109. XCTAssertEqual(0, self.fakeConnection.inStreamId);
  110. XCTAssertEqual(0, self.fakeConnection.ackedS2dMap.count);
  111. XCTAssertEqual(0, self.fakeConnection.unackedS2dIds.count);
  112. [self stubSocketDisconnect:self.fakeConnection.socket];
  113. }
  114. - (void)testSignInAndThenSignOut {
  115. [self.fakeConnection signIn];
  116. [self stubSocketDisconnect:self.fakeConnection.socket];
  117. [self.fakeConnection signOut];
  118. XCTAssertEqual(kFIRMessagingSecureSocketClosed, self.fakeConnection.socket.state);
  119. }
  120. - (void)testSuccessfulSignIn {
  121. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
  122. XCTAssertEqual(self.fakeConnection.state, kFIRMessagingConnectionSignedIn);
  123. XCTAssertEqual(self.fakeConnection.outStreamId, 2);
  124. XCTAssertTrue(self.didSuccessfullySendData);
  125. }
  126. - (void)testSignOut_whenSignedIn {
  127. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
  128. // should be signed in now
  129. id mockSocket = self.fakeConnection.socket;
  130. [self.fakeConnection signOut];
  131. XCTAssertEqual(self.fakeConnection.state, kFIRMessagingConnectionNotConnected);
  132. XCTAssertEqual(self.fakeConnection.outStreamId, 3);
  133. XCTAssertNil([(FIRMessagingSecureSocket *)mockSocket delegate]);
  134. XCTAssertTrue(self.didSuccessfullySendData);
  135. OCMVerify([mockSocket sendData:[OCMArg any]
  136. withTag:kFIRMessagingProtoTagClose
  137. rmqId:[OCMArg isNil]]);
  138. }
  139. - (void)testReceiveCloseProto {
  140. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
  141. id mockSocket = self.fakeConnection.socket;
  142. GtalkClose *close = [[GtalkClose alloc] init];
  143. [self.fakeConnection secureSocket:mockSocket
  144. didReceiveData:[close data]
  145. withTag:kFIRMessagingProtoTagClose];
  146. XCTAssertEqual(self.fakeConnection.state, kFIRMessagingConnectionNotConnected);
  147. XCTAssertTrue(self.didSuccessfullySendData);
  148. }
  149. - (void)testLoginRequest {
  150. XCTAssertEqual(kFIRMessagingConnectionNotConnected, [self.fakeConnection state]);
  151. [self.fakeConnection setupConnectionSocket];
  152. id socketMock = OCMPartialMock(self.fakeConnection.socket);
  153. self.fakeConnection.socket = socketMock;
  154. [[[socketMock stub]
  155. andDo:^(NSInvocation *invocation) {
  156. [socketMock _fakeSuccessfulSocketConnect];
  157. }]
  158. connectToHost:[FIRMessagingFakeConnection fakeHost]
  159. port:[FIRMessagingFakeConnection fakePort]
  160. onRunLoop:[OCMArg any]];
  161. [[[socketMock stub] andCall:@selector(_sendData:withTag:rmqId:) onObject:self]
  162. // do nothing
  163. sendData:[OCMArg any]
  164. withTag:kFIRMessagingProtoTagLoginRequest
  165. rmqId:[OCMArg isNil]];
  166. // swizzle disconnect socket
  167. OCMVerify([[[socketMock stub] andCall:@selector(_disconnectSocket)
  168. onObject:self] disconnect]);
  169. currentProtoSendTag = kFIRMessagingProtoTagLoginRequest;
  170. // send login request
  171. [self.fakeConnection connectToSocket:socketMock];
  172. // verify login request sent
  173. XCTAssertEqual(1, self.fakeConnection.outStreamId);
  174. XCTAssertTrue(self.didSuccessfullySendData);
  175. }
  176. - (void)testLoginRequest_withPendingMessagesInRmq {
  177. // TODO: add fake messages to rmq and test login request with them
  178. }
  179. - (void)testLoginRequest_withSuccessfulResponse {
  180. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
  181. OCMVerify([self.mockClient didLoginWithConnection:[OCMArg isEqual:self.fakeConnection]]);
  182. // should send a heartbeat ping too
  183. XCTAssertEqual(self.fakeConnection.outStreamId, 2);
  184. // update for the received login response proto
  185. XCTAssertEqual(self.fakeConnection.inStreamId, 1);
  186. // did send data during login
  187. XCTAssertTrue(self.didSuccessfullySendData);
  188. }
  189. - (void)testConnectionTimeout {
  190. XCTAssertEqual(kFIRMessagingConnectionNotConnected, [self.fakeConnection state]);
  191. [self.fakeConnection setupConnectionSocket];
  192. id socketMock = OCMPartialMock(self.fakeConnection.socket);
  193. self.fakeConnection.socket = socketMock;
  194. [[[socketMock stub]
  195. andDo:^(NSInvocation *invocation) {
  196. [socketMock _fakeSuccessfulSocketConnect];
  197. }]
  198. connectToHost:[FIRMessagingFakeConnection fakeHost]
  199. port:[FIRMessagingFakeConnection fakePort]
  200. onRunLoop:[OCMArg any]];
  201. [self.fakeConnection connectToSocket:socketMock];
  202. XCTAssertEqual(self.fakeConnection.state, kFIRMessagingConnectionConnected);
  203. GtalkLoginResponse *response = [[GtalkLoginResponse alloc] init];
  204. [response setId_p:@""];
  205. // connection timeout has been scheduled
  206. // should disconnect since we wait for more time
  207. XCTestExpectation *disconnectExpectation =
  208. [self expectationWithDescription:
  209. @"FCM connection should timeout without receiving "
  210. @"any data for a timeout interval"];
  211. [[[socketMock stub]
  212. andDo:^(NSInvocation *invocation) {
  213. [self _disconnectSocket];
  214. [disconnectExpectation fulfill];
  215. }] disconnect];
  216. // simulate connection receiving login response
  217. [self.fakeConnection secureSocket:socketMock
  218. didReceiveData:[response data]
  219. withTag:kFIRMessagingProtoTagLoginResponse];
  220. [self waitForExpectationsWithTimeout:2.0
  221. handler:^(NSError *error) {
  222. XCTAssertNil(error);
  223. }];
  224. [socketMock verify];
  225. XCTAssertEqual(self.fakeConnection.state, kFIRMessagingConnectionNotConnected);
  226. }
  227. - (void)testDataMessageReceive {
  228. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
  229. GtalkDataMessageStanza *stanza = [[GtalkDataMessageStanza alloc] init];
  230. [stanza setCategory:@"special"];
  231. [stanza setFrom:@"xyz"];
  232. [self.fakeConnection secureSocket:self.fakeConnection.socket
  233. didReceiveData:[stanza data]
  234. withTag:kFIRMessagingProtoTagDataMessageStanza];
  235. OCMVerify([self.mockClient connectionDidRecieveMessage:[OCMArg checkWithBlock:^BOOL(id obj) {
  236. GtalkDataMessageStanza *message = (GtalkDataMessageStanza *)obj;
  237. return [[message category] isEqual:@"special"] && [[message from] isEqual:@"xyz"];
  238. }]]);
  239. // did send data while login
  240. XCTAssertTrue(self.didSuccessfullySendData);
  241. }
  242. - (void)testDataMessageReceiveWithInvalidTag {
  243. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
  244. GtalkDataMessageStanza *stanza = [[GtalkDataMessageStanza alloc] init];
  245. BOOL didCauseException = NO;
  246. @try {
  247. [self.fakeConnection secureSocket:self.fakeConnection.socket
  248. didReceiveData:[stanza data]
  249. withTag:kFIRMessagingProtoTagInvalid];
  250. } @catch (NSException *exception) {
  251. didCauseException = YES;
  252. } @finally {
  253. }
  254. XCTAssertFalse(didCauseException);
  255. }
  256. - (void)testDataMessageReceiveWithTagThatDoesntEquateToClass {
  257. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
  258. GtalkDataMessageStanza *stanza = [[GtalkDataMessageStanza alloc] init];
  259. BOOL didCauseException = NO;
  260. int8_t tagWhichDoesntEquateToClass = INT8_MAX;
  261. @try {
  262. [self.fakeConnection secureSocket:self.fakeConnection.socket
  263. didReceiveData:[stanza data]
  264. withTag:tagWhichDoesntEquateToClass];
  265. } @catch (NSException *exception) {
  266. didCauseException = YES;
  267. } @finally {
  268. }
  269. XCTAssertFalse(didCauseException);
  270. }
  271. - (void)testHeartbeatSend {
  272. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection]; // outstreamId should be 2
  273. XCTAssertEqual(self.fakeConnection.outStreamId, 2);
  274. [self.fakeConnection sendHeartbeatPing];
  275. id mockSocket = self.fakeConnection.socket;
  276. OCMVerify([mockSocket sendData:[OCMArg any]
  277. withTag:kFIRMessagingProtoTagHeartbeatPing
  278. rmqId:[OCMArg isNil]]);
  279. XCTAssertEqual(self.fakeConnection.outStreamId, 3);
  280. // did send data
  281. XCTAssertTrue(self.didSuccessfullySendData);
  282. }
  283. - (void)testHeartbeatReceived {
  284. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
  285. XCTAssertEqual(self.fakeConnection.outStreamId, 2);
  286. GtalkHeartbeatPing *ping = [[GtalkHeartbeatPing alloc] init];
  287. [self.fakeConnection secureSocket:self.fakeConnection.socket
  288. didReceiveData:[ping data]
  289. withTag:kFIRMessagingProtoTagHeartbeatPing];
  290. XCTAssertEqual(self.fakeConnection.inStreamId, 2);
  291. id mockSocket = self.fakeConnection.socket;
  292. OCMVerify([mockSocket sendData:[OCMArg any]
  293. withTag:kFIRMessagingProtoTagHeartbeatAck
  294. rmqId:[OCMArg isNil]]);
  295. XCTAssertEqual(self.fakeConnection.outStreamId, 3);
  296. // did send data
  297. XCTAssertTrue(self.didSuccessfullySendData);
  298. }
  299. // TODO: Add tests for Selective/Stream ACK's
  300. #pragma mark - Stubs
  301. - (void)_disconnectSocket {
  302. self.fakeConnection.socket.state = kFIRMessagingSecureSocketClosed;
  303. }
  304. - (void)_sendData:(NSData *)data withTag:(int8_t)tag rmqId:(NSString *)rmqId {
  305. _GTMDevLog(@"FIRMessaging Socket: Send data with Tag: %d rmq: %@", tag, rmqId);
  306. if (currentProtoSendTag > 0) {
  307. XCTAssertEqual(tag, currentProtoSendTag);
  308. }
  309. self.didSuccessfullySendData = YES;
  310. }
  311. #pragma mark - Private Helpers
  312. /**
  313. * Stub socket disconnect to prevent spurious assert. Since we mock the socket object being
  314. * used by the connection, while we teardown the client we also disconnect the socket to tear
  315. * it down. Since we are using mock sockets we need to stub the `disconnect` to prevent some
  316. * assertions from taking place.
  317. * The `_disconectSocket` has the gist of the actual socket disconnect without any assertions.
  318. */
  319. - (void)stubSocketDisconnect:(id)mockSocket {
  320. [[[mockSocket stub] andCall:@selector(_disconnectSocket)
  321. onObject:self] disconnect];
  322. [mockSocket verify];
  323. }
  324. - (void)mockSuccessfulSignIn {
  325. XCTAssertEqual(kFIRMessagingConnectionNotConnected, [self.fakeConnection state]);
  326. [self.fakeConnection setupConnectionSocket];
  327. id socketMock = OCMPartialMock(self.fakeConnection.socket);
  328. self.fakeConnection.socket = socketMock;
  329. [[[socketMock stub]
  330. andDo:^(NSInvocation *invocation) {
  331. [socketMock _fakeSuccessfulSocketConnect];
  332. }]
  333. connectToHost:[FIRMessagingFakeConnection fakeHost]
  334. port:[FIRMessagingFakeConnection fakePort]
  335. onRunLoop:[OCMArg any]];
  336. [[[socketMock stub] andCall:@selector(_sendData:withTag:rmqId:) onObject:self]
  337. // do nothing
  338. sendData:[OCMArg any]
  339. withTag:kFIRMessagingProtoTagLoginRequest
  340. rmqId:[OCMArg isNil]];
  341. // send login request
  342. currentProtoSendTag = kFIRMessagingProtoTagLoginRequest;
  343. [self.fakeConnection connectToSocket:socketMock];
  344. GtalkLoginResponse *response = [[GtalkLoginResponse alloc] init];
  345. [response setId_p:@""];
  346. // simulate connection receiving login response
  347. [self.fakeConnection secureSocket:socketMock
  348. didReceiveData:[response data]
  349. withTag:kFIRMessagingProtoTagLoginResponse];
  350. OCMVerify([self.mockClient didLoginWithConnection:[OCMArg isEqual:self.fakeConnection]]);
  351. // should receive data
  352. XCTAssertTrue(self.didSuccessfullySendData);
  353. // should send a heartbeat ping too
  354. XCTAssertEqual(self.fakeConnection.outStreamId, 2);
  355. // update for the received login response proto
  356. XCTAssertEqual(self.fakeConnection.inStreamId, 1);
  357. }
  358. - (void)setupSuccessfulLoginRequestWithConnection:(FIRMessagingConnection *)fakeConnection {
  359. [fakeConnection setupConnectionSocket];
  360. id socketMock = OCMPartialMock(fakeConnection.socket);
  361. fakeConnection.socket = socketMock;
  362. [[[socketMock stub]
  363. andDo:^(NSInvocation *invocation) {
  364. [socketMock _fakeSuccessfulSocketConnect];
  365. }]
  366. connectToHost:[FIRMessagingFakeConnection fakeHost]
  367. port:[FIRMessagingFakeConnection fakePort]
  368. onRunLoop:[OCMArg any]];
  369. [[[socketMock stub] andCall:@selector(_sendData:withTag:rmqId:) onObject:self]
  370. // do nothing
  371. sendData:[OCMArg any]
  372. withTag:kFIRMessagingProtoTagLoginRequest
  373. rmqId:[OCMArg isNil]];
  374. // swizzle disconnect socket
  375. [[[socketMock stub] andCall:@selector(_disconnectSocket)
  376. onObject:self] disconnect];
  377. // send login request
  378. currentProtoSendTag = kFIRMessagingProtoTagLoginRequest;
  379. [fakeConnection connectToSocket:socketMock];
  380. GtalkLoginResponse *response = [[GtalkLoginResponse alloc] init];
  381. [response setId_p:@""];
  382. // simulate connection receiving login response
  383. [fakeConnection secureSocket:socketMock
  384. didReceiveData:[response data]
  385. withTag:kFIRMessagingProtoTagLoginResponse];
  386. }
  387. @end