FIRMessagingConnectionTest.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477
  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. OCMVerify([[[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. }
  175. - (void)testLoginRequest_withPendingMessagesInRmq {
  176. // TODO: add fake messages to rmq and test login request with them
  177. }
  178. - (void)testLoginRequest_withSuccessfulResponse {
  179. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
  180. OCMVerify([self.mockClient didLoginWithConnection:[OCMArg isEqual:self.fakeConnection]]);
  181. // should send a heartbeat ping too
  182. XCTAssertEqual(self.fakeConnection.outStreamId, 2);
  183. // update for the received login response proto
  184. XCTAssertEqual(self.fakeConnection.inStreamId, 1);
  185. // did send data during login
  186. XCTAssertTrue(self.didSuccessfullySendData);
  187. }
  188. - (void)testConnectionTimeout {
  189. XCTAssertEqual(kFIRMessagingConnectionNotConnected, [self.fakeConnection state]);
  190. [self.fakeConnection setupConnectionSocket];
  191. id socketMock = OCMPartialMock(self.fakeConnection.socket);
  192. self.fakeConnection.socket = socketMock;
  193. [[[socketMock stub]
  194. andDo:^(NSInvocation *invocation) {
  195. [socketMock _fakeSuccessfulSocketConnect];
  196. }]
  197. connectToHost:[FIRMessagingFakeConnection fakeHost]
  198. port:[FIRMessagingFakeConnection fakePort]
  199. onRunLoop:[OCMArg any]];
  200. [self.fakeConnection connectToSocket:socketMock];
  201. XCTAssertEqual(self.fakeConnection.state, kFIRMessagingConnectionConnected);
  202. GtalkLoginResponse *response = [[GtalkLoginResponse alloc] init];
  203. [response setId_p:@""];
  204. // connection timeout has been scheduled
  205. // should disconnect since we wait for more time
  206. XCTestExpectation *disconnectExpectation =
  207. [self expectationWithDescription:
  208. @"FCM connection should timeout without receiving "
  209. @"any data for a timeout interval"];
  210. [[[socketMock stub]
  211. andDo:^(NSInvocation *invocation) {
  212. [self _disconnectSocket];
  213. [disconnectExpectation fulfill];
  214. }] disconnect];
  215. // simulate connection receiving login response
  216. [self.fakeConnection secureSocket:socketMock
  217. didReceiveData:[response data]
  218. withTag:kFIRMessagingProtoTagLoginResponse];
  219. [self waitForExpectationsWithTimeout:2.0
  220. handler:^(NSError *error) {
  221. XCTAssertNil(error);
  222. }];
  223. [socketMock verify];
  224. XCTAssertEqual(self.fakeConnection.state, kFIRMessagingConnectionNotConnected);
  225. }
  226. - (void)testDataMessageReceive {
  227. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
  228. GtalkDataMessageStanza *stanza = [[GtalkDataMessageStanza alloc] init];
  229. [stanza setCategory:@"special"];
  230. [stanza setFrom:@"xyz"];
  231. [self.fakeConnection secureSocket:self.fakeConnection.socket
  232. didReceiveData:[stanza data]
  233. withTag:kFIRMessagingProtoTagDataMessageStanza];
  234. OCMVerify([self.mockClient connectionDidRecieveMessage:[OCMArg checkWithBlock:^BOOL(id obj) {
  235. GtalkDataMessageStanza *message = (GtalkDataMessageStanza *)obj;
  236. return [[message category] isEqual:@"special"] && [[message from] isEqual:@"xyz"];
  237. }]]);
  238. // did send data while login
  239. XCTAssertTrue(self.didSuccessfullySendData);
  240. }
  241. - (void)testDataMessageReceiveWithInvalidTag {
  242. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
  243. GtalkDataMessageStanza *stanza = [[GtalkDataMessageStanza alloc] init];
  244. BOOL didCauseException = NO;
  245. @try {
  246. [self.fakeConnection secureSocket:self.fakeConnection.socket
  247. didReceiveData:[stanza data]
  248. withTag:kFIRMessagingProtoTagInvalid];
  249. } @catch (NSException *exception) {
  250. didCauseException = YES;
  251. } @finally {
  252. }
  253. XCTAssertFalse(didCauseException);
  254. }
  255. - (void)testDataMessageReceiveWithTagThatDoesntEquateToClass {
  256. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
  257. GtalkDataMessageStanza *stanza = [[GtalkDataMessageStanza alloc] init];
  258. BOOL didCauseException = NO;
  259. int8_t tagWhichDoesntEquateToClass = INT8_MAX;
  260. @try {
  261. [self.fakeConnection secureSocket:self.fakeConnection.socket
  262. didReceiveData:[stanza data]
  263. withTag:tagWhichDoesntEquateToClass];
  264. } @catch (NSException *exception) {
  265. didCauseException = YES;
  266. } @finally {
  267. }
  268. XCTAssertFalse(didCauseException);
  269. }
  270. - (void)testHeartbeatSend {
  271. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection]; // outstreamId should be 2
  272. XCTAssertEqual(self.fakeConnection.outStreamId, 2);
  273. [self.fakeConnection sendHeartbeatPing];
  274. id mockSocket = self.fakeConnection.socket;
  275. OCMVerify([mockSocket sendData:[OCMArg any]
  276. withTag:kFIRMessagingProtoTagHeartbeatPing
  277. rmqId:[OCMArg isNil]]);
  278. XCTAssertEqual(self.fakeConnection.outStreamId, 3);
  279. // did send data
  280. XCTAssertTrue(self.didSuccessfullySendData);
  281. }
  282. - (void)testHeartbeatReceived {
  283. [self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
  284. XCTAssertEqual(self.fakeConnection.outStreamId, 2);
  285. GtalkHeartbeatPing *ping = [[GtalkHeartbeatPing alloc] init];
  286. [self.fakeConnection secureSocket:self.fakeConnection.socket
  287. didReceiveData:[ping data]
  288. withTag:kFIRMessagingProtoTagHeartbeatPing];
  289. XCTAssertEqual(self.fakeConnection.inStreamId, 2);
  290. id mockSocket = self.fakeConnection.socket;
  291. OCMVerify([mockSocket sendData:[OCMArg any]
  292. withTag:kFIRMessagingProtoTagHeartbeatAck
  293. rmqId:[OCMArg isNil]]);
  294. XCTAssertEqual(self.fakeConnection.outStreamId, 3);
  295. // did send data
  296. XCTAssertTrue(self.didSuccessfullySendData);
  297. }
  298. // TODO: Add tests for Selective/Stream ACK's
  299. #pragma mark - Stubs
  300. - (void)_disconnectSocket {
  301. self.fakeConnection.socket.state = kFIRMessagingSecureSocketClosed;
  302. }
  303. - (void)_sendData:(NSData *)data withTag:(int8_t)tag rmqId:(NSString *)rmqId {
  304. NSLog(@"FIRMessaging Socket: Send data with Tag: %d rmq: %@", tag, rmqId);
  305. if (currentProtoSendTag > 0) {
  306. XCTAssertEqual(tag, currentProtoSendTag);
  307. }
  308. self.didSuccessfullySendData = YES;
  309. }
  310. #pragma mark - Private Helpers
  311. /**
  312. * Stub socket disconnect to prevent spurious assert. Since we mock the socket object being
  313. * used by the connection, while we teardown the client we also disconnect the socket to tear
  314. * it down. Since we are using mock sockets we need to stub the `disconnect` to prevent some
  315. * assertions from taking place.
  316. * The `_disconectSocket` has the gist of the actual socket disconnect without any assertions.
  317. */
  318. - (void)stubSocketDisconnect:(id)mockSocket {
  319. [[[mockSocket stub] andCall:@selector(_disconnectSocket)
  320. onObject:self] disconnect];
  321. [mockSocket verify];
  322. }
  323. - (void)mockSuccessfulSignIn {
  324. XCTAssertEqual(kFIRMessagingConnectionNotConnected, [self.fakeConnection state]);
  325. [self.fakeConnection setupConnectionSocket];
  326. id socketMock = OCMPartialMock(self.fakeConnection.socket);
  327. self.fakeConnection.socket = socketMock;
  328. [[[socketMock stub]
  329. andDo:^(NSInvocation *invocation) {
  330. [socketMock _fakeSuccessfulSocketConnect];
  331. }]
  332. connectToHost:[FIRMessagingFakeConnection fakeHost]
  333. port:[FIRMessagingFakeConnection fakePort]
  334. onRunLoop:[OCMArg any]];
  335. [[[socketMock stub] andCall:@selector(_sendData:withTag:rmqId:) onObject:self]
  336. // do nothing
  337. sendData:[OCMArg any]
  338. withTag:kFIRMessagingProtoTagLoginRequest
  339. rmqId:[OCMArg isNil]];
  340. // send login request
  341. currentProtoSendTag = kFIRMessagingProtoTagLoginRequest;
  342. [self.fakeConnection connectToSocket:socketMock];
  343. GtalkLoginResponse *response = [[GtalkLoginResponse alloc] init];
  344. [response setId_p:@""];
  345. // simulate connection receiving login response
  346. [self.fakeConnection secureSocket:socketMock
  347. didReceiveData:[response data]
  348. withTag:kFIRMessagingProtoTagLoginResponse];
  349. OCMVerify([self.mockClient didLoginWithConnection:[OCMArg isEqual:self.fakeConnection]]);
  350. // should receive data
  351. XCTAssertTrue(self.didSuccessfullySendData);
  352. // should send a heartbeat ping too
  353. XCTAssertEqual(self.fakeConnection.outStreamId, 2);
  354. // update for the received login response proto
  355. XCTAssertEqual(self.fakeConnection.inStreamId, 1);
  356. }
  357. - (void)setupSuccessfulLoginRequestWithConnection:(FIRMessagingConnection *)fakeConnection {
  358. [fakeConnection setupConnectionSocket];
  359. id socketMock = OCMPartialMock(fakeConnection.socket);
  360. fakeConnection.socket = socketMock;
  361. [[[socketMock stub]
  362. andDo:^(NSInvocation *invocation) {
  363. [socketMock _fakeSuccessfulSocketConnect];
  364. }]
  365. connectToHost:[FIRMessagingFakeConnection fakeHost]
  366. port:[FIRMessagingFakeConnection fakePort]
  367. onRunLoop:[OCMArg any]];
  368. [[[socketMock stub] andCall:@selector(_sendData:withTag:rmqId:) onObject:self]
  369. // do nothing
  370. sendData:[OCMArg any]
  371. withTag:kFIRMessagingProtoTagLoginRequest
  372. rmqId:[OCMArg isNil]];
  373. // swizzle disconnect socket
  374. [[[socketMock stub] andCall:@selector(_disconnectSocket)
  375. onObject:self] disconnect];
  376. // send login request
  377. currentProtoSendTag = kFIRMessagingProtoTagLoginRequest;
  378. [fakeConnection connectToSocket:socketMock];
  379. GtalkLoginResponse *response = [[GtalkLoginResponse alloc] init];
  380. [response setId_p:@""];
  381. // simulate connection receiving login response
  382. [fakeConnection secureSocket:socketMock
  383. didReceiveData:[response data]
  384. withTag:kFIRMessagingProtoTagLoginResponse];
  385. }
  386. @end