Skip to content

Commit 459b9c4

Browse files
committed
Add tests; rename create to setup; cleanup
1 parent 7994a9a commit 459b9c4

33 files changed

Lines changed: 1368 additions & 68 deletions

File tree

aws/aws-config/src/fuzz/java/software/amazon/smithy/java/aws/config/AwsProfileFileParserFuzzTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import java.nio.charset.StandardCharsets;
1010

1111
/**
12-
* Fuzz test for the AWS config file parser. Ensures no input can cause unexpected exceptions,
12+
* Fuzz test for the AWS config file parser. Helps detect when an input causes unexpected exceptions,
1313
* OOM, or infinite loops. The only acceptable exception is {@link ConfigFileParseException}.
1414
*/
1515
class AwsProfileFileParserFuzzTest {

aws/aws-config/src/test/java/software/amazon/smithy/java/aws/config/AwsProfileFileParserTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import static org.junit.jupiter.api.Assertions.assertThrows;
1010
import static org.junit.jupiter.api.Assertions.assertTrue;
1111

12+
import java.util.LinkedHashMap;
1213
import java.util.List;
1314
import java.util.Map;
1415
import org.junit.jupiter.api.Test;
@@ -237,7 +238,7 @@ void whitespaceOnlyLineWithinSubPropertiesIsBlank() {
237238
}
238239

239240
private static Map<String, String> mapValues(Map<String, RawProperty> props) {
240-
Map<String, String> out = new java.util.LinkedHashMap<>();
241+
Map<String, String> out = new LinkedHashMap<>();
241242
for (var e : props.entrySet()) {
242243
out.put(e.getKey(), e.getValue().value);
243244
}

aws/aws-config/src/test/java/software/amazon/smithy/java/aws/config/SepParserConformanceTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@
1818
import java.util.ArrayList;
1919
import java.util.Iterator;
2020
import java.util.LinkedHashMap;
21+
import java.util.LinkedHashSet;
2122
import java.util.List;
2223
import java.util.Map;
24+
import java.util.Set;
2325
import java.util.stream.Stream;
2426
import org.junit.jupiter.api.DynamicTest;
2527
import org.junit.jupiter.api.TestFactory;
@@ -209,8 +211,8 @@ private void assertSsoSessionsMatch(Map<String, Map<String, Object>> expected, A
209211
}
210212
}
211213

212-
private static java.util.Set<String> profileNameSet(AwsProfileFile file) {
213-
java.util.Set<String> names = new java.util.LinkedHashSet<>();
214+
private static Set<String> profileNameSet(AwsProfileFile file) {
215+
Set<String> names = new LinkedHashSet<>();
214216
for (AwsProfile p : file.profiles()) {
215217
names.add(p.name());
216218
}

aws/aws-credential-chain/src/main/java/software/amazon/smithy/java/aws/credentials/chain/ChainIdentityProvider.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* SPI for registering an identity provider into a credential/token chain.
1313
*
1414
* <p>Implementations are discovered via the language's plugin mechanism (e.g., {@code ServiceLoader}
15-
* in Java) and sorted by {@link #ordering()} before {@link #create} is called. A provider
15+
* in Java) and sorted by {@link #ordering()} before {@link #setup} is called. A provider
1616
* registers its resolver by calling {@link ChainSetup#addResolver} or
1717
* {@link ChainSetup#addTerminalResolver} from within {@code create()}.
1818
*/
@@ -47,5 +47,5 @@ default Set<CredentialFeatureId> featureIds() {
4747
* @param identityType the identity class the chain is resolving.
4848
* @param setup the chain setup context.
4949
*/
50-
void create(Class<? extends Identity> identityType, ChainSetup setup);
50+
void setup(Class<? extends Identity> identityType, ChainSetup setup);
5151
}

aws/aws-credential-chain/src/main/java/software/amazon/smithy/java/aws/credentials/chain/ChainSetup.java

Lines changed: 180 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -9,106 +9,256 @@
99
import java.util.List;
1010
import java.util.Set;
1111
import java.util.concurrent.ScheduledExecutorService;
12-
import software.amazon.smithy.java.auth.api.identity.Identity;
12+
import java.util.function.Function;
1313
import software.amazon.smithy.java.auth.api.identity.IdentityResolver;
1414
import software.amazon.smithy.java.aws.config.AwsProfile;
1515
import software.amazon.smithy.java.aws.config.AwsProfileFile;
1616
import software.amazon.smithy.java.context.Context;
17+
import software.amazon.smithy.utils.SmithyInternalApi;
1718

1819
/**
19-
* Mutable assembly context passed to each {@link ChainIdentityProvider#create} during chain
20-
* construction. Providers use this to read shared state and register resolvers.
20+
* Mutable assembly context passed to each {@link ChainIdentityProvider#setup} during
21+
* credential chain construction.
2122
*
22-
* <p>When {@link #addTerminalResolver} is called, assembly stops — no further providers are invoked.
23+
* <p>Providers use this to:
24+
* <ul>
25+
* <li>Read shared state ({@link #profile()}, {@link #profileFile()}, {@link #executor()})</li>
26+
* <li>Write shared state ({@link #setProfileFile(AwsProfileFile)}, {@link #setProfile(AwsProfile)})</li>
27+
* <li>Register resolvers ({@link #addResolver(IdentityResolver)}, {@link #addTerminalResolver(IdentityResolver)})</li>
28+
* <li>Read environment variables ({@link #getenv(String)})</li>
29+
* </ul>
30+
*
31+
* <p>When {@link #addTerminalResolver(IdentityResolver)} is called, assembly stops immediately
32+
* and no further providers are invoked.
33+
*
34+
* <p>This class is not thread-safe. It is used only during the single-threaded assembly phase.
35+
* Resolvers MUST NOT retain a reference to this object.
2336
*/
2437
public final class ChainSetup {
2538
private final ScheduledExecutorService executor;
2639
private final String profileNameOverride;
2740
private final Context properties;
2841
private final List<NamedResolver> resolvers = new ArrayList<>();
42+
private final Function<String, String> envFn;
2943
private AwsProfileFile profileFile;
3044
private AwsProfile profile;
3145
private boolean terminal;
32-
33-
// Current provider being assembled (set by the chain before calling create())
3446
private ChainIdentityProvider currentProvider;
3547

36-
public ChainSetup(ScheduledExecutorService executor) {
37-
this(executor, null);
48+
private ChainSetup(Builder builder) {
49+
this.executor = builder.executor;
50+
this.profileNameOverride = builder.profileNameOverride;
51+
this.properties = Context.create();
52+
this.envFn = builder.envFn;
3853
}
3954

40-
public ChainSetup(ScheduledExecutorService executor, String profileNameOverride) {
41-
this.executor = executor;
42-
this.profileNameOverride = profileNameOverride;
43-
this.properties = Context.create();
55+
/**
56+
* Creates a new builder for {@link ChainSetup}.
57+
*
58+
* @return a new builder.
59+
*/
60+
public static Builder builder() {
61+
return new Builder();
4462
}
4563

46-
/** Shared executor for background credential refresh. */
64+
/**
65+
* Returns the shared executor for scheduling background credential refresh tasks.
66+
*
67+
* @return the scheduled executor, or {@code null} if none was configured.
68+
*/
4769
public ScheduledExecutorService executor() {
4870
return executor;
4971
}
5072

51-
/** Client-specified profile name override, or {@code null} for default resolution. */
73+
/**
74+
* Returns the client-specified profile name override, or {@code null} to use the
75+
* default resolution order ({@code AWS_PROFILE} env var, {@code aws.profile} system
76+
* property, then {@code "default"}).
77+
*
78+
* @return the profile name override, or {@code null}.
79+
*/
5280
public String profileNameOverride() {
5381
return profileNameOverride;
5482
}
5583

56-
/** General-purpose property bag for sharing state between providers. */
84+
/**
85+
* Returns the value of the given environment variable, or {@code null} if not set.
86+
*
87+
* <p>In production this delegates to {@link System#getenv(String)}. In tests, a custom
88+
* function can be provided via {@link Builder#env(Function)} for isolation.
89+
*
90+
* @param name the environment variable name.
91+
* @return the value, or {@code null}.
92+
*/
93+
public String getenv(String name) {
94+
return envFn.apply(name);
95+
}
96+
97+
/**
98+
* Returns a general-purpose typed property bag for sharing additional state between
99+
* providers during assembly.
100+
*
101+
* @return the shared context properties.
102+
*/
57103
public Context properties() {
58104
return properties;
59105
}
60106

61-
/** The parsed AWS config/credentials file, or {@code null} if not yet loaded. */
107+
/**
108+
* Returns the parsed AWS config/credentials file, or {@code null} if not yet loaded.
109+
*
110+
* <p>Populated by the {@code SHARED_CONFIG} provider during assembly.
111+
*
112+
* @return the parsed profile file, or {@code null}.
113+
*/
62114
public AwsProfileFile profileFile() {
63115
return profileFile;
64116
}
65117

66-
/** Sets the parsed profile file. Called by the SHARED_CONFIG provider. */
118+
/**
119+
* Sets the parsed AWS config/credentials file. Called by the {@code SHARED_CONFIG}
120+
* provider during assembly.
121+
*
122+
* @param profileFile the parsed profile file.
123+
*/
67124
public void setProfileFile(AwsProfileFile profileFile) {
68125
this.profileFile = profileFile;
69126
}
70127

71-
/** The active profile, or {@code null} if not yet loaded. */
128+
/**
129+
* Returns the active AWS profile, or {@code null} if not yet loaded.
130+
*
131+
* <p>Populated by the {@code SHARED_CONFIG} provider during assembly.
132+
*
133+
* @return the active profile, or {@code null}.
134+
*/
72135
public AwsProfile profile() {
73136
return profile;
74137
}
75138

76-
/** Sets the active profile. Called by the SHARED_CONFIG provider. */
139+
/**
140+
* Sets the active AWS profile. Called by the {@code SHARED_CONFIG} provider during assembly.
141+
*
142+
* @param profile the active profile.
143+
*/
77144
public void setProfile(AwsProfile profile) {
78145
this.profile = profile;
79146
}
80147

81148
/**
82-
* Registers a resolver at the current provider's position. Assembly continues.
149+
* Registers a resolver at the current provider's position. Assembly continues after
150+
* this call. May be called multiple times to register multiple resolvers that stack
151+
* at this position.
152+
*
153+
* @param resolver the identity resolver to register.
83154
*/
84155
public void addResolver(IdentityResolver<?> resolver) {
85156
resolvers.add(new NamedResolver(currentProvider.name(), currentProvider.featureIds(), resolver));
86157
}
87158

88159
/**
89-
* Registers a resolver and stops assembly. No further providers will be called.
160+
* Registers a resolver and stops assembly immediately. No further providers will be
161+
* called. Use when the credential source is authoritative once detected (e.g.,
162+
* environment variables contain a complete set of credentials, or a profile explicitly
163+
* configures assume-role).
164+
*
165+
* @param resolver the identity resolver to register.
90166
*/
91167
public void addTerminalResolver(IdentityResolver<?> resolver) {
92168
resolvers.add(new NamedResolver(currentProvider.name(), currentProvider.featureIds(), resolver));
93169
this.terminal = true;
94170
}
95171

96-
// --- Package-private, used by CredentialChain ---
97-
172+
/**
173+
* Sets the current provider being assembled. Called by the chain before invoking
174+
* each provider's {@link ChainIdentityProvider#setup} method so that
175+
* {@link #addResolver} and {@link #addTerminalResolver} can associate the resolver
176+
* with the correct provider name and feature IDs.
177+
*
178+
* <p>This method is public to support unit testing of individual providers in
179+
* isolation. Production code should not call this directly.
180+
*
181+
* @param provider the provider currently being assembled.
182+
*/
183+
@SmithyInternalApi
98184
public void setCurrentProvider(ChainIdentityProvider provider) {
99185
this.currentProvider = provider;
100186
}
101187

188+
/**
189+
* Returns the list of resolvers registered during assembly, in the order they were added.
190+
*
191+
* @return the ordered list of named resolvers.
192+
*/
193+
public List<NamedResolver> resolvers() {
194+
return resolvers;
195+
}
196+
102197
boolean isTerminal() {
103198
return terminal;
104199
}
105200

106-
public List<NamedResolver> resolvers() {
107-
return resolvers;
108-
}
201+
/**
202+
* A resolver paired with the name and feature IDs of the provider that registered it.
203+
*
204+
* @param name the canonical name of the provider that registered this resolver.
205+
* @param featureIds the feature IDs to emit on successful resolution.
206+
* @param resolver the identity resolver.
207+
*/
208+
public record NamedResolver(String name, Set<CredentialFeatureId> featureIds, IdentityResolver<?> resolver) {}
209+
210+
/**
211+
* Builder for {@link ChainSetup}.
212+
*/
213+
public static final class Builder {
214+
private ScheduledExecutorService executor;
215+
private String profileNameOverride;
216+
private Function<String, String> envFn = System::getenv;
109217

110-
public record NamedResolver<I extends Identity>(
111-
String name,
112-
Set<CredentialFeatureId> featureIds,
113-
IdentityResolver<I> resolver) {}
218+
private Builder() {}
219+
220+
/**
221+
* Sets the shared executor for background credential refresh.
222+
*
223+
* @param executor the scheduled executor.
224+
* @return this builder.
225+
*/
226+
public Builder executor(ScheduledExecutorService executor) {
227+
this.executor = executor;
228+
return this;
229+
}
230+
231+
/**
232+
* Sets the profile name override. When set, the {@code SHARED_CONFIG} provider
233+
* uses this name instead of resolving from {@code AWS_PROFILE} or system properties.
234+
*
235+
* @param profileNameOverride the profile name to use.
236+
* @return this builder.
237+
*/
238+
public Builder profileNameOverride(String profileNameOverride) {
239+
this.profileNameOverride = profileNameOverride;
240+
return this;
241+
}
242+
243+
/**
244+
* Sets the function used to resolve environment variables. Defaults to
245+
* {@link System#getenv(String)}. Override in tests for isolation.
246+
*
247+
* @param envFn the environment variable lookup function.
248+
* @return this builder.
249+
*/
250+
public Builder env(Function<String, String> envFn) {
251+
this.envFn = envFn;
252+
return this;
253+
}
254+
255+
/**
256+
* Builds the {@link ChainSetup}.
257+
*
258+
* @return the constructed setup.
259+
*/
260+
public ChainSetup build() {
261+
return new ChainSetup(this);
262+
}
263+
}
114264
}

0 commit comments

Comments
 (0)