2017-05-15 12:27:07 -07:00
|
|
|
/*
|
|
|
|
|
* 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>
|
2020-02-07 14:27:18 -08:00
|
|
|
#import <XCTest/XCTest.h>
|
2017-05-15 12:27:07 -07:00
|
|
|
|
2020-02-07 14:27:18 -08:00
|
|
|
#import <FirebaseInstallations/FIRInstallations.h>
|
2019-08-07 17:00:01 -07:00
|
|
|
#import <FirebaseInstanceID/FIRInstanceID_Private.h>
|
2018-07-06 09:37:23 -07:00
|
|
|
#import <GoogleUtilities/GULReachabilityChecker.h>
|
2017-05-15 12:27:07 -07:00
|
|
|
|
2019-08-08 09:50:35 -07:00
|
|
|
#import "Example/Messaging/Tests/FIRMessagingFakeConnection.h"
|
2019-08-07 17:00:01 -07:00
|
|
|
#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/NSError+FIRMessaging.h"
|
2019-08-08 09:50:35 -07:00
|
|
|
#import "Firebase/Messaging/Protos/GtalkCore.pbobjc.h"
|
2019-08-07 17:00:01 -07:00
|
|
|
|
2017-05-15 12:27:07 -07:00
|
|
|
static NSString *const kDeviceAuthId = @"123456";
|
|
|
|
|
static NSString *const kSecretToken = @"56789";
|
|
|
|
|
|
2019-04-16 13:10:08 -07:00
|
|
|
@interface FIRInstanceID (exposedForTests)
|
2017-05-15 12:27:07 -07:00
|
|
|
|
2019-04-16 13:10:08 -07:00
|
|
|
+ (FIRInstanceID *)instanceIDForTests;
|
2017-05-15 12:27:07 -07:00
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@interface FIRMessagingClient () <FIRMessagingConnectionDelegate>
|
|
|
|
|
|
|
|
|
|
@property(nonatomic, readwrite, strong) FIRMessagingConnection *connection;
|
|
|
|
|
@property(nonatomic, readwrite, assign) int64_t lastConnectedTimestamp;
|
|
|
|
|
@property(nonatomic, readwrite, assign) int64_t lastDisconnectedTimestamp;
|
|
|
|
|
@property(nonatomic, readwrite, assign) NSUInteger subscribeRetryCount;
|
|
|
|
|
@property(nonatomic, readwrite, assign) NSUInteger connectRetryCount;
|
|
|
|
|
|
|
|
|
|
- (NSTimeInterval)connectionTimeoutInterval;
|
|
|
|
|
- (void)setupConnection;
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@interface FIRMessagingConnection () <FIRMessagingSecureSocketDelegate>
|
|
|
|
|
|
|
|
|
|
@property(nonatomic, readwrite, strong) FIRMessagingSecureSocket *socket;
|
|
|
|
|
|
|
|
|
|
- (void)setupConnectionSocket;
|
|
|
|
|
- (void)connectToSocket:(FIRMessagingSecureSocket *)socket;
|
|
|
|
|
- (NSTimeInterval)connectionTimeoutInterval;
|
|
|
|
|
- (void)sendHeartbeatPing;
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@interface FIRMessagingSecureSocket ()
|
|
|
|
|
|
|
|
|
|
@property(nonatomic, readwrite, assign) FIRMessagingSecureSocketState state;
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@interface FIRMessagingClientTest : XCTestCase
|
|
|
|
|
|
|
|
|
|
@property(nonatomic, readwrite, strong) FIRMessagingClient *client;
|
|
|
|
|
@property(nonatomic, readwrite, strong) id mockClient;
|
|
|
|
|
@property(nonatomic, readwrite, strong) id mockReachability;
|
|
|
|
|
@property(nonatomic, readwrite, strong) id mockRmqManager;
|
|
|
|
|
@property(nonatomic, readwrite, strong) id mockClientDelegate;
|
|
|
|
|
@property(nonatomic, readwrite, strong) id mockDataMessageManager;
|
2019-04-16 13:10:08 -07:00
|
|
|
@property(nonatomic, readwrite, strong) id mockInstanceID;
|
2019-12-20 10:54:11 -05:00
|
|
|
@property(nonatomic, readwrite, strong) id mockInstallations;
|
2019-04-16 13:10:08 -07:00
|
|
|
|
2017-05-15 12:27:07 -07:00
|
|
|
// argument callback blocks
|
|
|
|
|
@property(nonatomic, readwrite, copy) FIRMessagingConnectCompletionHandler connectCompletion;
|
|
|
|
|
@property(nonatomic, readwrite, copy) FIRMessagingTopicOperationCompletion subscribeCompletion;
|
|
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation FIRMessagingClientTest
|
|
|
|
|
|
|
|
|
|
- (void)setUp {
|
|
|
|
|
[super setUp];
|
2019-12-20 10:54:11 -05:00
|
|
|
_mockClientDelegate = OCMStrictProtocolMock(@protocol(FIRMessagingClientDelegate));
|
2018-07-06 09:37:23 -07:00
|
|
|
_mockReachability = OCMClassMock([GULReachabilityChecker class]);
|
2017-05-15 12:27:07 -07:00
|
|
|
_mockRmqManager = OCMClassMock([FIRMessagingRmqManager class]);
|
|
|
|
|
_client = [[FIRMessagingClient alloc] initWithDelegate:_mockClientDelegate
|
2019-12-20 10:54:11 -05:00
|
|
|
reachability:_mockReachability
|
|
|
|
|
rmq2Manager:_mockRmqManager];
|
2017-05-15 12:27:07 -07:00
|
|
|
_mockClient = OCMPartialMock(_client);
|
|
|
|
|
_mockDataMessageManager = OCMClassMock([FIRMessagingDataMessageManager class]);
|
|
|
|
|
[_mockClient setDataMessageManager:_mockDataMessageManager];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)tearDown {
|
|
|
|
|
// remove all handlers
|
|
|
|
|
[self tearDownMocksAndHandlers];
|
|
|
|
|
// Mock all sockets to disconnect in a nice way
|
|
|
|
|
[[[(id)self.client.connection.socket stub] andDo:^(NSInvocation *invocation) {
|
2020-02-07 14:27:18 -08:00
|
|
|
self.client.connection.socket.state = kFIRMessagingSecureSocketClosed;
|
2017-05-15 12:27:07 -07:00
|
|
|
}] disconnect];
|
|
|
|
|
|
|
|
|
|
[self.client teardown];
|
|
|
|
|
[super tearDown];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)tearDownMocksAndHandlers {
|
|
|
|
|
self.connectCompletion = nil;
|
|
|
|
|
self.subscribeCompletion = nil;
|
2019-12-20 10:54:11 -05:00
|
|
|
self.mockInstanceID = nil;
|
|
|
|
|
self.mockInstallations = nil;
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)setupConnectionWithFakeLoginResult:(BOOL)loginResult
|
|
|
|
|
heartbeatTimeout:(NSTimeInterval)heartbeatTimeout {
|
|
|
|
|
[self setupFakeConnectionWithClass:[FIRMessagingFakeConnection class]
|
|
|
|
|
withSetupCompletionHandler:^(FIRMessagingConnection *connection) {
|
2020-02-07 14:27:18 -08:00
|
|
|
FIRMessagingFakeConnection *fakeConnection = (FIRMessagingFakeConnection *)connection;
|
|
|
|
|
fakeConnection.shouldFakeSuccessLogin = loginResult;
|
|
|
|
|
fakeConnection.fakeConnectionTimeout = heartbeatTimeout;
|
2017-05-15 12:27:07 -07:00
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testSetupConnection {
|
|
|
|
|
XCTAssertNil(self.client.connection);
|
|
|
|
|
[self.client setupConnection];
|
|
|
|
|
XCTAssertNotNil(self.client.connection);
|
|
|
|
|
XCTAssertNotNil(self.client.connection.delegate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testConnectSuccess_withCachedFcmDefaults {
|
|
|
|
|
[self addFIRMessagingPreferenceKeysToUserDefaults];
|
|
|
|
|
|
|
|
|
|
// login request should be successful
|
|
|
|
|
[self setupConnectionWithFakeLoginResult:YES heartbeatTimeout:1.0];
|
|
|
|
|
|
2020-02-07 14:27:18 -08:00
|
|
|
XCTestExpectation *setupConnection =
|
|
|
|
|
[self expectationWithDescription:@"Fcm should successfully setup a connection"];
|
2017-05-15 12:27:07 -07:00
|
|
|
|
|
|
|
|
[self.client connectWithHandler:^(NSError *error) {
|
2020-02-07 14:27:18 -08:00
|
|
|
XCTAssertNil(error);
|
|
|
|
|
[setupConnection fulfill];
|
2017-05-15 12:27:07 -07:00
|
|
|
}];
|
|
|
|
|
|
2020-02-07 14:27:18 -08:00
|
|
|
[self waitForExpectationsWithTimeout:1.0
|
|
|
|
|
handler:^(NSError *error) {
|
|
|
|
|
XCTAssertNil(error);
|
|
|
|
|
}];
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testsConnectWithNoNetworkError_withCachedFcmDefaults {
|
|
|
|
|
// connection timeout interval is 1s
|
|
|
|
|
[[[self.mockClient stub] andReturnValue:@(1)] connectionTimeoutInterval];
|
|
|
|
|
[self addFIRMessagingPreferenceKeysToUserDefaults];
|
|
|
|
|
|
|
|
|
|
[self setupFakeConnectionWithClass:[FIRMessagingFakeFailConnection class]
|
|
|
|
|
withSetupCompletionHandler:^(FIRMessagingConnection *connection) {
|
2020-02-07 14:27:18 -08:00
|
|
|
FIRMessagingFakeFailConnection *fakeConnection =
|
|
|
|
|
(FIRMessagingFakeFailConnection *)connection;
|
|
|
|
|
fakeConnection.shouldFakeSuccessLogin = NO;
|
|
|
|
|
// should fail only once
|
|
|
|
|
fakeConnection.failCount = 1;
|
2017-05-15 12:27:07 -07:00
|
|
|
}];
|
|
|
|
|
|
2020-02-07 14:27:18 -08:00
|
|
|
XCTestExpectation *connectExpectation =
|
|
|
|
|
[self expectationWithDescription:@"Should retry connection if once failed"];
|
2017-05-15 12:27:07 -07:00
|
|
|
[self.client connectWithHandler:^(NSError *error) {
|
2020-02-07 14:27:18 -08:00
|
|
|
XCTAssertNotNil(error);
|
|
|
|
|
XCTAssertEqual(kFIRMessagingErrorCodeNetwork, error.code);
|
|
|
|
|
[connectExpectation fulfill];
|
2017-05-15 12:27:07 -07:00
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
[self waitForExpectationsWithTimeout:10.0
|
|
|
|
|
handler:^(NSError *error) {
|
2020-02-07 14:27:18 -08:00
|
|
|
XCTAssertNil(error);
|
2017-05-15 12:27:07 -07:00
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testConnectSuccessOnSecondTry_withCachedFcmDefaults {
|
|
|
|
|
// connection timeout interval is 1s
|
|
|
|
|
[[[self.mockClient stub] andReturnValue:@(1)] connectionTimeoutInterval];
|
|
|
|
|
[self addFIRMessagingPreferenceKeysToUserDefaults];
|
|
|
|
|
|
|
|
|
|
// the network is available
|
2020-02-07 14:27:18 -08:00
|
|
|
[[[self.mockReachability stub] andReturnValue:@(kGULReachabilityViaWifi)] reachabilityStatus];
|
2017-05-15 12:27:07 -07:00
|
|
|
|
|
|
|
|
[self setupFakeConnectionWithClass:[FIRMessagingFakeFailConnection class]
|
|
|
|
|
withSetupCompletionHandler:^(FIRMessagingConnection *connection) {
|
2020-02-07 14:27:18 -08:00
|
|
|
FIRMessagingFakeFailConnection *fakeConnection =
|
|
|
|
|
(FIRMessagingFakeFailConnection *)connection;
|
|
|
|
|
fakeConnection.shouldFakeSuccessLogin = NO;
|
|
|
|
|
// should fail only once
|
|
|
|
|
fakeConnection.failCount = 1;
|
2017-05-15 12:27:07 -07:00
|
|
|
}];
|
|
|
|
|
|
2020-02-07 14:27:18 -08:00
|
|
|
XCTestExpectation *connectExpectation =
|
|
|
|
|
[self expectationWithDescription:@"Should retry connection if once failed"];
|
2017-05-15 12:27:07 -07:00
|
|
|
[self.client connectWithHandler:^(NSError *error) {
|
2020-02-07 14:27:18 -08:00
|
|
|
XCTAssertNil(error);
|
|
|
|
|
[connectExpectation fulfill];
|
2017-05-15 12:27:07 -07:00
|
|
|
}];
|
|
|
|
|
|
|
|
|
|
[self waitForExpectationsWithTimeout:10.0
|
|
|
|
|
handler:^(NSError *error) {
|
2020-02-07 14:27:18 -08:00
|
|
|
XCTAssertNil(error);
|
|
|
|
|
XCTAssertTrue([self.client isConnectionActive]);
|
2017-05-15 12:27:07 -07:00
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)testDisconnectAfterConnect {
|
|
|
|
|
// setup the connection
|
|
|
|
|
[self addFIRMessagingPreferenceKeysToUserDefaults];
|
|
|
|
|
|
|
|
|
|
// login request should be successful
|
|
|
|
|
// Connection should not timeout because of heartbeat failure. Therefore set heartbeatTimeout
|
|
|
|
|
// to a large value.
|
|
|
|
|
[self setupConnectionWithFakeLoginResult:YES heartbeatTimeout:100.0];
|
|
|
|
|
|
|
|
|
|
[[[self.mockClient stub] andReturnValue:@(1)] connectionTimeoutInterval];
|
|
|
|
|
|
|
|
|
|
// the network is available
|
2020-02-07 14:27:18 -08:00
|
|
|
[[[self.mockReachability stub] andReturnValue:@(kGULReachabilityViaWifi)] reachabilityStatus];
|
2017-05-15 12:27:07 -07:00
|
|
|
|
|
|
|
|
XCTestExpectation *setupConnection =
|
|
|
|
|
[self expectationWithDescription:@"Fcm should successfully setup a connection"];
|
|
|
|
|
|
|
|
|
|
__block int timesConnected = 0;
|
|
|
|
|
FIRMessagingConnectCompletionHandler handler = ^(NSError *error) {
|
2020-02-07 14:27:18 -08:00
|
|
|
XCTAssertNil(error);
|
|
|
|
|
timesConnected++;
|
|
|
|
|
if (timesConnected == 1) {
|
|
|
|
|
[setupConnection fulfill];
|
|
|
|
|
// disconnect the connection after some time
|
|
|
|
|
FIRMessagingFakeConnection *fakeConnection =
|
|
|
|
|
(FIRMessagingFakeConnection *)[self.mockClient connection];
|
|
|
|
|
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (0.2 * NSEC_PER_SEC));
|
|
|
|
|
dispatch_after(time, dispatch_get_main_queue(), ^{
|
|
|
|
|
// disconnect now
|
|
|
|
|
[(FIRMessagingFakeConnection *)fakeConnection mockSocketDisconnect];
|
|
|
|
|
[(FIRMessagingFakeConnection *)fakeConnection disconnectNow];
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
XCTFail(@"Fcm should only connect at max 2 times");
|
|
|
|
|
}
|
2017-05-15 12:27:07 -07:00
|
|
|
};
|
|
|
|
|
[self.mockClient connectWithHandler:handler];
|
|
|
|
|
|
|
|
|
|
// reconnect after disconnect
|
|
|
|
|
XCTAssertTrue(self.client.isConnectionActive);
|
|
|
|
|
|
|
|
|
|
[self waitForExpectationsWithTimeout:10.0
|
|
|
|
|
handler:^(NSError *error) {
|
2020-02-07 14:27:18 -08:00
|
|
|
XCTAssertNil(error);
|
|
|
|
|
XCTAssertNotEqual(self.client.lastDisconnectedTimestamp, 0);
|
|
|
|
|
XCTAssertTrue(self.client.isConnectionActive);
|
2017-05-15 12:27:07 -07:00
|
|
|
}];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#pragma mark - Private Helpers
|
|
|
|
|
|
|
|
|
|
- (void)setupFakeConnectionWithClass:(Class)connectionClass
|
|
|
|
|
withSetupCompletionHandler:(void (^)(FIRMessagingConnection *))handler {
|
|
|
|
|
[[[self.mockClient stub] andDo:^(NSInvocation *invocation) {
|
2020-02-07 14:27:18 -08:00
|
|
|
self.client.connection =
|
|
|
|
|
[[connectionClass alloc] initWithAuthID:kDeviceAuthId
|
|
|
|
|
token:kSecretToken
|
|
|
|
|
host:[FIRMessagingFakeConnection fakeHost]
|
|
|
|
|
port:[FIRMessagingFakeConnection fakePort]
|
|
|
|
|
runLoop:[NSRunLoop mainRunLoop]
|
|
|
|
|
rmq2Manager:self.mockRmqManager
|
|
|
|
|
fcmManager:self.mockDataMessageManager];
|
|
|
|
|
self.client.connection.delegate = self.client;
|
|
|
|
|
handler(self.client.connection);
|
2017-05-15 12:27:07 -07:00
|
|
|
}] setupConnection];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
- (void)addFIRMessagingPreferenceKeysToUserDefaults {
|
2019-12-20 10:54:11 -05:00
|
|
|
// `+[FIRInstallations installations]` supposed to be used on `-[FIRInstanceID start]` to get
|
|
|
|
|
// `FIRInstallations` default instance. Need to stub it before.
|
|
|
|
|
self.mockInstallations = OCMClassMock([FIRInstallations class]);
|
|
|
|
|
OCMStub([self.mockInstallations installations]).andReturn(self.mockInstallations);
|
|
|
|
|
|
|
|
|
|
self.mockInstanceID = OCMPartialMock([FIRInstanceID instanceIDForTests]);
|
|
|
|
|
OCMStub([self.mockInstanceID tryToLoadValidCheckinInfo]).andReturn(YES);
|
|
|
|
|
OCMStub([self.mockInstanceID deviceAuthID]).andReturn(kDeviceAuthId);
|
|
|
|
|
OCMStub([self.mockInstanceID secretToken]).andReturn(kSecretToken);
|
2017-05-15 12:27:07 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@end
|