diff --git a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/HostedUIOptions.swift b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/HostedUIOptions.swift index 90460bb078..161bf320d4 100644 --- a/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/HostedUIOptions.swift +++ b/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/HostedUIOptions.swift @@ -56,11 +56,11 @@ extension HostedUIOptions: Codable { self.providerInfo = try values.decode(HostedUIProviderInfo.self, forKey: .providerInfo) self.preferPrivateSession = try values.decode(Bool.self, forKey: .preferPrivateSession) self.presentationAnchor = nil - self.nonce = try values.decode(String.self, forKey: .nonce) - self.language = try values.decode(String.self, forKey: .language) - self.loginHint = try values.decode(String.self, forKey: .loginHint) - self.prompt = try values.decode(String.self, forKey: .prompt) - self.resource = try values.decode(String.self, forKey: .resource) + self.nonce = try values.decodeIfPresent(String.self, forKey: .nonce) + self.language = try values.decodeIfPresent(String.self, forKey: .language) + self.loginHint = try values.decodeIfPresent(String.self, forKey: .loginHint) + self.prompt = try values.decodeIfPresent(String.self, forKey: .prompt) + self.resource = try values.decodeIfPresent(String.self, forKey: .resource) } func encode(to encoder: Encoder) throws { @@ -68,11 +68,11 @@ extension HostedUIOptions: Codable { try container.encode(scopes, forKey: .scopes) try container.encode(providerInfo, forKey: .providerInfo) try container.encode(preferPrivateSession, forKey: .preferPrivateSession) - try container.encode(nonce, forKey: .nonce) - try container.encode(language, forKey: .language) - try container.encode(loginHint, forKey: .loginHint) + try container.encodeIfPresent(nonce, forKey: .nonce) + try container.encodeIfPresent(language, forKey: .language) + try container.encodeIfPresent(loginHint, forKey: .loginHint) try container.encodeIfPresent(prompt, forKey: .prompt) - try container.encode(resource, forKey: .resource) + try container.encodeIfPresent(resource, forKey: .resource) } } diff --git a/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/HostedUIOptionsTests.swift b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/HostedUIOptionsTests.swift new file mode 100644 index 0000000000..76e5e37c4d --- /dev/null +++ b/AmplifyPlugins/Auth/Tests/AWSCognitoAuthPluginUnitTests/Support/HostedUIOptionsTests.swift @@ -0,0 +1,269 @@ +// +// Copyright Amazon.com Inc. or its affiliates. +// All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +import Amplify +import XCTest +@testable import AWSCognitoAuthPlugin + +class HostedUIOptionsTests: XCTestCase { + + // MARK: - Decoding + + /// - Given: A valid json payload with non-nil values depicting a `HostedUIOptions` + /// - When: The payload is decoded + /// - Then: The payload is decoded successfully + func testHostedUIOptionsDecodeWithNonNullValuesSuccess() { + let jsonString = + """ + { + "scopes": [ + "phone", + "email", + "openid", + "profile" + ], + "providerInfo": { + "idpIdentifier": "dummyIdentifier" + }, + "preferPrivateSession": true + } + """ + + do { + let data = jsonString.data(using: .utf8) + guard let data else { + XCTFail("Input JSON is invalid") + return + } + let hostedUIOptions = try JSONDecoder().decode( + HostedUIOptions.self, from: data + ) + + XCTAssertEqual(hostedUIOptions.scopes.count, 4) + XCTAssertTrue(hostedUIOptions.scopes.contains("phone")) + XCTAssertTrue(hostedUIOptions.scopes.contains("email")) + XCTAssertTrue(hostedUIOptions.scopes.contains("openid")) + XCTAssertTrue(hostedUIOptions.scopes.contains("profile")) + XCTAssertEqual(hostedUIOptions.preferPrivateSession, true) + XCTAssertNil(hostedUIOptions.presentationAnchor) + XCTAssertNil(hostedUIOptions.providerInfo.authProvider) + XCTAssertEqual(hostedUIOptions.providerInfo.idpIdentifier, "dummyIdentifier") + } catch { + XCTFail("Decoding failed with error: \(error)") + } + } + + /// - Given: A valid json payload with null values for optional fields depicting a `HostedUIOptions` + /// - When: The payload is decoded + /// - Then: The payload is decoded successfully + func testHostedUIOptionsDecodeWithNullValuesSuccess() { + let jsonString = + """ + { + "scopes": [ + "phone", + "email", + "openid", + "profile" + ], + "providerInfo": { + "idpIdentifier": "dummyIdentifier" + }, + "preferPrivateSession": true, + "nonce": null, + "lang": null, + "login_hint": null, + "prompt": null, + "resource": null + } + """ + + do { + let data = jsonString.data(using: .utf8) + guard let data else { + XCTFail("Input JSON is invalid") + return + } + let hostedUIOptions = try JSONDecoder().decode( + HostedUIOptions.self, from: data + ) + + XCTAssertEqual(hostedUIOptions.scopes.count, 4) + XCTAssertTrue(hostedUIOptions.scopes.contains("phone")) + XCTAssertTrue(hostedUIOptions.scopes.contains("email")) + XCTAssertTrue(hostedUIOptions.scopes.contains("openid")) + XCTAssertTrue(hostedUIOptions.scopes.contains("profile")) + XCTAssertEqual(hostedUIOptions.preferPrivateSession, true) + XCTAssertNil(hostedUIOptions.presentationAnchor) + XCTAssertNil(hostedUIOptions.providerInfo.authProvider) + XCTAssertEqual(hostedUIOptions.providerInfo.idpIdentifier, "dummyIdentifier") + } catch { + XCTFail("Decoding failed with error: \(error)") + } + } + + /// - Given: A valid json payload with valid values for optional fields depicting a `HostedUIOptions` + /// - When: The payload is decoded + /// - Then: The payload is decoded successfully + func testHostedUIOptionsDecodeWithOptionalValuesSuccess() { + let jsonString = + """ + { + "scopes": [ + "phone", + "email", + "openid", + "profile" + ], + "providerInfo": { + "idpIdentifier": "dummyIdentifier" + }, + "preferPrivateSession": true, + "nonce": "dummyNonce", + "lang": "en", + "login_hint": "username", + "prompt": "login consent", + "resource": "myapp://" + } + """ + + do { + let data = jsonString.data(using: .utf8) + guard let data else { + XCTFail("Input JSON is invalid") + return + } + let hostedUIOptions = try JSONDecoder().decode( + HostedUIOptions.self, from: data + ) + + XCTAssertEqual(hostedUIOptions.scopes.count, 4) + XCTAssertTrue(hostedUIOptions.scopes.contains("phone")) + XCTAssertTrue(hostedUIOptions.scopes.contains("email")) + XCTAssertTrue(hostedUIOptions.scopes.contains("openid")) + XCTAssertTrue(hostedUIOptions.scopes.contains("profile")) + XCTAssertEqual(hostedUIOptions.preferPrivateSession, true) + XCTAssertNil(hostedUIOptions.presentationAnchor) + XCTAssertNil(hostedUIOptions.providerInfo.authProvider) + XCTAssertEqual(hostedUIOptions.providerInfo.idpIdentifier, "dummyIdentifier") + XCTAssertEqual(hostedUIOptions.nonce, "dummyNonce") + XCTAssertEqual(hostedUIOptions.language, "en") + XCTAssertEqual(hostedUIOptions.loginHint, "username") + XCTAssertEqual(hostedUIOptions.prompt, "login consent") + XCTAssertEqual(hostedUIOptions.resource, "myapp://") + } catch { + XCTFail("Decoding failed with error: \(error)") + } + } + + /// - Given: A valid json payload with null value for scopes field depicting a `HostedUIOptions` + /// - When: The payload is decoded + /// - Then: The operation should throw an error + func testHostedUIOptionsDecodeWithNullScopesFailure() { + let jsonString = + """ + { + "scopes": null, + "providerInfo": { + "idpIdentifier": "dummyIdentifier" + }, + "preferPrivateSession": true + } + """ + + do { + let data = jsonString.data(using: .utf8) + guard let data else { + XCTFail("Input JSON is invalid") + return + } + + _ = try JSONDecoder().decode( + HostedUIOptions.self, from: data + ) + + XCTFail("Decoding should not succeed") + } catch { + XCTAssertNotNil(error) + guard case DecodingError.valueNotFound = error else { + XCTFail("Error should be of type valueNotFound") + return + } + } + } + + /// - Given: A valid json payload with invalid value type for scopes field depicting a `HostedUIOptions` + /// - When: The payload is decoded + /// - Then: The operation should throw an error + func testHostedUIOptionsDecodeWithInvalidScopesFailure() { + let jsonString = + """ + { + "scopes": "email", + "providerInfo": { + "idpIdentifier": "dummyIdentifier" + }, + "preferPrivateSession": true + } + """ + + do { + let data = jsonString.data(using: .utf8) + guard let data else { + XCTFail("Input JSON is invalid") + return + } + + _ = try JSONDecoder().decode( + HostedUIOptions.self, from: data + ) + + XCTFail("Decoding should not succeed") + } catch { + XCTAssertNotNil(error) + guard case DecodingError.typeMismatch = error else { + XCTFail("Error should be of type typeMismatch") + return + } + } + } + + /// - Given: A valid json payload with missing key for scopes field depicting a `HostedUIOptions` + /// - When: The payload is decoded + /// - Then: The operation should throw an error + func testHostedUIOptionsDecodeWithMissingScopesFailure() { + let jsonString = + """ + { + "providerInfo": { + "idpIdentifier": "dummyIdentifier" + }, + "preferPrivateSession": true + } + """ + + do { + let data = jsonString.data(using: .utf8) + guard let data else { + XCTFail("Input JSON is invalid") + return + } + + _ = try JSONDecoder().decode( + HostedUIOptions.self, from: data + ) + + XCTFail("Decoding should not succeed") + } catch { + XCTAssertNotNil(error) + guard case DecodingError.keyNotFound = error else { + XCTFail("Error should be of type typeMismatch") + return + } + } + } +}