Skip to content

Commit

Permalink
test: Adding additional unit tests for Auth, Push Notifications and S…
Browse files Browse the repository at this point in the history
…torage plugins (#3291)
  • Loading branch information
ruisebas authored Oct 24, 2023
1 parent b6f5bc9 commit 4a796c4
Show file tree
Hide file tree
Showing 26 changed files with 4,637 additions and 199 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,6 @@
BlueprintName = "AWSCognitoAuthPluginUnitTests"
ReferencedContainer = "container:">
</BuildableReference>
<SkippedTests>
<Test
Identifier = "EscapeHatchTests">
</Test>
</SkippedTests>
</TestableReference>
<TestableReference
skipped = "NO">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,24 +76,6 @@ public struct AWSAuthCognitoSession: AuthSession,

}

/// Internal Helpers for managing session tokens
internal extension AWSAuthCognitoSession {
func areTokensExpiring(in seconds: TimeInterval? = nil) -> Bool {

guard let tokens = try? userPoolTokensResult.get(),
let idTokenClaims = try? AWSAuthService().getTokenClaims(tokenString: tokens.idToken).get(),
let accessTokenClaims = try? AWSAuthService().getTokenClaims(tokenString: tokens.idToken).get(),
let idTokenExpiration = idTokenClaims["exp"]?.doubleValue,
let accessTokenExpiration = accessTokenClaims["exp"]?.doubleValue else {
return true
}

// If the session expires < X minutes return it
return (Date(timeIntervalSince1970: idTokenExpiration).compare(Date(timeIntervalSinceNow: seconds ?? 0)) == .orderedDescending &&
Date(timeIntervalSince1970: accessTokenExpiration).compare(Date(timeIntervalSinceNow: seconds ?? 0)) == .orderedDescending)
}
}

