Explorar o código

feat(config): SharedConfigStore 共享配置片段读写 + operate 单测工程(TDD 4 绿)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
huangjie hai 2 días
pai
achega
ca30cb2

+ 60 - 0
ivf_tl_operate_2.0/ivf_tl_Operate.Tests/SharedConfigStoreTests.cs

@@ -0,0 +1,60 @@
+using System.IO;
+using ivf_tl_Operate.Helpers;
+using Xunit;
+
+namespace ivf_tl_Operate.Tests
+{
+    public class SharedConfigStoreTests
+    {
+        private static string TempFile() =>
+            Path.Combine(Path.GetTempPath(), "tl-shared-test-" + Path.GetRandomFileName() + ".config");
+
+        [Fact]
+        public void Read_missing_file_returns_null()
+        {
+            var path = TempFile();
+            Assert.Null(SharedConfigStore.Read(path, "urlIp"));
+        }
+
+        [Fact]
+        public void Write_then_Read_roundtrips()
+        {
+            var path = TempFile();
+            try
+            {
+                SharedConfigStore.Write(path, "urlIp", "http://10.0.0.5");
+                SharedConfigStore.Write(path, "urlPort", "10010");
+                Assert.Equal("http://10.0.0.5", SharedConfigStore.Read(path, "urlIp"));
+                Assert.Equal("10010", SharedConfigStore.Read(path, "urlPort"));
+            }
+            finally { File.Delete(path); }
+        }
+
+        [Fact]
+        public void Write_existing_key_updates_value()
+        {
+            var path = TempFile();
+            try
+            {
+                SharedConfigStore.Write(path, "mqttIp", "192.168.0.108");
+                SharedConfigStore.Write(path, "mqttIp", "192.168.0.200");
+                Assert.Equal("192.168.0.200", SharedConfigStore.Read(path, "mqttIp"));
+            }
+            finally { File.Delete(path); }
+        }
+
+        [Fact]
+        public void Written_file_is_appSettings_fragment()
+        {
+            var path = TempFile();
+            try
+            {
+                SharedConfigStore.Write(path, "kfkaIP", "192.168.0.108");
+                var text = File.ReadAllText(path);
+                Assert.Contains("<appSettings>", text);
+                Assert.Contains("key=\"kfkaIP\"", text);
+            }
+            finally { File.Delete(path); }
+        }
+    }
+}

+ 16 - 0
ivf_tl_operate_2.0/ivf_tl_Operate.Tests/ivf_tl_Operate.Tests.csproj

@@ -0,0 +1,16 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>net6.0-windows</TargetFramework>
+    <Nullable>disable</Nullable>
+    <IsPackable>false</IsPackable>
+    <UseWPF>true</UseWPF>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
+    <PackageReference Include="xunit" Version="2.6.2" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.5.4" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\ivf_tl_Operate\ivf_tl_Operate.csproj" />
+  </ItemGroup>
+</Project>

+ 10 - 0
ivf_tl_operate_2.0/ivf_tl_Operate.sln

@@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ivf_tl_Entity", "ivf_tl_Ent
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ivf_tl_Services", "ivf_tl_Services\ivf_tl_Services.csproj", "{CFD5576A-28B1-4428-AB32-F2257D7C5063}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ivf_tl_Operate.Tests", "ivf_tl_Operate.Tests\ivf_tl_Operate.Tests.csproj", "{1E443584-01C6-4960-926C-DA70FBB38B1E}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -41,6 +43,14 @@ Global
 		{CFD5576A-28B1-4428-AB32-F2257D7C5063}.Release|Any CPU.Build.0 = Release|Any CPU
 		{CFD5576A-28B1-4428-AB32-F2257D7C5063}.Release|x64.ActiveCfg = Release|Any CPU
 		{CFD5576A-28B1-4428-AB32-F2257D7C5063}.Release|x64.Build.0 = Release|Any CPU
+		{1E443584-01C6-4960-926C-DA70FBB38B1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{1E443584-01C6-4960-926C-DA70FBB38B1E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{1E443584-01C6-4960-926C-DA70FBB38B1E}.Debug|x64.ActiveCfg = Debug|Any CPU
+		{1E443584-01C6-4960-926C-DA70FBB38B1E}.Debug|x64.Build.0 = Debug|Any CPU
+		{1E443584-01C6-4960-926C-DA70FBB38B1E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{1E443584-01C6-4960-926C-DA70FBB38B1E}.Release|Any CPU.Build.0 = Release|Any CPU
+		{1E443584-01C6-4960-926C-DA70FBB38B1E}.Release|x64.ActiveCfg = Release|Any CPU
+		{1E443584-01C6-4960-926C-DA70FBB38B1E}.Release|x64.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 71 - 0
ivf_tl_operate_2.0/ivf_tl_Operate/Helpers/SharedConfigStore.cs

@@ -0,0 +1,71 @@
+using System.IO;
+using System.Xml.Linq;
+
+namespace ivf_tl_Operate.Helpers
+{
+    /// <summary>
+    /// 配置收敛:operate↔control 共享连接键的唯一数据源文件(tl-shared.config)读写。
+    /// 文件格式 = 独立 appSettings 片段(&lt;appSettings&gt;&lt;add key.. value../&gt;&lt;/appSettings&gt;),
+    /// 正是 &lt;appSettings file="…"&gt; 期望的外部文件格式 → 两进程经 file= 只读合并即读到。
+    /// 用 XDocument 直写,绕开 ConfigurationManager 写回 file= 的不确定行为。
+    /// </summary>
+    public static class SharedConfigStore
+    {
+        /// <summary>读片段文件里某键值;文件不存在或键缺失返回 null。</summary>
+        public static string Read(string path, string key)
+        {
+            if (!File.Exists(path)) return null;
+            try
+            {
+                var doc = XDocument.Load(path);
+                foreach (var add in doc.Descendants("add"))
+                {
+                    if ((string)add.Attribute("key") == key)
+                        return (string)add.Attribute("value");
+                }
+                return null;
+            }
+            catch
+            {
+                return null;
+            }
+        }
+
+        /// <summary>写某键值(存在则更新,不存在则新增);文件不存在则创建片段骨架。</summary>
+        public static void Write(string path, string key, string value)
+        {
+            XDocument doc;
+            if (File.Exists(path))
+            {
+                try { doc = XDocument.Load(path); }
+                catch { doc = NewDoc(); }
+            }
+            else
+            {
+                doc = NewDoc();
+            }
+
+            var root = doc.Root ?? new XElement("appSettings");
+            if (doc.Root == null) doc.Add(root);
+
+            XElement target = null;
+            foreach (var add in root.Elements("add"))
+            {
+                if ((string)add.Attribute("key") == key) { target = add; break; }
+            }
+            if (target == null)
+            {
+                target = new XElement("add", new XAttribute("key", key), new XAttribute("value", value ?? ""));
+                root.Add(target);
+            }
+            else
+            {
+                target.SetAttributeValue("value", value ?? "");
+            }
+            doc.Save(path);
+        }
+
+        private static XDocument NewDoc() =>
+            new XDocument(new XDeclaration("1.0", "utf-8", null), new XElement("appSettings"));
+    }
+}