Files
YKanBan/YKanBan.Tests/Storage/AppData/AppConfigStoreTests.cs
T

200 lines
8.2 KiB
C#

using YKanBan.Storage.AppData;
using YKanBan.Tests.TestUtilities;
namespace YKanBan.Tests.Storage.AppData;
/// <summary>
/// Tests for app.json round-tripping, kebab-case naming and graceful
/// degradation on a missing or corrupt file.
/// </summary>
[TestClass]
public class AppConfigStoreTests {
[TestMethod]
public void LoadMissingFileReturnsDefaults() {
using var directory = new TempDirectory();
var store = new AppConfigStore(Path.Combine(directory.FullPath, "app.json"));
AppConfig config = store.Load();
Assert.AreEqual(AppConfig.CurrentFormatVersion, config.Version);
Assert.AreEqual(AppConfig.DefaultLanguage, config.Language);
Assert.AreEqual(ThemeOption.FollowSystem, config.Theme);
Assert.AreEqual(SortOption.IdAscending, config.Display.CardSort);
Assert.AreEqual(SortOption.IdAscending, config.Display.ColumnSort);
Assert.AreEqual(ColumnWidthPreset.Standard, config.Display.ColumnWidth.Preset);
Assert.AreEqual(320, config.Display.ColumnWidth.CustomPixels);
Assert.IsTrue(config.Confirmations.DeleteCard);
Assert.IsTrue(config.Confirmations.DeleteColumn);
Assert.IsTrue(config.Confirmations.DeleteTag);
Assert.IsTrue(config.Confirmations.DiscardEdit);
}
[TestMethod]
public void LoadCorruptFileBacksUpAndReturnsDefaults() {
using var directory = new TempDirectory();
string path = Path.Combine(directory.FullPath, "app.json");
File.WriteAllText(path, "{ this is not valid json");
var store = new AppConfigStore(path);
AppConfig config = store.Load();
Assert.AreEqual(AppConfig.CurrentFormatVersion, config.Version);
Assert.IsTrue(File.Exists(path + ".bak"));
Assert.AreEqual("{ this is not valid json", File.ReadAllText(path + ".bak"));
}
[TestMethod]
public void LoadJsonNullReturnsDefaults() {
using var directory = new TempDirectory();
string path = Path.Combine(directory.FullPath, "app.json");
File.WriteAllText(path, "null");
var store = new AppConfigStore(path);
Assert.AreEqual(AppConfig.CurrentFormatVersion, store.Load().Version);
}
[TestMethod]
public void LoadNumericEnumValueIsRejectedAndBackedUp() {
using var directory = new TempDirectory();
string path = Path.Combine(directory.FullPath, "app.json");
// Integer enum values are not accepted; the file is treated like corrupt.
File.WriteAllText(path, """{ "theme": 1 }""");
var store = new AppConfigStore(path);
AppConfig config = store.Load();
Assert.AreEqual(ThemeOption.FollowSystem, config.Theme);
Assert.IsTrue(File.Exists(path + ".bak"));
}
[TestMethod]
public void LoadUnreadableFileReturnsDefaults() {
// File sharing is only reliably enforced on Windows, so this branch is exercised there.
if (!OperatingSystem.IsWindows()) {
return;
}
using var directory = new TempDirectory();
string path = Path.Combine(directory.FullPath, "app.json");
File.WriteAllText(path, """{ "version": 1 }""");
// Hold the file exclusively so the store's read fails with an I/O error.
using FileStream hold = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.None);
var store = new AppConfigStore(path);
Assert.AreEqual(AppConfig.CurrentFormatVersion, store.Load().Version);
}
[TestMethod]
public void LoadReadsKebabCaseKeysAndEnumValues() {
using var directory = new TempDirectory();
string path = Path.Combine(directory.FullPath, "app.json");
File.WriteAllText(path, $$"""
{
"version": {{AppConfig.CurrentFormatVersion}},
"language": "zh-Hans",
"theme": "dark",
"display": {
"column-width": { "preset": "custom", "custom-pixels": 400 },
"card-sort": "title-asc",
"column-sort": "id-desc"
},
"confirmations": {
"delete-card": false,
"delete-column": false,
"delete-tag": false,
"discard-edit": false
}
}
""");
var store = new AppConfigStore(path);
AppConfig config = store.Load();
Assert.AreEqual("zh-Hans", config.Language);
Assert.AreEqual(ThemeOption.Dark, config.Theme);
Assert.AreEqual(SortOption.TitleAscending, config.Display.CardSort);
Assert.AreEqual(SortOption.IdDescending, config.Display.ColumnSort);
Assert.AreEqual(ColumnWidthPreset.Custom, config.Display.ColumnWidth.Preset);
Assert.AreEqual(400, config.Display.ColumnWidth.CustomPixels);
Assert.IsFalse(config.Confirmations.DeleteCard);
Assert.IsFalse(config.Confirmations.DeleteColumn);
Assert.IsFalse(config.Confirmations.DeleteTag);
Assert.IsFalse(config.Confirmations.DiscardEdit);
}
[TestMethod]
public void LoadMissingSectionsFillsDefaults() {
using var directory = new TempDirectory();
string path = Path.Combine(directory.FullPath, "app.json");
File.WriteAllText(path, $$"""{ "version": {{AppConfig.CurrentFormatVersion}} }""");
var store = new AppConfigStore(path);
AppConfig config = store.Load();
Assert.IsNotNull(config.Display);
Assert.IsNotNull(config.Display.ColumnWidth);
Assert.IsNotNull(config.Confirmations);
Assert.AreEqual(ColumnWidthPreset.Standard, config.Display.ColumnWidth.Preset);
}
[TestMethod]
public void SaveWritesKebabCaseKeysAndEnumValues() {
using var directory = new TempDirectory();
string path = Path.Combine(directory.FullPath, "app.json");
var store = new AppConfigStore(path);
var config = new AppConfig {
Theme = ThemeOption.FollowSystem,
Display = new DisplaySettings {
CardSort = SortOption.UpdatedDescending,
ColumnSort = SortOption.CreatedAscending,
ColumnWidth = new ColumnWidthSettings { Preset = ColumnWidthPreset.Custom, CustomPixels = 500 },
},
Confirmations = new ConfirmationSettings { DeleteColumn = false },
};
store.Save(config);
string json = File.ReadAllText(path);
StringAssert.Contains(json, "\"delete-column\"");
StringAssert.Contains(json, "\"discard-edit\"");
StringAssert.Contains(json, "\"card-sort\"");
StringAssert.Contains(json, "\"column-sort\"");
StringAssert.Contains(json, "\"custom-pixels\"");
StringAssert.Contains(json, "\"follow-system\"");
StringAssert.Contains(json, "\"updated-desc\"");
StringAssert.Contains(json, "\"created-asc\"");
// No member-name CamelCase must leak into the file.
Assert.IsFalse(json.Contains("deleteColumn", StringComparison.Ordinal));
Assert.IsFalse(json.Contains("CustomPixels", StringComparison.Ordinal));
}
[TestMethod]
public void SaveThenLoadRoundTrips() {
using var directory = new TempDirectory();
string path = Path.Combine(directory.FullPath, "app.json");
var store = new AppConfigStore(path);
var config = new AppConfig {
Language = "zh-Hans",
Theme = ThemeOption.Light,
Display = new DisplaySettings {
CardSort = SortOption.CreatedDescending,
ColumnSort = SortOption.TitleAscending,
ColumnWidth = new ColumnWidthSettings { Preset = ColumnWidthPreset.Wide },
},
Confirmations = new ConfirmationSettings { DiscardEdit = false },
};
store.Save(config);
AppConfig reloaded = store.Load();
Assert.AreEqual(config.Language, reloaded.Language);
Assert.AreEqual(config.Theme, reloaded.Theme);
Assert.AreEqual(config.Display.CardSort, reloaded.Display.CardSort);
Assert.AreEqual(config.Display.ColumnSort, reloaded.Display.ColumnSort);
Assert.AreEqual(config.Display.ColumnWidth.Preset, reloaded.Display.ColumnWidth.Preset);
Assert.AreEqual(config.Confirmations.DiscardEdit, reloaded.Confirmations.DiscardEdit);
}
}