SourcesManagerTests.cs 17 KB

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