SourceGeneratorTests.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. using Apq.Cfg;
  2. namespace Apq.Cfg.Tests;
  3. /// <summary>
  4. /// Source Generator 生成的配置类测试
  5. /// </summary>
  6. public class SourceGeneratorTests : IDisposable
  7. {
  8. private readonly string _testDir;
  9. public SourceGeneratorTests()
  10. {
  11. _testDir = Path.Combine(Path.GetTempPath(), $"ApqCfgSgTests_{Guid.NewGuid():N}");
  12. Directory.CreateDirectory(_testDir);
  13. }
  14. [Fact]
  15. public void BindFrom_SimpleProperties_ShouldBindCorrectly()
  16. {
  17. // Arrange
  18. var jsonPath = Path.Combine(_testDir, "simple.json");
  19. File.WriteAllText(jsonPath, @"{
  20. ""Database"": {
  21. ""ConnectionString"": ""Server=localhost;Database=test"",
  22. ""Timeout"": 30,
  23. ""MaxRetries"": 3,
  24. ""EnableLogging"": true
  25. }
  26. }");
  27. var cfg = new CfgBuilder()
  28. .AddJson(jsonPath, level: 0, writeable: false)
  29. .Build();
  30. // Act
  31. var section = cfg.GetSection("Database");
  32. var config = TestDatabaseConfig.BindFrom(section);
  33. // Assert
  34. Assert.Equal("Server=localhost;Database=test", config.ConnectionString);
  35. Assert.Equal(30, config.Timeout);
  36. Assert.Equal(3, config.MaxRetries);
  37. Assert.True(config.EnableLogging);
  38. }
  39. [Fact]
  40. public void BindFrom_NestedObject_ShouldBindCorrectly()
  41. {
  42. // Arrange
  43. var jsonPath = Path.Combine(_testDir, "nested.json");
  44. File.WriteAllText(jsonPath, @"{
  45. ""App"": {
  46. ""Name"": ""TestApp"",
  47. ""Version"": ""1.0.0"",
  48. ""Database"": {
  49. ""ConnectionString"": ""Server=db"",
  50. ""Timeout"": 60,
  51. ""MaxRetries"": 5,
  52. ""EnableLogging"": false
  53. }
  54. }
  55. }");
  56. var cfg = new CfgBuilder()
  57. .AddJson(jsonPath, level: 0, writeable: false)
  58. .Build();
  59. // Act
  60. var section = cfg.GetSection("App");
  61. var config = TestAppConfig.BindFrom(section);
  62. // Assert
  63. Assert.Equal("TestApp", config.Name);
  64. Assert.Equal("1.0.0", config.Version);
  65. Assert.NotNull(config.Database);
  66. Assert.Equal("Server=db", config.Database.ConnectionString);
  67. Assert.Equal(60, config.Database.Timeout);
  68. }
  69. [Fact]
  70. public void BindFrom_ArrayProperty_ShouldBindCorrectly()
  71. {
  72. // Arrange
  73. var jsonPath = Path.Combine(_testDir, "array.json");
  74. File.WriteAllText(jsonPath, @"{
  75. ""Server"": {
  76. ""Name"": ""WebServer"",
  77. ""Ports"": [80, 443, 8080],
  78. ""AllowedHosts"": [""localhost"", ""127.0.0.1"", ""example.com""]
  79. }
  80. }");
  81. var cfg = new CfgBuilder()
  82. .AddJson(jsonPath, level: 0, writeable: false)
  83. .Build();
  84. // Act
  85. var section = cfg.GetSection("Server");
  86. var config = TestServerConfig.BindFrom(section);
  87. // Assert
  88. Assert.Equal("WebServer", config.Name);
  89. Assert.NotNull(config.Ports);
  90. Assert.Equal(3, config.Ports.Length);
  91. Assert.Equal(80, config.Ports[0]);
  92. Assert.Equal(443, config.Ports[1]);
  93. Assert.Equal(8080, config.Ports[2]);
  94. Assert.NotNull(config.AllowedHosts);
  95. Assert.Equal(3, config.AllowedHosts.Count);
  96. Assert.Contains("localhost", config.AllowedHosts);
  97. }
  98. [Fact]
  99. public void BindFrom_DictionaryProperty_ShouldBindCorrectly()
  100. {
  101. // Arrange
  102. var jsonPath = Path.Combine(_testDir, "dict.json");
  103. File.WriteAllText(jsonPath, @"{
  104. ""Features"": {
  105. ""Name"": ""FeatureFlags"",
  106. ""Flags"": {
  107. ""DarkMode"": true,
  108. ""BetaFeatures"": false,
  109. ""Analytics"": true
  110. }
  111. }
  112. }");
  113. var cfg = new CfgBuilder()
  114. .AddJson(jsonPath, level: 0, writeable: false)
  115. .Build();
  116. // Act
  117. var section = cfg.GetSection("Features");
  118. var config = TestFeaturesConfig.BindFrom(section);
  119. // Assert
  120. Assert.Equal("FeatureFlags", config.Name);
  121. Assert.NotNull(config.Flags);
  122. Assert.Equal(3, config.Flags.Count);
  123. Assert.True(config.Flags["DarkMode"]);
  124. Assert.False(config.Flags["BetaFeatures"]);
  125. Assert.True(config.Flags["Analytics"]);
  126. }
  127. [Fact]
  128. public void BindFrom_NullableProperties_ShouldHandleNulls()
  129. {
  130. // Arrange
  131. var jsonPath = Path.Combine(_testDir, "nullable.json");
  132. File.WriteAllText(jsonPath, @"{
  133. ""Optional"": {
  134. ""RequiredValue"": ""test"",
  135. ""OptionalInt"": 42
  136. }
  137. }");
  138. var cfg = new CfgBuilder()
  139. .AddJson(jsonPath, level: 0, writeable: false)
  140. .Build();
  141. // Act
  142. var section = cfg.GetSection("Optional");
  143. var config = TestOptionalConfig.BindFrom(section);
  144. // Assert
  145. Assert.Equal("test", config.RequiredValue);
  146. Assert.Equal(42, config.OptionalInt);
  147. Assert.Null(config.OptionalString);
  148. }
  149. [Fact]
  150. public void BindTo_ExistingObject_ShouldUpdateProperties()
  151. {
  152. // Arrange
  153. var jsonPath = Path.Combine(_testDir, "update.json");
  154. File.WriteAllText(jsonPath, @"{
  155. ""Database"": {
  156. ""ConnectionString"": ""NewConnection"",
  157. ""Timeout"": 120
  158. }
  159. }");
  160. var cfg = new CfgBuilder()
  161. .AddJson(jsonPath, level: 0, writeable: false)
  162. .Build();
  163. var existingConfig = new TestDatabaseConfig
  164. {
  165. ConnectionString = "OldConnection",
  166. Timeout = 30,
  167. MaxRetries = 5,
  168. EnableLogging = true
  169. };
  170. // Act
  171. var section = cfg.GetSection("Database");
  172. TestDatabaseConfig.BindTo(section, existingConfig);
  173. // Assert
  174. Assert.Equal("NewConnection", existingConfig.ConnectionString);
  175. Assert.Equal(120, existingConfig.Timeout);
  176. // 未在配置中指定的属性保持原值
  177. Assert.Equal(5, existingConfig.MaxRetries);
  178. Assert.True(existingConfig.EnableLogging);
  179. }
  180. [Fact]
  181. public void BindFrom_EmptySection_ShouldReturnDefaultValues()
  182. {
  183. // Arrange
  184. var jsonPath = Path.Combine(_testDir, "empty.json");
  185. File.WriteAllText(jsonPath, @"{}");
  186. var cfg = new CfgBuilder()
  187. .AddJson(jsonPath, level: 0, writeable: false)
  188. .Build();
  189. // Act
  190. var section = cfg.GetSection("NonExistent");
  191. var config = TestDatabaseConfig.BindFrom(section);
  192. // Assert
  193. Assert.Null(config.ConnectionString);
  194. Assert.Equal(0, config.Timeout);
  195. Assert.Equal(0, config.MaxRetries);
  196. Assert.False(config.EnableLogging);
  197. }
  198. [Fact]
  199. public void GeneratedExtension_ShouldWork()
  200. {
  201. // Arrange
  202. var jsonPath = Path.Combine(_testDir, "ext.json");
  203. File.WriteAllText(jsonPath, @"{
  204. ""TestDatabase"": {
  205. ""ConnectionString"": ""ExtTest"",
  206. ""Timeout"": 99
  207. }
  208. }");
  209. var cfg = new CfgBuilder()
  210. .AddJson(jsonPath, level: 0, writeable: false)
  211. .Build();
  212. // Act - 使用生成的扩展方法
  213. var config = cfg.GetTestDatabaseConfig();
  214. // Assert
  215. Assert.Equal("ExtTest", config.ConnectionString);
  216. Assert.Equal(99, config.Timeout);
  217. }
  218. public void Dispose()
  219. {
  220. try
  221. {
  222. if (Directory.Exists(_testDir))
  223. {
  224. Directory.Delete(_testDir, true);
  225. }
  226. }
  227. catch { }
  228. }
  229. }
  230. // 测试用配置类 - 使用 [CfgSection] 特性标记
  231. [CfgSection("TestDatabase")]
  232. public partial class TestDatabaseConfig
  233. {
  234. public string? ConnectionString { get; set; }
  235. public int Timeout { get; set; }
  236. public int MaxRetries { get; set; }
  237. public bool EnableLogging { get; set; }
  238. }
  239. [CfgSection("App")]
  240. public partial class TestAppConfig
  241. {
  242. public string? Name { get; set; }
  243. public string? Version { get; set; }
  244. public TestDatabaseConfig? Database { get; set; }
  245. }
  246. [CfgSection("Server")]
  247. public partial class TestServerConfig
  248. {
  249. public string? Name { get; set; }
  250. public int[]? Ports { get; set; }
  251. public List<string>? AllowedHosts { get; set; }
  252. }
  253. [CfgSection("Features")]
  254. public partial class TestFeaturesConfig
  255. {
  256. public string? Name { get; set; }
  257. public Dictionary<string, bool>? Flags { get; set; }
  258. }
  259. [CfgSection("Optional")]
  260. public partial class TestOptionalConfig
  261. {
  262. public string? RequiredValue { get; set; }
  263. public int? OptionalInt { get; set; }
  264. public string? OptionalString { get; set; }
  265. }