diff --git a/OptionStudy.Next/5多样性配置源/JsonConfigurationSourceTest.cs b/OptionStudy.Next/5多样性配置源/JsonConfigurationSourceTest.cs
new file mode 100644
index 0000000..389349b
--- /dev/null
+++ b/OptionStudy.Next/5多样性配置源/JsonConfigurationSourceTest.cs
@@ -0,0 +1,385 @@
+using OptionStudy.UnitApp.Next;
+
+using System.Globalization;
+
+namespace OptionStudy.Next
+{
+ ///
+ /// Json文件 配置源
+ ///
+ public class JsonConfigurationSourceTest : IDisposable
+ {
+ private readonly ITestOutputHelper testOutput;
+
+ public JsonConfigurationSourceTest(ITestOutputHelper testOutputHelper)
+ {
+ this.testOutput = testOutputHelper;
+ }
+
+ #region 空值测试
+ ///
+ /// Key值为没有内容的空对象{}
+ ///
+ [Fact]
+ public void EmptyObject_AddsAsNull_Test()
+ {
+ var json = @"{""key"": { }}";
+
+ var jsonProvider = new JsonConfigurationProvider(new JsonConfigurationSource());
+ jsonProvider.Load(StringToStream(json));
+
+ Assert.True(jsonProvider.TryGet("key",out string? keyValue));
+ Assert.Null(keyValue);
+ }
+
+ ///
+ /// key值为null
+ ///
+ [Fact]
+ public void NullObject_AddsEmptyString_Test()
+ {
+ var json = @"{ ""key"": null}";
+
+ var jsonProvider = new JsonConfigurationProvider(new JsonConfigurationSource());
+ jsonProvider.Load(StringToStream(json));
+
+ Assert.True(jsonProvider.TryGet("key", out string? value));
+ Assert.Equal("", value);
+ }
+
+ ///
+ /// 没有父类的嵌套项
+ ///
+ [Fact]
+ public void NestedObject_DoesNotAddParent_Test()
+ {
+ var json = @"
+ {
+ ""key"":
+ {
+ ""nested"": ""value""
+ }
+ }
+ ";
+
+ var jsonProvider = new JsonConfigurationProvider(new JsonConfigurationSource());
+ jsonProvider.Load(StringToStream(json));
+
+ Assert.False(jsonProvider.TryGet("key", out _));
+ Assert.True(jsonProvider.TryGet("key:nested",out string? nestedValue));
+ Assert.Equal("value", nestedValue);
+ }
+ #endregion
+
+ ///
+ /// 获取json数据源,从json文本
+ ///
+ [Fact]
+ public void BuildConfigurationSource_FromJsonStream()
+ {
+ var json = @"
+ {
+ ""firstname"": ""test"",
+ ""test.last.name"": ""last.name"",
+ ""residential.address"": {
+ ""street.name"": ""Something street"",
+ ""zipcode"": ""12345""
+ }
+ }";
+
+ var config = new ConfigurationBuilder().AddJsonStream(StringToStream(json)).Build();
+
+ Assert.Equal("test", config["firstname"]);
+ Assert.Equal("last.name", config["test.last.name"]);
+ Assert.Equal("Something street", config["residential.address:STREET.name"]);
+ Assert.Equal("12345", config["residential.address:zipcode"]);
+ }
+
+ ///
+ /// JsonStream 为 null时,构建配置源异常
+ ///
+ [Fact]
+ public void BuildConfigurationSource_FromNullJsonStream()
+ {
+ Assert.Throws(() =>
+ {
+ _ = new ConfigurationBuilder().AddJsonStream(null).Build();
+ });
+ }
+
+ ///
+ /// 提供者为 JsonStreamProvider (流提供者)时,配置源重新加载异常
+ ///
+ [Fact]
+ public void ConfigurationSource_ReloadThrows_FromStreamProvider()
+ {
+ var json = @"
+ {
+ ""firstname"": ""test""
+ }";
+ var config = new ConfigurationBuilder().AddJsonStream(StringToStream(json)).Build();
+
+ Assert.Throws(() => config.Reload());
+ }
+
+ ///
+ /// 从有效的json中加载键值对
+ ///
+ [Fact]
+ public void ConfigurationSource_LoadKeyValuePairs_FromValidJson()
+ {
+ var json = @"
+ {
+ ""firstname"": ""test"",
+ ""test.last.name"": ""last.name"",
+ ""residential.address"": {
+ ""street.name"": ""Something street"",
+ ""zipcode"": ""12345""
+ }
+ }";
+
+ var jsonSource = new JsonStreamConfigurationSource ();
+ jsonSource.Stream = StringToStream(json);
+
+ var jsonPorvider = jsonSource.Build(new ConfigurationBuilder()) as JsonStreamConfigurationProvider;
+ //jsonPorvider?.Load(StringToStream(json));
+
+ var root = new ConfigurationBuilder()
+ .Add(jsonSource)
+ //.Add(jsonPorvider.Source) //或者这种写法
+ .Build();
+
+ //推荐使用下面的AddJsonStream()扩展方法,上面只是展示了一步步构建 ConfigurationRoot 的过程
+ //new ConfigurationBuilder().AddJsonStream(StringToStream(json)).Build();
+
+ Assert.Equal("test", root.GetValue("firstname"));
+ Assert.Equal("last.name", root.GetValue("test.last.name"));
+ Assert.Equal("Something street", root.GetValue("residential.address:STREET.name"));
+ Assert.Equal("12345", root.GetValue("residential.address:zipcode"));
+ }
+
+ ///
+ /// 获取空字符串值
+ ///
+ [Fact]
+ public void ConfigurationSource_LoadEmptyValue_Test()
+ {
+ var json = @"
+ {
+ ""name"": """"
+ }";
+
+ var configurationRoot = new ConfigurationBuilder().AddJsonStream(StringToStream(json)).Build();
+
+ Assert.Equal(string.Empty, configurationRoot.GetValue("name"));
+ }
+
+ ///
+ /// 按文化区域获取配置项
+ ///
+ [Fact]
+ public void ConfigurationSource_LoadWithCulture()
+ {
+ var previousCulture = CultureInfo.CurrentCulture;
+
+ try
+ {
+ CultureInfo.CurrentCulture = new CultureInfo("fr-FR");
+
+ var json = @"
+ {
+ ""number"": 3.14
+ }";
+
+ var configurationRoot = new ConfigurationBuilder().AddJsonStream(StringToStream(json)).Build();
+ Assert.Equal("3.14", configurationRoot.GetValue("number"));
+ }
+ finally
+ {
+ CultureInfo.CurrentCulture = previousCulture;
+ }
+ }
+
+ ///
+ /// Json无根元素时,抛出异常
+ ///
+ [Fact]
+ public void ConfigurationSource_NonObjectRootIsInvalid()
+ {
+ var json = @"""test""";
+
+ var exception = Assert.Throws(() => LoadProvider(json));
+
+ Assert.NotNull(exception.Message);
+ }
+
+ ///
+ /// 支持并忽略 json 中的注释
+ ///
+ [Fact]
+ public void ConfigurationSource_SupportAndIgnoreComments()
+ {
+ var json = @"/* 注释将被忽略后,正常解析 */
+ {/* Comments */
+ ""name"": /* Comments */ ""test"",
+ ""address"": {
+ ""street"": ""Something street"", /* Comments */
+ ""zipcode"": ""12345""
+ }
+ }";
+
+ var configurationRoot = new ConfigurationBuilder().AddJsonStream(StringToStream(json)).Build();
+
+ Assert.Equal("test", configurationRoot.GetValue("name"));
+ Assert.Equal("Something street", configurationRoot.GetValue("address:street"));
+ Assert.Equal("12345", configurationRoot.GetValue("address:zipcode"));
+ }
+
+ ///
+ /// 支持并忽略json尾部逗号
+ ///
+ [Fact]
+ public void ConfigurationSource_SupportAndIgnoreTrailingCommas()
+ {
+ var json = @"
+ {
+ ""firstname"": ""test"",
+ ""test.last.name"": ""last.name"",
+ ""residential.address"": {
+ ""street.name"": ""Something street"",
+ ""zipcode"": ""12345"",
+ },
+ }";
+
+ var configurationRoot = new ConfigurationBuilder().AddJsonStream(StringToStream(json)).Build();
+
+ Assert.Equal("test", configurationRoot.GetValue("firstname"));
+ Assert.Equal("last.name", configurationRoot.GetValue("test.last.name"));
+ Assert.Equal("Something street", configurationRoot.GetValue("residential.address:STREET.name"));
+ Assert.Equal("12345", configurationRoot.GetValue("residential.address:zipcode"));
+ }
+
+ ///
+ /// 在完成分析之前发现意外结束时抛出异常
+ ///
+ [Fact]
+ public void ConfigurationProvider_ThrowExceptionWhenUnexpectedEndFoundBeforeFinishParsing()
+ {
+ var json = @"{
+ ""name"": ""test"",
+ ""address"": {
+ ""street"": ""Something street"",
+ ""zipcode"": ""12345""
+ }
+ /* Missing a right brace here*/";
+ var exception = Assert.Throws(() =>
+ {
+ LoadProvider(json);
+ });
+ Assert.Contains("Could not parse the JSON file.", exception.Message);
+ }
+
+ ///
+ /// 在完成分析之前缺少封闭大括号时抛出异常
+ ///
+ [Fact]
+ public void ConfigurationProvider_ThrowExceptionWhenMissingCurlyBeforeFinishParsing()
+ {
+ var json = @"
+ {
+ ""Data"": {
+ ";
+
+ var exception = Assert.Throws(() => LoadProvider(json));
+ Assert.Contains("Could not parse the JSON file.", exception.Message);
+ }
+
+ ///
+ /// 文件路径为null时,抛出异常
+ ///
+ [Fact]
+ public void ConfigurationSource_ThrowExceptionWhenPassingNullAsFilePath()
+ {
+ Assert.Throws(() => new ConfigurationBuilder().AddJsonFile(path: null));
+ }
+
+ ///
+ /// 文件路径为空字符串时,抛出异常
+ ///
+ [Fact]
+ public void ConfigurationSource_ThrowExceptionWhenPassingEmptyStringAsFilePath()
+ {
+ Assert.Throws(() => new ConfigurationBuilder().AddJsonFile(string.Empty));
+ }
+
+ ///
+ /// json文件不存在时,抛出异常
+ ///
+ [Fact]
+ public void ConfigurationSource_Throws_On_Missing_Configuration_File()
+ {
+ var config = new ConfigurationBuilder().AddJsonFile("NotExistingConfig.json", optional: false);
+ var exception = Assert.Throws(() => config.Build());
+
+ // Assert
+ Assert.StartsWith($"The configuration file 'NotExistingConfig.json' was not found and is not optional. The expected physical path was '", exception.Message);
+ }
+
+ ///
+ /// json文件不存在时,不抛出异常
+ ///
+ [Fact]
+ public void ConfigurationSource_Does_Not_Throw_On_Optional_Configuration()
+ {
+ var config = new ConfigurationBuilder().AddJsonFile("NotExistingConfig.json", optional: true).Build();
+ }
+
+ ///
+ /// json内容为空白时,抛出异常
+ ///
+ [Fact]
+ public void ConfigurationSource_ThrowFormatExceptionWhenFileIsEmpty()
+ {
+ var exception = Assert.Throws(() => LoadProvider(@""));
+ Assert.Contains("Could not parse the JSON file.", exception.Message);
+ }
+
+ [Fact]
+ public void UseJsonConfigurationSource_Test()
+ {
+ var root = new ConfigurationBuilder()
+ .AddEnvironmentVariables()
+ .AddJsonFile("Configs/appsettings.json",true,true)
+ .Build();
+
+ var option = root.Get();
+
+ Assert.NotNull(option);
+ }
+
+ #region 私有辅助方法
+ private Stream StringToStream(string str)
+ {
+ var memStream = new MemoryStream();
+ var textWriter = new StreamWriter(memStream);
+ textWriter.Write(str);
+ textWriter.Flush();
+ memStream.Seek(0, SeekOrigin.Begin);
+
+ return memStream;
+ }
+
+ private JsonConfigurationProvider LoadProvider(string json)
+ {
+ var p = new JsonConfigurationProvider(new JsonConfigurationSource { Optional = true });
+ p.Load(StringToStream(json));
+ return p;
+ }
+ #endregion
+
+ public void Dispose()
+ {
+
+ }
+ }
+}