Files
firebase-ios-sdk/Example/Messaging/Tests/FIRMessagingConnectionTest.m
T

479 lines
17 KiB
Objective-C

/*
* Copyright 2017 Google
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import "Example/Messaging/Tests/FIRMessagingFakeConnection.h"
#import "Firebase/Messaging/FIRMessagingClient.h"
#import "Firebase/Messaging/FIRMessagingConnection.h"
#import "Firebase/Messaging/FIRMessagingDataMessageManager.h"
#import "Firebase/Messaging/FIRMessagingRmqManager.h"
#import "Firebase/Messaging/FIRMessagingSecureSocket.h"
#import "Firebase/Messaging/FIRMessagingUtilities.h"
#import "Firebase/Messaging/Protos/GtalkCore.pbobjc.h"
static NSString *const kDeviceAuthId = @"123456";
static NSString *const kSecretToken = @"56789";
// used to verify if we are sending in the right proto or not.
// set it to negative value to disable this check
static FIRMessagingProtoTag currentProtoSendTag;
@interface FIRMessagingSecureSocket ()
@property(nonatomic, readwrite, assign) FIRMessagingSecureSocketState state;
@end
@interface FIRMessagingSecureSocket (test_FIRMessagingConnection)
- (void)_successconnectToHost:(NSString *)host
port:(NSUInteger)port
onRunLoop:(NSRunLoop *)runLoop;
- (void)_fakeSuccessfulSocketConnect;
@end
@implementation FIRMessagingSecureSocket (test_FIRMessagingConnection)
- (void)_successconnectToHost:(NSString *)host
port:(NSUInteger)port
onRunLoop:(NSRunLoop *)runLoop {
// created ports, opened streams
// invoke callback async
[self _fakeSuccessfulSocketConnect];
}
- (void)_fakeSuccessfulSocketConnect {
self.state = kFIRMessagingSecureSocketOpen;
[self.delegate secureSocketDidConnect:self];
}
@end
// make sure these are defined in FIRMessagingConnection
@interface FIRMessagingConnection () <FIRMessagingSecureSocketDelegate>
@property(nonatomic, readwrite, assign) int64_t lastLoginServerTimestamp;
@property(nonatomic, readwrite, assign) int lastStreamIdAcked;
@property(nonatomic, readwrite, assign) int inStreamId;
@property(nonatomic, readwrite, assign) int outStreamId;
@property(nonatomic, readwrite, strong) FIRMessagingSecureSocket *socket;
@property(nonatomic, readwrite, strong) NSMutableArray *unackedS2dIds;
@property(nonatomic, readwrite, strong) NSMutableDictionary *ackedS2dMap;
@property(nonatomic, readwrite, strong) NSMutableArray *d2sInfos;
- (void)setupConnectionSocket;
- (void)connectToSocket:(FIRMessagingSecureSocket *)socket;
- (NSTimeInterval)connectionTimeoutInterval;
- (void)sendHeartbeatPing;
@end
@interface FIRMessagingConnectionTest : XCTestCase
@property(nonatomic, readwrite, assign) BOOL didSuccessfullySendData;
@property(nonatomic, readwrite, strong) NSUserDefaults *userDefaults;
@property(nonatomic, readwrite, strong) FIRMessagingConnection *fakeConnection;
@property(nonatomic, readwrite, strong) id mockClient;
@property(nonatomic, readwrite, strong) id mockConnection;
@property(nonatomic, readwrite, strong) id mockRmq;
@property(nonatomic, readwrite, strong) id mockDataMessageManager;
@end
@implementation FIRMessagingConnectionTest
- (void)setUp {
[super setUp];
_userDefaults = [[NSUserDefaults alloc] init];
_mockRmq = OCMClassMock([FIRMessagingRmqManager class]);
_mockDataMessageManager = OCMClassMock([FIRMessagingDataMessageManager class]);
// fake connection is only used to simulate the socket behavior
_fakeConnection = [[FIRMessagingFakeConnection alloc] initWithAuthID:kDeviceAuthId
token:kSecretToken
host:[FIRMessagingFakeConnection fakeHost]
port:[FIRMessagingFakeConnection fakePort]
runLoop:[NSRunLoop currentRunLoop]
rmq2Manager:_mockRmq
fcmManager:_mockDataMessageManager];
_mockClient = OCMClassMock([FIRMessagingClient class]);
_fakeConnection.delegate = _mockClient;
_mockConnection = OCMPartialMock(_fakeConnection);
_didSuccessfullySendData = NO;
}
- (void)tearDown {
[self.fakeConnection teardown];
[super tearDown];
}
- (void)testInitialConnectionNotConnected {
XCTAssertEqual(kFIRMessagingConnectionNotConnected, [self.fakeConnection state]);
}
- (void)testSuccessfulSocketConnection {
[self.fakeConnection signIn];
// should be connected now
XCTAssertEqual(kFIRMessagingConnectionConnected, self.fakeConnection.state);
XCTAssertEqual(0, self.fakeConnection.lastStreamIdAcked);
XCTAssertEqual(0, self.fakeConnection.inStreamId);
XCTAssertEqual(0, self.fakeConnection.ackedS2dMap.count);
XCTAssertEqual(0, self.fakeConnection.unackedS2dIds.count);
[self stubSocketDisconnect:self.fakeConnection.socket];
}
- (void)testSignInAndThenSignOut {
[self.fakeConnection signIn];
[self stubSocketDisconnect:self.fakeConnection.socket];
[self.fakeConnection signOut];
XCTAssertEqual(kFIRMessagingSecureSocketClosed, self.fakeConnection.socket.state);
}
- (void)testSuccessfulSignIn {
[self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
XCTAssertEqual(self.fakeConnection.state, kFIRMessagingConnectionSignedIn);
XCTAssertEqual(self.fakeConnection.outStreamId, 2);
XCTAssertTrue(self.didSuccessfullySendData);
}
- (void)testSignOut_whenSignedIn {
[self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
// should be signed in now
id mockSocket = self.fakeConnection.socket;
[self.fakeConnection signOut];
XCTAssertEqual(self.fakeConnection.state, kFIRMessagingConnectionNotConnected);
XCTAssertEqual(self.fakeConnection.outStreamId, 3);
XCTAssertNil([(FIRMessagingSecureSocket *)mockSocket delegate]);
XCTAssertTrue(self.didSuccessfullySendData);
OCMVerify([mockSocket sendData:[OCMArg any]
withTag:kFIRMessagingProtoTagClose
rmqId:[OCMArg isNil]]);
}
- (void)testReceiveCloseProto {
[self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
id mockSocket = self.fakeConnection.socket;
GtalkClose *close = [[GtalkClose alloc] init];
[self.fakeConnection secureSocket:mockSocket
didReceiveData:[close data]
withTag:kFIRMessagingProtoTagClose];
XCTAssertEqual(self.fakeConnection.state, kFIRMessagingConnectionNotConnected);
XCTAssertTrue(self.didSuccessfullySendData);
}
- (void)testLoginRequest {
XCTAssertEqual(kFIRMessagingConnectionNotConnected, [self.fakeConnection state]);
[self.fakeConnection setupConnectionSocket];
id socketMock = OCMPartialMock(self.fakeConnection.socket);
self.fakeConnection.socket = socketMock;
[[[socketMock stub]
andDo:^(NSInvocation *invocation) {
[socketMock _fakeSuccessfulSocketConnect];
}]
connectToHost:[FIRMessagingFakeConnection fakeHost]
port:[FIRMessagingFakeConnection fakePort]
onRunLoop:[OCMArg any]];
[[[socketMock stub] andCall:@selector(_sendData:withTag:rmqId:) onObject:self]
// do nothing
sendData:[OCMArg any]
withTag:kFIRMessagingProtoTagLoginRequest
rmqId:[OCMArg isNil]];
// swizzle disconnect socket
[[[socketMock stub] andCall:@selector(_disconnectSocket)
onObject:self] disconnect];
currentProtoSendTag = kFIRMessagingProtoTagLoginRequest;
// send login request
[self.fakeConnection connectToSocket:socketMock];
// verify login request sent
XCTAssertEqual(1, self.fakeConnection.outStreamId);
XCTAssertTrue(self.didSuccessfullySendData);
[socketMock verify];
}
- (void)testLoginRequest_withPendingMessagesInRmq {
// TODO: add fake messages to rmq and test login request with them
}
- (void)testLoginRequest_withSuccessfulResponse {
[self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
OCMVerify([self.mockClient didLoginWithConnection:[OCMArg isEqual:self.fakeConnection]]);
// should send a heartbeat ping too
XCTAssertEqual(self.fakeConnection.outStreamId, 2);
// update for the received login response proto
XCTAssertEqual(self.fakeConnection.inStreamId, 1);
// did send data during login
XCTAssertTrue(self.didSuccessfullySendData);
}
- (void)testConnectionTimeout {
XCTAssertEqual(kFIRMessagingConnectionNotConnected, [self.fakeConnection state]);
[self.fakeConnection setupConnectionSocket];
id socketMock = OCMPartialMock(self.fakeConnection.socket);
self.fakeConnection.socket = socketMock;
[[[socketMock stub]
andDo:^(NSInvocation *invocation) {
[socketMock _fakeSuccessfulSocketConnect];
}]
connectToHost:[FIRMessagingFakeConnection fakeHost]
port:[FIRMessagingFakeConnection fakePort]
onRunLoop:[OCMArg any]];
[self.fakeConnection connectToSocket:socketMock];
XCTAssertEqual(self.fakeConnection.state, kFIRMessagingConnectionConnected);
GtalkLoginResponse *response = [[GtalkLoginResponse alloc] init];
[response setId_p:@""];
// connection timeout has been scheduled
// should disconnect since we wait for more time
XCTestExpectation *disconnectExpectation =
[self expectationWithDescription:
@"FCM connection should timeout without receiving "
@"any data for a timeout interval"];
[[[socketMock stub]
andDo:^(NSInvocation *invocation) {
[self _disconnectSocket];
[disconnectExpectation fulfill];
}] disconnect];
// simulate connection receiving login response
[self.fakeConnection secureSocket:socketMock
didReceiveData:[response data]
withTag:kFIRMessagingProtoTagLoginResponse];
[self waitForExpectationsWithTimeout:2.0
handler:^(NSError *error) {
XCTAssertNil(error);
}];
[socketMock verify];
XCTAssertEqual(self.fakeConnection.state, kFIRMessagingConnectionNotConnected);
}
- (void)testDataMessageReceive {
[self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
GtalkDataMessageStanza *stanza = [[GtalkDataMessageStanza alloc] init];
[stanza setCategory:@"special"];
[stanza setFrom:@"xyz"];
[self.fakeConnection secureSocket:self.fakeConnection.socket
didReceiveData:[stanza data]
withTag:kFIRMessagingProtoTagDataMessageStanza];
OCMVerify([self.mockClient connectionDidRecieveMessage:[OCMArg checkWithBlock:^BOOL(id obj) {
GtalkDataMessageStanza *message = (GtalkDataMessageStanza *)obj;
return [[message category] isEqual:@"special"] && [[message from] isEqual:@"xyz"];
}]]);
// did send data while login
XCTAssertTrue(self.didSuccessfullySendData);
}
- (void)testDataMessageReceiveWithInvalidTag {
[self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
GtalkDataMessageStanza *stanza = [[GtalkDataMessageStanza alloc] init];
BOOL didCauseException = NO;
@try {
[self.fakeConnection secureSocket:self.fakeConnection.socket
didReceiveData:[stanza data]
withTag:kFIRMessagingProtoTagInvalid];
} @catch (NSException *exception) {
didCauseException = YES;
} @finally {
}
XCTAssertFalse(didCauseException);
}
- (void)testDataMessageReceiveWithTagThatDoesntEquateToClass {
[self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
GtalkDataMessageStanza *stanza = [[GtalkDataMessageStanza alloc] init];
BOOL didCauseException = NO;
int8_t tagWhichDoesntEquateToClass = INT8_MAX;
@try {
[self.fakeConnection secureSocket:self.fakeConnection.socket
didReceiveData:[stanza data]
withTag:tagWhichDoesntEquateToClass];
} @catch (NSException *exception) {
didCauseException = YES;
} @finally {
}
XCTAssertFalse(didCauseException);
}
- (void)testHeartbeatSend {
[self setupSuccessfulLoginRequestWithConnection:self.fakeConnection]; // outstreamId should be 2
XCTAssertEqual(self.fakeConnection.outStreamId, 2);
[self.fakeConnection sendHeartbeatPing];
id mockSocket = self.fakeConnection.socket;
OCMVerify([mockSocket sendData:[OCMArg any]
withTag:kFIRMessagingProtoTagHeartbeatPing
rmqId:[OCMArg isNil]]);
XCTAssertEqual(self.fakeConnection.outStreamId, 3);
// did send data
XCTAssertTrue(self.didSuccessfullySendData);
}
- (void)testHeartbeatReceived {
[self setupSuccessfulLoginRequestWithConnection:self.fakeConnection];
XCTAssertEqual(self.fakeConnection.outStreamId, 2);
GtalkHeartbeatPing *ping = [[GtalkHeartbeatPing alloc] init];
[self.fakeConnection secureSocket:self.fakeConnection.socket
didReceiveData:[ping data]
withTag:kFIRMessagingProtoTagHeartbeatPing];
XCTAssertEqual(self.fakeConnection.inStreamId, 2);
id mockSocket = self.fakeConnection.socket;
OCMVerify([mockSocket sendData:[OCMArg any]
withTag:kFIRMessagingProtoTagHeartbeatAck
rmqId:[OCMArg isNil]]);
XCTAssertEqual(self.fakeConnection.outStreamId, 3);
// did send data
XCTAssertTrue(self.didSuccessfullySendData);
}
// TODO: Add tests for Selective/Stream ACK's
#pragma mark - Stubs
- (void)_disconnectSocket {
self.fakeConnection.socket.state = kFIRMessagingSecureSocketClosed;
}
- (void)_sendData:(NSData *)data withTag:(int8_t)tag rmqId:(NSString *)rmqId {
NSLog(@"FIRMessaging Socket: Send data with Tag: %d rmq: %@", tag, rmqId);
if (currentProtoSendTag > 0) {
XCTAssertEqual(tag, currentProtoSendTag);
}
self.didSuccessfullySendData = YES;
}
#pragma mark - Private Helpers
/**
* Stub socket disconnect to prevent spurious assert. Since we mock the socket object being
* used by the connection, while we teardown the client we also disconnect the socket to tear
* it down. Since we are using mock sockets we need to stub the `disconnect` to prevent some
* assertions from taking place.
* The `_disconectSocket` has the gist of the actual socket disconnect without any assertions.
*/
- (void)stubSocketDisconnect:(id)mockSocket {
[[[mockSocket stub] andCall:@selector(_disconnectSocket)
onObject:self] disconnect];
[mockSocket verify];
}
- (void)mockSuccessfulSignIn {
XCTAssertEqual(kFIRMessagingConnectionNotConnected, [self.fakeConnection state]);
[self.fakeConnection setupConnectionSocket];
id socketMock = OCMPartialMock(self.fakeConnection.socket);
self.fakeConnection.socket = socketMock;
[[[socketMock stub]
andDo:^(NSInvocation *invocation) {
[socketMock _fakeSuccessfulSocketConnect];
}]
connectToHost:[FIRMessagingFakeConnection fakeHost]
port:[FIRMessagingFakeConnection fakePort]
onRunLoop:[OCMArg any]];
[[[socketMock stub] andCall:@selector(_sendData:withTag:rmqId:) onObject:self]
// do nothing
sendData:[OCMArg any]
withTag:kFIRMessagingProtoTagLoginRequest
rmqId:[OCMArg isNil]];
// send login request
currentProtoSendTag = kFIRMessagingProtoTagLoginRequest;
[self.fakeConnection connectToSocket:socketMock];
GtalkLoginResponse *response = [[GtalkLoginResponse alloc] init];
[response setId_p:@""];
// simulate connection receiving login response
[self.fakeConnection secureSocket:socketMock
didReceiveData:[response data]
withTag:kFIRMessagingProtoTagLoginResponse];
OCMVerify([self.mockClient didLoginWithConnection:[OCMArg isEqual:self.fakeConnection]]);
// should receive data
XCTAssertTrue(self.didSuccessfullySendData);
// should send a heartbeat ping too
XCTAssertEqual(self.fakeConnection.outStreamId, 2);
// update for the received login response proto
XCTAssertEqual(self.fakeConnection.inStreamId, 1);
}
- (void)setupSuccessfulLoginRequestWithConnection:(FIRMessagingConnection *)fakeConnection {
[fakeConnection setupConnectionSocket];
id socketMock = OCMPartialMock(fakeConnection.socket);
fakeConnection.socket = socketMock;
[[[socketMock stub]
andDo:^(NSInvocation *invocation) {
[socketMock _fakeSuccessfulSocketConnect];
}]
connectToHost:[FIRMessagingFakeConnection fakeHost]
port:[FIRMessagingFakeConnection fakePort]
onRunLoop:[OCMArg any]];
[[[socketMock stub] andCall:@selector(_sendData:withTag:rmqId:) onObject:self]
// do nothing
sendData:[OCMArg any]
withTag:kFIRMessagingProtoTagLoginRequest
rmqId:[OCMArg isNil]];
// swizzle disconnect socket
[[[socketMock stub] andCall:@selector(_disconnectSocket)
onObject:self] disconnect];
// send login request
currentProtoSendTag = kFIRMessagingProtoTagLoginRequest;
[fakeConnection connectToSocket:socketMock];
GtalkLoginResponse *response = [[GtalkLoginResponse alloc] init];
[response setId_p:@""];
// simulate connection receiving login response
[fakeConnection secureSocket:socketMock
didReceiveData:[response data]
withTag:kFIRMessagingProtoTagLoginResponse];
}
@end