@@ -105,7 +105,7 @@ void resolveCredentials_fallsBackToLegacy_noAccountId() {
105105 "\" SecretAccessKey\" :\" SECRET_ACCESS_KEY\" ," +
106106 "\" Token\" :\" SESSION_TOKEN\" ," +
107107 "\" Expiration\" :\" %s\" ," +
108- "\" Code\" :\" Success\" }" , // No AccountId field at all
108+ "\" Code\" :\" Success\" }" ,
109109 DateUtils .formatIso8601Date (Instant .now ().plus (Duration .ofDays (1 )))
110110 );
111111
@@ -159,119 +159,82 @@ void resolveCredentials_withInvalidProfile_throwsException() {
159159 }
160160
161161 @ Test
162- void resolveCredentials_withUnstableProfile_noAccountId_refreshesCredentials () {
163- String firstCredentials = String .format (
164- "{\" AccessKeyId\" :\" ASIAIOSFODNN7EXAMPLE\" ," +
165- "\" SecretAccessKey\" :\" wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\" ," +
166- "\" Token\" :\" AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKw\" ," +
167- "\" Expiration\" :\" %s\" ," +
168- "\" Code\" :\" Success\" ," +
169- "\" Type\" :\" AWS-HMAC\" ," +
170- "\" LastUpdated\" :\" 2025-03-18T20:53:17.832308Z\" ," +
171- "\" UnexpectedElement7\" :{\" Name\" :\" ignore-me-7\" }}" ,
172- DateUtils .formatIso8601Date (Instant .now ().plus (Duration .ofDays (1 )))
173- );
174-
175- String secondCredentials = String .format (
176- "{\" AccessKeyId\" :\" ASIAIOSFODNN7EXAMPLE\" ," +
177- "\" SecretAccessKey\" :\" wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\" ," +
178- "\" Token\" :\" AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKw\" ," +
162+ void resolveCredentials_cachesProfile_maintainsAccountId () {
163+ String credentialsWithAccountId = String .format (
164+ "{\" AccessKeyId\" :\" ACCESS_KEY_ID\" ," +
165+ "\" SecretAccessKey\" :\" SECRET_ACCESS_KEY\" ," +
166+ "\" Token\" :\" SESSION_TOKEN\" ," +
179167 "\" Expiration\" :\" %s\" ," +
180- "\" Code\" :\" Success\" ," +
181- "\" Type\" :\" AWS-HMAC\" ," +
182- "\" LastUpdated\" :\" 2025-03-18T20:53:17.832308Z\" ," +
183- "\" UnexpectedElement7\" :{\" Name\" :\" ignore-me-7\" }}" ,
184- DateUtils .formatIso8601Date (Instant .now ().plus (Duration .ofDays (1 )))
168+ "\" AccountId\" :\" %s\" }" ,
169+ DateUtils .formatIso8601Date (Instant .now ().plus (Duration .ofDays (1 ))),
170+ ACCOUNT_ID
185171 );
186172
187- wireMockServer .stubFor (get (urlPathEqualTo (CREDENTIALS_EXTENDED_RESOURCE_PATH ))
188- .inScenario ("Profile Change No AccountId" )
189- .whenScenarioStateIs ("Started" )
190- .willReturn (aResponse ().withBody ("my-profile-0007" ))
191- .willSetStateTo ("First Profile" ));
192-
193- wireMockServer .stubFor (get (urlPathEqualTo (CREDENTIALS_EXTENDED_RESOURCE_PATH + "my-profile-0007" ))
194- .inScenario ("Profile Change No AccountId" )
195- .whenScenarioStateIs ("First Profile" )
196- .willReturn (aResponse ().withBody (firstCredentials ))
197- .willSetStateTo ("First Profile Done" ));
198-
199- wireMockServer .stubFor (get (urlPathEqualTo (CREDENTIALS_EXTENDED_RESOURCE_PATH + "my-profile-0007" ))
200- .inScenario ("Profile Change No AccountId" )
201- .whenScenarioStateIs ("First Profile Done" )
202- .willReturn (aResponse ().withStatus (404 ))
203- .willSetStateTo ("Profile Changed" ));
204-
205- wireMockServer .stubFor (get (urlPathEqualTo (CREDENTIALS_EXTENDED_RESOURCE_PATH ))
206- .inScenario ("Profile Change No AccountId" )
207- .whenScenarioStateIs ("Profile Changed" )
208- .willReturn (aResponse ().withBody ("my-profile-0007-b" )));
209-
210- wireMockServer .stubFor (get (urlPathEqualTo (CREDENTIALS_EXTENDED_RESOURCE_PATH + "my-profile-0007-b" ))
211- .willReturn (aResponse ().withBody (secondCredentials )));
212-
213- wireMockServer .stubFor (put (urlPathEqualTo (TOKEN_RESOURCE_PATH ))
214- .willReturn (aResponse ().withBody (TOKEN_STUB )));
215-
173+ stubSecureCredentialsResponse (aResponse ().withBody (credentialsWithAccountId ), true );
216174 InstanceProfileCredentialsProvider provider = InstanceProfileCredentialsProvider .builder ().build ();
217175
176+ // First call
218177 AwsCredentials creds1 = provider .resolveCredentials ();
219- assertThat (creds1 .accountId ()).isEmpty ();
220- assertThat (creds1 .accessKeyId ()).isEqualTo ("ASIAIOSFODNN7EXAMPLE" );
178+ assertThat (creds1 .accountId ()).hasValue (ACCOUNT_ID );
221179
180+ // Second call - should use cached profile
222181 AwsCredentials creds2 = provider .resolveCredentials ();
223- assertThat (creds2 .accountId ()).isEmpty ();
224- assertThat (creds2 .accessKeyId ()).isEqualTo ("ASIAIOSFODNN7EXAMPLE" );
182+ assertThat (creds2 .accountId ()).hasValue (ACCOUNT_ID );
183+
184+ // Verify profile discovery only called once
185+ verify (1 , getRequestedFor (urlPathEqualTo (CREDENTIALS_EXTENDED_RESOURCE_PATH )));
225186 }
226187
227188 @ Test
228- void resolveCredentials_withDiscoveredInvalidProfile_noAccountId_throwsException () {
229- String invalidProfile = "my-profile-0008" ;
189+ void resolveCredentials_withNon404Error_doesNotFallbackToLegacy () {
190+ wireMockServer .stubFor (put (urlPathEqualTo (TOKEN_RESOURCE_PATH ))
191+ .willReturn (aResponse ().withBody (TOKEN_STUB )));
230192
231193 wireMockServer .stubFor (get (urlPathEqualTo (CREDENTIALS_EXTENDED_RESOURCE_PATH ))
232- .willReturn (aResponse ().withBody (invalidProfile )));
194+ .willReturn (aResponse ().withBody (PROFILE_NAME )));
233195
234- wireMockServer .stubFor (get (urlPathEqualTo (CREDENTIALS_EXTENDED_RESOURCE_PATH + invalidProfile ))
235- .willReturn (aResponse ().withStatus (404 )));
196+ wireMockServer .stubFor (get (urlPathEqualTo (CREDENTIALS_EXTENDED_RESOURCE_PATH + PROFILE_NAME ))
197+ .willReturn (aResponse ().withStatus (500 ). withBody ( "Internal Server Error" )));
236198
237- wireMockServer .stubFor (get (urlPathEqualTo (CREDENTIALS_RESOURCE_PATH + invalidProfile ))
238- .willReturn (aResponse ().withStatus (404 )));
239-
240- wireMockServer .stubFor (put (urlPathEqualTo (TOKEN_RESOURCE_PATH ))
241- .willReturn (aResponse ().withBody (TOKEN_STUB )));
242199
243200 InstanceProfileCredentialsProvider provider = InstanceProfileCredentialsProvider .builder ().build ();
244201
245202 assertThatThrownBy (() -> provider .resolveCredentials ())
246203 .isInstanceOf (SdkClientException .class )
247204 .hasMessageContaining ("Failed to load credentials from IMDS" );
248- }
249205
206+ // Verify extended endpoint was called
207+ verify (1 , getRequestedFor (urlPathEqualTo (CREDENTIALS_EXTENDED_RESOURCE_PATH )));
208+ verify (1 , getRequestedFor (urlPathEqualTo (CREDENTIALS_EXTENDED_RESOURCE_PATH + PROFILE_NAME )));
209+
210+ // Verify legacy endpoint was NOT called
211+ verify (0 , getRequestedFor (urlPathEqualTo (CREDENTIALS_RESOURCE_PATH )));
212+ verify (0 , getRequestedFor (urlPathEqualTo (CREDENTIALS_RESOURCE_PATH + PROFILE_NAME )));
213+ }
214+
250215 @ Test
251- void resolveCredentials_cachesProfile_maintainsAccountId () {
252- String credentialsWithAccountId = String .format (
253- "{\" AccessKeyId\" :\" ACCESS_KEY_ID\" ," +
254- "\" SecretAccessKey\" :\" SECRET_ACCESS_KEY\" ," +
255- "\" Token\" :\" SESSION_TOKEN\" ," +
256- "\" Expiration\" :\" %s\" ," +
257- "\" AccountId\" :\" %s\" }" ,
258- DateUtils .formatIso8601Date (Instant .now ().plus (Duration .ofDays (1 ))),
259- ACCOUNT_ID
260- );
216+ void resolveCredentials_withNon404ErrorOnProfileDiscovery_doesNotFallbackToLegacy () {
217+ wireMockServer .stubFor (put (urlPathEqualTo (TOKEN_RESOURCE_PATH ))
218+ .willReturn (aResponse ().withBody (TOKEN_STUB )));
261219
262- stubSecureCredentialsResponse ( aResponse (). withBody ( credentialsWithAccountId ), true );
263- InstanceProfileCredentialsProvider provider = InstanceProfileCredentialsProvider . builder (). build ( );
220+ wireMockServer . stubFor ( get ( urlPathEqualTo ( CREDENTIALS_EXTENDED_RESOURCE_PATH ))
221+ . willReturn ( aResponse (). withStatus ( 403 ). withBody ( "Forbidden" )) );
264222
265- // First call
266- AwsCredentials creds1 = provider .resolveCredentials ();
267- assertThat (creds1 .accountId ()).hasValue (ACCOUNT_ID );
223+ InstanceProfileCredentialsProvider provider = InstanceProfileCredentialsProvider .builder ().build ();
268224
269- // Second call - should use cached profile
270- AwsCredentials creds2 = provider . resolveCredentials ();
271- assertThat ( creds2 . accountId ()). hasValue ( ACCOUNT_ID );
225+ assertThatThrownBy (() -> provider . resolveCredentials ())
226+ . isInstanceOf ( SdkClientException . class )
227+ . hasMessageContaining ( "Failed to load credentials from IMDS" );
272228
273- // Verify profile discovery only called once
229+ // Verify extended endpoint was called
274230 verify (1 , getRequestedFor (urlPathEqualTo (CREDENTIALS_EXTENDED_RESOURCE_PATH )));
231+
232+ // Verify profile-specific endpoint was NOT called
233+ verify (0 , getRequestedFor (urlPathEqualTo (CREDENTIALS_EXTENDED_RESOURCE_PATH + PROFILE_NAME )));
234+
235+ // Verify legacy endpoint was NOT called
236+ verify (0 , getRequestedFor (urlPathEqualTo (CREDENTIALS_RESOURCE_PATH )));
237+ verify (0 , getRequestedFor (urlPathEqualTo (CREDENTIALS_RESOURCE_PATH + PROFILE_NAME )));
275238 }
276239
277240 private void stubSecureCredentialsResponse (com .github .tomakehurst .wiremock .client .ResponseDefinitionBuilder responseDefinitionBuilder , boolean useExtended ) {
0 commit comments