BoundaryConditionTests.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. namespace Apq.Cfg.Tests;
  2. /// <summary>
  3. /// 边界条件测试
  4. /// </summary>
  5. public class BoundaryConditionTests : IDisposable
  6. {
  7. private readonly string _testDir;
  8. public BoundaryConditionTests()
  9. {
  10. _testDir = Path.Combine(Path.GetTempPath(), $"ApqCfgBoundaryTests_{Guid.NewGuid():N}");
  11. Directory.CreateDirectory(_testDir);
  12. }
  13. public void Dispose()
  14. {
  15. if (Directory.Exists(_testDir))
  16. {
  17. try { Directory.Delete(_testDir, true); }
  18. catch { }
  19. }
  20. }
  21. #region 空值和空字符串测试
  22. [Fact]
  23. public void Get_EmptyKey_ReturnsNull()
  24. {
  25. // Arrange
  26. var jsonPath = Path.Combine(_testDir, "config.json");
  27. File.WriteAllText(jsonPath, """{"Key": "Value"}""");
  28. using var cfg = new CfgBuilder()
  29. .AddJson(jsonPath, level: 0, writeable: false)
  30. .Build();
  31. // Act & Assert
  32. Assert.Null(cfg.Get(""));
  33. }
  34. [Fact]
  35. public void Exists_EmptyKey_ReturnsFalse()
  36. {
  37. // Arrange
  38. var jsonPath = Path.Combine(_testDir, "config.json");
  39. File.WriteAllText(jsonPath, """{"Key": "Value"}""");
  40. using var cfg = new CfgBuilder()
  41. .AddJson(jsonPath, level: 0, writeable: false)
  42. .Build();
  43. // Act & Assert
  44. Assert.False(cfg.Exists(""));
  45. }
  46. [Fact]
  47. public void Set_EmptyStringValue_Works()
  48. {
  49. // Arrange
  50. var jsonPath = Path.Combine(_testDir, "config.json");
  51. File.WriteAllText(jsonPath, """{"Key": "Value"}""");
  52. using var cfg = new CfgBuilder()
  53. .AddJson(jsonPath, level: 0, writeable: true, isPrimaryWriter: true)
  54. .Build();
  55. // Act
  56. cfg.Set("EmptyValue", "");
  57. // Assert
  58. Assert.Equal("", cfg.Get("EmptyValue"));
  59. }
  60. [Fact]
  61. public void Set_NullValue_Works()
  62. {
  63. // Arrange
  64. var jsonPath = Path.Combine(_testDir, "config.json");
  65. File.WriteAllText(jsonPath, """{"Key": "Value"}""");
  66. using var cfg = new CfgBuilder()
  67. .AddJson(jsonPath, level: 0, writeable: true, isPrimaryWriter: true)
  68. .Build();
  69. // Act
  70. cfg.Set("NullValue", null);
  71. // Assert
  72. Assert.Null(cfg.Get("NullValue"));
  73. }
  74. #endregion
  75. #region 特殊字符键名测试
  76. [Fact]
  77. public void Get_KeyWithColon_ReturnsNestedValue()
  78. {
  79. // Arrange
  80. var jsonPath = Path.Combine(_testDir, "config.json");
  81. File.WriteAllText(jsonPath, """{"Parent": {"Child": "NestedValue"}}""");
  82. using var cfg = new CfgBuilder()
  83. .AddJson(jsonPath, level: 0, writeable: false)
  84. .Build();
  85. // Act & Assert
  86. Assert.Equal("NestedValue", cfg.Get("Parent:Child"));
  87. }
  88. [Fact]
  89. public void Get_KeyWithSpecialChars_Works()
  90. {
  91. // Arrange
  92. var jsonPath = Path.Combine(_testDir, "config.json");
  93. File.WriteAllText(jsonPath, """{"Key-With-Dashes": "Value1", "Key_With_Underscores": "Value2", "Key.With.Dots": "Value3"}""");
  94. using var cfg = new CfgBuilder()
  95. .AddJson(jsonPath, level: 0, writeable: false)
  96. .Build();
  97. // Act & Assert
  98. Assert.Equal("Value1", cfg.Get("Key-With-Dashes"));
  99. Assert.Equal("Value2", cfg.Get("Key_With_Underscores"));
  100. Assert.Equal("Value3", cfg.Get("Key.With.Dots"));
  101. }
  102. [Fact]
  103. public void Get_KeyWithUnicode_Works()
  104. {
  105. // Arrange
  106. var jsonPath = Path.Combine(_testDir, "config.json");
  107. File.WriteAllText(jsonPath, """{"中文键": "中文值", "日本語キー": "日本語値", "한국어키": "한국어값"}""");
  108. using var cfg = new CfgBuilder()
  109. .AddJson(jsonPath, level: 0, writeable: false)
  110. .Build();
  111. // Act & Assert
  112. Assert.Equal("中文值", cfg.Get("中文键"));
  113. Assert.Equal("日本語値", cfg.Get("日本語キー"));
  114. Assert.Equal("한국어값", cfg.Get("한국어키"));
  115. }
  116. [Fact]
  117. public void Get_KeyWithNumbers_Works()
  118. {
  119. // Arrange
  120. var jsonPath = Path.Combine(_testDir, "config.json");
  121. File.WriteAllText(jsonPath, """{"123": "NumericKey", "Key123": "MixedKey", "123Key": "NumericPrefix"}""");
  122. using var cfg = new CfgBuilder()
  123. .AddJson(jsonPath, level: 0, writeable: false)
  124. .Build();
  125. // Act & Assert
  126. Assert.Equal("NumericKey", cfg.Get("123"));
  127. Assert.Equal("MixedKey", cfg.Get("Key123"));
  128. Assert.Equal("NumericPrefix", cfg.Get("123Key"));
  129. }
  130. #endregion
  131. #region 空文件和大文件测试
  132. [Fact]
  133. public void Build_EmptyJsonFile_Works()
  134. {
  135. // Arrange
  136. var jsonPath = Path.Combine(_testDir, "empty.json");
  137. File.WriteAllText(jsonPath, "{}");
  138. // Act
  139. using var cfg = new CfgBuilder()
  140. .AddJson(jsonPath, level: 0, writeable: false)
  141. .Build();
  142. // Assert
  143. Assert.Null(cfg.Get("AnyKey"));
  144. Assert.False(cfg.Exists("AnyKey"));
  145. }
  146. [Fact]
  147. public void Build_LargeJsonFile_Works()
  148. {
  149. // Arrange
  150. var jsonPath = Path.Combine(_testDir, "large.json");
  151. var sb = new System.Text.StringBuilder();
  152. sb.Append("{");
  153. for (int i = 0; i < 10000; i++)
  154. {
  155. if (i > 0) sb.Append(",");
  156. sb.Append($"\"Key{i}\": \"Value{i}\"");
  157. }
  158. sb.Append("}");
  159. File.WriteAllText(jsonPath, sb.ToString());
  160. // Act
  161. using var cfg = new CfgBuilder()
  162. .AddJson(jsonPath, level: 0, writeable: false)
  163. .Build();
  164. // Assert
  165. Assert.Equal("Value0", cfg.Get("Key0"));
  166. Assert.Equal("Value9999", cfg.Get("Key9999"));
  167. Assert.Equal("Value5000", cfg.Get("Key5000"));
  168. }
  169. [Fact]
  170. public void Build_DeeplyNestedJson_Works()
  171. {
  172. // Arrange
  173. var jsonPath = Path.Combine(_testDir, "nested.json");
  174. // 创建 10 层嵌套
  175. var json = """
  176. {
  177. "L1": {
  178. "L2": {
  179. "L3": {
  180. "L4": {
  181. "L5": {
  182. "L6": {
  183. "L7": {
  184. "L8": {
  185. "L9": {
  186. "L10": "DeepValue"
  187. }
  188. }
  189. }
  190. }
  191. }
  192. }
  193. }
  194. }
  195. }
  196. }
  197. """;
  198. File.WriteAllText(jsonPath, json);
  199. // Act
  200. using var cfg = new CfgBuilder()
  201. .AddJson(jsonPath, level: 0, writeable: false)
  202. .Build();
  203. // Assert
  204. Assert.Equal("DeepValue", cfg.Get("L1:L2:L3:L4:L5:L6:L7:L8:L9:L10"));
  205. }
  206. #endregion
  207. #region 长键名和长值测试
  208. [Fact]
  209. public void Get_VeryLongKey_Works()
  210. {
  211. // Arrange
  212. var jsonPath = Path.Combine(_testDir, "config.json");
  213. var longKey = new string('K', 1000);
  214. File.WriteAllText(jsonPath, $"{{\"{longKey}\": \"Value\"}}");
  215. using var cfg = new CfgBuilder()
  216. .AddJson(jsonPath, level: 0, writeable: false)
  217. .Build();
  218. // Act & Assert
  219. Assert.Equal("Value", cfg.Get(longKey));
  220. }
  221. [Fact]
  222. public void Get_VeryLongValue_Works()
  223. {
  224. // Arrange
  225. var jsonPath = Path.Combine(_testDir, "config.json");
  226. var longValue = new string('V', 10000);
  227. File.WriteAllText(jsonPath, $"{{\"Key\": \"{longValue}\"}}");
  228. using var cfg = new CfgBuilder()
  229. .AddJson(jsonPath, level: 0, writeable: false)
  230. .Build();
  231. // Act & Assert
  232. Assert.Equal(longValue, cfg.Get("Key"));
  233. }
  234. [Fact]
  235. public async Task Set_VeryLongValue_PersistsCorrectly()
  236. {
  237. // Arrange
  238. var jsonPath = Path.Combine(_testDir, "config.json");
  239. File.WriteAllText(jsonPath, """{"Key": "Value"}""");
  240. var longValue = new string('V', 10000);
  241. using var cfg = new CfgBuilder()
  242. .AddJson(jsonPath, level: 0, writeable: true, isPrimaryWriter: true)
  243. .Build();
  244. // Act
  245. cfg.Set("LongKey", longValue);
  246. await cfg.SaveAsync();
  247. // Assert
  248. using var cfg2 = new CfgBuilder()
  249. .AddJson(jsonPath, level: 0, writeable: false)
  250. .Build();
  251. Assert.Equal(longValue, cfg2.Get("LongKey"));
  252. }
  253. #endregion
  254. #region 数组和复杂结构测试
  255. [Fact]
  256. public void Get_ArrayElement_Works()
  257. {
  258. // Arrange
  259. var jsonPath = Path.Combine(_testDir, "config.json");
  260. File.WriteAllText(jsonPath, """{"Items": ["First", "Second", "Third"]}""");
  261. using var cfg = new CfgBuilder()
  262. .AddJson(jsonPath, level: 0, writeable: false)
  263. .Build();
  264. // Act & Assert
  265. Assert.Equal("First", cfg.Get("Items:0"));
  266. Assert.Equal("Second", cfg.Get("Items:1"));
  267. Assert.Equal("Third", cfg.Get("Items:2"));
  268. }
  269. [Fact]
  270. public void Get_NestedArrayElement_Works()
  271. {
  272. // Arrange
  273. var jsonPath = Path.Combine(_testDir, "config.json");
  274. File.WriteAllText(jsonPath, """
  275. {
  276. "Users": [
  277. {"Name": "Alice", "Age": 30},
  278. {"Name": "Bob", "Age": 25}
  279. ]
  280. }
  281. """);
  282. using var cfg = new CfgBuilder()
  283. .AddJson(jsonPath, level: 0, writeable: false)
  284. .Build();
  285. // Act & Assert
  286. Assert.Equal("Alice", cfg.Get("Users:0:Name"));
  287. Assert.Equal("30", cfg.Get("Users:0:Age"));
  288. Assert.Equal("Bob", cfg.Get("Users:1:Name"));
  289. Assert.Equal("25", cfg.Get("Users:1:Age"));
  290. }
  291. #endregion
  292. #region 多层级边界测试
  293. [Fact]
  294. public void MultiLevel_SameKeyDifferentLevels_HigherWins()
  295. {
  296. // Arrange
  297. var level0Path = Path.Combine(_testDir, "level0.json");
  298. var level1Path = Path.Combine(_testDir, "level1.json");
  299. var level2Path = Path.Combine(_testDir, "level2.json");
  300. File.WriteAllText(level0Path, """{"Key": "Level0"}""");
  301. File.WriteAllText(level1Path, """{"Key": "Level1"}""");
  302. File.WriteAllText(level2Path, """{"Key": "Level2"}""");
  303. using var cfg = new CfgBuilder()
  304. .AddJson(level0Path, level: 0, writeable: false)
  305. .AddJson(level1Path, level: 1, writeable: false)
  306. .AddJson(level2Path, level: 2, writeable: false)
  307. .Build();
  308. // Act & Assert
  309. Assert.Equal("Level2", cfg.Get("Key"));
  310. }
  311. [Fact]
  312. public void MultiLevel_NegativeLevel_Works()
  313. {
  314. // Arrange
  315. var negativePath = Path.Combine(_testDir, "negative.json");
  316. var zeroPath = Path.Combine(_testDir, "zero.json");
  317. File.WriteAllText(negativePath, """{"Key": "Negative"}""");
  318. File.WriteAllText(zeroPath, """{"Key": "Zero"}""");
  319. using var cfg = new CfgBuilder()
  320. .AddJson(negativePath, level: -1, writeable: false)
  321. .AddJson(zeroPath, level: 0, writeable: false)
  322. .Build();
  323. // Act & Assert - level 0 应该覆盖 level -1
  324. Assert.Equal("Zero", cfg.Get("Key"));
  325. }
  326. [Fact]
  327. public void MultiLevel_LargeGapBetweenLevels_Works()
  328. {
  329. // Arrange
  330. var level0Path = Path.Combine(_testDir, "level0.json");
  331. var level1000Path = Path.Combine(_testDir, "level1000.json");
  332. File.WriteAllText(level0Path, """{"Key": "Level0"}""");
  333. File.WriteAllText(level1000Path, """{"Key": "Level1000"}""");
  334. using var cfg = new CfgBuilder()
  335. .AddJson(level0Path, level: 0, writeable: false)
  336. .AddJson(level1000Path, level: 1000, writeable: false)
  337. .Build();
  338. // Act & Assert
  339. Assert.Equal("Level1000", cfg.Get("Key"));
  340. }
  341. #endregion
  342. #region 类型转换边界测试
  343. [Fact]
  344. public void Get_IntMaxValue_Works()
  345. {
  346. // Arrange
  347. var jsonPath = Path.Combine(_testDir, "config.json");
  348. File.WriteAllText(jsonPath, $"{{\"MaxInt\": {int.MaxValue}}}");
  349. using var cfg = new CfgBuilder()
  350. .AddJson(jsonPath, level: 0, writeable: false)
  351. .Build();
  352. // Act & Assert
  353. Assert.Equal(int.MaxValue, cfg.Get<int>("MaxInt"));
  354. }
  355. [Fact]
  356. public void Get_IntMinValue_Works()
  357. {
  358. // Arrange
  359. var jsonPath = Path.Combine(_testDir, "config.json");
  360. File.WriteAllText(jsonPath, $"{{\"MinInt\": {int.MinValue}}}");
  361. using var cfg = new CfgBuilder()
  362. .AddJson(jsonPath, level: 0, writeable: false)
  363. .Build();
  364. // Act & Assert
  365. Assert.Equal(int.MinValue, cfg.Get<int>("MinInt"));
  366. }
  367. [Fact]
  368. public void Get_BooleanStrings_Work()
  369. {
  370. // Arrange
  371. var jsonPath = Path.Combine(_testDir, "config.json");
  372. File.WriteAllText(jsonPath, """{"True1": true, "True2": "true", "True3": "True", "False1": false, "False2": "false"}""");
  373. using var cfg = new CfgBuilder()
  374. .AddJson(jsonPath, level: 0, writeable: false)
  375. .Build();
  376. // Act & Assert
  377. Assert.True(cfg.Get<bool>("True1"));
  378. Assert.True(cfg.Get<bool>("True2"));
  379. Assert.True(cfg.Get<bool>("True3"));
  380. Assert.False(cfg.Get<bool>("False1"));
  381. Assert.False(cfg.Get<bool>("False2"));
  382. }
  383. [Fact]
  384. public void Get_DoubleSpecialValues_Work()
  385. {
  386. // Arrange
  387. var jsonPath = Path.Combine(_testDir, "config.json");
  388. File.WriteAllText(jsonPath, """{"Zero": 0.0, "NegativeZero": -0.0, "Small": 0.0000001}""");
  389. using var cfg = new CfgBuilder()
  390. .AddJson(jsonPath, level: 0, writeable: false)
  391. .Build();
  392. // Act & Assert
  393. Assert.Equal(0.0, cfg.Get<double>("Zero"));
  394. Assert.Equal(0.0000001, cfg.Get<double>("Small"), 10);
  395. }
  396. #endregion
  397. #region Optional 文件测试
  398. [Fact]
  399. public void Build_OptionalMissingFile_Works()
  400. {
  401. // Arrange
  402. var existingPath = Path.Combine(_testDir, "existing.json");
  403. var missingPath = Path.Combine(_testDir, "missing.json");
  404. File.WriteAllText(existingPath, """{"Key": "Value"}""");
  405. // Act - optional: true 不应抛出异常
  406. using var cfg = new CfgBuilder()
  407. .AddJson(existingPath, level: 0, writeable: false, optional: true)
  408. .AddJson(missingPath, level: 1, writeable: false, optional: true)
  409. .Build();
  410. // Assert
  411. Assert.Equal("Value", cfg.Get("Key"));
  412. }
  413. [Fact]
  414. public void Build_RequiredMissingFile_ThrowsException()
  415. {
  416. // Arrange
  417. var missingPath = Path.Combine(_testDir, "missing.json");
  418. // Act & Assert - optional: false 应抛出异常
  419. var ex = Assert.ThrowsAny<Exception>(() =>
  420. {
  421. using var cfg = new CfgBuilder()
  422. .AddJson(missingPath, level: 0, writeable: false, optional: false)
  423. .Build();
  424. // 尝试访问配置以触发加载
  425. cfg.ToMicrosoftConfiguration();
  426. });
  427. // 验证是文件未找到相关的异常
  428. Assert.True(ex is FileNotFoundException || ex.InnerException is FileNotFoundException ||
  429. ex.Message.Contains("not found", StringComparison.OrdinalIgnoreCase) ||
  430. ex.Message.Contains("找不到", StringComparison.OrdinalIgnoreCase),
  431. $"Expected file not found exception, but got: {ex.GetType().Name}: {ex.Message}");
  432. }
  433. #endregion
  434. }