CfgBuilderAdvancedTests.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. using Apq.Cfg.Changes;
  2. namespace Apq.Cfg.Tests;
  3. /// <summary>
  4. /// CfgBuilder 高级功能测试(编码阈值、多层级写入等)
  5. /// </summary>
  6. public class CfgBuilderAdvancedTests : IDisposable
  7. {
  8. private readonly string _testDir;
  9. public CfgBuilderAdvancedTests()
  10. {
  11. _testDir = Path.Combine(Path.GetTempPath(), $"ApqCfgTests_{Guid.NewGuid():N}");
  12. Directory.CreateDirectory(_testDir);
  13. }
  14. public void Dispose()
  15. {
  16. if (Directory.Exists(_testDir))
  17. {
  18. Directory.Delete(_testDir, true);
  19. }
  20. }
  21. [Fact]
  22. public void WithEncodingConfidenceThreshold_SetsThreshold()
  23. {
  24. // Arrange
  25. var jsonPath = Path.Combine(_testDir, "config.json");
  26. File.WriteAllText(jsonPath, """{"Key": "Value"}""");
  27. // Act - 设置不同的阈值不应抛出异常
  28. using var cfg = new CfgBuilder()
  29. .WithEncodingConfidenceThreshold(0.8f)
  30. .AddJson(jsonPath, level: 0, writeable: false)
  31. .Build();
  32. // Assert
  33. Assert.Equal("Value", cfg.Get("Key"));
  34. }
  35. [Fact]
  36. public void WithEncodingConfidenceThreshold_ClampsValue()
  37. {
  38. // Arrange
  39. var jsonPath = Path.Combine(_testDir, "config.json");
  40. File.WriteAllText(jsonPath, """{"Key": "Value"}""");
  41. // Act - 超出范围的值应被钳制
  42. using var cfg1 = new CfgBuilder()
  43. .WithEncodingConfidenceThreshold(1.5f) // 超过 1.0
  44. .AddJson(jsonPath, level: 0, writeable: false)
  45. .Build();
  46. using var cfg2 = new CfgBuilder()
  47. .WithEncodingConfidenceThreshold(-0.5f) // 小于 0.0
  48. .AddJson(jsonPath, level: 0, writeable: false)
  49. .Build();
  50. // Assert - 不应抛出异常
  51. Assert.Equal("Value", cfg1.Get("Key"));
  52. Assert.Equal("Value", cfg2.Get("Key"));
  53. }
  54. [Fact]
  55. public async Task Set_WithTargetLevel_WritesToSpecificLevel()
  56. {
  57. // Arrange
  58. var basePath = Path.Combine(_testDir, "base.json");
  59. var overridePath = Path.Combine(_testDir, "override.json");
  60. File.WriteAllText(basePath, """{"Setting": "BaseValue"}""");
  61. File.WriteAllText(overridePath, """{"Setting": "OverrideValue"}""");
  62. using var cfg = new CfgBuilder()
  63. .AddJson(basePath, level: 0, writeable: true, isPrimaryWriter: true)
  64. .AddJson(overridePath, level: 1, writeable: true, isPrimaryWriter: true)
  65. .Build();
  66. // Act - 写入到 level 0
  67. cfg.Set("NewKey", "Level0Value", targetLevel: 0);
  68. await cfg.SaveAsync(targetLevel: 0);
  69. // Assert - 验证写入到了正确的文件
  70. using var cfgBase = new CfgBuilder()
  71. .AddJson(basePath, level: 0, writeable: false)
  72. .Build();
  73. Assert.Equal("Level0Value", cfgBase.Get("NewKey"));
  74. }
  75. [Fact]
  76. public async Task Remove_WithTargetLevel_RemovesFromSpecificLevel()
  77. {
  78. // Arrange
  79. var basePath = Path.Combine(_testDir, "base.json");
  80. var overridePath = Path.Combine(_testDir, "override.json");
  81. File.WriteAllText(basePath, """{"BaseKey": "BaseValue", "SharedKey": "BaseShared"}""");
  82. File.WriteAllText(overridePath, """{"OverrideKey": "OverrideValue", "SharedKey": "OverrideShared"}""");
  83. using var cfg = new CfgBuilder()
  84. .AddJson(basePath, level: 0, writeable: true, isPrimaryWriter: true)
  85. .AddJson(overridePath, level: 1, writeable: true, isPrimaryWriter: true)
  86. .Build();
  87. // Act - 从 level 1 删除
  88. cfg.Remove("OverrideKey", targetLevel: 1);
  89. await cfg.SaveAsync(targetLevel: 1);
  90. // Assert
  91. using var cfgOverride = new CfgBuilder()
  92. .AddJson(overridePath, level: 0, writeable: false)
  93. .Build();
  94. var removedValue = cfgOverride.Get("OverrideKey");
  95. Assert.True(string.IsNullOrEmpty(removedValue));
  96. }
  97. [Fact]
  98. public async Task SaveAsync_WithTargetLevel_OnlySavesSpecificLevel()
  99. {
  100. // Arrange
  101. var basePath = Path.Combine(_testDir, "base.json");
  102. var overridePath = Path.Combine(_testDir, "override.json");
  103. File.WriteAllText(basePath, """{"Key": "Base"}""");
  104. File.WriteAllText(overridePath, """{"Key": "Override"}""");
  105. using var cfg = new CfgBuilder()
  106. .AddJson(basePath, level: 0, writeable: true, isPrimaryWriter: true)
  107. .AddJson(overridePath, level: 1, writeable: true, isPrimaryWriter: true)
  108. .Build();
  109. // Act - 在两个层级都设置值,但只保存 level 1
  110. cfg.Set("Level0Key", "Value0", targetLevel: 0);
  111. cfg.Set("Level1Key", "Value1", targetLevel: 1);
  112. await cfg.SaveAsync(targetLevel: 1);
  113. // Assert - level 1 应该保存了,level 0 不应该保存
  114. using var cfgOverride = new CfgBuilder()
  115. .AddJson(overridePath, level: 0, writeable: false)
  116. .Build();
  117. Assert.Equal("Value1", cfgOverride.Get("Level1Key"));
  118. using var cfgBase = new CfgBuilder()
  119. .AddJson(basePath, level: 0, writeable: false)
  120. .Build();
  121. Assert.Null(cfgBase.Get("Level0Key")); // 未保存
  122. }
  123. [Fact]
  124. public void Set_WithoutTargetLevel_WritesToHighestLevel()
  125. {
  126. // Arrange
  127. var basePath = Path.Combine(_testDir, "base.json");
  128. var overridePath = Path.Combine(_testDir, "override.json");
  129. File.WriteAllText(basePath, """{"Key": "Base"}""");
  130. File.WriteAllText(overridePath, """{"Key": "Override"}""");
  131. using var cfg = new CfgBuilder()
  132. .AddJson(basePath, level: 0, writeable: true, isPrimaryWriter: true)
  133. .AddJson(overridePath, level: 1, writeable: true, isPrimaryWriter: true)
  134. .Build();
  135. // Act - 不指定 targetLevel,应该写入最高层级
  136. cfg.Set("NewKey", "NewValue");
  137. // Assert - 通过 Get 验证值已设置(在 Pending 中)
  138. Assert.Equal("NewValue", cfg.Get("NewKey"));
  139. }
  140. [Fact]
  141. public async Task DisposeAsync_ReleasesResources()
  142. {
  143. // Arrange
  144. var jsonPath = Path.Combine(_testDir, "config.json");
  145. File.WriteAllText(jsonPath, """{"Key": "Value"}""");
  146. var cfg = new CfgBuilder()
  147. .AddJson(jsonPath, level: 0, writeable: false, reloadOnChange: true)
  148. .Build();
  149. // Act - 使用 DisposeAsync 释放资源
  150. await cfg.DisposeAsync();
  151. // Assert - 再次调用 DisposeAsync 不应抛出异常(幂等性)
  152. await cfg.DisposeAsync();
  153. }
  154. [Fact]
  155. public async Task DisposeAsync_AfterDispose_IsIdempotent()
  156. {
  157. // Arrange
  158. var jsonPath = Path.Combine(_testDir, "config.json");
  159. File.WriteAllText(jsonPath, """{"Key": "Value"}""");
  160. var cfg = new CfgBuilder()
  161. .AddJson(jsonPath, level: 0, writeable: false)
  162. .Build();
  163. // Act - 先同步释放,再异步释放
  164. cfg.Dispose();
  165. await cfg.DisposeAsync();
  166. // Assert - 不应抛出异常
  167. }
  168. [Fact]
  169. public void Set_WithoutWritableSource_ThrowsException()
  170. {
  171. // Arrange
  172. var jsonPath = Path.Combine(_testDir, "config.json");
  173. File.WriteAllText(jsonPath, """{"Key": "Value"}""");
  174. using var cfg = new CfgBuilder()
  175. .AddJson(jsonPath, level: 0, writeable: false) // 不可写
  176. .Build();
  177. // Act & Assert
  178. var ex = Assert.Throws<InvalidOperationException>(() => cfg.Set("NewKey", "NewValue"));
  179. Assert.Contains("没有可写的配置源", ex.Message);
  180. }
  181. [Fact]
  182. public void Remove_WithoutWritableSource_ThrowsException()
  183. {
  184. // Arrange
  185. var jsonPath = Path.Combine(_testDir, "config.json");
  186. File.WriteAllText(jsonPath, """{"Key": "Value"}""");
  187. using var cfg = new CfgBuilder()
  188. .AddJson(jsonPath, level: 0, writeable: false) // 不可写
  189. .Build();
  190. // Act & Assert
  191. var ex = Assert.Throws<InvalidOperationException>(() => cfg.Remove("Key"));
  192. Assert.Contains("没有可写的配置源", ex.Message);
  193. }
  194. [Fact]
  195. public void Set_WithInvalidTargetLevel_ThrowsException()
  196. {
  197. // Arrange
  198. var jsonPath = Path.Combine(_testDir, "config.json");
  199. File.WriteAllText(jsonPath, """{"Key": "Value"}""");
  200. using var cfg = new CfgBuilder()
  201. .AddJson(jsonPath, level: 0, writeable: true, isPrimaryWriter: true)
  202. .Build();
  203. // Act & Assert - 指定不存在的层级
  204. var ex = Assert.Throws<InvalidOperationException>(() => cfg.Set("NewKey", "NewValue", targetLevel: 999));
  205. Assert.Contains("没有可写的配置源", ex.Message);
  206. }
  207. [Fact]
  208. public async Task SaveAsync_WithEmptyPending_DoesNothing()
  209. {
  210. // Arrange
  211. var jsonPath = Path.Combine(_testDir, "config.json");
  212. File.WriteAllText(jsonPath, """{"Key": "Value"}""");
  213. var originalContent = File.ReadAllText(jsonPath);
  214. using var cfg = new CfgBuilder()
  215. .AddJson(jsonPath, level: 0, writeable: true, isPrimaryWriter: true)
  216. .Build();
  217. // Act - 不设置任何值,直接保存
  218. await cfg.SaveAsync();
  219. // Assert - 文件内容不应改变
  220. var newContent = File.ReadAllText(jsonPath);
  221. Assert.Equal(originalContent, newContent);
  222. }
  223. [Fact]
  224. public async Task SaveAsync_WithInvalidTargetLevel_DoesNothing()
  225. {
  226. // Arrange
  227. var jsonPath = Path.Combine(_testDir, "config.json");
  228. File.WriteAllText(jsonPath, """{"Key": "Value"}""");
  229. using var cfg = new CfgBuilder()
  230. .AddJson(jsonPath, level: 0, writeable: true, isPrimaryWriter: true)
  231. .Build();
  232. // Act - 指定不存在的层级,不应抛出异常
  233. await cfg.SaveAsync(targetLevel: 999);
  234. // Assert - 不应抛出异常,静默返回
  235. }
  236. [Fact]
  237. public void AddSource_CustomSource_Works()
  238. {
  239. // Arrange
  240. var jsonPath = Path.Combine(_testDir, "config.json");
  241. var jsonPath2 = Path.Combine(_testDir, "config2.json");
  242. File.WriteAllText(jsonPath, """{"Key": "Value1"}""");
  243. File.WriteAllText(jsonPath2, """{"Key2": "Value2"}""");
  244. // Act - 使用 AddJson 添加多个源验证 AddSource 间接工作
  245. using var cfg = new CfgBuilder()
  246. .AddJson(jsonPath, level: 0, writeable: false)
  247. .AddJson(jsonPath2, level: 1, writeable: false)
  248. .Build();
  249. // Assert
  250. Assert.Equal("Value1", cfg.Get("Key"));
  251. Assert.Equal("Value2", cfg.Get("Key2"));
  252. }
  253. }