Files
PLib/tests/PLib.Tests/Settings/AppSettingsStoreTests.cs
T

135 lines
4.6 KiB
C#

using Microsoft.Extensions.Configuration;
using PLib.Application.Library;
using PLib.Desktop.Services;
using PLib.Desktop.Settings;
using PLib.Infrastructure.Storage;
using Shouldly;
namespace PLib.Tests.Settings;
/// <summary>
/// The settings file is not just storage — it is a live configuration source. These tests
/// close the loop: what the store writes has to be what the options binder reads back.
/// </summary>
public sealed class AppSettingsStoreTests : IDisposable
{
private readonly TempPaths _paths = new();
public void Dispose() => _paths.Dispose();
[Fact]
public async Task Saved_settings_are_readable_by_the_configuration_binder()
{
var settings = new AppSettings
{
Folders = [@"C:\videos", @"D:\more videos"],
ThumbnailWidth = 640,
ThumbnailPositionRatio = 0.25,
MaxIndexingConcurrency = 8,
MinimumFileSizeInBytes = 2_097_152,
Theme = ThemeMode.Light,
};
await new JsonAppSettingsStore(_paths).SaveAsync(settings, Token);
var (library, appearance) = Reload();
library.Folders.ShouldBe(settings.Folders);
library.ThumbnailWidth.ShouldBe(640);
library.ThumbnailPositionRatio.ShouldBe(0.25);
library.MaxIndexingConcurrency.ShouldBe(8);
library.MinimumFileSizeInBytes.ShouldBe(2_097_152);
appearance.Theme.ShouldBe(ThemeMode.Light);
}
[Fact]
public async Task Removing_a_folder_actually_shortens_the_stored_list()
{
var store = new JsonAppSettingsStore(_paths);
var settings = Sample with { Folders = [@"C:\a", @"C:\b", @"C:\c"] };
await store.SaveAsync(settings, Token);
await store.SaveAsync(settings with { Folders = [@"C:\a", @"C:\c"] }, Token);
// Configuration merges arrays by index, so a shorter list is the case most likely
// to leave a stale entry behind.
Reload().Library.Folders.ShouldBe([@"C:\a", @"C:\c"]);
}
[Fact]
public async Task Keys_the_settings_screen_does_not_know_about_survive_a_save()
{
await File.WriteAllTextAsync(
Path.Combine(_paths.DataDirectory, "settings.json"),
"""{ "Library": { "VideoExtensions": [ ".mp4" ] }, "Experimental": { "Flag": true } }""",
Token);
await new JsonAppSettingsStore(_paths).SaveAsync(Sample, Token);
var configuration = Build();
configuration["Experimental:Flag"].ShouldBe("True");
configuration["Library:VideoExtensions:0"].ShouldBe(".mp4");
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void Only_changes_that_affect_the_contents_of_the_library_ask_for_a_rescan(bool cosmeticOnly)
{
var changed = cosmeticOnly
? Sample with { ThumbnailWidth = 999, Theme = ThemeMode.Dark }
: Sample with { Folders = [@"C:\elsewhere"] };
changed.RequiresRescanComparedTo(Sample).ShouldBe(!cosmeticOnly);
}
private static CancellationToken Token => TestContext.Current.CancellationToken;
private static AppSettings Sample => new()
{
Folders = [@"C:\videos"],
ThumbnailWidth = 480,
ThumbnailPositionRatio = 0.15,
MaxIndexingConcurrency = 4,
MinimumFileSizeInBytes = 65_536,
Theme = ThemeMode.System,
};
private IConfigurationRoot Build() => new ConfigurationBuilder()
.AddJsonFile(Path.Combine(_paths.DataDirectory, "settings.json"), optional: false)
.Build();
private (LibraryOptions Library, AppearanceOptions Appearance) Reload()
{
var configuration = Build();
var library = new LibraryOptions();
configuration.GetSection(LibraryOptions.SectionName).Bind(library);
var appearance = new AppearanceOptions();
configuration.GetSection(AppearanceOptions.SectionName).Bind(appearance);
return (library, appearance);
}
private sealed class TempPaths : IAppPaths, IDisposable
{
public TempPaths()
{
DataDirectory = Path.Combine(Path.GetTempPath(), $"plib-tests-{Guid.CreateVersion7()}");
ThumbnailDirectory = Path.Combine(DataDirectory, "thumbnails");
DatabaseFile = Path.Combine(DataDirectory, "library.db");
Directory.CreateDirectory(ThumbnailDirectory);
}
public string DataDirectory { get; }
public string ThumbnailDirectory { get; }
public string DatabaseFile { get; }
public void Dispose() => Directory.Delete(DataDirectory, recursive: true);
}
}