JsonCfgTests.cs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323
  1. namespace Apq.Cfg.Tests;
  2. /// <summary>
  3. /// JSON 配置源测试
  4. /// </summary>
  5. public class JsonCfgTests : IDisposable
  6. {
  7. private readonly string _testDir;
  8. public JsonCfgTests()
  9. {
  10. _testDir = Path.Combine(Path.GetTempPath(), $"ApqCfgTests_{Guid.NewGuid():N}");
  11. Directory.CreateDirectory(_testDir);
  12. }
  13. public void Dispose()
  14. {
  15. if (Directory.Exists(_testDir))
  16. {
  17. Directory.Delete(_testDir, true);
  18. }
  19. }
  20. [Fact]
  21. public void Get_SimpleValue_ReturnsCorrectValue()
  22. {
  23. // Arrange
  24. var jsonPath = Path.Combine(_testDir, "config.json");
  25. File.WriteAllText(jsonPath, """
  26. {
  27. "AppName": "TestApp",
  28. "Version": "1.0.0"
  29. }
  30. """);
  31. using var cfg = new CfgBuilder()
  32. .AddJson(jsonPath, level: 0, writeable: false)
  33. .Build();
  34. // Act & Assert
  35. Assert.Equal("TestApp", cfg["AppName"]);
  36. Assert.Equal("1.0.0", cfg["Version"]);
  37. }
  38. [Fact]
  39. public void Get_NestedValue_ReturnsCorrectValue()
  40. {
  41. // Arrange
  42. var jsonPath = Path.Combine(_testDir, "config.json");
  43. File.WriteAllText(jsonPath, """
  44. {
  45. "Database": {
  46. "Host": "localhost",
  47. "Port": 5432
  48. }
  49. }
  50. """);
  51. using var cfg = new CfgBuilder()
  52. .AddJson(jsonPath, level: 0, writeable: false)
  53. .Build();
  54. // Act & Assert
  55. Assert.Equal("localhost", cfg["Database:Host"]);
  56. Assert.Equal("5432", cfg["Database:Port"]);
  57. }
  58. [Fact]
  59. public void Get_TypedValue_ReturnsCorrectType()
  60. {
  61. // Arrange
  62. var jsonPath = Path.Combine(_testDir, "config.json");
  63. File.WriteAllText(jsonPath, """
  64. {
  65. "Settings": {
  66. "MaxRetries": 3,
  67. "Enabled": true,
  68. "Timeout": 30.5
  69. }
  70. }
  71. """);
  72. using var cfg = new CfgBuilder()
  73. .AddJson(jsonPath, level: 0, writeable: false)
  74. .Build();
  75. // Act & Assert
  76. Assert.Equal(3, cfg.GetValue<int>("Settings:MaxRetries"));
  77. Assert.True(cfg.GetValue<bool>("Settings:Enabled"));
  78. Assert.Equal(30.5, cfg.GetValue<double>("Settings:Timeout"));
  79. }
  80. [Fact]
  81. public void Exists_ExistingKey_ReturnsTrue()
  82. {
  83. // Arrange
  84. var jsonPath = Path.Combine(_testDir, "config.json");
  85. File.WriteAllText(jsonPath, """{"Key": "Value"}""");
  86. using var cfg = new CfgBuilder()
  87. .AddJson(jsonPath, level: 0, writeable: false)
  88. .Build();
  89. // Act & Assert
  90. Assert.True(cfg.Exists("Key"));
  91. Assert.False(cfg.Exists("NonExistent"));
  92. }
  93. [Fact]
  94. public async Task Set_AndSave_PersistsValue()
  95. {
  96. // Arrange
  97. var jsonPath = Path.Combine(_testDir, "config.json");
  98. File.WriteAllText(jsonPath, """{"Original": "Value"}""");
  99. using var cfg = new CfgBuilder()
  100. .AddJson(jsonPath, level: 0, writeable: true, isPrimaryWriter: true)
  101. .Build();
  102. // Act
  103. cfg.SetValue("NewKey", "NewValue");
  104. await cfg.SaveAsync();
  105. // Assert - 重新读取验证
  106. using var cfg2 = new CfgBuilder()
  107. .AddJson(jsonPath, level: 0, writeable: false)
  108. .Build();
  109. Assert.Equal("NewValue", cfg2["NewKey"]);
  110. Assert.Equal("Value", cfg2["Original"]);
  111. }
  112. [Fact]
  113. public async Task Remove_AndSave_RemovesKey()
  114. {
  115. // Arrange
  116. var jsonPath = Path.Combine(_testDir, "config.json");
  117. File.WriteAllText(jsonPath, """{"ToRemove": "Value", "ToKeep": "Value2"}""");
  118. using var cfg = new CfgBuilder()
  119. .AddJson(jsonPath, level: 0, writeable: true, isPrimaryWriter: true)
  120. .Build();
  121. // Act
  122. cfg.Remove("ToRemove");
  123. await cfg.SaveAsync();
  124. // Assert
  125. using var cfg2 = new CfgBuilder()
  126. .AddJson(jsonPath, level: 0, writeable: false)
  127. .Build();
  128. // 验证删除后值为 null 或空字符串
  129. var removedValue = cfg2["ToRemove"];
  130. Assert.True(string.IsNullOrEmpty(removedValue), $"Expected null or empty, but got: '{removedValue}'");
  131. Assert.Equal("Value2", cfg2["ToKeep"]);
  132. }
  133. [Fact]
  134. public void MultiLevel_HigherLevelOverrides()
  135. {
  136. // Arrange
  137. var basePath = Path.Combine(_testDir, "base.json");
  138. var overridePath = Path.Combine(_testDir, "override.json");
  139. File.WriteAllText(basePath, """
  140. {
  141. "Setting1": "BaseValue1",
  142. "Setting2": "BaseValue2"
  143. }
  144. """);
  145. File.WriteAllText(overridePath, """
  146. {
  147. "Setting1": "OverrideValue1"
  148. }
  149. """);
  150. using var cfg = new CfgBuilder()
  151. .AddJson(basePath, level: 0, writeable: false)
  152. .AddJson(overridePath, level: 1, writeable: false)
  153. .Build();
  154. // Act & Assert
  155. Assert.Equal("OverrideValue1", cfg["Setting1"]); // 被覆盖
  156. Assert.Equal("BaseValue2", cfg["Setting2"]); // 保持原值
  157. }
  158. [Fact]
  159. public void ToMicrosoftConfiguration_ReturnsValidConfiguration()
  160. {
  161. // Arrange
  162. var jsonPath = Path.Combine(_testDir, "config.json");
  163. File.WriteAllText(jsonPath, """
  164. {
  165. "App": {
  166. "Name": "TestApp"
  167. }
  168. }
  169. """);
  170. using var cfg = new CfgBuilder()
  171. .AddJson(jsonPath, level: 0, writeable: false)
  172. .Build();
  173. // Act
  174. var msConfig = cfg.ToMicrosoftConfiguration();
  175. // Assert
  176. Assert.NotNull(msConfig);
  177. Assert.Equal("TestApp", msConfig["App:Name"]);
  178. }
  179. [Fact]
  180. public void Get_LongValue_ReturnsCorrectType()
  181. {
  182. // Arrange
  183. var jsonPath = Path.Combine(_testDir, "config.json");
  184. File.WriteAllText(jsonPath, """{"BigNumber": 9223372036854775807}""");
  185. using var cfg = new CfgBuilder()
  186. .AddJson(jsonPath, level: 0, writeable: false)
  187. .Build();
  188. // Act & Assert
  189. Assert.Equal(9223372036854775807L, cfg.GetValue<long>("BigNumber"));
  190. }
  191. [Fact]
  192. public void Get_DecimalValue_ReturnsCorrectType()
  193. {
  194. // Arrange
  195. var jsonPath = Path.Combine(_testDir, "config.json");
  196. File.WriteAllText(jsonPath, """{"Price": 123.456}""");
  197. using var cfg = new CfgBuilder()
  198. .AddJson(jsonPath, level: 0, writeable: false)
  199. .Build();
  200. // Act & Assert
  201. Assert.Equal(123.456m, cfg.GetValue<decimal>("Price"));
  202. }
  203. public enum TestLogLevel { Debug, Info, Warning, Error }
  204. [Fact]
  205. public void Get_EnumValue_ReturnsCorrectType()
  206. {
  207. // Arrange
  208. var jsonPath = Path.Combine(_testDir, "config.json");
  209. File.WriteAllText(jsonPath, """{"LogLevel": "Warning"}""");
  210. using var cfg = new CfgBuilder()
  211. .AddJson(jsonPath, level: 0, writeable: false)
  212. .Build();
  213. // Act & Assert
  214. Assert.Equal(TestLogLevel.Warning, cfg.GetValue<TestLogLevel>("LogLevel"));
  215. }
  216. [Fact]
  217. public void Get_EnumValue_CaseInsensitive()
  218. {
  219. // Arrange
  220. var jsonPath = Path.Combine(_testDir, "config.json");
  221. File.WriteAllText(jsonPath, """{"LogLevel": "warning"}""");
  222. using var cfg = new CfgBuilder()
  223. .AddJson(jsonPath, level: 0, writeable: false)
  224. .Build();
  225. // Act & Assert
  226. Assert.Equal(TestLogLevel.Warning, cfg.GetValue<TestLogLevel>("LogLevel"));
  227. }
  228. [Fact]
  229. public void Get_InvalidValue_ReturnsDefault()
  230. {
  231. // Arrange
  232. var jsonPath = Path.Combine(_testDir, "config.json");
  233. File.WriteAllText(jsonPath, """{"NotANumber": "abc"}""");
  234. using var cfg = new CfgBuilder()
  235. .AddJson(jsonPath, level: 0, writeable: false)
  236. .Build();
  237. // Act & Assert - 无效值返回默认值(与 Microsoft.Extensions.Configuration 行为一致)
  238. Assert.Equal(default(int), cfg.GetValue<int>("NotANumber"));
  239. }
  240. [Fact]
  241. public void Get_NonExistentKey_ReturnsDefault()
  242. {
  243. // Arrange
  244. var jsonPath = Path.Combine(_testDir, "config.json");
  245. File.WriteAllText(jsonPath, """{"Key": "Value"}""");
  246. using var cfg = new CfgBuilder()
  247. .AddJson(jsonPath, level: 0, writeable: false)
  248. .Build();
  249. // Act & Assert
  250. Assert.Null(cfg["NonExistent"]);
  251. Assert.Equal(default, cfg.GetValue<int>("NonExistent"));
  252. Assert.Null(cfg.GetValue<string>("NonExistent"));
  253. }
  254. [Fact]
  255. public void Get_NullableInt_ReturnsCorrectValue()
  256. {
  257. // Arrange
  258. var jsonPath = Path.Combine(_testDir, "config.json");
  259. File.WriteAllText(jsonPath, """{"NullableInt": 42}""");
  260. using var cfg = new CfgBuilder()
  261. .AddJson(jsonPath, level: 0, writeable: false)
  262. .Build();
  263. // Act & Assert
  264. Assert.Equal(42, cfg.GetValue<int?>("NullableInt"));
  265. Assert.Null(cfg.GetValue<int?>("NonExistent"));
  266. }
  267. }