SourcesManagerTests.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  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 Load_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 Load_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 Load_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 Load_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 Load_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 Load_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 Load_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 Load_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. //[Fact]
  180. //public void Update_WithValidJson_UpdatesThemeScope ()
  181. //{
  182. // // Arrange
  183. // var sourcesManager = new SourcesManager ();
  184. // var themeScope = new ThemeScope ();
  185. // themeScope.LoadHardCodedDefaults ();
  186. // themeScope ["Button.DefaultShadowStyle"].PropertyValue = ShadowStyle.Opaque;
  187. // var json = """
  188. // {
  189. // "Button.DefaultShadowStyle": "None"
  190. // }
  191. // """;
  192. // var source = "test.json";
  193. // var location = ConfigLocations.HardCoded;
  194. // // Act
  195. // bool result = sourcesManager.Load (themeScope, json, source, location);
  196. // // Assert
  197. // Assert.True (result);
  198. // Assert.Equal (Key.Z.WithCtrl, themeScope ["Application.QuitKey"].PropertyValue as Key);
  199. // Assert.Contains (source, sourcesManager.Sources.Values);
  200. //}
  201. #endregion
  202. #region Load
  203. [Fact]
  204. public void Load_WithNullResourceName_ReturnsFalse ()
  205. {
  206. // Arrange
  207. var sourcesManager = new SourcesManager ();
  208. var settingsScope = new SettingsScope ();
  209. settingsScope.LoadHardCodedDefaults ();
  210. settingsScope ["Application.QuitKey"].PropertyValue = Key.Q.WithCtrl;
  211. var assembly = Assembly.GetExecutingAssembly ();
  212. var location = ConfigLocations.AppResources;
  213. // Act
  214. bool result = sourcesManager.Load (settingsScope, assembly, null, location);
  215. // Assert
  216. Assert.False (result);
  217. }
  218. [Fact]
  219. public void Load_WithValidResource_UpdatesSettingsScope ()
  220. {
  221. // Arrange
  222. var sourcesManager = new SourcesManager ();
  223. var settingsScope = new SettingsScope ();
  224. var assembly = Assembly.GetAssembly (typeof (ConfigurationManager));
  225. var resourceName = "Terminal.Gui.Resources.config.json";
  226. var location = ConfigLocations.LibraryResources;
  227. // Act
  228. bool result = sourcesManager.Load (settingsScope, assembly!, resourceName, location);
  229. // Assert
  230. Assert.True (result);
  231. // Verify settingsScope is updated as expected
  232. }
  233. [Fact]
  234. public void Load_Runtime_Overrides ()
  235. {
  236. // Arrange
  237. var sourcesManager = new SourcesManager ();
  238. var settingsScope = new SettingsScope ();
  239. var assembly = Assembly.GetAssembly (typeof (ConfigurationManager));
  240. var resourceName = "Terminal.Gui.Resources.config.json";
  241. var location = ConfigLocations.LibraryResources;
  242. sourcesManager.Load (settingsScope, assembly!, resourceName, location);
  243. Assert.Equal (Key.Esc, settingsScope ["Application.QuitKey"].PropertyValue);
  244. var runtimeJson = """
  245. {
  246. "Application.QuitKey": "Ctrl+Z"
  247. }
  248. """;
  249. var runtimeSource = "runtime.json";
  250. var runtimeLocation = ConfigLocations.Runtime;
  251. var runtimeStream = new MemoryStream ();
  252. var writer = new StreamWriter (runtimeStream);
  253. writer.Write (runtimeJson);
  254. writer.Flush ();
  255. runtimeStream.Position = 0;
  256. // Act
  257. bool result = sourcesManager.Load (settingsScope, runtimeStream, runtimeSource, runtimeLocation);
  258. // Assert
  259. Assert.True (result);
  260. // Verify settingsScope is updated as expected
  261. Assert.Equal (Key.Z.WithCtrl, settingsScope ["Application.QuitKey"].PropertyValue);
  262. }
  263. #endregion
  264. #region ToJson and ToStream
  265. [Fact]
  266. public void ToJson_WithValidScope_ReturnsJsonString ()
  267. {
  268. // Arrange
  269. var sourcesManager = new SourcesManager ();
  270. var settingsScope = new SettingsScope ();
  271. settingsScope.LoadHardCodedDefaults ();
  272. settingsScope ["Application.QuitKey"].PropertyValue = Key.Q.WithCtrl;
  273. // Act
  274. string json = sourcesManager.ToJson (settingsScope);
  275. // Assert
  276. Assert.Contains ("""Application.QuitKey": "Ctrl+Q""", json);
  277. }
  278. [Fact]
  279. public void ToStream_WithValidScope_ReturnsStream ()
  280. {
  281. // Arrange
  282. var sourcesManager = new SourcesManager ();
  283. var settingsScope = new SettingsScope ();
  284. settingsScope.LoadHardCodedDefaults ();
  285. settingsScope ["Application.QuitKey"].PropertyValue = Key.Q.WithCtrl;
  286. // Act
  287. var stream = sourcesManager.ToStream (settingsScope);
  288. // Assert
  289. Assert.NotNull (stream);
  290. stream.Position = 0;
  291. var reader = new StreamReader (stream);
  292. string json = reader.ReadToEnd ();
  293. Assert.Contains ("""Application.QuitKey": "Ctrl+Q""", json);
  294. }
  295. #endregion
  296. #region Sources Dictionary Tests
  297. [Fact]
  298. public void Sources_Dictionary_IsInitializedEmpty ()
  299. {
  300. // Arrange & Act
  301. var sourcesManager = new SourcesManager ();
  302. // Assert
  303. Assert.NotNull (sourcesManager.Sources);
  304. Assert.Empty (sourcesManager.Sources);
  305. }
  306. [Fact]
  307. public void Load_WhenCalledMultipleTimes_MaintainsLastSourceForLocation ()
  308. {
  309. // Arrange
  310. var sourcesManager = new SourcesManager ();
  311. var settingsScope = new SettingsScope ();
  312. // Act - Update with first source for location
  313. var firstSource = "first.json";
  314. sourcesManager.Load (settingsScope, """{"Application.QuitKey": "Ctrl+A"}""", firstSource, ConfigLocations.Runtime);
  315. // Update with second source for same location
  316. var secondSource = "second.json";
  317. sourcesManager.Load (settingsScope, """{"Application.QuitKey": "Ctrl+B"}""", secondSource, ConfigLocations.Runtime);
  318. // Assert - Only the last source should be stored for the location
  319. Assert.Single (sourcesManager.Sources);
  320. Assert.Equal (secondSource, sourcesManager.Sources [ConfigLocations.Runtime]);
  321. }
  322. [Fact]
  323. public void Load_WithDifferentLocations_AddsAllSourcesToCollection ()
  324. {
  325. // Arrange
  326. var sourcesManager = new SourcesManager ();
  327. var settingsScope = new SettingsScope ();
  328. ConfigLocations [] locations =
  329. [
  330. ConfigLocations.LibraryResources,
  331. ConfigLocations.Runtime,
  332. ConfigLocations.AppCurrent,
  333. ConfigLocations.GlobalHome
  334. ];
  335. // Act - Update with different sources for different locations
  336. foreach (var location in locations)
  337. {
  338. var source = $"config-{location}.json";
  339. sourcesManager.Load (settingsScope, """{"Application.QuitKey": "Ctrl+Z"}""", source, location);
  340. }
  341. // Assert
  342. Assert.Equal (locations.Length, sourcesManager.Sources.Count);
  343. foreach (var location in locations)
  344. {
  345. Assert.Contains (location, sourcesManager.Sources.Keys);
  346. Assert.Equal ($"config-{location}.json", sourcesManager.Sources [location]);
  347. }
  348. }
  349. [Fact]
  350. public void Load_AddsResourceSourceToCollection ()
  351. {
  352. // Arrange
  353. var sourcesManager = new SourcesManager ();
  354. var settingsScope = new SettingsScope ();
  355. var assembly = Assembly.GetAssembly (typeof (ConfigurationManager));
  356. var resourceName = "Terminal.Gui.Resources.config.json";
  357. var location = ConfigLocations.LibraryResources;
  358. // Act
  359. bool result = sourcesManager.Load (settingsScope, assembly!, resourceName, location);
  360. // Assert
  361. Assert.True (result);
  362. Assert.Contains (location, sourcesManager.Sources.Keys);
  363. Assert.Equal ($"resource://[{assembly!.GetName ().Name}]/{resourceName}", sourcesManager.Sources [location]);
  364. }
  365. [Fact]
  366. public void Load_WithNonExistentFileAndDifferentLocations_TracksAllSources ()
  367. {
  368. // Arrange
  369. var sourcesManager = new SourcesManager ();
  370. var settingsScope = new SettingsScope ();
  371. // Define multiple files and locations
  372. var fileLocations = new Dictionary<string, ConfigLocations> (StringComparer.InvariantCultureIgnoreCase)
  373. {
  374. { "file1.json", ConfigLocations.AppCurrent },
  375. { "file2.json", ConfigLocations.GlobalHome },
  376. { "file3.json", ConfigLocations.AppHome }
  377. };
  378. // Act
  379. foreach (var pair in fileLocations)
  380. {
  381. sourcesManager.Load (settingsScope, pair.Key, pair.Value);
  382. }
  383. // Assert
  384. Assert.Equal (fileLocations.Count, sourcesManager.Sources.Count);
  385. foreach (var pair in fileLocations)
  386. {
  387. Assert.Contains (pair.Value, sourcesManager.Sources.Keys);
  388. Assert.Equal (pair.Key, sourcesManager.Sources [pair.Value]);
  389. }
  390. }
  391. [Fact]
  392. public void Sources_IsPreservedAcrossOperations ()
  393. {
  394. // Arrange
  395. var sourcesManager = new SourcesManager ();
  396. var settingsScope = new SettingsScope ();
  397. // First operation - file update
  398. var filePath = "testfile.json";
  399. var location1 = ConfigLocations.AppCurrent;
  400. sourcesManager.Load (settingsScope, filePath, location1);
  401. // Second operation - json string update
  402. var jsonSource = "jsonstring";
  403. var location2 = ConfigLocations.Runtime;
  404. sourcesManager.Load (settingsScope, """{"Application.QuitKey": "Ctrl+Z"}""", jsonSource, location2);
  405. // Perform a stream operation
  406. var streamSource = "streamdata";
  407. var location3 = ConfigLocations.GlobalCurrent;
  408. var stream = new MemoryStream ();
  409. var writer = new StreamWriter (stream);
  410. writer.Write ("""{"Application.QuitKey": "Ctrl+Z"}""");
  411. writer.Flush ();
  412. stream.Position = 0;
  413. sourcesManager.Load (settingsScope, stream, streamSource, location3);
  414. // Assert - all sources should be preserved
  415. Assert.Equal (3, sourcesManager.Sources.Count);
  416. Assert.Equal (filePath, sourcesManager.Sources [location1]);
  417. Assert.Equal (jsonSource, sourcesManager.Sources [location2]);
  418. Assert.Equal (streamSource, sourcesManager.Sources [location3]);
  419. }
  420. [Fact]
  421. public void Sources_StaysConsistentWhenUpdateFails ()
  422. {
  423. // Arrange
  424. var sourcesManager = new SourcesManager ();
  425. var settingsScope = new SettingsScope ();
  426. // Add one successful source
  427. var validSource = "valid.json";
  428. var validLocation = ConfigLocations.Runtime;
  429. sourcesManager.Load (settingsScope, """{"Application.QuitKey": "Ctrl+Z"}""", validSource, validLocation);
  430. try
  431. {
  432. // Configure to throw on errors
  433. ConfigurationManager.ThrowOnJsonErrors = true;
  434. // Act & Assert - attempt to update with invalid JSON
  435. var invalidSource = "invalid.json";
  436. var invalidLocation = ConfigLocations.AppCurrent;
  437. var invalidJson = "{ invalid json }";
  438. Assert.Throws<JsonException> (
  439. () =>
  440. sourcesManager.Load (settingsScope, invalidJson, invalidSource, invalidLocation));
  441. // The valid source should still be there
  442. Assert.Single (sourcesManager.Sources);
  443. Assert.Equal (validSource, sourcesManager.Sources [validLocation]);
  444. // The invalid source should not have been added
  445. Assert.DoesNotContain (invalidLocation, sourcesManager.Sources.Keys);
  446. }
  447. finally
  448. {
  449. // Reset for other tests
  450. ConfigurationManager.ThrowOnJsonErrors = false;
  451. }
  452. }
  453. #endregion
  454. }