using DeepDrftModels.DTOs; using DeepDrftPublic.Client.Services; using DeepDrftPublic.Client.ViewModels; using Models.Common; using NetBlocks.Models; namespace DeepDrftTests; /// /// Unit tests for the release-embed track resolution (). The VM is /// the data half of FramePlayer's release path: resolve a release EntryKey to its ordered track list, /// which the page then stages (track 0) and arms into the queue. It is pure async logic over the two /// data-service seams, so it is exercised here against recording fakes — no browser, no JS, no HTTP. /// Coverage: ordered non-empty resolution, single-track release, release-not-found, and a release /// that resolves to no tracks (the "leave the bar idle, don't throw" path). /// [TestFixture] public class FramePlayerReleaseResolutionTests { private FakeReleaseData _releaseData = null!; private FakeTrackData _trackData = null!; private FramePlayerViewModel _vm = null!; private const string ReleaseKey = "release-key-1"; private const long ReleaseId = 42; [SetUp] public void SetUp() { _releaseData = new FakeReleaseData(); _trackData = new FakeTrackData(); _vm = new FramePlayerViewModel(_releaseData, _trackData); } private static ReleaseDto Release() => new() { Id = ReleaseId, EntryKey = ReleaseKey, Title = "Album" }; private static List Tracks(int count) => Enumerable.Range(1, count) .Select(i => new TrackDto { EntryKey = $"track-{i}", TrackName = $"Track {i}", TrackNumber = i }) .ToList(); [Test] public async Task Load_ResolvesOrderedNonEmptyTrackList_AndQueriesByResolvedReleaseId() { _releaseData.Release = Release(); _trackData.Page = Tracks(3); await _vm.Load(ReleaseKey); Assert.Multiple(() => { Assert.That(_vm.Tracks.Select(t => t.EntryKey), Is.EqualTo(new[] { "track-1", "track-2", "track-3" })); // The track page must be narrowed by the resolved release.Id (the int FK join), sorted by // the explicit TrackNumber ordinal — the same resolution the Cut detail page uses. Assert.That(_trackData.LastReleaseId, Is.EqualTo(ReleaseId)); Assert.That(_trackData.LastSortColumn, Is.EqualTo("TrackNumber")); }); } [Test] public async Task Load_WithSingleTrackRelease_ResolvesAOneItemList() { _releaseData.Release = Release(); _trackData.Page = Tracks(1); await _vm.Load(ReleaseKey); Assert.That(_vm.Tracks, Has.Count.EqualTo(1)); Assert.That(_vm.Tracks[0].EntryKey, Is.EqualTo("track-1")); } [Test] public async Task Load_WhenReleaseNotFound_LeavesTracksEmptyAndDoesNotQueryTracks() { _releaseData.Release = null; // GetByEntryKey returns a fail result. await _vm.Load(ReleaseKey); Assert.Multiple(() => { Assert.That(_vm.Tracks, Is.Empty); Assert.That(_trackData.WasQueried, Is.False); }); } [Test] public async Task Load_WhenReleaseResolvesToNoTracks_LeavesTracksEmptyWithoutThrowing() { _releaseData.Release = Release(); _trackData.Page = new List(); // empty page await _vm.Load(ReleaseKey); Assert.That(_vm.Tracks, Is.Empty); } [Test] public async Task Load_ResetsTracksFromAPriorLoad() { _releaseData.Release = Release(); _trackData.Page = Tracks(3); await _vm.Load(ReleaseKey); Assert.That(_vm.Tracks, Has.Count.EqualTo(3)); // A second load whose release is not found must not retain the prior album's tracks. _releaseData.Release = null; await _vm.Load("another-key"); Assert.That(_vm.Tracks, Is.Empty); } private sealed class FakeReleaseData : IReleaseDataService { public ReleaseDto? Release { get; set; } public Task> GetByEntryKey(string entryKey) => Task.FromResult(Release is null ? ApiResult.CreateFailResult("not found") : ApiResult.CreatePassResult(Release)); public Task>> GetPaged( string? medium, int page, int pageSize, string? sortColumn = null, bool sortDescending = false, string? search = null, string? genre = null) => throw new NotSupportedException("FramePlayerViewModel does not page releases."); } private sealed class FakeTrackData : ITrackDataService { public List Page { get; set; } = new(); public bool WasQueried { get; private set; } public long? LastReleaseId { get; private set; } public string? LastSortColumn { get; private set; } public Task>> GetPage( int pageNumber, int pageSize, string? sortColumn = null, bool sortDescending = false, string? searchText = null, string? album = null, string? genre = null, long? releaseId = null) { WasQueried = true; LastReleaseId = releaseId; LastSortColumn = sortColumn; var paged = new PagedResult { Items = Page, TotalCount = Page.Count, Page = pageNumber, PageSize = pageSize, }; return Task.FromResult(ApiResult>.CreatePassResult(paged)); } // Inert remainder — FramePlayerViewModel only calls GetPage. public Task>> GetAlbums() => throw new NotSupportedException(); public Task>> GetGenres() => throw new NotSupportedException(); public Task> GetTrack(string trackId) => throw new NotSupportedException(); public Task> GetTrackWaveform(string trackEntryKey) => throw new NotSupportedException(); public Task> GetRandomTrack() => throw new NotSupportedException(); } }