@@ -20,22 +20,313 @@ import Testing
2020
2121@testable import SotoCore
2222
23- @Suite ( " Profile Configuration Loader " )
23+ @Suite ( " Profile Configuration Loader " , . serialized )
2424struct ProfileConfigurationLoaderTests {
2525
26+ // Helper to create a temp config file
27+ func createTempConfigFile( content: String ) throws -> String {
28+ let tempDir = FileManager . default. temporaryDirectory. appendingPathComponent ( UUID ( ) . uuidString)
29+ try FileManager . default. createDirectory ( at: tempDir, withIntermediateDirectories: true )
30+ let configPath = tempDir. appendingPathComponent ( " config " )
31+ try content. write ( to: configPath, atomically: true , encoding: . utf8)
32+ return configPath. path
33+ }
34+
35+ func removeTempFile( _ path: String ) {
36+ let url = URL ( fileURLWithPath: path)
37+ try ? FileManager . default. removeItem ( at: url. deletingLastPathComponent ( ) )
38+ }
39+
2640 @Test ( " Load configuration with missing profile throws error " )
27- func loadConfigurationMissingProfile( ) {
41+ func loadConfigurationMissingProfile( ) throws {
42+ let configContent = """
43+ [default]
44+ login_session = test-session
45+ """
46+
47+ let configPath = try createTempConfigFile ( content: configContent)
48+ defer { removeTempFile ( configPath) }
49+
2850 let loader = ProfileConfigurationLoader ( )
2951
3052 // Trying to load a non-existent profile should throw
31- #expect( throws: LoginError . self ) {
53+ #expect( throws: LoginError . profileNotFound ( " nonexistent " ) ) {
3254 try loader. loadConfiguration (
33- profileName: " nonexistent-profile-12345 " ,
34- cacheDirectoryOverride: nil
55+ profileName: " nonexistent " ,
56+ cacheDirectoryOverride: nil ,
57+ configPath: configPath
3558 )
3659 }
3760 }
3861
62+ @Test ( " Load configuration with valid default profile " )
63+ func loadConfigurationDefaultProfile( ) throws {
64+ let configContent = """
65+ [default]
66+ login_session = test-session-123
67+ region = us-west-2
68+ """
69+
70+ let configPath = try createTempConfigFile ( content: configContent)
71+ defer { removeTempFile ( configPath) }
72+
73+ let loader = ProfileConfigurationLoader ( )
74+ let config = try loader. loadConfiguration (
75+ profileName: nil ,
76+ cacheDirectoryOverride: nil ,
77+ configPath: configPath
78+ )
79+
80+ #expect( config. loginSession == " test-session-123 " )
81+ #expect( config. region == . uswest2)
82+ #expect( config. endpoint == " us-west-2.signin.aws.amazon.com " )
83+ }
84+
85+ @Test ( " Load configuration with named profile " )
86+ func loadConfigurationNamedProfile( ) throws {
87+ let configContent = """
88+ [default]
89+ login_session = default-session
90+
91+ [profile dev]
92+ login_session = dev-session-456
93+ region = eu-west-1
94+ """
95+
96+ let configPath = try createTempConfigFile ( content: configContent)
97+ defer { removeTempFile ( configPath) }
98+
99+ let loader = ProfileConfigurationLoader ( )
100+ let config = try loader. loadConfiguration (
101+ profileName: " dev " ,
102+ cacheDirectoryOverride: nil ,
103+ configPath: configPath
104+ )
105+
106+ #expect( config. loginSession == " dev-session-456 " )
107+ #expect( config. region == . euwest1)
108+ #expect( config. endpoint == " eu-west-1.signin.aws.amazon.com " )
109+ }
110+
111+ @Test ( " Load configuration with missing login_session throws error " )
112+ func loadConfigurationMissingLoginSession( ) throws {
113+ let configContent = """
114+ [default]
115+ region = us-east-1
116+ """
117+
118+ let configPath = try createTempConfigFile ( content: configContent)
119+ defer { removeTempFile ( configPath) }
120+
121+ let loader = ProfileConfigurationLoader ( )
122+
123+ #expect( throws: LoginError . loginSessionMissing) {
124+ try loader. loadConfiguration (
125+ profileName: nil ,
126+ cacheDirectoryOverride: nil ,
127+ configPath: configPath
128+ )
129+ }
130+ }
131+
132+ @Test ( " Load configuration uses AWS_REGION env var when profile has no region " )
133+ func loadConfigurationUsesEnvRegion( ) throws {
134+ let configContent = """
135+ [default]
136+ login_session = test-session
137+ """
138+
139+ let configPath = try createTempConfigFile ( content: configContent)
140+ defer { removeTempFile ( configPath) }
141+
142+ let originalRegion = ProcessInfo . processInfo. environment [ " AWS_REGION " ]
143+ setenv ( " AWS_REGION " , " ap-southeast-1 " , 1 )
144+ defer {
145+ if let region = originalRegion {
146+ setenv ( " AWS_REGION " , region, 1 )
147+ } else {
148+ unsetenv ( " AWS_REGION " )
149+ }
150+ }
151+
152+ let loader = ProfileConfigurationLoader ( )
153+ let config = try loader. loadConfiguration (
154+ profileName: nil ,
155+ cacheDirectoryOverride: nil ,
156+ configPath: configPath
157+ )
158+
159+ #expect( config. region == . apsoutheast1)
160+ }
161+
162+ @Test ( " Load configuration defaults to us-east-1 when no region specified " )
163+ func loadConfigurationDefaultsToUsEast1( ) throws {
164+ let configContent = """
165+ [default]
166+ login_session = test-session
167+ """
168+
169+ let configPath = try createTempConfigFile ( content: configContent)
170+ defer { removeTempFile ( configPath) }
171+
172+ let originalRegion = ProcessInfo . processInfo. environment [ " AWS_REGION " ]
173+ unsetenv ( " AWS_REGION " )
174+ defer {
175+ if let region = originalRegion {
176+ setenv ( " AWS_REGION " , region, 1 )
177+ }
178+ }
179+
180+ let loader = ProfileConfigurationLoader ( )
181+ let config = try loader. loadConfiguration (
182+ profileName: nil ,
183+ cacheDirectoryOverride: nil ,
184+ configPath: configPath
185+ )
186+
187+ #expect( config. region == . useast1)
188+ }
189+
190+ @Test ( " Load configuration with cache directory override " )
191+ func loadConfigurationWithCacheOverride( ) throws {
192+ let configContent = """
193+ [default]
194+ login_session = test-session
195+ region = us-east-1
196+ """
197+
198+ let configPath = try createTempConfigFile ( content: configContent)
199+ defer { removeTempFile ( configPath) }
200+
201+ let loader = ProfileConfigurationLoader ( )
202+ let config = try loader. loadConfiguration (
203+ profileName: nil ,
204+ cacheDirectoryOverride: " /custom/cache/dir " ,
205+ configPath: configPath
206+ )
207+
208+ #expect( config. cacheDirectory == " /custom/cache/dir " )
209+ }
210+
211+ @Test ( " Load configuration with cache directory from environment " )
212+ func loadConfigurationWithCacheFromEnv( ) throws {
213+ let configContent = """
214+ [default]
215+ login_session = test-session
216+ region = us-east-1
217+ """
218+
219+ let configPath = try createTempConfigFile ( content: configContent)
220+ defer { removeTempFile ( configPath) }
221+
222+ let originalCache = ProcessInfo . processInfo. environment [ LoginConfiguration . cacheEnvVar]
223+ setenv ( LoginConfiguration . cacheEnvVar, " /env/cache/dir " , 1 )
224+ defer {
225+ if let cache = originalCache {
226+ setenv ( LoginConfiguration . cacheEnvVar, cache, 1 )
227+ } else {
228+ unsetenv ( LoginConfiguration . cacheEnvVar)
229+ }
230+ }
231+
232+ let loader = ProfileConfigurationLoader ( )
233+ let config = try loader. loadConfiguration (
234+ profileName: nil ,
235+ cacheDirectoryOverride: nil ,
236+ configPath: configPath
237+ )
238+
239+ #expect( config. cacheDirectory == " /env/cache/dir " )
240+ }
241+
242+ @Test ( " Load configuration with config file not found throws error " )
243+ func loadConfigurationFileNotFound( ) {
244+ let loader = ProfileConfigurationLoader ( )
245+
246+ #expect( throws: LoginError . configFileNotFound ( " /nonexistent/config " ) ) {
247+ try loader. loadConfiguration (
248+ profileName: nil ,
249+ cacheDirectoryOverride: nil ,
250+ configPath: " /nonexistent/config "
251+ )
252+ }
253+ }
254+
255+ @Test ( " Load configuration with multiple profiles " )
256+ func loadConfigurationMultipleProfiles( ) throws {
257+ let configContent = """
258+ [default]
259+ login_session = default-session
260+ region = us-east-1
261+
262+ [profile staging]
263+ login_session = staging-session
264+ region = us-west-1
265+
266+ [profile production]
267+ login_session = prod-session
268+ region = eu-central-1
269+ """
270+
271+ let configPath = try createTempConfigFile ( content: configContent)
272+ defer { removeTempFile ( configPath) }
273+
274+ let loader = ProfileConfigurationLoader ( )
275+
276+ // Test default profile
277+ let defaultConfig = try loader. loadConfiguration (
278+ profileName: nil ,
279+ cacheDirectoryOverride: nil ,
280+ configPath: configPath
281+ )
282+ #expect( defaultConfig. loginSession == " default-session " )
283+ #expect( defaultConfig. region == . useast1)
284+
285+ // Test staging profile
286+ let stagingConfig = try loader. loadConfiguration (
287+ profileName: " staging " ,
288+ cacheDirectoryOverride: nil ,
289+ configPath: configPath
290+ )
291+ #expect( stagingConfig. loginSession == " staging-session " )
292+ #expect( stagingConfig. region == . uswest1)
293+
294+ // Test production profile
295+ let prodConfig = try loader. loadConfiguration (
296+ profileName: " production " ,
297+ cacheDirectoryOverride: nil ,
298+ configPath: configPath
299+ )
300+ #expect( prodConfig. loginSession == " prod-session " )
301+ #expect( prodConfig. region == . eucentral1)
302+ }
303+
304+ @Test ( " Load configuration with profile containing extra fields " )
305+ func loadConfigurationWithExtraFields( ) throws {
306+ let configContent = """
307+ [default]
308+ login_session = test-session
309+ region = us-east-1
310+ output = json
311+ cli_pager =
312+ some_other_field = value
313+ """
314+
315+ let configPath = try createTempConfigFile ( content: configContent)
316+ defer { removeTempFile ( configPath) }
317+
318+ let loader = ProfileConfigurationLoader ( )
319+ let config = try loader. loadConfiguration (
320+ profileName: nil ,
321+ cacheDirectoryOverride: nil ,
322+ configPath: configPath
323+ )
324+
325+ // Should successfully load despite extra fields
326+ #expect( config. loginSession == " test-session " )
327+ #expect( config. region == . useast1)
328+ }
329+
39330 @Test (
40331 " Endpoint construction for various regions " ,
41332 arguments: [
0 commit comments