167 lines
5.8 KiB
C#
167 lines
5.8 KiB
C#
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Options;
|
|
using NSubstitute;
|
|
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,
|
|
Volume = 0.35,
|
|
IsMuted = true,
|
|
};
|
|
|
|
await CreateStore().SaveAsync(settings, Token);
|
|
|
|
var (library, appearance, playback) = 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);
|
|
playback.Volume.ShouldBe(0.35);
|
|
playback.IsMuted.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Removing_a_folder_actually_shortens_the_stored_list()
|
|
{
|
|
var store = CreateStore();
|
|
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 CreateStore().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,
|
|
Volume = 0.8,
|
|
IsMuted = false,
|
|
};
|
|
|
|
/// <summary>
|
|
/// The store composes <see cref="AppSettings.Current"/> from configuration, which these
|
|
/// tests do not exercise — every case here supplies the snapshot it wants to write.
|
|
/// </summary>
|
|
private JsonAppSettingsStore CreateStore() => new(
|
|
_paths,
|
|
Monitor(new LibraryOptions()),
|
|
Monitor(new AppearanceOptions()),
|
|
Monitor(new PlaybackOptions()));
|
|
|
|
private static IOptionsMonitor<T> Monitor<T>(T value)
|
|
{
|
|
var monitor = Substitute.For<IOptionsMonitor<T>>();
|
|
monitor.CurrentValue.Returns(value);
|
|
return monitor;
|
|
}
|
|
|
|
private IConfigurationRoot Build() => new ConfigurationBuilder()
|
|
.AddJsonFile(Path.Combine(_paths.DataDirectory, "settings.json"), optional: false)
|
|
.Build();
|
|
|
|
private (LibraryOptions Library, AppearanceOptions Appearance, PlaybackOptions Playback) Reload()
|
|
{
|
|
var configuration = Build();
|
|
|
|
var library = new LibraryOptions();
|
|
configuration.GetSection(LibraryOptions.SectionName).Bind(library);
|
|
|
|
var appearance = new AppearanceOptions();
|
|
configuration.GetSection(AppearanceOptions.SectionName).Bind(appearance);
|
|
|
|
var playback = new PlaybackOptions();
|
|
configuration.GetSection(PlaybackOptions.SectionName).Bind(playback);
|
|
|
|
return (library, appearance, playback);
|
|
}
|
|
|
|
private sealed class TempPaths : IAppPaths, IDisposable
|
|
{
|
|
public TempPaths()
|
|
{
|
|
DataDirectory = Path.Combine(Path.GetTempPath(), $"plib-tests-{Guid.CreateVersion7()}");
|
|
ThumbnailDirectory = Path.Combine(DataDirectory, "thumbnails");
|
|
PreviewDirectory = Path.Combine(DataDirectory, "previews");
|
|
DatabaseFile = Path.Combine(DataDirectory, "library.db");
|
|
|
|
Directory.CreateDirectory(ThumbnailDirectory);
|
|
Directory.CreateDirectory(PreviewDirectory);
|
|
}
|
|
|
|
public string DataDirectory { get; }
|
|
|
|
public string ThumbnailDirectory { get; }
|
|
|
|
public string PreviewDirectory { get; }
|
|
|
|
public string DatabaseFile { get; }
|
|
|
|
public void Dispose() => Directory.Delete(DataDirectory, recursive: true);
|
|
}
|
|
}
|