From 813ef34469a7429d1929db2facf7defa4b2fc78e Mon Sep 17 00:00:00 2001
From: bicijinlian <bicijinlian@163.com>
Date: Sun, 18 Dec 2022 23:42:37 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=EF=BC=9A=E5=A4=9A=E6=A0=B7?=
 =?UTF-8?q?=E6=80=A7=E9=85=8D=E7=BD=AE=E6=BA=90?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../ChainedConfigurationSourceTest.cs         |  32 ++
 .../CommandLineConfigurationSourceTest.cs     | 302 ++++++++++++++++++
 ...ronmentVariablesConfigurationSourceTest.cs |  76 +++++
 .../MemoryConfigurationSourceTest.cs          |  51 +++
 OptionStudy.Next/OptionStudy.Next.csproj      |   4 -
 OptionStudy.Next/Usings.cs                    |   3 +
 6 files changed, 464 insertions(+), 4 deletions(-)
 create mode 100644 OptionStudy.Next/5多样性配置源/ChainedConfigurationSourceTest.cs
 create mode 100644 OptionStudy.Next/5多样性配置源/CommandLineConfigurationSourceTest.cs
 create mode 100644 OptionStudy.Next/5多样性配置源/EnvironmentVariablesConfigurationSourceTest.cs
 create mode 100644 OptionStudy.Next/5多样性配置源/MemoryConfigurationSourceTest.cs

