SourcesManagerTests.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. using System.Reflection;
  2. using System.Text.Json;
  3. namespace UnitTests_Parallelizable.ConfigurationTests;
  4. public class SourcesManagerTests
  5. {
  6. #region Update (Stream)
  7. [Fact]
  8. public void Update_WithNullSettingsScope_ReturnsFalse ()
  9. {
  10. // Arrange
  11. var sourcesManager = new SourcesManager ();
  12. var stream = new MemoryStream ();
  13. var source = "test.json";
  14. var location = ConfigLocations.AppCurrent;
  15. // Act
  16. bool result = sourcesManager.Load (null, stream, source, location);
  17. // Assert
  18. Assert.False (result);
  19. }
  20. [Fact]
  21. public void Update_WithValidStream_UpdatesSettingsScope ()
  22. {
  23. // Arrange
  24. var sourcesManager = new SourcesManager ();
  25. var settingsScope = new SettingsScope ();
  26. settingsScope.LoadHardCodedDefaults ();
  27. settingsScope ["Application.QuitKey"].PropertyValue = Key.Q.WithCtrl;
  28. var json = """
  29. {
  30. "Application.QuitKey": "Ctrl+Z"
  31. }
  32. """;
  33. var location = ConfigLocations.HardCoded;
  34. var source = "stream";
  35. var stream = new MemoryStream ();
  36. var writer = new StreamWriter (stream);
  37. writer.Write (json);
  38. writer.Flush ();
  39. stream.Position = 0;
  40. // Act
  41. bool result = sourcesManager.Load (settingsScope, stream, source, location);
  42. // Assert
  43. // Assert
  44. Assert.True (result);
  45. Assert.Equal (Key.Z.WithCtrl, settingsScope ["Application.QuitKey"].PropertyValue as Key);
  46. Assert.Contains (source, sourcesManager.Sources.Values);
  47. }
  48. [Fact]
  49. public void Update_WithInvalidJson_AddsJsonError ()
  50. {
  51. // Arrange
  52. var sourcesManager = new SourcesManager ();
  53. var settingsScope = new SettingsScope ();
  54. var invalidJson = "{ invalid json }";
  55. var stream = new MemoryStream ();
  56. var writer = new StreamWriter (stream);
  57. writer.Write (invalidJson);
  58. writer.Flush ();
  59. stream.Position = 0;
  60. var source = "test.json";
  61. var location = ConfigLocations.AppCurrent;
  62. // Act
  63. bool result = sourcesManager.Load (settingsScope, stream, source, location);
  64. // Assert
  65. Assert.False (result);
  66. // Assuming AddJsonError logs errors, verify the error was logged (mock or inspect logs if possible).
  67. }
  68. #endregion
  69. #region Update (FilePath)
  70. [Fact]
  71. public void Update_WithNonExistentFile_AddsToSourcesAndReturnsTrue ()
  72. {
  73. // Arrange
  74. var sourcesManager = new SourcesManager ();
  75. var settingsScope = new SettingsScope ();
  76. var filePath = "nonexistent.json";
  77. var location = ConfigLocations.AppCurrent;
  78. // Act
  79. bool result = sourcesManager.Load (settingsScope, filePath, location);
  80. // Assert
  81. Assert.True (result);
  82. Assert.Contains (filePath, sourcesManager.Sources.Values);
  83. }
  84. [Fact]
  85. public void Update_WithValidFile_UpdatesSettingsScope ()
  86. {
  87. // Arrange
  88. var sourcesManager = new SourcesManager ();
  89. var settingsScope = new SettingsScope ();
  90. settingsScope.LoadHardCodedDefaults ();
  91. settingsScope ["Application.QuitKey"].PropertyValue = Key.Q.WithCtrl;
  92. var json = """
  93. {
  94. "Application.QuitKey": "Ctrl+Z"
  95. }
  96. """;
  97. var source = Path.GetTempFileName ();
  98. var location = ConfigLocations.HardCoded;
  99. File.WriteAllText (source, json);
  100. try
  101. {
  102. // Act
  103. bool result = sourcesManager.Load (settingsScope, source, location);
  104. // Assert
  105. Assert.True (result);
  106. Assert.Equal (Key.Z.WithCtrl, settingsScope ["Application.QuitKey"].PropertyValue as Key);
  107. Assert.Contains (source, sourcesManager.Sources.Values);
  108. }
  109. finally
  110. {
  111. // Cleanup
  112. File.Delete (source);
  113. }
  114. }
  115. [Fact]
  116. public void Update_WithIOException_RetriesAndFailsGracefully ()
  117. {
  118. // Arrange
  119. var sourcesManager = new SourcesManager ();
  120. var settingsScope = new SettingsScope ();
  121. settingsScope.LoadHardCodedDefaults ();
  122. var filePath = "locked.json";
  123. var json = "{\"Application.UseSystemConsole\": true}";
  124. File.WriteAllText (filePath, json);
  125. var location = ConfigLocations.AppCurrent;
  126. try
  127. {
  128. using FileStream fileStream = File.Open (filePath, FileMode.Open, FileAccess.Read, FileShare.None);
  129. // Act
  130. bool result = sourcesManager.Load (settingsScope, filePath, location);
  131. // Assert
  132. Assert.False (result);
  133. }
  134. finally
  135. {
  136. // Cleanup
  137. File.Delete (filePath);
  138. }
  139. }
  140. #endregion
  141. #region Update (Json String)
  142. [Fact]
  143. public void Update_WithNullOrEmptyJson_ReturnsFalse ()
  144. {
  145. // Arrange
  146. var sourcesManager = new SourcesManager ();
  147. var settingsScope = new SettingsScope ();
  148. var source = "test.json";
  149. var location = ConfigLocations.AppCurrent;
  150. // Act
  151. bool resultWithNull = sourcesManager.Load (settingsScope, json: null, source, location);
  152. bool resultWithEmpty = sourcesManager.Load (settingsScope, string.Empty, source, location);
  153. // Assert
  154. Assert.False (resultWithNull);
  155. Assert.False (resultWithEmpty);
  156. }
  157. [Fact]
  158. public void Update_WithValidJson_UpdatesSettingsScope ()
  159. {
  160. // Arrange
  161. var sourcesManager = new SourcesManager ();
  162. var settingsScope = new SettingsScope ();
  163. settingsScope.LoadHardCodedDefaults ();
  164. settingsScope ["Application.QuitKey"].PropertyValue = Key.Q.WithCtrl;
  165. var json = """
  166. {
  167. "Application.QuitKey": "Ctrl+Z"
  168. }
  169. """;
  170. var source = "test.json";
  171. var location = ConfigLocations.HardCoded;
  172. // Act
  173. bool result = sourcesManager.Load (settingsScope, json, source, location);
  174. // Assert
  175. Assert.True (result);
  176. Assert.Equal (Key.Z.WithCtrl, settingsScope ["Application.QuitKey"].PropertyValue as Key);
  177. Assert.Contains (source, sourcesManager.Sources.Values);
  178. }
  179. #endregion
  180. #region Load
  181. [Fact]
  182. public void Load_WithNullResourceName_ReturnsFalse ()
  183. {
  184. // Arrange
  185. var sourcesManager = new SourcesManager ();
  186. var settingsScope = new SettingsScope ();
  187. settingsScope.LoadHardCodedDefaults ();
  188. settingsScope ["Application.QuitKey"].PropertyValue = Key.Q.WithCtrl;
  189. var assembly = Assembly.GetExecutingAssembly ();
  190. var location = ConfigLocations.AppResources;
  191. // Act
  192. bool result = sourcesManager.Load (settingsScope, assembly, null, location);
  193. // Assert
  194. Assert.False (result);
  195. }
  196. [Fact]
  197. public void Load_WithValidResource_UpdatesSettingsScope ()
  198. {
  199. // Arrange
  200. var sourcesManager = new SourcesManager ();
  201. var settingsScope = new SettingsScope ();
  202. var assembly = Assembly.GetAssembly (typeof (ConfigurationManager));
  203. var resourceName = "Terminal.Gui.Resources.config.json";
  204. var location = ConfigLocations.LibraryResources;
  205. // Act
  206. bool result = sourcesManager.Load (settingsScope, assembly!, resourceName, location);
  207. // Assert
  208. Assert.True (result);
  209. // Verify settingsScope is updated as expected
  210. }
  211. [Fact]
  212. public void Load_Runtime_Overrides ()
  213. {
  214. // Arrange
  215. var sourcesManager = new SourcesManager ();
  216. var settingsScope = new SettingsScope ();
  217. var assembly = Assembly.GetAssembly (typeof (ConfigurationManager));
  218. var resourceName = "Terminal.Gui.Resources.config.json";
  219. var location = ConfigLocations.LibraryResources;
  220. sourcesManager.Load (settingsScope, assembly!, resourceName, location);
  221. Assert.Equal (Key.Esc, settingsScope ["Application.QuitKey"].PropertyValue);
  222. var runtimeJson = """
  223. {
  224. "Application.QuitKey": "Ctrl+Z"
  225. }
  226. """;
  227. var runtimeSource = "runtime.json";
  228. var runtimeLocation = ConfigLocations.Runtime;
  229. var runtimeStream = new MemoryStream ();
  230. var writer = new StreamWriter (runtimeStream);
  231. writer.Write (runtimeJson);
  232. writer.Flush ();
  233. runtimeStream.Position = 0;
  234. // Act
  235. bool result = sourcesManager.Load (settingsScope, runtimeStream, runtimeSource, runtimeLocation);
  236. // Assert
  237. Assert.True (result);
  238. // Verify settingsScope is updated as expected
  239. Assert.Equal (Key.Z.WithCtrl, settingsScope ["Application.QuitKey"].PropertyValue);
  240. }
  241. #endregion
  242. #region ToJson and ToStream
  243. [Fact]
  244. public void ToJson_WithValidScope_ReturnsJsonString ()
  245. {
  246. // Arrange
  247. var sourcesManager = new SourcesManager ();
  248. var settingsScope = new SettingsScope ();
  249. settingsScope.LoadHardCodedDefaults ();
  250. settingsScope ["Application.QuitKey"].PropertyValue = Key.Q.WithCtrl;
  251. // Act
  252. string json = sourcesManager.ToJson (settingsScope);
  253. // Assert
  254. Assert.Contains ("""Application.QuitKey": "Ctrl+Q""", json);
  255. }
  256. [Fact]
  257. public void ToStream_WithValidScope_ReturnsStream ()
  258. {
  259. // Arrange
  260. var sourcesManager = new SourcesManager ();
  261. var settingsScope = new SettingsScope ();
  262. settingsScope.LoadHardCodedDefaults ();
  263. settingsScope ["Application.QuitKey"].PropertyValue = Key.Q.WithCtrl;
  264. // Act
  265. var stream = sourcesManager.ToStream (settingsScope);
  266. // Assert
  267. Assert.NotNull (stream);
  268. stream.Position = 0;
  269. var reader = new StreamReader (stream);
  270. string json = reader.ReadToEnd ();
  271. Assert.Contains ("""Application.QuitKey": "Ctrl+Q""", json);
  272. }
  273. #endregion
  274. #region Sources Dictionary Tests
  275. [Fact]
  276. public void Sources_Dictionary_IsInitializedEmpty ()
  277. {
  278. // Arrange & Act
  279. var sourcesManager = new SourcesManager ();
  280. // Assert
  281. Assert.NotNull (sourcesManager.Sources);
  282. Assert.Empty (sourcesManager.Sources);
  283. }
  284. [Fact]
  285. public void Update_WhenCalledMultipleTimes_MaintainsLastSourceForLocation ()
  286. {
  287. // Arrange
  288. var sourcesManager = new SourcesManager ();
  289. var settingsScope = new SettingsScope ();
  290. // Act - Update with first source for location
  291. var firstSource = "first.json";
  292. sourcesManager.Load (settingsScope, """{"Application.QuitKey": "Ctrl+A"}""", firstSource, ConfigLocations.Runtime);
  293. // Update with second source for same location
  294. var secondSource = "second.json";
  295. sourcesManager.Load (settingsScope, """{"Application.QuitKey": "Ctrl+B"}""", secondSource, ConfigLocations.Runtime);
  296. // Assert - Only the last source should be stored for the location
  297. Assert.Single (sourcesManager.Sources);
  298. Assert.Equal (secondSource, sourcesManager.Sources [ConfigLocations.Runtime]);
  299. }
  300. [Fact]
  301. public void Update_WithDifferentLocations_AddsAllSourcesToCollection ()
  302. {
  303. // Arrange
  304. var sourcesManager = new SourcesManager ();
  305. var settingsScope = new SettingsScope ();
  306. ConfigLocations [] locations =
  307. [
  308. ConfigLocations.LibraryResources,
  309. ConfigLocations.Runtime,
  310. ConfigLocations.AppCurrent,
  311. ConfigLocations.GlobalHome
  312. ];
  313. // Act - Update with different sources for different locations
  314. foreach (var location in locations)
  315. {
  316. var source = $"config-{location}.json";
  317. sourcesManager.Load (settingsScope, """{"Application.QuitKey": "Ctrl+Z"}""", source, location);
  318. }
  319. // Assert
  320. Assert.Equal (locations.Length, sourcesManager.Sources.Count);
  321. foreach (var location in locations)
  322. {
  323. Assert.Contains (location, sourcesManager.Sources.Keys);
  324. Assert.Equal ($"config-{location}.json", sourcesManager.Sources [location]);
  325. }
  326. }
  327. [Fact]
  328. public void Load_AddsResourceSourceToCollection ()
  329. {
  330. // Arrange
  331. var sourcesManager = new SourcesManager ();
  332. var settingsScope = new SettingsScope ();
  333. var assembly = Assembly.GetAssembly (typeof (ConfigurationManager));
  334. var resourceName = "Terminal.Gui.Resources.config.json";
  335. var location = ConfigLocations.LibraryResources;
  336. // Act
  337. bool result = sourcesManager.Load (settingsScope, assembly!, resourceName, location);
  338. // Assert
  339. Assert.True (result);
  340. Assert.Contains (location, sourcesManager.Sources.Keys);
  341. Assert.Equal ($"resource://[{assembly!.GetName ().Name}]/{resourceName}", sourcesManager.Sources [location]);
  342. }
  343. [Fact]
  344. public void Update_WithNonExistentFileAndDifferentLocations_TracksAllSources ()
  345. {
  346. // Arrange
  347. var sourcesManager = new SourcesManager ();
  348. var settingsScope = new SettingsScope ();
  349. // Define multiple files and locations
  350. var fileLocations = new Dictionary<string, ConfigLocations> (StringComparer.InvariantCultureIgnoreCase)
  351. {
  352. { "file1.json", ConfigLocations.AppCurrent },
  353. { "file2.json", ConfigLocations.GlobalHome },
  354. { "file3.json", ConfigLocations.AppHome }
  355. };
  356. // Act
  357. foreach (var pair in fileLocations)
  358. {
  359. sourcesManager.Load (settingsScope, pair.Key, pair.Value);
  360. }
  361. // Assert
  362. Assert.Equal (fileLocations.Count, sourcesManager.Sources.Count);
  363. foreach (var pair in fileLocations)
  364. {
  365. Assert.Contains (pair.Value, sourcesManager.Sources.Keys);
  366. Assert.Equal (pair.Key, sourcesManager.Sources [pair.Value]);
  367. }
  368. }
  369. [Fact]
  370. public void Sources_IsPreservedAcrossOperations ()
  371. {
  372. // Arrange
  373. var sourcesManager = new SourcesManager ();
  374. var settingsScope = new SettingsScope ();
  375. // First operation - file update
  376. var filePath = "testfile.json";
  377. var location1 = ConfigLocations.AppCurrent;
  378. sourcesManager.Load (settingsScope, filePath, location1);
  379. // Second operation - json string update
  380. var jsonSource = "jsonstring";
  381. var location2 = ConfigLocations.Runtime;
  382. sourcesManager.Load (settingsScope, """{"Application.QuitKey": "Ctrl+Z"}""", jsonSource, location2);
  383. // Perform a stream operation
  384. var streamSource = "streamdata";
  385. var location3 = ConfigLocations.GlobalCurrent;
  386. var stream = new MemoryStream ();
  387. var writer = new StreamWriter (stream);
  388. writer.Write ("""{"Application.QuitKey": "Ctrl+Z"}""");
  389. writer.Flush ();
  390. stream.Position = 0;
  391. sourcesManager.Load (settingsScope, stream, streamSource, location3);
  392. // Assert - all sources should be preserved
  393. Assert.Equal (3, sourcesManager.Sources.Count);
  394. Assert.Equal (filePath, sourcesManager.Sources [location1]);
  395. Assert.Equal (jsonSource, sourcesManager.Sources [location2]);
  396. Assert.Equal (streamSource, sourcesManager.Sources [location3]);
  397. }
  398. [Fact]
  399. public void Sources_StaysConsistentWhenUpdateFails ()
  400. {
  401. // Arrange
  402. var sourcesManager = new SourcesManager ();
  403. var settingsScope = new SettingsScope ();
  404. // Add one successful source
  405. var validSource = "valid.json";
  406. var validLocation = ConfigLocations.Runtime;
  407. sourcesManager.Load (settingsScope, """{"Application.QuitKey": "Ctrl+Z"}""", validSource, validLocation);
  408. try
  409. {
  410. // Configure to throw on errors
  411. ConfigurationManager.ThrowOnJsonErrors = true;
  412. // Act & Assert - attempt to update with invalid JSON
  413. var invalidSource = "invalid.json";
  414. var invalidLocation = ConfigLocations.AppCurrent;
  415. var invalidJson = "{ invalid json }";
  416. Assert.Throws<JsonException> (
  417. () =>
  418. sourcesManager.Load (settingsScope, invalidJson, invalidSource, invalidLocation));
  419. // The valid source should still be there
  420. Assert.Single (sourcesManager.Sources);
  421. Assert.Equal (validSource, sourcesManager.Sources [validLocation]);
  422. // The invalid source should not have been added
  423. Assert.DoesNotContain (invalidLocation, sourcesManager.Sources.Keys);
  424. }
  425. finally
  426. {
  427. // Reset for other tests
  428. ConfigurationManager.ThrowOnJsonErrors = false;
  429. }
  430. }
  431. #endregion
  432. }