extension AWSAuthCognitoSession: Equatable {
public static func == (lhs: AWSAuthCognitoSession, rhs: AWSAuthCognitoSession) -> Bool {
switch (lhs.getCognitoTokens(), rhs.getCognitoTokens()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,10 @@ public struct AWSCognitoUserPoolTokens: AuthCognitoTokens {
case (.some(let idTokenValue), .none):
expirationDoubleValue = idTokenValue
case (.none, .none):
expirationDoubleValue = 0
expirationDoubleValue = Date().timeIntervalSince1970
}

self.expiration = Date().addingTimeInterval(TimeInterval((expirationDoubleValue ?? 0)))
self.expiration = Date(timeIntervalSince1970: TimeInterval(expirationDoubleValue))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,27 +25,6 @@ struct AuthCognitoSignedOutSessionHelper {
return authSession
}

/// Guest/SignedOut session with any unhandled error
///
/// The unhandled error is passed as identityId and aws credentials result. UserSub and Cognito Tokens will still
/// have signOut error.
///
/// - Parameter error: Unhandled error
/// - Returns: Session will have isSignedIn = false
private static func makeSignedOutSession(withUnhandledError error: AuthError) -> AWSAuthCognitoSession {

let identityIdError = error
let awsCredentialsError = error

let tokensError = makeCognitoTokensSignedOutError()

let authSession = AWSAuthCognitoSession(isSignedIn: false,
identityIdResult: .failure(identityIdError),
awsCredentialsResult: .failure(awsCredentialsError),
cognitoTokensResult: .failure(tokensError))
return authSession
}

/// Guest/SignOut session when the guest access is not enabled.
/// - Returns: Session with isSignedIn = false
static func makeSessionWithNoGuestAccess() -> AWSAuthCognitoSession {
Expand All @@ -68,26 +47,6 @@ struct AuthCognitoSignedOutSessionHelper {
return authSession
}

private static func makeOfflineSignedOutSession() -> AWSAuthCognitoSession {
let identityIdError = AuthError.service(
AuthPluginErrorConstants.identityIdOfflineError.errorDescription,
AuthPluginErrorConstants.identityIdOfflineError.recoverySuggestion,
AWSCognitoAuthError.network)

let awsCredentialsError = AuthError.service(
AuthPluginErrorConstants.awsCredentialsOfflineError.errorDescription,
AuthPluginErrorConstants.awsCredentialsOfflineError.recoverySuggestion,
AWSCognitoAuthError.network)

let tokensError = makeCognitoTokensSignedOutError()

let authSession = AWSAuthCognitoSession(isSignedIn: false,
identityIdResult: .failure(identityIdError),
awsCredentialsResult: .failure(awsCredentialsError),
cognitoTokensResult: .failure(tokensError))
return authSession
}

/// Guest/SignedOut session with couldnot retreive either aws credentials or identity id.
/// - Returns: Session will have isSignedIn = false
private static func makeSignedOutSessionWithServiceIssue() -> AWSAuthCognitoSession {
Expand All @@ -109,13 +68,6 @@ struct AuthCognitoSignedOutSessionHelper {
return authSession
}

private static func makeUserSubSignedOutError() -> AuthError {
let userSubError = AuthError.signedOut(
AuthPluginErrorConstants.userSubSignOutError.errorDescription,
AuthPluginErrorConstants.userSubSignOutError.recoverySuggestion)
return userSubError
}

private static func makeCognitoTokensSignedOutError() -> AuthError {
let tokensError = AuthError.signedOut(
AuthPluginErrorConstants.cognitoTokensSignOutError.errorDescription,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class HostedUIASWebAuthenticationSession: NSObject, HostedUISessionBehavior {
callback: @escaping (Result<[URLQueryItem], HostedUIError>) -> Void) {
#if os(iOS) || os(macOS)
self.webPresentation = presentationAnchor
let aswebAuthenticationSession = ASWebAuthenticationSession(
let aswebAuthenticationSession = createAuthenticationSession(
url: url,
callbackURLScheme: callbackScheme,
completionHandler: { url, error in
Expand Down Expand Up @@ -58,6 +58,16 @@ class HostedUIASWebAuthenticationSession: NSObject, HostedUISessionBehavior {
}

#if os(iOS) || os(macOS)
var authenticationSessionFactory = ASWebAuthenticationSession.init(url:callbackURLScheme:completionHandler:)

private func createAuthenticationSession(
url: URL,
callbackURLScheme: String?,
completionHandler: @escaping ASWebAuthenticationSession.CompletionHandler
) -> ASWebAuthenticationSession {
return authenticationSessionFactory(url, callbackURLScheme, completionHandler)
}

private func convertHostedUIError(_ error: Error) -> HostedUIError {
if let asWebAuthError = error as? ASWebAuthenticationSessionError {
switch asWebAuthError.code {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,122 @@ class MigrateLegacyCredentialStoreTests: XCTestCase {

await fulfillment(
of: [migrationCompletionInvoked],

timeout: 0.1
)
}

/// - Given: A credential store with an invalid environment
/// - When: The migration legacy store action is executed
/// - Then: An error event of type configuration is dispatched
func testExecute_withInvalidEnvironment_shouldDispatchError() async {
let expectation = expectation(description: "noEnvironment")
let action = MigrateLegacyCredentialStore()
await action.execute(
withDispatcher: MockDispatcher { event in
guard let event = event as? CredentialStoreEvent,
case let .throwError(error) = event.eventType else {
XCTFail("Expected failure due to no CredentialEnvironment")
expectation.fulfill()
return
}
XCTAssertEqual(error, .configuration(message: AuthPluginErrorConstants.configurationError))
expectation.fulfill()
},
environment: MockInvalidEnvironment()
)
await fulfillment(of: [expectation], timeout: 1)
}

/// - Given: A credential store with an environment that only has identity pool
/// - When: The migration legacy store action is executed
/// - Then:
/// - A .loadCredentialStore event with type .amplifyCredentials is dispatched
/// - An .identityPoolOnly credential is saved
func testExecute_withoutUserPool_andWithoutLoginsTokens_shouldDispatchLoadEvent() async {
let expectation = expectation(description: "noUserPoolTokens")
let action = MigrateLegacyCredentialStore()
await action.execute(
withDispatcher: MockDispatcher { event in
guard let event = event as? CredentialStoreEvent,
case .loadCredentialStore(let type) = event.eventType else {
XCTFail("Expected .loadCredentialStore")
expectation.fulfill()
return
}
XCTAssertEqual(type, .amplifyCredentials)
expectation.fulfill()
},
environment: CredentialEnvironment(
authConfiguration: .identityPools(.testData),
credentialStoreEnvironment: BasicCredentialStoreEnvironment(
amplifyCredentialStoreFactory: {
MockAmplifyCredentialStoreBehavior(
saveCredentialHandler: { codableCredentials in
guard let amplifyCredentials = codableCredentials as? AmplifyCredentials,
case .identityPoolOnly(_, let credentials) = amplifyCredentials else {
XCTFail("Expected .identityPoolOnly")
return
}
XCTAssertFalse(credentials.sessionToken.isEmpty)
}
)
},
legacyKeychainStoreFactory: { _ in
MockKeychainStoreBehavior(data: "hostedUI")
}),
logger: MigrateLegacyCredentialStore.log
)
)
await fulfillment(of: [expectation], timeout: 1)
}

/// - Given: A credential store with an environment that only has identity pool
/// - When: The migration legacy store action is executed
/// - A .loadCredentialStore event with type .amplifyCredentials is dispatched
/// - An .identityPoolWithFederation credential is saved
func testExecute_withoutUserPool_andWithLoginsTokens_shouldDispatchLoadEvent() async {
let expectation = expectation(description: "noUserPoolTokens")
let action = MigrateLegacyCredentialStore()
await action.execute(
withDispatcher: MockDispatcher { event in
guard let event = event as? CredentialStoreEvent,
case .loadCredentialStore(let type) = event.eventType else {
XCTFail("Expected .loadCredentialStore")
expectation.fulfill()
return
}
XCTAssertEqual(type, .amplifyCredentials)
expectation.fulfill()
},
environment: CredentialEnvironment(
authConfiguration: .identityPools(.testData),
credentialStoreEnvironment: BasicCredentialStoreEnvironment(
amplifyCredentialStoreFactory: {
MockAmplifyCredentialStoreBehavior(
saveCredentialHandler: { codableCredentials in
guard let amplifyCredentials = codableCredentials as? AmplifyCredentials,
case .identityPoolWithFederation(let token, _, _) = amplifyCredentials else {
XCTFail("Expected .identityPoolWithFederation")
return
}

XCTAssertEqual(token.token, "token")
XCTAssertEqual(token.provider.userPoolProviderName, "provider")
}
)
},
legacyKeychainStoreFactory: { _ in
let data = try! JSONEncoder().encode([
"provider": "token"
])
return MockKeychainStoreBehavior(
data: String(decoding: data, as: UTF8.self)
)
}),
logger: action.log
)
)
await fulfillment(of: [expectation], timeout: 1)
}
}
Loading

0 comments on commit 4a796c4

Please sign in to comment.