FIRMessagingConnectionTest.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  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 <OCMock/OCMock.h>
  17. #import <XCTest/XCTest.h>
  18. #import "Example/Messaging/Tests/FIRMessagingFakeConnection.h"
  19. #import "Firebase/Messaging/FIRMessagingClient.h"
  20. #import "Firebase/Messaging/FIRMessagingConnection.h"
  21. #import "Firebase/Messaging/FIRMessagingDataMessageManager.h"
  22. #import "Firebase/Messaging/FIRMessagingRmqManager.h"
  23. #import "Firebase/Messaging/FIRMessagingSecureSocket.h"
  24. #import "Firebase/Messaging/FIRMessagingUtilities.h"
  25. #import "Firebase/Messaging/Protos/GtalkCore.pbobjc.h"
  26. static NSString *const kDeviceAuthId = @"123456";
  27. static NSString *const kSecretToken = @"56789";
  28. // used to verify if we are sending in the right proto or not.
  29. // set it to negative value to disable this check
  30. static FIRMessagingProtoTag currentProtoSendTag;
  31. @interface FIRMessagingSecureSocket ()
  32. @property(nonatomic, readwrite, assign) FIRMessagingSecureSocketState state;
  33. @end
  34. @interface FIRMessagingSecureSocket (test_FIRMessagingConnection)
  35. - (void)_successconnectToHost:(NSString *)host
  36. port:(NSUInteger)port
  37. onRunLoop:(NSRunLoop *)runLoop;
  38. - (void)_fakeSuccessfulSocketConnect;
  39. @end
  40. @implementation FIRMessagingSecureSocket (test_FIRMessagingConnection)
  41. - (void)_successconnectToHost:(NSString *)host
  42. port:(NSUInteger)port
  43. onRunLoop:(NSRunLoop *)runLoop {
  44. // created ports, opened streams
  45. // invoke callback async
  46. [self _fakeSuccessfulSocketConnect];
  47. }
  48. - (void)_fakeSuccessfulSocketConnect {
  49. self.state = kFIRMessagingSecureSocketOpen;
  50. [self.delegate secureSocketDidConnect:self];
  51. }
  52. @end
  53. // make sure these are defined in FIRMessagingConnection
  54. @interface FIRMessagingConnection () <FIRMessagingSecureSocketDelegate>
  55. @property(nonatomic, readwrite, assign) int64_t lastLoginServerTimestamp;
  56. @property(nonatomic, readwrite, assign) int lastStreamIdAcked;
  57. @property(nonatomic, readwrite, assign) int inStreamId;
  58. @property(nonatomic, readwrite, assign) int outStreamId;
  59. @property(nonatomic, readwrite, strong) FIRMessagingSecureSocket *socket;
  60. @property(nonatomic, readwrite, strong) NSMutableArray *unackedS2dIds;
  61. @property(nonatomic, readwrite, strong) NSMutableDictionary *ackedS2dMap;
  62. @property(nonatomic, readwrite, strong) NSMutableArray *d2sInfos;
  63. - (void)setupConnectionSocket;
  64. - (void)connectToSocket:(FIRMessagingSecureSocket *)socket;
  65. - (NSTimeInterval)connectionTimeoutInterval;
  66. - (void)sendHeartbeatPing;
  67. @end
  68. @interface FIRMessagingConnectionTest : XCTestCase
  69. @property(nonatomic, readwrite, assign) BOOL didSuccessfullySendData;
  70. @property(nonatomic, readwrite, strong) NSUserDefaults *userDefaults;
  71. @property(nonatomic, readwrite, strong) FIRMessagingConnection *fakeConnection;
  72. @property(nonatomic, readwrite, strong) id mockClient;
  73. @property(nonatomic, readwrite, strong) id mockConnection;
  74. @property(nonatomic, readwrite, strong) id mockRmq;
  75. @property(nonatomic, readwrite, strong) id mockDataMessageManager;
  76. @end
  77. @implementation FIRMessagingConnectionTest
  78. - (void)setUp {
  79. [super setUp];
  80. _userDefaults = [[NSUserDefaults alloc] init];
  81. _mockRmq = OCMClassMock([FIRMessagingRmqManager class]);
  82. _mockDataMessageManager = OCMClassMock([FIRMessagingDataMessageManager class]);
  83. // fake connection is only used to simulate the socket behavior
  84. _fakeConnection = [[FIRMessagingFakeConnection alloc] initWithAuthID:kDeviceAuthId
  85. token:kSecretToken
  86. host:[FIRMessagingFakeConnection fakeHost]
  87. port:[FIRMessagingFakeConnection fakePort]
  88. runLoop:[NSRunLoop currentRunLoop]
  89. rmq2Manager:_mockRmq
  90. fcmManager:_mockDataMessageManager];
  91. _mockClient = OCMClassMock([FIRMessagingClient class]);
  92. _fakeConnection.delegate = _mockClient;
  93. _mockConnection = OCMPartialMock(_fakeConnection);
  94. _didSuccessfullySendData = NO;
  95. }
  96. - (void)tearDown {
  97. [self.fakeConnection teardown];
  98. [super tearDown];
  99. }
  100. - (void)testInitialConnectionNotConnected {
  101. XCTAssertEqual(kFIRMessagingConnectionNotConnected, [self.fakeConnection state]);
  102. }
  103. - (void)testSuccessfulSocketConnection {
  104. [self.fakeConnection signIn];
  105. // should be connected now
  106. XCTAssertEqual(kFIRMessagingConnectionConnected, self.fakeConnection.state);
  107. XCTAssertEqual(0, self.fakeConnection.lastStreamIdAcked);
  108. XCTAssertEqual(0, self.fakeConnection.inStreamId);
  109. XCTAssertEqual(0, self.fakeConnection.ackedS2dMap.count);
  110. XCTAssertEqual(0, self.fakeConnection.unackedS2dIds.count);
  111. [self stubSocketDisconnect:self.fakeConnection.socket];
  112. }
  113. - (void)testSignInAndThenSignOut {
  114. [self.fakeConnection signIn];
  115. [self stubSocketDisconnect:self.fakeConnection.socket];
  116. [self.fakeConnection signOut];
  117. XCTAssertEqual(kFIRMessagingSecureSocketClosed, self.fakeConnection.socket.state);
  118. }
  119. - (void)testSuccessfulSignIn {
  120. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
  121. XCTAssertEqual(self.fakeConnection.state, kFIRMessagingConnectionSignedIn);
  122. XCTAssertEqual(self.fakeConnection.outStreamId, 2);
  123. XCTAssertTrue(self.didSuccessfullySendData);
  124. }
  125. - (void)testSignOut_whenSignedIn {
  126. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
  127. // should be signed in now
  128. id mockSocket = self.fakeConnection.socket;
  129. [self.fakeConnection signOut];
  130. XCTAssertEqual(self.fakeConnection.state, kFIRMessagingConnectionNotConnected);
  131. XCTAssertEqual(self.fakeConnection.outStreamId, 3);
  132. XCTAssertNil([(FIRMessagingSecureSocket *)mockSocket delegate]);
  133. XCTAssertTrue(self.didSuccessfullySendData);
  134. OCMVerify([mockSocket sendData:[OCMArg any]
  135. withTag:kFIRMessagingProtoTagClose
  136. rmqId:[OCMArg isNil]]);
  137. }
  138. - (void)testReceiveCloseProto {
  139. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
  140. id mockSocket = self.fakeConnection.socket;
  141. GtalkClose *close = [[GtalkClose alloc] init];
  142. [self.fakeConnection secureSocket:mockSocket
  143. didReceiveData:[close data]
  144. withTag:kFIRMessagingProtoTagClose];
  145. XCTAssertEqual(self.fakeConnection.state, kFIRMessagingConnectionNotConnected);
  146. XCTAssertTrue(self.didSuccessfullySendData);
  147. }
  148. - (void)testLoginRequest {
  149. XCTAssertEqual(kFIRMessagingConnectionNotConnected, [self.fakeConnection state]);
  150. [self.fakeConnection setupConnectionSocket];
  151. id socketMock = OCMPartialMock(self.fakeConnection.socket);
  152. self.fakeConnection.socket = socketMock;
  153. [[[socketMock stub]
  154. andDo:^(NSInvocation *invocation) {
  155. [socketMock _fakeSuccessfulSocketConnect];
  156. }]
  157. connectToHost:[FIRMessagingFakeConnection fakeHost]
  158. port:[FIRMessagingFakeConnection fakePort]
  159. onRunLoop:[OCMArg any]];
  160. [[[socketMock stub] andCall:@selector(_sendData:withTag:rmqId:) onObject:self]
  161. // do nothing
  162. sendData:[OCMArg any]
  163. withTag:kFIRMessagingProtoTagLoginRequest
  164. rmqId:[OCMArg isNil]];
  165. // swizzle disconnect socket
  166. [[[socketMock stub] andCall:@selector(_disconnectSocket)
  167. onObject:self] disconnect];
  168. currentProtoSendTag = kFIRMessagingProtoTagLoginRequest;
  169. // send login request
  170. [self.fakeConnection connectToSocket:socketMock];
  171. // verify login request sent
  172. XCTAssertEqual(1, self.fakeConnection.outStreamId);
  173. XCTAssertTrue(self.didSuccessfullySendData);
  174. [socketMock verify];
  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. NSLog(@"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