diff --git a/OptionStudy.Next/5多样性配置源/ChainedConfigurationSourceTest.cs b/OptionStudy.Next/5多样性配置源/ChainedConfigurationSourceTest.cs
new file mode 100644
index 0000000..275d046
--- /dev/null
+++ b/OptionStudy.Next/5多样性配置源/ChainedConfigurationSourceTest.cs
@@ -0,0 +1,32 @@
+
+namespace OptionStudy.Next
+{
+    /// <summary>
+    /// Chained 配置源
+    /// </summary>
+    public class ChainedConfigurationSourceTest: IDisposable
+    {
+        private readonly ITestOutputHelper testOutput;
+
+        public ChainedConfigurationSourceTest(ITestOutputHelper testOutputHelper)
+        {
+            this.testOutput = testOutputHelper;
+        }
+
+        /// <summary>
+        ///  使用 Chained配置源
+        /// </summary>
+        [Fact]
+        public void Use_Test()
+        {
+            
+
+            testOutput.WriteLine("使用 Chained 配置源!");
+        }
+
+        public void Dispose()
+        {
+
+        }
+    }
+}
diff --git a/OptionStudy.Next/5多样性配置源/CommandLineConfigurationSourceTest.cs b/OptionStudy.Next/5多样性配置源/CommandLineConfigurationSourceTest.cs
new file mode 100644
index 0000000..c109c40
--- /dev/null
+++ b/OptionStudy.Next/5多样性配置源/CommandLineConfigurationSourceTest.cs
@@ -0,0 +1,302 @@
+
+using OptionStudy.UnitApp.Next;
+
+namespace OptionStudy.Next
+{
+    /// <summary>
+    /// 命令行 配置源
+    /// 命令行参数分为
+    /// 单参数:用=将参数名和参数值按以下形式指定 
+    ///     {name}={value}
+    ///     {prefix}{name}={value} prefix目前支持三种 “/”“--”“-”,其中“-”需配合 命令行参数映射一起使用
+    /// 双参数:使用 空格 将参数名和参数值隔开的形式,形如 {name}={value} 或 {prefix}{name}={value}
+    /// </summary>
+    public class CommandLineConfigurationSourceTest : IDisposable
+    {
+        private readonly ITestOutputHelper testOutput;
+
+        public CommandLineConfigurationSourceTest(ITestOutputHelper testOutputHelper)
+        {
+            this.testOutput = testOutputHelper;
+        }
+
+        /// <summary>
+        /// 创建 CommandLineConfigurationProvider
+        /// </summary>
+        [Fact]
+        public void Create_CommandLineConfigurationProvider_Test()
+        {
+            var args = new string[]
+            {
+                "AppName=argAppName",
+                "/AppVersion=1.1.1.1"
+            };
+            var cmdLineProvier = new CommandLineConfigurationProvider(args);
+            cmdLineProvier.Load();
+
+            var childKeys = cmdLineProvier.GetChildKeys(new string[0],null);
+            cmdLineProvier.TryGet("AppName", out string? appName);
+            cmdLineProvier.TryGet("AppVersion", out string? appVersion);
+
+            Assert.Equal(2,childKeys.Count());
+            Assert.Equal("argAppName", appName);
+            Assert.Equal("1.1.1.1", appVersion);
+        }
+
+        /// <summary>
+        /// 使用 CommandLineConfiguration
+        /// </summary>
+        [Fact]
+        public void Build_CommandLineConfiguration_Test()
+        {
+            var args = new string[]
+            {
+                "AppName=argAppName",
+                "/AppVersion=1.1.1.1",
+                "--EMail:ReceiveAddress=arg@163.com",
+                "--EMail:Recipient=arg",
+            };
+
+            var root = new ConfigurationBuilder().AddCommandLine(args).Build();
+
+            var option = root.Get<AppOption>();
+
+            Assert.NotNull(option);
+            Assert.Equal("argAppName",option.AppName);
+            Assert.Equal(new Version(1,1,1,1), option.AppVersion);
+            Assert.Equal("arg@163.com", option.EMail?.ReceiveAddress);
+            Assert.Equal("arg", option.EMail?.Recipient);
+        }
+
+        /// <summary>
+        /// 忽略中间不规范的值
+        /// </summary>
+        [Fact]
+        public void IgnoreValuesInMiddle_Test()
+        {
+            var args = new string[]
+            {
+                "Key1=Value1",
+                "--Key2=Value2",
+                "/Key3=Value3",
+                "Bogus1",           //忽略,只有一个名没有值
+                "--Key4", "Value4",
+                "Bogus2",           //忽略,只有一个名没有值
+                "/Key5", "Value5",
+                "Bogus3"            //忽略,只有一个名没有值
+            };
+
+            var root = new ConfigurationBuilder().AddCommandLine(args).Build();
+
+            Assert.Equal("Value1", root.GetValue<string>("Key1"));
+            Assert.Equal("Value2", root.GetValue<string>("Key2"));
+            Assert.Equal("Value3", root.GetValue<string>("Key3"));
+            Assert.Equal("Value4", root.GetValue<string>("Key4"));
+            Assert.Equal("Value5", root.GetValue<string>("Key5"));
+            Assert.Equal(5, root.GetChildren().Count());
+        }
+
+        /// <summary>
+        /// 使用参数映射:-前辍必需配合参数映射一起使用
+        /// </summary>
+        [Fact]
+        public void Use_SwitchMappings_Test()
+        {
+            var args = new string[]
+            {
+                "-K1=Value1",
+                "--Key2=Value2",
+                "/Key3=Value3",
+                "--Key4", "Value4",
+                "/Key5", "Value5",
+                "/Key6=Value6"
+            };
+            var switchMappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+            {
+                { "-K1", "LongKey1" },
+                { "--Key2", "SuperLongKey2" },
+                { "--Key6", "SuchALongKey6"}
+            };
+
+            var root = new ConfigurationBuilder().AddCommandLine(args,switchMappings).Build();
+
+            Assert.Equal("Value1", root.GetValue<string>("LongKey1"));
+            Assert.Equal("Value2", root.GetValue<string>("SuperLongKey2"));
+            Assert.Equal("Value3", root.GetValue<string>("Key3"));
+            Assert.Equal("Value4", root.GetValue<string>("Key4"));
+            Assert.Equal("Value5", root.GetValue<string>("Key5"));
+            Assert.Equal("Value6", root.GetValue<string>("SuchALongKey6"));
+        }
+
+        /// <summary>
+        /// 参数映射错误时,抛出异常
+        /// </summary>
+        [Fact]
+        public void ThrowException_SwitchMappings_HasError_Test()
+        {
+            // Arrange
+            var args = new string[]
+            {
+                "-K1=Value1",       //映射被错过
+                "--Key2=Value2",
+                "/Key3=Value3",
+                "--Key4", "Value4",
+                "/Key5", "Value5"
+            };
+            var switchMappings = new Dictionary<string, string>(StringComparer.Ordinal)
+            {
+                { "--KEY1", "LongKey1" },
+                { "--key1", "SuperLongKey1" },
+                { "-Key2", "LongKey2" },
+                { "-KEY2", "LongKey2"}
+            };
+
+            // Find out the duplicate expected be be reported
+            var expectedDup = string.Empty;
+            var set = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
+            foreach (var mapping in switchMappings)
+            {
+                if (set.Contains(mapping.Key))
+                {
+                    expectedDup = mapping.Key;
+                    break;
+                }
+
+                set.Add(mapping.Key);
+            }
+
+            var expectedMsg = new ArgumentException("switchMappings").Message;
+
+            // Act
+            var exception = Assert.Throws<ArgumentException>
+            (
+                () => new CommandLineConfigurationProvider(args, switchMappings)
+            );
+
+            // Assert
+            Assert.Contains(expectedMsg, exception.Message);
+        }
+
+        /// <summary>
+        /// 包含无效键名时,抛出异常
+        /// </summary>
+        [Fact]
+        public void ThrowException_SwitchMappings_ContainInvalidKey_Test()
+        {
+            var args = new string[]
+            {
+                "-K1=Value1",
+                "--Key2=Value2",
+                "/Key3=Value3",
+                "--Key4", "Value4",
+                "/Key5", "Value5"
+            };
+            var switchMappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+            {
+                { "-K1", "LongKey1" },
+                { "--Key2", "SuperLongKey2" },
+                { "/Key3", "AnotherSuperLongKey3" }
+            };
+
+            Assert.Throws<ArgumentException>
+            (
+                () => new CommandLineConfigurationProvider(args, switchMappings)
+            );
+        }
+
+        /// <summary>
+        ///  命令行参数为null,抛出异常
+        /// </summary>
+        [Fact]
+        public void ThrowException_NullIsPassedToConstructorAsArgs_Test()
+        {
+            string[] args = null;
+            var expectedMsg = new ArgumentNullException("args").Message;
+
+            var exception = Assert.Throws<ArgumentNullException>(() => new CommandLineConfigurationProvider(args));
+
+            Assert.Equal(expectedMsg, exception.Message);
+        }
+
+        /// <summary>
+        ///  键重复时覆盖值
+        /// </summary>
+        [Fact]
+        public void OverrideValueWhenKeyIsDuplicated_Test()
+        {
+            var args = new string[]
+            {
+                "/Key1=Value1",
+                "--Key1=Value2"
+            };
+            var cmdLineConfig = new CommandLineConfigurationProvider(args);
+
+            cmdLineConfig.Load();
+
+            cmdLineConfig.TryGet("Key1", out string? value1);
+
+            Assert.Equal("Value2", value1);
+        }
+
+        /// <summary>
+        /// 忽略缺少Key的值
+        /// </summary>
+        [Fact]
+        public void IgnoreWhenValueForAKeyIsMissing_Test()
+        {
+            var args = new string[]
+            {
+                "--Key1", "Value1",
+                "/Key2" /* The value for Key2 is missing here */
+            };
+
+            var cmdLineConfig = new CommandLineConfigurationProvider(args);
+            cmdLineConfig.Load();
+
+            cmdLineConfig.TryGet("Key1", out string? value1);
+
+            Assert.Single(cmdLineConfig.GetChildKeys(new string[0], null));
+            Assert.Equal("Value1", value1);
+        }
+
+        /// <summary>
+        /// 忽略无法识别的参数
+        /// </summary>
+        [Fact]
+        public void IgnoreWhenAnArgumentCannotBeRecognized_Test()
+        {
+            var args = new string[]
+            {
+                "ArgWithoutPrefixAndEqualSign"
+            };
+            var cmdLineConfig = new CommandLineConfigurationProvider(args);
+            cmdLineConfig.Load();
+            Assert.Empty(cmdLineConfig.GetChildKeys(new string[0], null));
+        }
+
+        /// <summary>
+        ///  忽略未被映射的参数
+        ///  如果没有映射,则异常;有映射,但key不在映射中时,则忽略
+        /// </summary>
+        [Fact]
+        public void IgnoreWhenShortSwitchNotDefined_Test()
+        {
+            var args = new string[]
+            {
+                "-Key1", "Value1",
+            };
+            var switchMappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
+            {
+                { "-Key2", "LongKey2" }
+            };
+            var cmdLineConfig = new CommandLineConfigurationProvider(args, switchMappings);
+            cmdLineConfig.Load();
+            Assert.Empty(cmdLineConfig.GetChildKeys(new string[0], ""));
+        }
+
+        public void Dispose()
+        {
+
+        }
+    }
+}
diff --git a/OptionStudy.Next/5多样性配置源/EnvironmentVariablesConfigurationSourceTest.cs b/OptionStudy.Next/5多样性配置源/EnvironmentVariablesConfigurationSourceTest.cs
new file mode 100644
index 0000000..bded6c1
--- /dev/null
+++ b/OptionStudy.Next/5多样性配置源/EnvironmentVariablesConfigurationSourceTest.cs
@@ -0,0 +1,76 @@
+
+
+
+using OptionStudy.UnitApp.Next;
+
+namespace OptionStudy.Next
+{
+    /// <summary>
+    /// 环境变量 配置源
+    /// 环境变量分类:系统环境变量、用户环境变量、进程环境变量
+    /// 环境变量设置:系统图形工具、编程、启动文件 launchSettings.json(VS环境比较方便)
+    /// </summary>
+    public class EnvironmentVariablesConfigurationSourceTest : IDisposable
+    {
+        private readonly ITestOutputHelper testOutput;
+
+        public EnvironmentVariablesConfigurationSourceTest(ITestOutputHelper testOutputHelper)
+        {
+            this.testOutput = testOutputHelper;
+        }
+
+        /// <summary>
+        ///  设置的进程环境变量,在一次测试运行中会保留
+        ///  因为一次测试运行中的多个测试方法,是在一个进程中运行的。
+        /// </summary>
+        [Theory]
+        [InlineData("xUnit-First")]
+        [InlineData("xUnit-Second")]
+        public void ProcessEnvironmentVariables_Set_Test(string value)
+        {
+            var envName = "xUnit:AppName";
+            var env = Environment.GetEnvironmentVariable(envName);
+            //一次运行多个测试用例时:
+            //第一个测试用例运行时,是null值,但第二个用例运行时就有值,其值为第一个用例设置的值
+            if (env == null)
+            {
+                testOutput.WriteLine($"当前用例参数为 {value}, 环境变量 {nameof(envName)} 的值为 null ");
+            }
+            else
+            {
+                testOutput.WriteLine($"当前用例参数为 {value}, 环境变量 {nameof(envName)} 的值为 {env} ");
+            }
+
+            //设置环境变量,看看在另一个测试工里会不会保留设置的环境变量值
+            Environment.SetEnvironmentVariable(envName, value, EnvironmentVariableTarget.Process);
+        }
+
+        /// <summary>
+        ///  设置进程环境变量
+        /// </summary>
+        [Fact]
+        public void Load_Set_ProcessEnvironmentVariables_Test()
+        {
+            // 设置进程环境变量
+            Environment.SetEnvironmentVariable("xUnit:AppName", "xunitAppName", EnvironmentVariableTarget.Process);
+            Environment.SetEnvironmentVariable("xUnit:AppVersion", "2.2.2.2", EnvironmentVariableTarget.Process);
+            Environment.SetEnvironmentVariable("xUnit:EMail:ReceiveAddress", "xunit@163.com", EnvironmentVariableTarget.Process);
+            Environment.SetEnvironmentVariable("xUnit:Email:Recipient", "xunit", EnvironmentVariableTarget.Process);
+
+            // 获取配置对象
+            var root =  new ConfigurationBuilder().AddEnvironmentVariables("xUnit:").Build();
+            var option = root.Get<AppOption>();
+
+            Assert.NotNull(option);
+            Assert.Equal("xunitAppName",option.AppName);
+            Assert.Equal(new Version(2,2,2,2),option.AppVersion);
+            Assert.Equal("xunit@163.com",option.EMail?.ReceiveAddress);
+            Assert.Equal("xunit",option.EMail?.Recipient);
+        }
+
+        public void Dispose()
+        {
+
+        }
+    }
+}
diff --git a/OptionStudy.Next/5多样性配置源/MemoryConfigurationSourceTest.cs b/OptionStudy.Next/5多样性配置源/MemoryConfigurationSourceTest.cs
new file mode 100644
index 0000000..91f6095
--- /dev/null
+++ b/OptionStudy.Next/5多样性配置源/MemoryConfigurationSourceTest.cs
@@ -0,0 +1,51 @@
+
+using OptionStudy.UnitApp.Next;
+
+namespace OptionStudy.Next
+{
+    /// <summary>
+    /// 内存 配置源
+    /// </summary>
+    public class MemoryConfigurationSourceTest : IDisposable
+    {
+        private readonly ITestOutputHelper testOutput;
+
+        public MemoryConfigurationSourceTest(ITestOutputHelper testOutputHelper)
+        {
+            this.testOutput = testOutputHelper;
+        }
+
+        /// <summary>
+        ///  使用内存配置源
+        /// </summary>
+        [Fact]
+        public void Use_Test()
+        {
+            IDictionary<string, string?> memoryData = new Dictionary<string, string?>()
+            {
+                ["AppName"] = "MemoryAppName",
+                ["AppVersion"] = "0.0.0.1",
+                ["EMail:ReceiveAddress"] = "memory@163.com",
+                ["EMail:Recipient"] = "memory",
+            };
+
+            var root = new ConfigurationBuilder().AddInMemoryCollection(memoryData).Build();
+            var configOption = root.Get<AppOption>();
+
+            //MemoryConfigurationProvider 可以执行添加、设置等操作
+            var provider = root.Providers.First() as MemoryConfigurationProvider;
+            provider?.Add("MyAdd", "MyValue");
+            provider?.Set("AppVersion", "2.0.0.0");
+
+            Assert.NotNull(configOption);
+            Assert.Equal("memory", configOption.EMail?.Recipient);
+
+            testOutput.WriteLine("使用 内存配置源!");
+        }
+
+        public void Dispose()
+        {
+
+        }
+    }
+}
diff --git a/OptionStudy.Next/OptionStudy.Next.csproj b/OptionStudy.Next/OptionStudy.Next.csproj
index 9fa8b26..e62b5b5 100644
--- a/OptionStudy.Next/OptionStudy.Next.csproj
+++ b/OptionStudy.Next/OptionStudy.Next.csproj
@@ -59,8 +59,4 @@
     </None>
   </ItemGroup>
 
-  <ItemGroup>
-    <Folder Include="5多样性配置源\" />
-  </ItemGroup>
-
 </Project>
diff --git a/OptionStudy.Next/Usings.cs b/OptionStudy.Next/Usings.cs
index 88a592b..ce36fb1 100644
--- a/OptionStudy.Next/Usings.cs
+++ b/OptionStudy.Next/Usings.cs
@@ -2,7 +2,10 @@ global using System;
 global using System.Linq;
 global using System.Text;
 global using System.Threading.Tasks;
+global using System.Collections;
 global using System.Collections.Generic;
+global using System.Collections.Concurrent;
+global using System.Collections.Specialized;
 
 global using Microsoft.Extensions.Configuration;
 global using Microsoft.Extensions.Configuration.Memory;