2026-03-12 18:48:13 +01:00
|
|
|
using System;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using ImageCatalog_2;
|
|
|
|
|
using ImageCatalog_2.Services;
|
|
|
|
|
using MaddoShared;
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
2026-03-12 19:40:58 +01:00
|
|
|
using NSubstitute;
|
|
|
|
|
using Shouldly;
|
2026-03-12 18:48:13 +01:00
|
|
|
|
|
|
|
|
namespace MaddoShared.Tests;
|
|
|
|
|
|
|
|
|
|
[TestClass]
|
|
|
|
|
public class DataModelCharacterizationTests
|
|
|
|
|
{
|
|
|
|
|
[TestMethod]
|
|
|
|
|
public void SelectSourceFolderCommand_RaisesEvent()
|
|
|
|
|
{
|
|
|
|
|
var model = CreateModel();
|
|
|
|
|
var raised = false;
|
|
|
|
|
model.SelectSourceFolderRequested += (_, _) => raised = true;
|
|
|
|
|
|
|
|
|
|
model.SelectSourceFolderCommand.Execute(null);
|
|
|
|
|
|
2026-03-12 19:40:58 +01:00
|
|
|
raised.ShouldBeTrue();
|
2026-03-12 18:48:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
|
public async Task SaveSettingsToFileAsync_DelegatesToSettingsService()
|
|
|
|
|
{
|
2026-03-12 19:40:58 +01:00
|
|
|
var settingsService = Substitute.For<ISettingsService>();
|
2026-03-12 18:48:13 +01:00
|
|
|
settingsService
|
2026-03-12 19:40:58 +01:00
|
|
|
.SaveSettingsAsync(Arg.Any<string>(), Arg.Any<object>())
|
2026-03-12 18:48:13 +01:00
|
|
|
.Returns(Task.CompletedTask);
|
|
|
|
|
|
2026-03-12 19:40:58 +01:00
|
|
|
var model = CreateModel(settingsService: settingsService);
|
2026-03-12 18:48:13 +01:00
|
|
|
|
|
|
|
|
await model.SaveSettingsToFileAsync("settings.xml");
|
|
|
|
|
|
2026-03-12 19:40:58 +01:00
|
|
|
await settingsService.Received(1)
|
|
|
|
|
.SaveSettingsAsync("settings.xml", model);
|
2026-03-12 18:48:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
|
public async Task LoadSettingsFromFileAsync_DelegatesToSettingsService()
|
|
|
|
|
{
|
2026-03-12 19:40:58 +01:00
|
|
|
var settingsService = Substitute.For<ISettingsService>();
|
2026-03-12 18:48:13 +01:00
|
|
|
settingsService
|
2026-03-12 19:40:58 +01:00
|
|
|
.LoadSettingsAsync(Arg.Any<string>(), Arg.Any<object>())
|
2026-03-12 18:48:13 +01:00
|
|
|
.Returns(Task.CompletedTask);
|
|
|
|
|
|
2026-03-12 19:40:58 +01:00
|
|
|
var model = CreateModel(settingsService: settingsService);
|
2026-03-12 18:48:13 +01:00
|
|
|
|
|
|
|
|
await model.LoadSettingsFromFileAsync("settings.xml");
|
|
|
|
|
|
2026-03-12 19:40:58 +01:00
|
|
|
await settingsService.Received(1)
|
|
|
|
|
.LoadSettingsAsync("settings.xml", model);
|
2026-03-12 18:48:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
|
public void ThumbnailOptionIndex_UpdatesAuthoritativeThumbnailState()
|
|
|
|
|
{
|
|
|
|
|
var model = CreateModel();
|
|
|
|
|
|
|
|
|
|
model.ThumbnailOptionIndex = (int)DataModel.ThumbnailOptionEnum.RaceTime;
|
|
|
|
|
|
2026-03-12 19:40:58 +01:00
|
|
|
model.ThumbnailOption.ShouldBe(DataModel.ThumbnailOptionEnum.RaceTime);
|
|
|
|
|
model.AddRaceTimeToThumbnails.ShouldBeTrue();
|
|
|
|
|
model.ThumbnailMode.ShouldBe("RaceTime");
|
2026-03-12 18:48:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
|
public void ProcessingChildChange_RaisesDataModelPropertyChanged()
|
|
|
|
|
{
|
|
|
|
|
var model = CreateModel();
|
|
|
|
|
string? changed = null;
|
|
|
|
|
model.PropertyChanged += (_, args) => changed = args.PropertyName;
|
|
|
|
|
|
|
|
|
|
model.Processing.SpeedCounter = "12.00 f/s";
|
|
|
|
|
|
2026-03-12 19:40:58 +01:00
|
|
|
changed.ShouldBe(nameof(DataModel.SpeedCounter));
|
|
|
|
|
model.SpeedCounter.ShouldBe("12.00 f/s");
|
2026-03-12 18:48:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
|
public void PathsNormalize_UpdatesFlattenedSourceAndDestination()
|
|
|
|
|
{
|
|
|
|
|
var model = CreateModel();
|
|
|
|
|
model.SourcePath = "\"C:/input\"";
|
|
|
|
|
model.DestinationPath = "C:/output";
|
|
|
|
|
|
|
|
|
|
model.Paths.NormalizePaths();
|
|
|
|
|
|
2026-03-12 19:40:58 +01:00
|
|
|
model.SourcePath.ShouldBe($"C:{System.IO.Path.DirectorySeparatorChar}input{System.IO.Path.DirectorySeparatorChar}");
|
|
|
|
|
model.DestinationPath.ShouldBe($"C:{System.IO.Path.DirectorySeparatorChar}output{System.IO.Path.DirectorySeparatorChar}");
|
2026-03-12 18:48:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
|
public void AiChildChange_RaisesDataModelPropertyChanged()
|
|
|
|
|
{
|
|
|
|
|
var model = CreateModel();
|
|
|
|
|
string? changed = null;
|
|
|
|
|
model.PropertyChanged += (_, args) => changed = args.PropertyName;
|
|
|
|
|
|
|
|
|
|
model.Ai.ModelsFolderPath = "K:/models";
|
|
|
|
|
|
2026-03-12 19:40:58 +01:00
|
|
|
changed.ShouldBe(nameof(DataModel.ModelsFolderPath));
|
|
|
|
|
model.ModelsFolderPath.ShouldBe("K:/models");
|
2026-03-12 18:48:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
|
public void RaceUploadChildChange_RaisesDataModelPropertyChanged()
|
|
|
|
|
{
|
|
|
|
|
var model = CreateModel();
|
|
|
|
|
string? changed = null;
|
|
|
|
|
model.PropertyChanged += (_, args) => changed = args.PropertyName;
|
|
|
|
|
|
|
|
|
|
model.RaceUpload.ApiLogin = "admin";
|
|
|
|
|
|
2026-03-12 19:40:58 +01:00
|
|
|
changed.ShouldBe(nameof(DataModel.ApiLogin));
|
|
|
|
|
model.ApiLogin.ShouldBe("admin");
|
2026-03-12 18:48:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
[TestMethod]
|
|
|
|
|
public void VisualChildChange_RaisesDataModelPropertyChanged()
|
|
|
|
|
{
|
|
|
|
|
var model = CreateModel();
|
|
|
|
|
string? changed = null;
|
|
|
|
|
model.PropertyChanged += (_, args) => changed = args.PropertyName;
|
|
|
|
|
|
|
|
|
|
model.Visual.FontSize = 42;
|
|
|
|
|
|
2026-03-12 19:40:58 +01:00
|
|
|
changed.ShouldBe(nameof(DataModel.FontSize));
|
|
|
|
|
model.FontSize.ShouldBe(42);
|
2026-03-12 18:48:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static DataModel CreateModel(
|
|
|
|
|
ISettingsService? settingsService = null,
|
|
|
|
|
ITestService? testService = null)
|
|
|
|
|
{
|
2026-03-12 19:40:58 +01:00
|
|
|
var mapper = Substitute.For<AutoMapper.IMapper>();
|
2026-03-12 18:48:13 +01:00
|
|
|
var picSettings = new PicSettings();
|
|
|
|
|
|
2026-03-12 19:40:58 +01:00
|
|
|
var imageCreator = Substitute.For<IImageCreator>();
|
2026-03-12 18:48:13 +01:00
|
|
|
imageCreator
|
2026-03-12 19:40:58 +01:00
|
|
|
.CreateImageAsync(Arg.Any<ImageState>(), Arg.Any<byte[]?>())
|
2026-03-12 18:48:13 +01:00
|
|
|
.Returns(Task.CompletedTask);
|
|
|
|
|
|
|
|
|
|
var imageCreationService = new ImageCreationService(
|
2026-03-12 19:40:58 +01:00
|
|
|
Substitute.For<ILogger<ImageCreationService>>(),
|
2026-03-12 18:48:13 +01:00
|
|
|
picSettings,
|
2026-03-12 19:40:58 +01:00
|
|
|
imageCreator);
|
2026-03-12 18:48:13 +01:00
|
|
|
|
|
|
|
|
var imageProcessingCoordinator = new ImageProcessingCoordinator(
|
|
|
|
|
imageCreationService,
|
2026-03-12 19:40:58 +01:00
|
|
|
Substitute.For<ILogger<ImageProcessingCoordinator>>());
|
2026-03-12 18:48:13 +01:00
|
|
|
|
|
|
|
|
var aiExtractionService = new AiExtractionService(
|
2026-03-12 19:40:58 +01:00
|
|
|
Substitute.For<ILogger<AiExtractionService>>());
|
2026-03-12 18:48:13 +01:00
|
|
|
|
|
|
|
|
return new DataModel(
|
2026-03-12 19:40:58 +01:00
|
|
|
testService ?? Substitute.For<ITestService>(),
|
|
|
|
|
settingsService ?? Substitute.For<ISettingsService>(),
|
2026-03-12 18:48:13 +01:00
|
|
|
imageCreationService,
|
|
|
|
|
aiExtractionService,
|
|
|
|
|
imageProcessingCoordinator,
|
|
|
|
|
picSettings,
|
|
|
|
|
mapper,
|
2026-03-12 19:40:58 +01:00
|
|
|
Substitute.For<ILogger<DataModel>>(),
|
2026-03-12 18:48:13 +01:00
|
|
|
versionProvider: null);
|
|
|
|
|
}
|
|
|
|
|
}
|