153 lines
6.2 KiB
C#
153 lines
6.2 KiB
C#
using Data.Data.Repositories;
|
|
using DeepDrftData;
|
|
using DeepDrftData.Data;
|
|
using DeepDrftData.Repositories;
|
|
using DeepDrftModels.DTOs;
|
|
using DeepDrftModels.Entities;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Models.Common;
|
|
|
|
namespace DeepDrftTests;
|
|
|
|
/// <summary>
|
|
/// Backs the public read path that the /cuts/{id} album page consumes (Phase 11 §3a, §3.3).
|
|
/// <c>CutDetailViewModel.Load</c> fetches an album's tracks through the releaseId-filtered track page
|
|
/// sorted by "TrackNumber"; that maps to <see cref="TrackRepository.GetPagedFilteredAsync"/> with a
|
|
/// <see cref="TrackFilter.ReleaseId"/> predicate and an <c>OrderBy(t => t.TrackNumber)</c>
|
|
/// expression. These tests exercise that exact query — the join narrowing, the explicit-ordinal
|
|
/// ordering (not insertion order), and the projection of TrackNumber onto the DTO the page renders.
|
|
///
|
|
/// Provider note: runs on the EF in-memory provider, which executes the ReleaseId equality, the
|
|
/// ordinal sort, and the count in process — every predicate this path uses (no ILike branch here).
|
|
/// Mirrors <see cref="TrackFilterQueryTests"/>.
|
|
/// </summary>
|
|
[TestFixture]
|
|
public class CutDetailTrackOrderingTests
|
|
{
|
|
private DeepDrftContext _context = null!;
|
|
|
|
[SetUp]
|
|
public void SetUp()
|
|
{
|
|
var options = new DbContextOptionsBuilder<DeepDrftContext>()
|
|
.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
|
|
.Options;
|
|
_context = new DeepDrftContext(options);
|
|
}
|
|
|
|
[TearDown]
|
|
public void TearDown() => _context.Dispose();
|
|
|
|
private TrackRepository CreateRepository()
|
|
=> new(_context, NullLogger<Repository<DeepDrftContext, TrackEntity>>.Instance);
|
|
|
|
private static ReleaseEntity Release(string title, string artist)
|
|
=> new() { EntryKey = Guid.NewGuid().ToString("N"), Title = title, Artist = artist };
|
|
|
|
// A track linked to the given release with an explicit ordinal.
|
|
private static TrackEntity Track(string name, int trackNumber, ReleaseEntity? release = null)
|
|
=> new()
|
|
{
|
|
EntryKey = Guid.NewGuid().ToString("N"),
|
|
TrackName = name,
|
|
TrackNumber = trackNumber,
|
|
Release = release,
|
|
};
|
|
|
|
private async Task SeedAsync(params TrackEntity[] tracks)
|
|
{
|
|
_context.Tracks.AddRange(tracks);
|
|
await _context.SaveChangesAsync();
|
|
}
|
|
|
|
// The album page's query: filter to one release, order by the explicit ordinal.
|
|
private static PagingParameters<TrackEntity> OrderedByTrackNumber()
|
|
=> new() { Page = 1, PageSize = 100, OrderBy = t => t.TrackNumber, IsDescending = false };
|
|
|
|
// The release-id filter narrows to that album only — a sibling release's tracks never leak in.
|
|
[Test]
|
|
public async Task ReleaseIdFilter_ReturnsOnlyThatReleasesTracks()
|
|
{
|
|
var albumA = Release("Album A", "Artist");
|
|
var albumB = Release("Album B", "Artist");
|
|
await SeedAsync(
|
|
Track("A-one", 1, albumA),
|
|
Track("A-two", 2, albumA),
|
|
Track("B-one", 1, albumB),
|
|
Track("Loose", 1));
|
|
|
|
var repo = CreateRepository();
|
|
var result = await repo.GetPagedFilteredAsync(
|
|
OrderedByTrackNumber(), new TrackFilter { ReleaseId = albumA.Id });
|
|
|
|
Assert.That(result.TotalCount, Is.EqualTo(2));
|
|
Assert.That(result.Items.Select(t => t.TrackName), Is.EquivalentTo(new[] { "A-one", "A-two" }));
|
|
}
|
|
|
|
// The ordering is by the explicit ordinal, not insertion order: tracks seeded out of order
|
|
// come back ascending by TrackNumber. This is the guarantee /cuts/{id} relies on for its rows.
|
|
[Test]
|
|
public async Task OrderByTrackNumber_SortsByExplicitOrdinalNotInsertionOrder()
|
|
{
|
|
var album = Release("Album", "Artist");
|
|
// Insert deliberately scrambled relative to the intended track order.
|
|
await SeedAsync(
|
|
Track("Third", 3, album),
|
|
Track("First", 1, album),
|
|
Track("Second", 2, album));
|
|
|
|
var repo = CreateRepository();
|
|
var result = await repo.GetPagedFilteredAsync(
|
|
OrderedByTrackNumber(), new TrackFilter { ReleaseId = album.Id });
|
|
|
|
Assert.That(
|
|
result.Items.Select(t => t.TrackName).ToList(),
|
|
Is.EqualTo(new[] { "First", "Second", "Third" }),
|
|
"rows must order by the explicit TrackNumber ordinal, not the order they were inserted");
|
|
Assert.That(
|
|
result.Items.Select(t => t.TrackNumber).ToList(),
|
|
Is.EqualTo(new[] { 1, 2, 3 }));
|
|
}
|
|
|
|
// The DTO the page renders carries the ordinal — TrackConverter projects TrackNumber onto
|
|
// TrackDto, so the row's number label and the saved order survive the entity -> DTO mapping.
|
|
[Test]
|
|
public async Task TrackConverter_ProjectsTrackNumberOntoDto()
|
|
{
|
|
var album = Release("Album", "Artist");
|
|
await SeedAsync(
|
|
Track("First", 1, album),
|
|
Track("Second", 2, album));
|
|
|
|
var repo = CreateRepository();
|
|
var result = await repo.GetPagedFilteredAsync(
|
|
OrderedByTrackNumber(), new TrackFilter { ReleaseId = album.Id });
|
|
|
|
var dtos = result.Items.Select(TrackConverter.Convert).ToList();
|
|
|
|
Assert.That(dtos.Select(d => d.TrackNumber).ToList(), Is.EqualTo(new[] { 1, 2 }));
|
|
Assert.That(dtos.Select(d => d.TrackName).ToList(), Is.EqualTo(new[] { "First", "Second" }));
|
|
}
|
|
|
|
// An album with no streamable tracks yields an empty page (no rows, no error) — the page header
|
|
// still renders; the track list is simply empty.
|
|
[Test]
|
|
public async Task ReleaseIdFilter_WithNoTracks_ReturnsEmptyPage()
|
|
{
|
|
var empty = Release("Empty Album", "Artist");
|
|
var other = Release("Other", "Artist");
|
|
await SeedAsync(Track("Other-one", 1, other));
|
|
// Persist the empty release with no tracks linked to it.
|
|
_context.Releases.Add(empty);
|
|
await _context.SaveChangesAsync();
|
|
|
|
var repo = CreateRepository();
|
|
var result = await repo.GetPagedFilteredAsync(
|
|
OrderedByTrackNumber(), new TrackFilter { ReleaseId = empty.Id });
|
|
|
|
Assert.That(result.TotalCount, Is.EqualTo(0));
|
|
Assert.That(result.Items, Is.Empty);
|
|
}
|
|
}
|