EncodingTests.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. using System.Text;
  2. using Apq.Cfg.EncodingSupport;
  3. using Apq.Cfg.Sources.File;
  4. namespace Apq.Cfg.Tests;
  5. /// <summary>
  6. /// 编码检测相关测试
  7. /// </summary>
  8. public class EncodingTests : IDisposable
  9. {
  10. private readonly string _testDir;
  11. public EncodingTests()
  12. {
  13. _testDir = Path.Combine(Path.GetTempPath(), $"ApqCfgEncodingTests_{Guid.NewGuid():N}");
  14. Directory.CreateDirectory(_testDir);
  15. }
  16. public void Dispose()
  17. {
  18. if (Directory.Exists(_testDir))
  19. {
  20. try { Directory.Delete(_testDir, true); }
  21. catch { }
  22. }
  23. }
  24. // ========== EncodingOptions 测试 ==========
  25. [Fact]
  26. public void EncodingOptions_Default_HasCorrectValues()
  27. {
  28. // Arrange & Act
  29. var options = Apq.Cfg.EncodingSupport.EncodingOptions.Default;
  30. // Assert
  31. Assert.Equal(Apq.Cfg.EncodingSupport.EncodingReadStrategy.AutoDetect, options.ReadStrategy);
  32. Assert.Equal(Apq.Cfg.EncodingSupport.EncodingWriteStrategy.Utf8NoBom, options.WriteStrategy);
  33. Assert.Equal(0.6f, options.ConfidenceThreshold);
  34. Assert.True(options.EnableCache);
  35. Assert.False(options.EnableLogging);
  36. }
  37. [Fact]
  38. public void EncodingOptions_PowerShell_HasUtf8WithBom()
  39. {
  40. // Arrange & Act
  41. var options = Apq.Cfg.EncodingSupport.EncodingOptions.PowerShell;
  42. // Assert
  43. Assert.Equal(Apq.Cfg.EncodingSupport.EncodingWriteStrategy.Utf8WithBom, options.WriteStrategy);
  44. }
  45. [Fact]
  46. public void EncodingOptions_GetWriteEncoding_Utf8NoBom()
  47. {
  48. // Arrange
  49. var options = new Apq.Cfg.EncodingSupport.EncodingOptions { WriteStrategy = Apq.Cfg.EncodingSupport.EncodingWriteStrategy.Utf8NoBom };
  50. // Act
  51. var encoding = options.GetWriteEncoding();
  52. // Assert
  53. Assert.IsType<UTF8Encoding>(encoding);
  54. var utf8 = (UTF8Encoding)encoding;
  55. Assert.Equal(0, utf8.GetPreamble().Length); // 无 BOM
  56. }
  57. [Fact]
  58. public void EncodingOptions_GetWriteEncoding_Utf8WithBom()
  59. {
  60. // Arrange
  61. var options = new Apq.Cfg.EncodingSupport.EncodingOptions { WriteStrategy = Apq.Cfg.EncodingSupport.EncodingWriteStrategy.Utf8WithBom };
  62. // Act
  63. var encoding = options.GetWriteEncoding();
  64. // Assert
  65. Assert.IsType<UTF8Encoding>(encoding);
  66. var utf8 = (UTF8Encoding)encoding;
  67. Assert.Equal(3, utf8.GetPreamble().Length); // 有 BOM
  68. }
  69. [Fact]
  70. public void EncodingOptions_GetWriteEncoding_Preserve()
  71. {
  72. // Arrange
  73. Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
  74. var options = new Apq.Cfg.EncodingSupport.EncodingOptions { WriteStrategy = Apq.Cfg.EncodingSupport.EncodingWriteStrategy.Preserve };
  75. var detectedEncoding = Encoding.GetEncoding("GB2312");
  76. // Act
  77. var encoding = options.GetWriteEncoding(detectedEncoding);
  78. // Assert
  79. Assert.Equal(detectedEncoding, encoding);
  80. }
  81. [Fact]
  82. public void EncodingOptions_GetWriteEncoding_Specified()
  83. {
  84. // Arrange
  85. var specifiedEncoding = Encoding.Unicode;
  86. var options = new Apq.Cfg.EncodingSupport.EncodingOptions
  87. {
  88. WriteStrategy = Apq.Cfg.EncodingSupport.EncodingWriteStrategy.Specified,
  89. WriteEncoding = specifiedEncoding
  90. };
  91. // Act
  92. var encoding = options.GetWriteEncoding();
  93. // Assert
  94. Assert.Equal(specifiedEncoding, encoding);
  95. }
  96. // ========== EncodingDetectionResult 测试 ==========
  97. [Fact]
  98. public void EncodingDetectionResult_ToString_FormatsCorrectly()
  99. {
  100. // Arrange
  101. var result = new Apq.Cfg.EncodingSupport.EncodingDetectionResult(
  102. Encoding.UTF8,
  103. 0.95f,
  104. Apq.Cfg.EncodingSupport.EncodingDetectionMethod.Bom,
  105. true,
  106. "UTF-8",
  107. "/test/file.json");
  108. // Act
  109. var str = result.ToString();
  110. // Assert
  111. Assert.Contains("Bom", str);
  112. Assert.Contains("BOM", str);
  113. Assert.Contains("95", str); // 95%
  114. }
  115. // ========== EncodingDetector 测试 ==========
  116. [Fact]
  117. public void EncodingDetector_Detect_Utf8NoBom()
  118. {
  119. // Arrange
  120. var filePath = Path.Combine(_testDir, "utf8_no_bom.json");
  121. File.WriteAllText(filePath, """{"key": "value"}""", new UTF8Encoding(false));
  122. // Act
  123. var result = Apq.Cfg.EncodingSupport.EncodingDetector.Default.Detect(filePath);
  124. // Assert
  125. Assert.NotNull(result);
  126. Assert.Equal(Apq.Cfg.EncodingSupport.EncodingDetectionMethod.UtfUnknown, result.Method);
  127. Assert.False(result.HasBom);
  128. }
  129. [Fact]
  130. public void EncodingDetector_Detect_Utf8WithBom()
  131. {
  132. // Arrange
  133. var filePath = Path.Combine(_testDir, "utf8_with_bom.json");
  134. File.WriteAllText(filePath, """{"key": "value"}""", new UTF8Encoding(true));
  135. // Act
  136. var result = Apq.Cfg.EncodingSupport.EncodingDetector.Default.Detect(filePath);
  137. // Assert
  138. Assert.NotNull(result);
  139. Assert.Equal(Apq.Cfg.EncodingSupport.EncodingDetectionMethod.Bom, result.Method);
  140. Assert.True(result.HasBom);
  141. }
  142. [Fact]
  143. public void EncodingDetector_Detect_Utf16Le()
  144. {
  145. // Arrange
  146. var filePath = Path.Combine(_testDir, "utf16_le.json");
  147. File.WriteAllText(filePath, """{"key": "value"}""", Encoding.Unicode);
  148. // Act
  149. var result = Apq.Cfg.EncodingSupport.EncodingDetector.Default.Detect(filePath);
  150. // Assert
  151. Assert.NotNull(result);
  152. Assert.Equal(Apq.Cfg.EncodingSupport.EncodingDetectionMethod.Bom, result.Method);
  153. Assert.True(result.HasBom);
  154. }
  155. [Fact]
  156. public void EncodingDetector_Detect_NonExistentFile_ReturnsFallback()
  157. {
  158. // Arrange
  159. var filePath = Path.Combine(_testDir, "non_existent.json");
  160. // Act
  161. var result = Apq.Cfg.EncodingSupport.EncodingDetector.Default.Detect(filePath);
  162. // Assert
  163. Assert.NotNull(result);
  164. Assert.Equal(Apq.Cfg.EncodingSupport.EncodingDetectionMethod.Fallback, result.Method);
  165. Assert.Equal(Encoding.UTF8, result.Encoding);
  166. }
  167. [Fact]
  168. public void EncodingDetector_CustomReadMapping_OverridesDetection()
  169. {
  170. // Arrange
  171. var detector = new Apq.Cfg.EncodingSupport.EncodingDetector();
  172. var filePath = Path.Combine(_testDir, "custom_mapping.json");
  173. File.WriteAllText(filePath, """{"key": "value"}""", new UTF8Encoding(false));
  174. var customEncoding = Encoding.Unicode;
  175. detector.MappingConfig.AddReadMapping(filePath, EncodingMappingType.ExactPath, customEncoding, priority: 100);
  176. // Act
  177. var result = detector.Detect(filePath);
  178. // Assert
  179. Assert.Equal(Apq.Cfg.EncodingSupport.EncodingDetectionMethod.UserSpecified, result.Method);
  180. Assert.Equal(customEncoding, result.Encoding);
  181. // Cleanup
  182. detector.MappingConfig.RemoveReadMapping(filePath);
  183. }
  184. [Fact]
  185. public void EncodingDetector_Cache_ReturnsFromCache()
  186. {
  187. // Arrange
  188. var detector = new Apq.Cfg.EncodingSupport.EncodingDetector();
  189. var filePath = Path.Combine(_testDir, "cached.json");
  190. File.WriteAllText(filePath, """{"key": "value"}""", new UTF8Encoding(true));
  191. var options = new Apq.Cfg.EncodingSupport.EncodingOptions { EnableCache = true };
  192. // Act - 第一次检测
  193. var result1 = detector.Detect(filePath, options);
  194. // Act - 第二次检测(应该从缓存获取)
  195. var result2 = detector.Detect(filePath, options);
  196. // Assert
  197. Assert.Equal(Apq.Cfg.EncodingSupport.EncodingDetectionMethod.Bom, result1.Method);
  198. Assert.Equal(Apq.Cfg.EncodingSupport.EncodingDetectionMethod.Cached, result2.Method);
  199. }
  200. [Fact]
  201. public void EncodingDetector_Cache_InvalidatesOnFileChange()
  202. {
  203. // Arrange
  204. var detector = new Apq.Cfg.EncodingSupport.EncodingDetector();
  205. var filePath = Path.Combine(_testDir, "invalidate.json");
  206. File.WriteAllText(filePath, """{"key": "value"}""", new UTF8Encoding(true));
  207. var options = new Apq.Cfg.EncodingSupport.EncodingOptions { EnableCache = true };
  208. // Act - 第一次检测
  209. var result1 = detector.Detect(filePath, options);
  210. // 修改文件
  211. Thread.Sleep(100); // 确保时间戳不同
  212. File.WriteAllText(filePath, """{"key": "new_value"}""", new UTF8Encoding(true));
  213. // Act - 第二次检测(缓存应该失效)
  214. var result2 = detector.Detect(filePath, options);
  215. // Assert
  216. Assert.Equal(Apq.Cfg.EncodingSupport.EncodingDetectionMethod.Bom, result1.Method);
  217. Assert.Equal(Apq.Cfg.EncodingSupport.EncodingDetectionMethod.Bom, result2.Method); // 不是 Cached
  218. }
  219. [Fact]
  220. public void EncodingDetector_ExtensionMapping_ReturnsCorrectEncoding()
  221. {
  222. // Arrange
  223. var detector = new Apq.Cfg.EncodingSupport.EncodingDetector();
  224. var ps1Path = Path.Combine(_testDir, "script.ps1");
  225. // Act
  226. var encoding = detector.MappingConfig.GetWriteEncoding(ps1Path);
  227. // Assert
  228. Assert.NotNull(encoding);
  229. Assert.IsType<UTF8Encoding>(encoding);
  230. Assert.Equal(3, encoding.GetPreamble().Length); // UTF-8 BOM
  231. }
  232. [Fact]
  233. public void EncodingDetector_Logging_InvokesHandler()
  234. {
  235. // Arrange
  236. var detector = new Apq.Cfg.EncodingSupport.EncodingDetector();
  237. var filePath = Path.Combine(_testDir, "logging.json");
  238. File.WriteAllText(filePath, """{"key": "value"}""", new UTF8Encoding(true));
  239. Apq.Cfg.EncodingSupport.EncodingDetectionResult? loggedResult = null;
  240. detector.OnEncodingDetected += result => loggedResult = result;
  241. var options = new Apq.Cfg.EncodingSupport.EncodingOptions { EnableLogging = true };
  242. // Act
  243. detector.Detect(filePath, options);
  244. // Assert
  245. Assert.NotNull(loggedResult);
  246. Assert.Equal(filePath, Path.GetFullPath(loggedResult.FilePath));
  247. }
  248. [Fact]
  249. public void EncodingDetector_ClearCache_RemovesAllEntries()
  250. {
  251. // Arrange
  252. var detector = new Apq.Cfg.EncodingSupport.EncodingDetector();
  253. var filePath = Path.Combine(_testDir, "clear_cache.json");
  254. File.WriteAllText(filePath, """{"key": "value"}""", new UTF8Encoding(true));
  255. var options = new Apq.Cfg.EncodingSupport.EncodingOptions { EnableCache = true };
  256. detector.Detect(filePath, options);
  257. // Act
  258. detector.ClearCache();
  259. var result = detector.Detect(filePath, options);
  260. // Assert
  261. Assert.NotEqual(Apq.Cfg.EncodingSupport.EncodingDetectionMethod.Cached, result.Method);
  262. }
  263. // ========== CfgBuilder 编码选项测试 ==========
  264. [Fact]
  265. public async Task CfgBuilder_WithEncodingOptions_WritesWithCorrectEncoding()
  266. {
  267. // Arrange
  268. var jsonPath = Path.Combine(_testDir, "encoding_test.json");
  269. File.WriteAllText(jsonPath, """{}""");
  270. var encodingOptions = new Apq.Cfg.EncodingSupport.EncodingOptions
  271. {
  272. WriteStrategy = Apq.Cfg.EncodingSupport.EncodingWriteStrategy.Utf8WithBom
  273. };
  274. using var cfg = new CfgBuilder()
  275. .AddJson(jsonPath, level: 0, writeable: true, encoding: encodingOptions)
  276. .Build();
  277. // Act
  278. cfg.Set("Key", "Value");
  279. await cfg.SaveAsync();
  280. // Assert - 检查文件是否有 BOM
  281. var bytes = File.ReadAllBytes(jsonPath);
  282. Assert.True(bytes.Length >= 3);
  283. Assert.Equal(0xEF, bytes[0]);
  284. Assert.Equal(0xBB, bytes[1]);
  285. Assert.Equal(0xBF, bytes[2]);
  286. }
  287. [Fact]
  288. public void CfgBuilder_AddReadEncodingMapping_SetsReadMapping()
  289. {
  290. // Arrange
  291. var filePath = Path.Combine(_testDir, "custom_read.json");
  292. var customEncoding = Encoding.Unicode;
  293. // Act
  294. new CfgBuilder()
  295. .AddReadEncodingMapping(filePath, customEncoding);
  296. // Assert
  297. var result = FileCfgSourceBase.EncodingDetector.Detect(filePath);
  298. Assert.Equal(Apq.Cfg.EncodingSupport.EncodingDetectionMethod.UserSpecified, result.Method);
  299. Assert.Equal(customEncoding, result.Encoding);
  300. // Cleanup
  301. FileCfgSourceBase.EncodingDetector.MappingConfig.RemoveReadMapping(filePath);
  302. }
  303. [Fact]
  304. public void CfgBuilder_AddWriteEncodingMapping_SetsWriteMapping()
  305. {
  306. // Arrange
  307. var filePath = Path.Combine(_testDir, "custom_write.json");
  308. var customEncoding = Encoding.Unicode;
  309. // Act
  310. new CfgBuilder()
  311. .AddWriteEncodingMapping(filePath, customEncoding);
  312. // Assert
  313. var writeEncoding = FileCfgSourceBase.EncodingDetector.MappingConfig.GetWriteEncoding(filePath);
  314. Assert.NotNull(writeEncoding);
  315. Assert.Equal(customEncoding, writeEncoding);
  316. // Cleanup
  317. FileCfgSourceBase.EncodingDetector.MappingConfig.RemoveWriteMapping(filePath);
  318. }
  319. [Fact]
  320. public void EncodingDetector_SeparateReadWriteMappings_WorkIndependently()
  321. {
  322. // Arrange
  323. Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
  324. var detector = new Apq.Cfg.EncodingSupport.EncodingDetector();
  325. var filePath = Path.Combine(_testDir, "separate_rw.json");
  326. File.WriteAllText(filePath, """{"key": "value"}""", new UTF8Encoding(false));
  327. var readEncoding = Encoding.GetEncoding("GB2312");
  328. var writeEncoding = Encoding.Unicode;
  329. // Act
  330. detector.MappingConfig.AddReadMapping(filePath, EncodingMappingType.ExactPath, readEncoding, priority: 100);
  331. detector.MappingConfig.AddWriteMapping(filePath, EncodingMappingType.ExactPath, writeEncoding, priority: 100);
  332. // Assert - 读取映射
  333. var detectResult = detector.Detect(filePath);
  334. Assert.Equal(readEncoding, detectResult.Encoding);
  335. // Assert - 写入映射
  336. var customWriteEncoding = detector.MappingConfig.GetWriteEncoding(filePath);
  337. Assert.Equal(writeEncoding, customWriteEncoding);
  338. // Cleanup
  339. detector.MappingConfig.RemoveReadMapping(filePath);
  340. detector.MappingConfig.RemoveWriteMapping(filePath);
  341. }
  342. [Fact]
  343. public void CfgBuilder_WithEncodingDetectionLogging_RegistersHandler()
  344. {
  345. // Arrange
  346. var logged = false;
  347. // Act
  348. new CfgBuilder()
  349. .WithEncodingDetectionLogging(_ => logged = true);
  350. var filePath = Path.Combine(_testDir, "log_test.json");
  351. File.WriteAllText(filePath, """{}""");
  352. var options = new Apq.Cfg.EncodingSupport.EncodingOptions { EnableLogging = true };
  353. FileCfgSourceBase.EncodingDetector.Detect(filePath, options);
  354. // Assert
  355. Assert.True(logged);
  356. }
  357. }