Skip to content

Commit e24b6ed

Browse files
committed
fix(parameters): Correctly check for empty values in AppConfig Parameters Provider.
1 parent 3fa2b45 commit e24b6ed

File tree

2 files changed

+52
-48
lines changed

2 files changed

+52
-48
lines changed

powertools-parameters/powertools-parameters-appconfig/src/main/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProvider.java

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
import java.util.HashMap;
1818
import java.util.Map;
19+
20+
import software.amazon.awssdk.core.SdkBytes;
1921
import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient;
2022
import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationRequest;
2123
import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationResponse;
@@ -47,7 +49,7 @@ public class AppConfigProvider extends BaseProvider {
4749
private final HashMap<String, EstablishedSession> establishedSessions = new HashMap<>();
4850

4951
AppConfigProvider(CacheManager cacheManager, TransformationManager transformationManager,
50-
AppConfigDataClient client, String environment, String application) {
52+
AppConfigDataClient client, String environment, String application) {
5153
super(cacheManager, transformationManager);
5254
this.client = client;
5355
this.application = application;
@@ -63,7 +65,6 @@ public static AppConfigProviderBuilder builder() {
6365
return new AppConfigProviderBuilder();
6466
}
6567

66-
6768
/**
6869
* Retrieve the parameter value from the AppConfig parameter store.<br />
6970
*
@@ -76,13 +77,12 @@ protected String getValue(String key) {
7677
// so that we can the initial token. If we already have a session, we can take
7778
// the next request token from there.
7879
EstablishedSession establishedSession = establishedSessions.getOrDefault(key, null);
79-
String sessionToken = establishedSession != null ?
80-
establishedSession.nextSessionToken :
81-
client.startConfigurationSession(StartConfigurationSessionRequest.builder()
82-
.applicationIdentifier(this.application)
83-
.environmentIdentifier(this.environment)
84-
.configurationProfileIdentifier(key)
85-
.build())
80+
String sessionToken = establishedSession != null ? establishedSession.nextSessionToken
81+
: client.startConfigurationSession(StartConfigurationSessionRequest.builder()
82+
.applicationIdentifier(this.application)
83+
.environmentIdentifier(this.environment)
84+
.configurationProfileIdentifier(key)
85+
.build())
8686
.initialConfigurationToken();
8787

8888
// Get the configuration using the token
@@ -93,16 +93,18 @@ protected String getValue(String key) {
9393
// Get the next session token we'll use next time we are asked for this key
9494
String nextSessionToken = response.nextPollConfigurationToken();
9595

96-
// Get the value of the key. Note that AppConfig will return null if the value
97-
// has not changed since we last asked for it in this session - in this case
98-
// we return the value we stashed at last request.
99-
String value = response.configuration() != null ?
100-
response.configuration().asUtf8String() : // if we have a new value, use it
101-
establishedSession != null ?
102-
establishedSession.lastConfigurationValue :
103-
// if we don't but we have a previous value, use that
104-
null; // otherwise we've got no value
105-
96+
// Get the value of the key. Note that AppConfig will return an empty value if the configuration has not changed
97+
// since we last asked for it in this session - in this case we return the value we stashed at last request.
98+
// https://docs.aws.amazon.com/appconfig/latest/userguide/appconfig-code-samples-using-API-read-configuration.html
99+
SdkBytes configFromApi = response.configuration();
100+
String value;
101+
if (configFromApi != null && configFromApi.asByteArray().length != 0) {
102+
value = configFromApi.asUtf8String();
103+
} else if (establishedSession != null) {
104+
value = establishedSession.lastConfigurationValue;
105+
} else {
106+
value = null;
107+
}
106108
// Update the cache so we can get the next value later
107109
establishedSessions.put(key, new EstablishedSession(nextSessionToken, value));
108110

powertools-parameters/powertools-parameters-appconfig/src/test/java/software/amazon/lambda/powertools/parameters/appconfig/AppConfigProviderTest.java

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414

1515
package software.amazon.lambda.powertools.parameters.appconfig;
1616

17+
import static org.assertj.core.api.Assertions.assertThat;
1718
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
1819
import static org.assertj.core.api.Assertions.assertThatRuntimeException;
19-
import static org.assertj.core.api.Assertions.assertThat;
2020
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
2121
import static org.mockito.MockitoAnnotations.openMocks;
2222
import static software.amazon.lambda.powertools.parameters.transform.Transformer.json;
@@ -27,6 +27,7 @@
2727
import org.mockito.Captor;
2828
import org.mockito.Mock;
2929
import org.mockito.Mockito;
30+
3031
import software.amazon.awssdk.core.SdkBytes;
3132
import software.amazon.awssdk.services.appconfigdata.AppConfigDataClient;
3233
import software.amazon.awssdk.services.appconfigdata.model.GetLatestConfigurationRequest;
@@ -36,7 +37,7 @@
3637
import software.amazon.lambda.powertools.parameters.cache.CacheManager;
3738
import software.amazon.lambda.powertools.parameters.transform.TransformationManager;
3839

39-
public class AppConfigProviderTest {
40+
class AppConfigProviderTest {
4041

4142
private final String environmentName = "test";
4243
private final String applicationName = "fakeApp";
@@ -53,7 +54,7 @@ public class AppConfigProviderTest {
5354
private AppConfigProvider provider;
5455

5556
@BeforeEach
56-
public void init() {
57+
void init() {
5758
openMocks(this);
5859

5960
provider = AppConfigProvider.builder()
@@ -65,15 +66,14 @@ public void init() {
6566
.build();
6667
}
6768

68-
6969
/**
7070
* Tests repeated calls to the AppConfigProvider for the same key behave correctly. This is more complicated than
7171
* it seems, as the service itself will return no-data if the value of a property remains unchanged since the
7272
* start of a session. This means the provider must cache the result and return it again if it gets no data, but
7373
* subsequent calls should once again return the new data.
7474
*/
7575
@Test
76-
public void getValueRetrievesValue() {
76+
void getValueRetrievesValue() {
7777
// Arrange
7878
StartConfigurationSessionResponse firstSession = StartConfigurationSessionResponse.builder()
7979
.initialConfigurationToken("token1")
@@ -92,21 +92,29 @@ public void getValueRetrievesValue() {
9292
GetLatestConfigurationResponse thirdResponse = GetLatestConfigurationResponse.builder()
9393
.nextPollConfigurationToken("token4")
9494
.build();
95+
// Forth response returns empty, which means the provider should yield the previous value again
96+
GetLatestConfigurationResponse forthResponse = GetLatestConfigurationResponse.builder()
97+
.nextPollConfigurationToken("token5")
98+
.configuration(SdkBytes.fromUtf8String(""))
99+
.build();
95100
Mockito.when(client.startConfigurationSession(startSessionRequestCaptor.capture()))
96101
.thenReturn(firstSession);
97102
Mockito.when(client.getLatestConfiguration(getLatestConfigurationRequestCaptor.capture()))
98-
.thenReturn(firstResponse, secondResponse, thirdResponse);
103+
.thenReturn(firstResponse, secondResponse, thirdResponse, forthResponse);
99104

100105
// Act
101106
String returnedValue1 = provider.getValue(defaultTestKey);
102107
String returnedValue2 = provider.getValue(defaultTestKey);
103108
String returnedValue3 = provider.getValue(defaultTestKey);
109+
String returnedValue4 = provider.getValue(defaultTestKey);
104110

105111
// Assert
106112
assertThat(returnedValue1).isEqualTo(firstResponse.configuration().asUtf8String());
107113
assertThat(returnedValue2).isEqualTo(secondResponse.configuration().asUtf8String());
108114
assertThat(returnedValue3).isEqualTo(secondResponse.configuration()
109115
.asUtf8String()); // Third response is mocked to return null and should re-use previous value
116+
assertThat(returnedValue4).isEqualTo(secondResponse.configuration()
117+
.asUtf8String()); // Forth response is mocked to return empty and should re-use previous value
110118
assertThat(startSessionRequestCaptor.getValue().applicationIdentifier()).isEqualTo(applicationName);
111119
assertThat(startSessionRequestCaptor.getValue().environmentIdentifier()).isEqualTo(environmentName);
112120
assertThat(startSessionRequestCaptor.getValue().configurationProfileIdentifier()).isEqualTo(defaultTestKey);
@@ -119,8 +127,7 @@ public void getValueRetrievesValue() {
119127
}
120128

121129
@Test
122-
public void getValueNoValueExists() {
123-
130+
void getValueNoValueExists() {
124131
// Arrange
125132
StartConfigurationSessionResponse session = StartConfigurationSessionResponse.builder()
126133
.initialConfigurationToken("token1")
@@ -136,17 +143,16 @@ public void getValueNoValueExists() {
136143
// Act
137144
String returnedValue = provider.getValue(defaultTestKey);
138145

139-
140146
// Assert
141-
assertThat(returnedValue).isEqualTo(null);
147+
assertThat(returnedValue).isNull();
142148
}
143149

144150
/**
145151
* If we mix requests for different keys together through the same provider, retrieval should
146152
* work as expected. This means two separate configuration sessions should be established with AppConfig.
147153
*/
148154
@Test
149-
public void multipleKeysRetrievalWorks() {
155+
void multipleKeysRetrievalWorks() {
150156
// Arrange
151157
String param1Key = "key1";
152158
StartConfigurationSessionResponse param1Session = StartConfigurationSessionResponse.builder()
@@ -184,49 +190,45 @@ public void multipleKeysRetrievalWorks() {
184190
param1Session.initialConfigurationToken());
185191
assertThat(getLatestConfigurationRequestCaptor.getAllValues().get(1).configurationToken()).isEqualTo(
186192
param2Session.initialConfigurationToken());
187-
188193
}
189194

190195
@Test
191-
public void getMultipleValuesThrowsException() {
192-
196+
void getMultipleValuesThrowsException() {
193197
// Act & Assert
194198
assertThatRuntimeException().isThrownBy(() -> provider.getMultipleValues("path"))
195199
.withMessage("Retrieving multiple parameter values is not supported with the AWS App Config Provider");
196200
}
197201

198202
@Test
199-
public void testAppConfigProviderBuilderMissingEnvironment_throwsException() {
200-
203+
void testAppConfigProviderBuilderMissingEnvironment_throwsException() {
201204
// Act & Assert
202205
assertThatIllegalStateException().isThrownBy(() -> AppConfigProvider.builder()
203-
.withCacheManager(new CacheManager())
204-
.withApplication(applicationName)
205-
.withClient(client)
206-
.build())
206+
.withCacheManager(new CacheManager())
207+
.withApplication(applicationName)
208+
.withClient(client)
209+
.build())
207210
.withMessage("No environment provided; please provide one");
208211
}
209212

210213
@Test
211-
public void testAppConfigProviderBuilderMissingApplication_throwsException() {
212-
214+
void testAppConfigProviderBuilderMissingApplication_throwsException() {
213215
// Act & Assert
214216
assertThatIllegalStateException().isThrownBy(() -> AppConfigProvider.builder()
215-
.withCacheManager(new CacheManager())
216-
.withEnvironment(environmentName)
217-
.withClient(client)
218-
.build())
217+
.withCacheManager(new CacheManager())
218+
.withEnvironment(environmentName)
219+
.withClient(client)
220+
.build())
219221
.withMessage("No application provided; please provide one");
220222
}
221-
@Test
222-
public void testAppConfigProvider_withoutParameter_shouldHaveDefaultTransformationManager() {
223223

224+
@Test
225+
void testAppConfigProvider_withoutParameter_shouldHaveDefaultTransformationManager() {
224226
// Act
225227
AppConfigProvider appConfigProvider = AppConfigProvider.builder()
226228
.withEnvironment("test")
227229
.withApplication("app")
228230
.build();
229231
// Assert
230-
assertDoesNotThrow(()->appConfigProvider.withTransformation(json));
232+
assertDoesNotThrow(() -> appConfigProvider.withTransformation(json));
231233
}
232234
}

0 commit comments

Comments
 (0)