From 58c748919d1052335907c5c981d8278eb030c1d3 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Mon, 28 Jul 2025 16:50:22 -0400 Subject: [PATCH 1/4] Remove content type check for appconfig --- .../AppConfig/AppConfigProcessor.cs | 18 +-- .../AppConfigEndToEndTests.cs | 36 +++++ .../AppConfigProcessorTests.cs | 148 ++++++++++++++++++ 3 files changed, 192 insertions(+), 10 deletions(-) create mode 100644 test/Amazon.Extensions.Configuration.SystemsManager.Tests/AppConfigProcessorTests.cs diff --git a/src/Amazon.Extensions.Configuration.SystemsManager/AppConfig/AppConfigProcessor.cs b/src/Amazon.Extensions.Configuration.SystemsManager/AppConfig/AppConfigProcessor.cs index 094fde8..2bc672a 100644 --- a/src/Amazon.Extensions.Configuration.SystemsManager/AppConfig/AppConfigProcessor.cs +++ b/src/Amazon.Extensions.Configuration.SystemsManager/AppConfig/AppConfigProcessor.cs @@ -17,6 +17,7 @@ using System.Collections.Generic; using System.IO; using System.Net.Http; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Amazon.AppConfigData; @@ -162,20 +163,17 @@ private async Task GetInitialConfigurationTokenAsync(IAmazonAppConfigDat return (await appConfigClient.StartConfigurationSessionAsync(request).ConfigureAwait(false)).InitialConfigurationToken; } - private static IDictionary ParseConfig(string contentType, Stream configuration) + public static IDictionary ParseConfig(string contentType, Stream configuration) { - // Content-Type has format "media-type; charset" or "media-type; boundary" (for multipart entities). - if (contentType != null) + try { - contentType = contentType.Split(';')[0]; + return JsonConfigurationParser.Parse(configuration); } - - switch (contentType) + catch (JsonException ex) { - case "application/json": - return JsonConfigurationParser.Parse(configuration); - default: - throw new NotImplementedException($"Not implemented AppConfig type: {contentType}"); + throw new InvalidOperationException( + $"Failed to parse AppConfig content as JSON. Content-Type was '{contentType}'. {ex.Message}", + ex); } } } diff --git a/test/Amazon.Extensions.Configuration.SystemsManager.Integ/AppConfigEndToEndTests.cs b/test/Amazon.Extensions.Configuration.SystemsManager.Integ/AppConfigEndToEndTests.cs index 2610f98..0f680c9 100644 --- a/test/Amazon.Extensions.Configuration.SystemsManager.Integ/AppConfigEndToEndTests.cs +++ b/test/Amazon.Extensions.Configuration.SystemsManager.Integ/AppConfigEndToEndTests.cs @@ -84,6 +84,42 @@ public async Task JsonWithCharsetConfiguration() } } + [Fact] + public async Task AppConfigWithOctetStreamContentType() + { + var configSettings = new Dictionary + { + { "key1", "value1" }, + { "key2", "value2" }, + { "database:connectionString", "server=localhost;database=test" }, + { "api:timeout", "30" } + }; + + // Create AppConfig with application/octet-stream content type (simulates Parameter Store backend) + (string applicationId, string environmentId, string configProfileId) = + await CreateAppConfigResourcesAsync("OctetStreamTest", configSettings, "application/octet-stream"); + + try + { + var builder = new ConfigurationBuilder() + .AddAppConfig(applicationId, environmentId, configProfileId, + new AWSOptions { Region = RegionEndpoint.USWest2 }, + TimeSpan.FromSeconds(5)); + + var configuration = builder.Build(); + + // Verify configuration loads correctly despite application/octet-stream content type + Assert.Equal("value1", configuration["key1"]); + Assert.Equal("value2", configuration["key2"]); + Assert.Equal("server=localhost;database=test", configuration["database:connectionString"]); + Assert.Equal("30", configuration["api:timeout"]); + } + finally + { + await CleanupAppConfigResourcesAsync(applicationId, environmentId, configProfileId); + } + } + private async Task CleanupAppConfigResourcesAsync(string applicationId, string environmentId, string configProfileId) { await _appConfigClient.DeleteEnvironmentAsync(new DeleteEnvironmentRequest {ApplicationId = applicationId, EnvironmentId = environmentId }); diff --git a/test/Amazon.Extensions.Configuration.SystemsManager.Tests/AppConfigProcessorTests.cs b/test/Amazon.Extensions.Configuration.SystemsManager.Tests/AppConfigProcessorTests.cs new file mode 100644 index 0000000..5dcef29 --- /dev/null +++ b/test/Amazon.Extensions.Configuration.SystemsManager.Tests/AppConfigProcessorTests.cs @@ -0,0 +1,148 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Amazon.Extensions.Configuration.SystemsManager.AppConfig; +using Xunit; + +namespace Amazon.Extensions.Configuration.SystemsManager.Tests +{ + public class AppConfigProcessorTests + { + [Fact] + public void ParseConfig_ApplicationJson_ParsesSuccessfully() + { + var jsonContent = "{\"key1\":\"value1\",\"key2\":\"value2\"}"; + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent)); + + var result = AppConfigProcessor.ParseConfig("application/json", stream); + + Assert.Equal("value1", result["key1"]); + Assert.Equal("value2", result["key2"]); + } + + [Fact] + public void ParseConfig_ApplicationOctetStream_ParsesJsonSuccessfully() + { + var jsonContent = "{\"key1\":\"value1\",\"key2\":\"value2\"}"; + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent)); + + var result = AppConfigProcessor.ParseConfig("application/octet-stream", stream); + + Assert.Equal("value1", result["key1"]); + Assert.Equal("value2", result["key2"]); + } + + [Fact] + public void ParseConfig_ApplicationJsonWithCharset_ParsesSuccessfully() + { + var jsonContent = "{\"key1\":\"value1\",\"key2\":\"value2\"}"; + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent)); + + var result = AppConfigProcessor.ParseConfig("application/json; charset=utf-8", stream); + + Assert.Equal("value1", result["key1"]); + Assert.Equal("value2", result["key2"]); + } + + [Fact] + public void ParseConfig_UnknownContentType_ParsesJsonSuccessfully() + { + var jsonContent = "{\"key1\":\"value1\",\"key2\":\"value2\"}"; + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent)); + + var result = AppConfigProcessor.ParseConfig("application/unknown", stream); + + Assert.Equal("value1", result["key1"]); + Assert.Equal("value2", result["key2"]); + } + + [Fact] + public void ParseConfig_NullContentType_ParsesJsonSuccessfully() + { + var jsonContent = "{\"key1\":\"value1\",\"key2\":\"value2\"}"; + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent)); + + var result = AppConfigProcessor.ParseConfig(null, stream); + + Assert.Equal("value1", result["key1"]); + Assert.Equal("value2", result["key2"]); + } + + [Fact] + public void ParseConfig_NestedJson_ParsesSuccessfully() + { + var jsonContent = "{\"section1\":{\"key1\":\"value1\",\"key2\":\"value2\"},\"section2\":{\"key3\":\"value3\"}}"; + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent)); + + var result = AppConfigProcessor.ParseConfig("application/octet-stream", stream); + + Assert.Equal("value1", result["section1:key1"]); + Assert.Equal("value2", result["section1:key2"]); + Assert.Equal("value3", result["section2:key3"]); + } + + [Fact] + public void ParseConfig_InvalidJson_ThrowsInvalidOperationException() + { + var invalidJsonContent = "{ invalid json content }"; + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(invalidJsonContent)); + + var exception = Assert.Throws(() => + AppConfigProcessor.ParseConfig("application/json", stream)); + + Assert.Contains("Failed to parse AppConfig content as JSON", exception.Message); + Assert.Contains("Content-Type was 'application/json'", exception.Message); + } + + [Fact] + public void ParseConfig_InvalidJsonWithOctetStream_ThrowsInvalidOperationException() + { + var invalidJsonContent = "not json at all"; + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(invalidJsonContent)); + + var exception = Assert.Throws(() => + AppConfigProcessor.ParseConfig("application/octet-stream", stream)); + + Assert.Contains("Failed to parse AppConfig content as JSON", exception.Message); + Assert.Contains("Content-Type was 'application/octet-stream'", exception.Message); + } + + [Fact] + public void ParseConfig_EmptyJson_ReturnsEmptyDictionary() + { + var jsonContent = "{}"; + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(jsonContent)); + + var result = AppConfigProcessor.ParseConfig("application/json", stream); + + Assert.Empty(result); + } + + [Fact] + public void ParseConfig_EmptyStream_ThrowsInvalidOperationException() + { + using var stream = new MemoryStream(); + + var exception = Assert.Throws(() => + AppConfigProcessor.ParseConfig("application/json", stream)); + + Assert.Contains("Failed to parse AppConfig content as JSON", exception.Message); + } + } +} From fbd8ece0a9a107c332b321dc15c8788fd72de47a Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Mon, 28 Jul 2025 16:55:33 -0400 Subject: [PATCH 2/4] dev config --- .../changes/976aa8d4-d143-4f7b-9bf5-762b166d7fb5.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .autover/changes/976aa8d4-d143-4f7b-9bf5-762b166d7fb5.json diff --git a/.autover/changes/976aa8d4-d143-4f7b-9bf5-762b166d7fb5.json b/.autover/changes/976aa8d4-d143-4f7b-9bf5-762b166d7fb5.json new file mode 100644 index 0000000..d0aa4fa --- /dev/null +++ b/.autover/changes/976aa8d4-d143-4f7b-9bf5-762b166d7fb5.json @@ -0,0 +1,11 @@ +{ + "Projects": [ + { + "Name": "Amazon.Extensions.Configuration.SystemsManager", + "Type": "Patch", + "ChangelogMessages": [ + "Removed content type check for app config. We now always try to parse json first" + ] + } + ] +} \ No newline at end of file From 779d444b4133806f539fa35924558a4b7a52b271 Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Tue, 29 Jul 2025 20:33:06 -0400 Subject: [PATCH 3/4] add internalvisibleto --- ...azon.Extensions.Configuration.SystemsManager.csproj | 10 +++++++++- .../AppConfig/AppConfigProcessor.cs | 2 +- ...xtensions.Configuration.SystemsManager.Tests.csproj | 2 ++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Amazon.Extensions.Configuration.SystemsManager/Amazon.Extensions.Configuration.SystemsManager.csproj b/src/Amazon.Extensions.Configuration.SystemsManager/Amazon.Extensions.Configuration.SystemsManager.csproj index fb57492..790b550 100644 --- a/src/Amazon.Extensions.Configuration.SystemsManager/Amazon.Extensions.Configuration.SystemsManager.csproj +++ b/src/Amazon.Extensions.Configuration.SystemsManager/Amazon.Extensions.Configuration.SystemsManager.csproj @@ -38,7 +38,15 @@ - + + + <_Parameter1> + Amazon.Extensions.Configuration.SystemsManager.Tests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100db5f59f098d27276c7833875a6263a3cc74ab17ba9a9df0b52aedbe7252745db7274d5271fd79c1f08f668ecfa8eaab5626fa76adc811d3c8fc55859b0d09d3bc0a84eecd0ba891f2b8a2fc55141cdcc37c2053d53491e650a479967c3622762977900eddbf1252ed08a2413f00a28f3a0752a81203f03ccb7f684db373518b4 + + + + + true ..\..\public.snk diff --git a/src/Amazon.Extensions.Configuration.SystemsManager/AppConfig/AppConfigProcessor.cs b/src/Amazon.Extensions.Configuration.SystemsManager/AppConfig/AppConfigProcessor.cs index 2bc672a..a1a2f82 100644 --- a/src/Amazon.Extensions.Configuration.SystemsManager/AppConfig/AppConfigProcessor.cs +++ b/src/Amazon.Extensions.Configuration.SystemsManager/AppConfig/AppConfigProcessor.cs @@ -163,7 +163,7 @@ private async Task GetInitialConfigurationTokenAsync(IAmazonAppConfigDat return (await appConfigClient.StartConfigurationSessionAsync(request).ConfigureAwait(false)).InitialConfigurationToken; } - public static IDictionary ParseConfig(string contentType, Stream configuration) + internal static IDictionary ParseConfig(string contentType, Stream configuration) { try { diff --git a/test/Amazon.Extensions.Configuration.SystemsManager.Tests/Amazon.Extensions.Configuration.SystemsManager.Tests.csproj b/test/Amazon.Extensions.Configuration.SystemsManager.Tests/Amazon.Extensions.Configuration.SystemsManager.Tests.csproj index c0efff9..c222faf 100644 --- a/test/Amazon.Extensions.Configuration.SystemsManager.Tests/Amazon.Extensions.Configuration.SystemsManager.Tests.csproj +++ b/test/Amazon.Extensions.Configuration.SystemsManager.Tests/Amazon.Extensions.Configuration.SystemsManager.Tests.csproj @@ -4,6 +4,8 @@ net8.0 true false + true + ..\..\public.snk From 890b2148e57d28bb3aa0c1242b831105bcd882cf Mon Sep 17 00:00:00 2001 From: Garrett Beatty Date: Tue, 29 Jul 2025 20:37:46 -0400 Subject: [PATCH 4/4] use modern internalvisibleto --- .../Amazon.Extensions.Configuration.SystemsManager.csproj | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Amazon.Extensions.Configuration.SystemsManager/Amazon.Extensions.Configuration.SystemsManager.csproj b/src/Amazon.Extensions.Configuration.SystemsManager/Amazon.Extensions.Configuration.SystemsManager.csproj index 790b550..9f53018 100644 --- a/src/Amazon.Extensions.Configuration.SystemsManager/Amazon.Extensions.Configuration.SystemsManager.csproj +++ b/src/Amazon.Extensions.Configuration.SystemsManager/Amazon.Extensions.Configuration.SystemsManager.csproj @@ -39,11 +39,7 @@ - - <_Parameter1> - Amazon.Extensions.Configuration.SystemsManager.Tests,PublicKey=0024000004800000940000000602000000240000525341310004000001000100db5f59f098d27276c7833875a6263a3cc74ab17ba9a9df0b52aedbe7252745db7274d5271fd79c1f08f668ecfa8eaab5626fa76adc811d3c8fc55859b0d09d3bc0a84eecd0ba891f2b8a2fc55141cdcc37c2053d53491e650a479967c3622762977900eddbf1252ed08a2413f00a28f3a0752a81203f03ccb7f684db373518b4 - - +