diff --git a/GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m b/GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m new file mode 100644 index 00000000..aaf5c71e --- /dev/null +++ b/GoogleSignIn/Tests/Unit/GIDEMMSupportTest.m @@ -0,0 +1,229 @@ +/* + * Copyright 2022 Google LLC + * + * 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 + +#if TARGET_OS_IOS && !TARGET_OS_MACCATALYST + +#import + +#import "GoogleSignIn/Sources/GIDEMMSupport.h" + +#import "GoogleSignIn/Sources/Public/GoogleSignIn/GIDSignIn.h" + +#import "GoogleSignIn/Sources/GIDEMMErrorHandler.h" +#import "GoogleSignIn/Sources/GIDMDMPasscodeState.h" + +#ifdef SWIFT_PACKAGE +@import AppAuth; +@import GoogleUtilities_MethodSwizzler; +@import GoogleUtilities_SwizzlerTestHelpers; +@import OCMock; +#else +#import +#import +#import +#import +#endif + +NS_ASSUME_NONNULL_BEGIN + +// The system name in old iOS versions. +static NSString *const kOldIOSName = @"iPhone OS"; + +// The system name in new iOS versions. +static NSString *const kNewIOSName = @"iOS"; + +// They keys in EMM dictionary. +static NSString *const kEMMKey = @"emm_support"; +static NSString *const kDeviceOSKey = @"device_os"; +static NSString *const kEMMPasscodeInfoKey = @"emm_passcode_info"; + +@interface GIDEMMSupportTest : XCTestCase +@end + +@implementation GIDEMMSupportTest + +- (void)testUpdatedEMMParametersWithParameters_NoEMMKey { + NSDictionary *originalParameters = @{ + @"not_emm_support_key" : @"xyz", + }; + + NSDictionary *updatedEMMParameters = + [GIDEMMSupport updatedEMMParametersWithParameters:originalParameters]; + + XCTAssertEqualObjects(updatedEMMParameters, originalParameters); +} + +- (void)testUpdateEMMParametersWithParameters_systemName { + [GULSwizzler swizzleClass:[UIDevice class] + selector:@selector(systemName) + isClassSelector:NO + withBlock:^(id sender) { return kNewIOSName; }]; + + NSDictionary *originalParameters = @{ + kEMMKey : @"xyz", + }; + + NSDictionary *updatedEMMParameters = + [GIDEMMSupport updatedEMMParametersWithParameters:originalParameters]; + + NSDictionary *expectedParameters = @{ + kEMMKey : @"xyz", + kDeviceOSKey : [NSString stringWithFormat:@"%@ %@", kNewIOSName, [self systemVersion]] + }; + + XCTAssertEqualObjects(updatedEMMParameters, expectedParameters); + + [self addTeardownBlock:^{ + [GULSwizzler unswizzleClass:[UIDevice class] + selector:@selector(systemName) + isClassSelector:NO]; + }]; +} + +// When the systemName is @"iPhone OS" we still get "iOS". +- (void)testUpdateEMMParametersWithParameters_systemNameNormalization { + [GULSwizzler swizzleClass:[UIDevice class] + selector:@selector(systemName) + isClassSelector:NO + withBlock:^(id sender) { return kOldIOSName; }]; + + NSDictionary *originalParameters = @{ + kEMMKey : @"xyz", + }; + + NSDictionary *updatedEMMParameters = + [GIDEMMSupport updatedEMMParametersWithParameters:originalParameters]; + + NSDictionary *expectedParameters = @{ + kEMMKey : @"xyz", + kDeviceOSKey : [NSString stringWithFormat:@"%@ %@", kNewIOSName, [self systemVersion]] + }; + + XCTAssertEqualObjects(updatedEMMParameters, expectedParameters); + + [self addTeardownBlock:^{ + [GULSwizzler unswizzleClass:[UIDevice class] + selector:@selector(systemName) + isClassSelector:NO]; + }]; +} + +- (void)testUpdateEMMParametersWithParameters_passcodInfo { + [GULSwizzler swizzleClass:[UIDevice class] + selector:@selector(systemName) + isClassSelector:NO + withBlock:^(id sender) { return kOldIOSName; }]; + + NSDictionary *originalParameters = @{ + kEMMKey : @"xyz", + kDeviceOSKey : @"old one", + kEMMPasscodeInfoKey : @"something", + }; + + NSDictionary *updatedEMMParameters = + [GIDEMMSupport updatedEMMParametersWithParameters:originalParameters]; + + NSDictionary *expectedParameters = @{ + kEMMKey : @"xyz", + kDeviceOSKey : [NSString stringWithFormat:@"%@ %@", kNewIOSName, [self systemVersion]], + kEMMPasscodeInfoKey : [GIDMDMPasscodeState passcodeState].info, + }; + + XCTAssertEqualObjects(updatedEMMParameters, expectedParameters); + + [self addTeardownBlock:^{ + [GULSwizzler unswizzleClass:[UIDevice class] + selector:@selector(systemName) + isClassSelector:NO]; + }]; + +} + +- (void)testHandleTokenFetchEMMError_errorIsEMM { + // Set expectations. + NSDictionary *errorJSON = @{ @"error" : @"EMM Specific Error" }; + NSError *emmError = [NSError errorWithDomain:@"anydomain" + code:12345 + userInfo:@{ OIDOAuthErrorResponseErrorKey : errorJSON }]; + id mockEMMErrorHandler = OCMStrictClassMock([GIDEMMErrorHandler class]); + [[[mockEMMErrorHandler stub] andReturn:mockEMMErrorHandler] sharedInstance]; + __block void (^savedCompletion)(void); + [[[mockEMMErrorHandler stub] andReturnValue:@YES] + handleErrorFromResponse:errorJSON completion:[OCMArg checkWithBlock:^(id arg) { + savedCompletion = arg; + return YES; + }]]; + + XCTestExpectation *notCalled = [self expectationWithDescription:@"Callback is not called"]; + notCalled.inverted = YES; + XCTestExpectation *called = [self expectationWithDescription:@"Callback is called"]; + + [GIDEMMSupport handleTokenFetchEMMError:emmError completion:^(NSError *error) { + [notCalled fulfill]; + [called fulfill]; + XCTAssertEqualObjects(error.domain, kGIDSignInErrorDomain); + XCTAssertEqual(error.code, kGIDSignInErrorCodeEMM); + }]; + + [self waitForExpectations:@[ notCalled ] timeout:1]; + savedCompletion(); + [self waitForExpectations:@[ called ] timeout:1]; +} + +- (void)testHandleTokenFetchEMMError_errorIsNotEMM { + // Set expectations. + NSDictionary *errorJSON = @{ @"error" : @"Not EMM Specific Error" }; + NSError *emmError = [NSError errorWithDomain:@"anydomain" + code:12345 + userInfo:@{ OIDOAuthErrorResponseErrorKey : errorJSON }]; + id mockEMMErrorHandler = OCMStrictClassMock([GIDEMMErrorHandler class]); + [[[mockEMMErrorHandler stub] andReturn:mockEMMErrorHandler] sharedInstance]; + __block void (^savedCompletion)(void); + [[[mockEMMErrorHandler stub] andReturnValue:@NO] + handleErrorFromResponse:errorJSON completion:[OCMArg checkWithBlock:^(id arg) { + savedCompletion = arg; + return YES; + }]]; + + XCTestExpectation *notCalled = [self expectationWithDescription:@"Callback is not called"]; + notCalled.inverted = YES; + XCTestExpectation *called = [self expectationWithDescription:@"Callback is called"]; + + [GIDEMMSupport handleTokenFetchEMMError:emmError completion:^(NSError *error) { + [notCalled fulfill]; + [called fulfill]; + XCTAssertEqualObjects(error.domain, @"anydomain"); + XCTAssertEqual(error.code, 12345); + }]; + + [self waitForExpectations:@[ notCalled ] timeout:1]; + savedCompletion(); + [self waitForExpectations:@[ called ] timeout:1]; +} + +# pragma mark - Helpers + +- (NSString *)systemVersion { + return [UIDevice currentDevice].systemVersion; +} + +@end + +NS_ASSUME_NONNULL_END + +#endif // TARGET_OS_IOS && !TARGET_OS_MACCATALYST