Skip to content

Commit ad1c5d5

Browse files
authored
fix(auth): fix HostedUIOptions decoding for optional fields (#4102)
* fix(auth): fix HostedUIOptions decoding for optional fields * fix swiftformat issues
1 parent 3102987 commit ad1c5d5

File tree

2 files changed

+278
-9
lines changed

2 files changed

+278
-9
lines changed

AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/CodeGen/Data/HostedUIOptions.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,23 +56,23 @@ extension HostedUIOptions: Codable {
5656
self.providerInfo = try values.decode(HostedUIProviderInfo.self, forKey: .providerInfo)
5757
self.preferPrivateSession = try values.decode(Bool.self, forKey: .preferPrivateSession)
5858
self.presentationAnchor = nil
59-
self.nonce = try values.decode(String.self, forKey: .nonce)
60-
self.language = try values.decode(String.self, forKey: .language)
61-
self.loginHint = try values.decode(String.self, forKey: .loginHint)
62-
self.prompt = try values.decode(String.self, forKey: .prompt)
63-
self.resource = try values.decode(String.self, forKey: .resource)
59+
self.nonce = try values.decodeIfPresent(String.self, forKey: .nonce)
60+
self.language = try values.decodeIfPresent(String.self, forKey: .language)
61+
self.loginHint = try values.decodeIfPresent(String.self, forKey: .loginHint)
62+
self.prompt = try values.decodeIfPresent(String.self, forKey: .prompt)
63+
self.resource = try values.decodeIfPresent(String.self, forKey: .resource)
6464
}
6565

6666
func encode(to encoder: Encoder) throws {
6767
var container = encoder.container(keyedBy: CodingKeys.self)
6868
try container.encode(scopes, forKey: .scopes)
6969
try container.encode(providerInfo, forKey: .providerInfo)
7070
try container.encode(preferPrivateSession, forKey: .preferPrivateSession)
71-
try container.encode(nonce, forKey: .nonce)
72-
try container.encode(language, forKey: .language)
73-
try container.encode(loginHint, forKey: .loginHint)
71+
try container.encodeIfPresent(nonce, forKey: .nonce)
72+
try container.encodeIfPresent(language, forKey: .language)
73+
try container.encodeIfPresent(loginHint, forKey: .loginHint)
7474
try container.encodeIfPresent(prompt, forKey: .prompt)
75-
try container.encode(resource, forKey: .resource)
75+
try container.encodeIfPresent(resource, forKey: .resource)
7676
}
7777
}
7878

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
//
2+
// Copyright Amazon.com Inc. or its affiliates.
3+
// All Rights Reserved.
4+
//
5+
// SPDX-License-Identifier: Apache-2.0
6+
//
7+
8+
import Amplify
9+
import XCTest
10+
@testable import AWSCognitoAuthPlugin
11+
12+
class HostedUIOptionsTests: XCTestCase {
13+
14+
// MARK: - Decoding
15+
16+
/// - Given: A valid json payload with non-nil values depicting a `HostedUIOptions`
17+
/// - When: The payload is decoded
18+
/// - Then: The payload is decoded successfully
19+
func testHostedUIOptionsDecodeWithNonNullValuesSuccess() {
20+
let jsonString =
21+
"""
22+
{
23+
"scopes": [
24+
"phone",
25+
"email",
26+
"openid",
27+
"profile"
28+
],
29+
"providerInfo": {
30+
"idpIdentifier": "dummyIdentifier"
31+
},
32+
"preferPrivateSession": true
33+
}
34+
"""
35+
36+
do {
37+
let data = jsonString.data(using: .utf8)
38+
guard let data else {
39+
XCTFail("Input JSON is invalid")
40+
return
41+
}
42+
let hostedUIOptions = try JSONDecoder().decode(
43+
HostedUIOptions.self, from: data
44+
)
45+
46+
XCTAssertEqual(hostedUIOptions.scopes.count, 4)
47+
XCTAssertTrue(hostedUIOptions.scopes.contains("phone"))
48+
XCTAssertTrue(hostedUIOptions.scopes.contains("email"))
49+
XCTAssertTrue(hostedUIOptions.scopes.contains("openid"))
50+
XCTAssertTrue(hostedUIOptions.scopes.contains("profile"))
51+
XCTAssertEqual(hostedUIOptions.preferPrivateSession, true)
52+
XCTAssertNil(hostedUIOptions.presentationAnchor)
53+
XCTAssertNil(hostedUIOptions.providerInfo.authProvider)
54+
XCTAssertEqual(hostedUIOptions.providerInfo.idpIdentifier, "dummyIdentifier")
55+
} catch {
56+
XCTFail("Decoding failed with error: \(error)")
57+
}
58+
}
59+
60+
/// - Given: A valid json payload with null values for optional fields depicting a `HostedUIOptions`
61+
/// - When: The payload is decoded
62+
/// - Then: The payload is decoded successfully
63+
func testHostedUIOptionsDecodeWithNullValuesSuccess() {
64+
let jsonString =
65+
"""
66+
{
67+
"scopes": [
68+
"phone",
69+
"email",
70+
"openid",
71+
"profile"
72+
],
73+
"providerInfo": {
74+
"idpIdentifier": "dummyIdentifier"
75+
},
76+
"preferPrivateSession": true,
77+
"nonce": null,
78+
"lang": null,
79+
"login_hint": null,
80+
"prompt": null,
81+
"resource": null
82+
}
83+
"""
84+
85+
do {
86+
let data = jsonString.data(using: .utf8)
87+
guard let data else {
88+
XCTFail("Input JSON is invalid")
89+
return
90+
}
91+
let hostedUIOptions = try JSONDecoder().decode(
92+
HostedUIOptions.self, from: data
93+
)
94+
95+
XCTAssertEqual(hostedUIOptions.scopes.count, 4)
96+
XCTAssertTrue(hostedUIOptions.scopes.contains("phone"))
97+
XCTAssertTrue(hostedUIOptions.scopes.contains("email"))
98+
XCTAssertTrue(hostedUIOptions.scopes.contains("openid"))
99+
XCTAssertTrue(hostedUIOptions.scopes.contains("profile"))
100+
XCTAssertEqual(hostedUIOptions.preferPrivateSession, true)
101+
XCTAssertNil(hostedUIOptions.presentationAnchor)
102+
XCTAssertNil(hostedUIOptions.providerInfo.authProvider)
103+
XCTAssertEqual(hostedUIOptions.providerInfo.idpIdentifier, "dummyIdentifier")
104+
} catch {
105+
XCTFail("Decoding failed with error: \(error)")
106+
}
107+
}
108+
109+
/// - Given: A valid json payload with valid values for optional fields depicting a `HostedUIOptions`
110+
/// - When: The payload is decoded
111+
/// - Then: The payload is decoded successfully
112+
func testHostedUIOptionsDecodeWithOptionalValuesSuccess() {
113+
let jsonString =
114+
"""
115+
{
116+
"scopes": [
117+
"phone",
118+
"email",
119+
"openid",
120+
"profile"
121+
],
122+
"providerInfo": {
123+
"idpIdentifier": "dummyIdentifier"
124+
},
125+
"preferPrivateSession": true,
126+
"nonce": "dummyNonce",
127+
"lang": "en",
128+
"login_hint": "username",
129+
"prompt": "login consent",
130+
"resource": "myapp://"
131+
}
132+
"""
133+
134+
do {
135+
let data = jsonString.data(using: .utf8)
136+
guard let data else {
137+
XCTFail("Input JSON is invalid")
138+
return
139+
}
140+
let hostedUIOptions = try JSONDecoder().decode(
141+
HostedUIOptions.self, from: data
142+
)
143+
144+
XCTAssertEqual(hostedUIOptions.scopes.count, 4)
145+
XCTAssertTrue(hostedUIOptions.scopes.contains("phone"))
146+
XCTAssertTrue(hostedUIOptions.scopes.contains("email"))
147+
XCTAssertTrue(hostedUIOptions.scopes.contains("openid"))
148+
XCTAssertTrue(hostedUIOptions.scopes.contains("profile"))
149+
XCTAssertEqual(hostedUIOptions.preferPrivateSession, true)
150+
XCTAssertNil(hostedUIOptions.presentationAnchor)
151+
XCTAssertNil(hostedUIOptions.providerInfo.authProvider)
152+
XCTAssertEqual(hostedUIOptions.providerInfo.idpIdentifier, "dummyIdentifier")
153+
XCTAssertEqual(hostedUIOptions.nonce, "dummyNonce")
154+
XCTAssertEqual(hostedUIOptions.language, "en")
155+
XCTAssertEqual(hostedUIOptions.loginHint, "username")
156+
XCTAssertEqual(hostedUIOptions.prompt, "login consent")
157+
XCTAssertEqual(hostedUIOptions.resource, "myapp://")
158+
} catch {
159+
XCTFail("Decoding failed with error: \(error)")
160+
}
161+
}
162+
163+
/// - Given: A valid json payload with null value for scopes field depicting a `HostedUIOptions`
164+
/// - When: The payload is decoded
165+
/// - Then: The operation should throw an error
166+
func testHostedUIOptionsDecodeWithNullScopesFailure() {
167+
let jsonString =
168+
"""
169+
{
170+
"scopes": null,
171+
"providerInfo": {
172+
"idpIdentifier": "dummyIdentifier"
173+
},
174+
"preferPrivateSession": true
175+
}
176+
"""
177+
178+
do {
179+
let data = jsonString.data(using: .utf8)
180+
guard let data else {
181+
XCTFail("Input JSON is invalid")
182+
return
183+
}
184+
185+
_ = try JSONDecoder().decode(
186+
HostedUIOptions.self, from: data
187+
)
188+
189+
XCTFail("Decoding should not succeed")
190+
} catch {
191+
XCTAssertNotNil(error)
192+
guard case DecodingError.valueNotFound = error else {
193+
XCTFail("Error should be of type valueNotFound")
194+
return
195+
}
196+
}
197+
}
198+
199+
/// - Given: A valid json payload with invalid value type for scopes field depicting a `HostedUIOptions`
200+
/// - When: The payload is decoded
201+
/// - Then: The operation should throw an error
202+
func testHostedUIOptionsDecodeWithInvalidScopesFailure() {
203+
let jsonString =
204+
"""
205+
{
206+
"scopes": "email",
207+
"providerInfo": {
208+
"idpIdentifier": "dummyIdentifier"
209+
},
210+
"preferPrivateSession": true
211+
}
212+
"""
213+
214+
do {
215+
let data = jsonString.data(using: .utf8)
216+
guard let data else {
217+
XCTFail("Input JSON is invalid")
218+
return
219+
}
220+
221+
_ = try JSONDecoder().decode(
222+
HostedUIOptions.self, from: data
223+
)
224+
225+
XCTFail("Decoding should not succeed")
226+
} catch {
227+
XCTAssertNotNil(error)
228+
guard case DecodingError.typeMismatch = error else {
229+
XCTFail("Error should be of type typeMismatch")
230+
return
231+
}
232+
}
233+
}
234+
235+
/// - Given: A valid json payload with missing key for scopes field depicting a `HostedUIOptions`
236+
/// - When: The payload is decoded
237+
/// - Then: The operation should throw an error
238+
func testHostedUIOptionsDecodeWithMissingScopesFailure() {
239+
let jsonString =
240+
"""
241+
{
242+
"providerInfo": {
243+
"idpIdentifier": "dummyIdentifier"
244+
},
245+
"preferPrivateSession": true
246+
}
247+
"""
248+
249+
do {
250+
let data = jsonString.data(using: .utf8)
251+
guard let data else {
252+
XCTFail("Input JSON is invalid")
253+
return
254+
}
255+
256+
_ = try JSONDecoder().decode(
257+
HostedUIOptions.self, from: data
258+
)
259+
260+
XCTFail("Decoding should not succeed")
261+
} catch {
262+
XCTAssertNotNil(error)
263+
guard case DecodingError.keyNotFound = error else {
264+
XCTFail("Error should be of type typeMismatch")
265+
return
266+
}
267+
}
268+
}
269+
}

0 commit comments

Comments
 (0)