| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- /*
- * Copyright 2019 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 <XCTest/XCTest.h>
- #import <OCMock/OCMock.h>
- #import "Firebase/InstanceID/FIRInstanceIDAuthService.h"
- #import "Firebase/InstanceID/FIRInstanceIDCheckinPreferences+Internal.h"
- #import "Firebase/InstanceID/FIRInstanceIDCheckinService.h"
- #import "Firebase/InstanceID/FIRInstanceIDStore.h"
- #import "Firebase/InstanceID/NSError+FIRInstanceID.h"
- static NSString *const kDeviceAuthId = @"device-id";
- static NSString *const kSecretToken = @"secret-token";
- static NSString *const kVersionInfo = @"1.0";
- @interface FIRInstanceIDCheckinService ()
- @property(nonatomic, readwrite, strong) FIRInstanceIDCheckinPreferences *checkinPreferences;
- @end
- @interface FIRInstanceIDAuthService ()
- @property(atomic, readwrite, assign) int64_t lastCheckinTimestampSeconds;
- @property(atomic, readwrite, assign) int64_t nextScheduledCheckinIntervalSeconds;
- @property(atomic, readwrite, assign) int checkinRetryCount;
- @property(nonatomic, readonly, strong)
- NSMutableArray<FIRInstanceIDDeviceCheckinCompletion> *checkinHandlers;
- @end
- @interface FIRInstanceIDAuthServiceTest : XCTestCase
- @property(nonatomic, readwrite, strong) FIRInstanceIDAuthService *authService;
- @property(nonatomic, readwrite, strong) FIRInstanceIDCheckinService *checkinService;
- @property(nonatomic, readwrite, strong) id mockCheckinService;
- @property(nonatomic, readwrite, strong) id mockStore;
- @property(nonatomic, readwrite, copy) FIRInstanceIDDeviceCheckinCompletion checkinCompletion;
- @end
- @implementation FIRInstanceIDAuthServiceTest
- - (void)setUp {
- [super setUp];
- _mockStore = OCMClassMock([FIRInstanceIDStore class]);
- _checkinService = [[FIRInstanceIDCheckinService alloc] init];
- _mockCheckinService = OCMPartialMock(_checkinService);
- _authService = [[FIRInstanceIDAuthService alloc] initWithCheckinService:_mockCheckinService
- store:_mockStore];
- // The tests here are to focus on checkin interval not locale change, so always set locale as
- // non-changed.
- [[NSUserDefaults standardUserDefaults] setObject:FIRInstanceIDCurrentLocale()
- forKey:kFIRInstanceIDUserDefaultsKeyLocale];
- }
- - (void)tearDown {
- _checkinCompletion = nil;
- [super tearDown];
- }
- /**
- * Test scheduling a checkin which completes successfully. Once the checkin is complete
- * we should have the valid checkin preferences in memory.
- */
- - (void)testScheduleCheckin_initialSuccess {
- XCTestExpectation *checkinExpectation =
- [self expectationWithDescription:@"Did call checkin service"];
- FIRInstanceIDCheckinPreferences *checkinPreferences = [self validCheckinPreferences];
- [[[self.mockCheckinService stub] andDo:^(NSInvocation *invocation) {
- self.checkinCompletion(checkinPreferences, nil);
- }] checkinWithExistingCheckin:[OCMArg any]
- completion:[OCMArg checkWithBlock:^BOOL(id obj) {
- [checkinExpectation fulfill];
- self.checkinCompletion = obj;
- return obj != nil;
- }]];
- // Always return YES for whether we succeeded in persisting the checkin
- [[self.mockStore stub] saveCheckinPreferences:[OCMArg any]
- handler:[OCMArg invokeBlockWithArgs:[NSNull null], nil]];
- [self.authService scheduleCheckin:YES];
- XCTAssertTrue([self.authService hasValidCheckinInfo]);
- XCTAssertEqual([self.authService checkinRetryCount], 1);
- [self waitForExpectationsWithTimeout:2.0 handler:NULL];
- }
- /**
- * Test scheduling a checkin which completes successfully, but fails to save, due to Keychain
- * errors.
- */
- - (void)testScheduleCheckin_successButFailureInSaving {
- XCTestExpectation *checkinFailureExpectation =
- [self expectationWithDescription:@"Did receive error after checkin"];
- FIRInstanceIDCheckinPreferences *checkinPreferences = [self validCheckinPreferences];
- [[[self.mockCheckinService stub] andDo:^(NSInvocation *invocation) {
- self.checkinCompletion(checkinPreferences, nil);
- }] checkinWithExistingCheckin:[OCMArg any]
- completion:[OCMArg checkWithBlock:^BOOL(id obj) {
- self.checkinCompletion = obj;
- return obj != nil;
- }]];
- // Always return NO for whether we succeeded in persisting the checkin, to simulate Keychain error
- [[self.mockStore stub] saveCheckinPreferences:[OCMArg any]
- handler:[OCMArg invokeBlockWithArgs:[OCMArg any], nil]];
- [self.authService
- fetchCheckinInfoWithHandler:^(FIRInstanceIDCheckinPreferences *checkin, NSError *error) {
- [checkinFailureExpectation fulfill];
- }];
- [self waitForExpectationsWithTimeout:2.0 handler:NULL];
- XCTAssertFalse([self.authService hasValidCheckinInfo]);
- }
- /**
- * Test scheduling multiple checkins to complete immediately. Each successive checkin should
- * be triggered immediately.
- */
- - (void)testMultipleScheduleCheckin_immediately {
- XCTestExpectation *checkinExpectation =
- [self expectationWithDescription:@"Did call checkin service"];
- __block int checkinHandlerInvocationCount = 0;
- FIRInstanceIDCheckinPreferences *checkinPreferences = [self validCheckinPreferences];
- [[[self.mockCheckinService stub] andDo:^(NSInvocation *invocation) {
- checkinHandlerInvocationCount++;
- // Mock successful Checkin after delay.
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
- dispatch_get_main_queue(), ^{
- [checkinExpectation fulfill];
- self.checkinCompletion(checkinPreferences, nil);
- });
- }] checkinWithExistingCheckin:[OCMArg any]
- completion:[OCMArg checkWithBlock:^BOOL(id obj) {
- self.checkinCompletion = obj;
- return obj != nil;
- }]];
- // Always return YES for whether we succeeded in persisting the checkin
- [[self.mockStore stub] saveCheckinPreferences:[OCMArg any]
- handler:[OCMArg invokeBlockWithArgs:[NSNull null], nil]];
- [self.authService scheduleCheckin:YES];
- // Schedule an immediate checkin again.
- // This should just return because the previous checkin isn't over yet.
- [self.authService scheduleCheckin:YES];
- [self waitForExpectationsWithTimeout:5.0 handler:NULL];
- XCTAssertTrue([self.authService hasValidCheckinInfo]);
- XCTAssertEqual([self.authService checkinRetryCount], 2);
- // Checkin handler should only be invoked once since the second checkin request should
- // return immediately.
- XCTAssertEqual(checkinHandlerInvocationCount, 1);
- }
- /**
- * Test multiple checkins scheduled. The second checkin should be scheduled after some
- * delay before the first checkin has returned. Since the latter checkin is not immediate
- * we should not run it since the first checkin is already scheduled to be executed later.
- */
- - (void)testMultipleScheduleCheckin_notImmediately {
- XCTestExpectation *checkinExpectation =
- [self expectationWithDescription:@"Did call checkin service"];
- FIRInstanceIDCheckinPreferences *checkinPreferences = [self validCheckinPreferences];
- [[[self.mockCheckinService stub] andDo:^(NSInvocation *invocation) {
- // Mock successful Checkin after delay.
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
- dispatch_get_main_queue(), ^{
- [checkinExpectation fulfill];
- self.checkinCompletion(checkinPreferences, nil);
- });
- }] checkinWithExistingCheckin:[OCMArg any]
- completion:[OCMArg checkWithBlock:^BOOL(id obj) {
- self.checkinCompletion = obj;
- return obj != nil;
- }]];
- // Always return YES for whether we succeeded in persisting the checkin
- [[self.mockStore stub] saveCheckinPreferences:[OCMArg any]
- handler:[OCMArg invokeBlockWithArgs:[NSNull null], nil]];
- [self.authService scheduleCheckin:YES];
- // Schedule another checkin after some delay while the first checkin has not yet returned
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)),
- dispatch_get_main_queue(), ^{
- [self.authService scheduleCheckin:NO];
- });
- [self waitForExpectationsWithTimeout:5.0 handler:NULL];
- XCTAssertTrue([self.authService hasValidCheckinInfo]);
- XCTAssertEqual([self.authService checkinRetryCount], 1);
- }
- /**
- * Test initial checkin failure which schedules another checkin which should succeed.
- */
- - (void)testInitialCheckinFailure_retrySuccess {
- XCTestExpectation *checkinExpectation =
- [self expectationWithDescription:@"Did call checkin service"];
- __block int checkinHandlerInvocationCount = 0;
- [[[self.mockCheckinService stub] andDo:^(NSInvocation *invocation) {
- checkinHandlerInvocationCount++;
- if (checkinHandlerInvocationCount == 1) {
- // Mock failure on first try
- NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeTimeout];
- self.checkinCompletion(nil, error);
- } else if (checkinHandlerInvocationCount == 2) {
- // Mock success on second try
- [checkinExpectation fulfill];
- self.checkinCompletion([self validCheckinPreferences], nil);
- } else {
- // We should not retry for a third time again.
- XCTFail(@"Invoking checkin handler invalid number of times.");
- }
- }] checkinWithExistingCheckin:[OCMArg any]
- completion:[OCMArg checkWithBlock:^BOOL(id obj) {
- self.checkinCompletion = obj;
- return obj != nil;
- }]];
- // Always return YES for whether we succeeded in persisting the checkin
- [[self.mockStore stub] saveCheckinPreferences:[OCMArg any]
- handler:[OCMArg invokeBlockWithArgs:[NSNull null], nil]];
- [self.authService scheduleCheckin:YES];
- // Schedule another checkin after some delay while the first checkin has not yet returned
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)),
- dispatch_get_main_queue(), ^{
- [self.authService scheduleCheckin:YES];
- XCTAssertTrue([self.authService hasValidCheckinInfo]);
- XCTAssertEqual([self.authService checkinRetryCount], 2);
- XCTAssertEqual(checkinHandlerInvocationCount, 2);
- });
- [self waitForExpectationsWithTimeout:5.0 handler:NULL];
- }
- /**
- * Test initial checkin failure which schedules another checkin which should succeed. If
- * a new checkin request comes after that we should not schedule a checkin as we have
- * already have valid checkin credentials.
- */
- - (void)testInitialCheckinFailure_multipleRetrySuccess {
- XCTestExpectation *checkinExpectation =
- [self expectationWithDescription:@"Did call checkin service"];
- __block int checkinHandlerInvocationCount = 0;
- [[[self.mockCheckinService stub] andDo:^(NSInvocation *invocation) {
- checkinHandlerInvocationCount++;
- if (checkinHandlerInvocationCount <= 2) {
- // Mock failure on first try
- NSError *error = [NSError errorWithFIRInstanceIDErrorCode:kFIRInstanceIDErrorCodeTimeout];
- self.checkinCompletion(nil, error);
- } else if (checkinHandlerInvocationCount == 3) {
- // Mock success on second try
- [checkinExpectation fulfill];
- self.checkinCompletion([self validCheckinPreferences], nil);
- } else {
- // We should not retry for a third time again.
- XCTFail(@"Invoking checkin handler invalid number of times.");
- }
- }] checkinWithExistingCheckin:[OCMArg any]
- completion:[OCMArg checkWithBlock:^BOOL(id obj) {
- self.checkinCompletion = obj;
- return obj != nil;
- }]];
- // Always return YES for whether we succeeded in persisting the checkin
- [[self.mockStore stub] saveCheckinPreferences:[OCMArg any]
- handler:[OCMArg invokeBlockWithArgs:[NSNull null], nil]];
- [self.authService scheduleCheckin:YES];
- [self waitForExpectationsWithTimeout:10.0 handler:NULL];
- XCTAssertTrue([self.authService hasValidCheckinInfo]);
- XCTAssertEqual([self.authService checkinRetryCount], 3);
- }
- /**
- * Performing multiple checkin requests should result in multiple handlers being
- * called back, but with only a single actual checkin fetch.
- */
- - (void)testMultipleCheckinHandlersWithSuccessfulCheckin {
- XCTestExpectation *allHandlersCalledExpectation =
- [self expectationWithDescription:@"All checkin handlers were called"];
- __block NSInteger checkinHandlerCallbackCount = 0;
- __block NSInteger checkinServiceInvocationCount = 0;
- // Always return a successful checkin, and count the number of times CheckinService is called
- [[[self.mockCheckinService stub] andDo:^(NSInvocation *invocation) {
- checkinServiceInvocationCount++;
- self.checkinCompletion([self validCheckinPreferences], nil);
- }] checkinWithExistingCheckin:[OCMArg any]
- completion:[OCMArg checkWithBlock:^BOOL(id obj) {
- self.checkinCompletion = obj;
- return obj != nil;
- }]];
- // Always return YES for whether we succeeded in persisting the checkin
- [[self.mockStore stub] saveCheckinPreferences:[OCMArg any]
- handler:[OCMArg invokeBlockWithArgs:[NSNull null], nil]];
- NSInteger numHandlers = 10;
- for (NSInteger i = 0; i < numHandlers; i++) {
- [self.authService
- fetchCheckinInfoWithHandler:^(FIRInstanceIDCheckinPreferences *checkin, NSError *error) {
- checkinHandlerCallbackCount++;
- if (checkinHandlerCallbackCount == numHandlers) {
- [allHandlersCalledExpectation fulfill];
- }
- }];
- }
- [self waitForExpectationsWithTimeout:1.0 handler:nil];
- XCTAssertEqual(checkinServiceInvocationCount, 1);
- XCTAssertEqual(checkinHandlerCallbackCount, numHandlers);
- }
- /**
- * Performing a scheduled checkin *and* simultaneous checkin request should result in
- * the number of pending checkin handlers to be 2 (one for the scheduled checkin, one for
- * the direct fetch).
- */
- - (void)testScheduledAndImmediateCheckinsWithMultipleHandler {
- XCTestExpectation *fetchHandlerCalledExpectation =
- [self expectationWithDescription:@"Direct checkin handler was called"];
- __block NSInteger checkinServiceInvocationCount = 0;
- // Always return a successful checkin, and count the number of times CheckinService is called
- [[[self.mockCheckinService stub] andDo:^(NSInvocation *invocation) {
- checkinServiceInvocationCount++;
- // Give the checkin service some time to complete the request
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)),
- dispatch_get_main_queue(), ^{
- self.checkinCompletion([self validCheckinPreferences], nil);
- });
- }] checkinWithExistingCheckin:[OCMArg any]
- completion:[OCMArg checkWithBlock:^BOOL(id obj) {
- self.checkinCompletion = obj;
- return obj != nil;
- }]];
- // Always return YES for whether we succeeded in persisting the checkin
- [[self.mockStore stub] saveCheckinPreferences:[OCMArg any]
- handler:[OCMArg invokeBlockWithArgs:[NSNull null], nil]];
- // Start a scheduled (though immediate) checkin
- [self.authService scheduleCheckin:YES];
- // Request a direct checkin fetch
- [self.authService
- fetchCheckinInfoWithHandler:^(FIRInstanceIDCheckinPreferences *checkin, NSError *error) {
- [fetchHandlerCalledExpectation fulfill];
- }];
- // At this point we should have checkinHandlers, one for scheduled, one for the direct fetch
- XCTAssertEqual(self.authService.checkinHandlers.count, 2);
- [self waitForExpectationsWithTimeout:0.5 handler:nil];
- // Make sure only one checkin fetch was performed
- XCTAssertEqual(checkinServiceInvocationCount, 1);
- }
- #pragma mark - Helper Methods
- - (FIRInstanceIDCheckinPreferences *)validCheckinPreferences {
- NSDictionary *gservicesData = @{
- kFIRInstanceIDVersionInfoStringKey : kVersionInfo,
- kFIRInstanceIDLastCheckinTimeKey : @(FIRInstanceIDCurrentTimestampInMilliseconds())
- };
- FIRInstanceIDCheckinPreferences *checkinPreferences =
- [[FIRInstanceIDCheckinPreferences alloc] initWithDeviceID:kDeviceAuthId
- secretToken:kSecretToken];
- [checkinPreferences updateWithCheckinPlistContents:gservicesData];
- return checkinPreferences;
- }
- @end
|