feat(release): front int PK with app-minted GUID EntryKey on the public addressing surface (P11 W5, 11.H)
This commit is contained in:
@@ -43,7 +43,7 @@ public class CutDetailTrackOrderingTests
|
||||
=> new(_context, NullLogger<Repository<DeepDrftContext, TrackEntity>>.Instance);
|
||||
|
||||
private static ReleaseEntity Release(string title, string artist)
|
||||
=> new() { Title = title, Artist = 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)
|
||||
|
||||
@@ -122,7 +122,7 @@ public class MediumWritePathTests
|
||||
|
||||
var release = new ReleaseEntity
|
||||
{
|
||||
Title = "Originally a Cut", Artist = "Artist A",
|
||||
EntryKey = "rk-flip", Title = "Originally a Cut", Artist = "Artist A",
|
||||
Medium = ReleaseMedium.Cut, ReleaseType = ReleaseType.EP,
|
||||
};
|
||||
var track = new TrackEntity { EntryKey = "ek-1", TrackName = "Track", Release = release };
|
||||
@@ -151,7 +151,7 @@ public class MediumWritePathTests
|
||||
{
|
||||
var sessionWithStaleType = new ReleaseEntity
|
||||
{
|
||||
Title = "Session", Artist = "A",
|
||||
EntryKey = "rk-stale", Title = "Session", Artist = "A",
|
||||
Medium = ReleaseMedium.Session, ReleaseType = ReleaseType.Album,
|
||||
};
|
||||
|
||||
@@ -169,7 +169,7 @@ public class MediumWritePathTests
|
||||
const string prose = "A late-night set\nrecorded at the Vault.";
|
||||
var entity = new ReleaseEntity
|
||||
{
|
||||
Title = "Live at the Vault", Artist = "Artist A",
|
||||
EntryKey = "rk-desc", Title = "Live at the Vault", Artist = "Artist A",
|
||||
Medium = ReleaseMedium.Session, Description = prose,
|
||||
};
|
||||
|
||||
@@ -184,7 +184,7 @@ public class MediumWritePathTests
|
||||
[Test]
|
||||
public void Convert_NullDescription_RoundTripsAsNull()
|
||||
{
|
||||
var entity = new ReleaseEntity { Title = "Studio Album", Artist = "Artist C", Description = null };
|
||||
var entity = new ReleaseEntity { EntryKey = "rk-nulldesc", Title = "Studio Album", Artist = "Artist C", Description = null };
|
||||
|
||||
var dto = TrackConverter.Convert(entity);
|
||||
Assert.That(dto.Description, Is.Null);
|
||||
@@ -222,7 +222,7 @@ public class MediumWritePathTests
|
||||
var repo = CreateRepository();
|
||||
ITrackService manager = CreateManager(repo);
|
||||
|
||||
var release = new ReleaseEntity { Title = "Studio Album", Artist = "Artist C", Medium = ReleaseMedium.Cut };
|
||||
var release = new ReleaseEntity { EntryKey = "rk-editdesc", Title = "Studio Album", Artist = "Artist C", Medium = ReleaseMedium.Cut };
|
||||
var track = new TrackEntity { EntryKey = "ek-1", TrackName = "Track", Release = release };
|
||||
_context.Tracks.Add(track);
|
||||
await _context.SaveChangesAsync();
|
||||
@@ -242,8 +242,8 @@ public class MediumWritePathTests
|
||||
[Test]
|
||||
public async Task GetPagedFilteredAsync_WithReleaseId_ReturnsOnlyThatReleasesTracks()
|
||||
{
|
||||
var first = new ReleaseEntity { Title = "Untitled", Artist = "Artist A" };
|
||||
var second = new ReleaseEntity { Title = "Untitled", Artist = "Artist B" };
|
||||
var first = new ReleaseEntity { EntryKey = "rk-first", Title = "Untitled", Artist = "Artist A" };
|
||||
var second = new ReleaseEntity { EntryKey = "rk-second", Title = "Untitled", Artist = "Artist B" };
|
||||
_context.Tracks.AddRange(
|
||||
new TrackEntity { EntryKey = "a1", TrackName = "A-One", Release = first },
|
||||
new TrackEntity { EntryKey = "a2", TrackName = "A-Two", Release = first },
|
||||
@@ -264,8 +264,8 @@ public class MediumWritePathTests
|
||||
[Test]
|
||||
public async Task GetPagedFilteredAsync_SameTitledReleases_ResolveDistinctlyById()
|
||||
{
|
||||
var first = new ReleaseEntity { Title = "Untitled", Artist = "Artist A" };
|
||||
var second = new ReleaseEntity { Title = "Untitled", Artist = "Artist B" };
|
||||
var first = new ReleaseEntity { EntryKey = "rk-first2", Title = "Untitled", Artist = "Artist A" };
|
||||
var second = new ReleaseEntity { EntryKey = "rk-second2", Title = "Untitled", Artist = "Artist B" };
|
||||
_context.Tracks.AddRange(
|
||||
new TrackEntity { EntryKey = "a1", TrackName = "A-One", Release = first },
|
||||
new TrackEntity { EntryKey = "b1", TrackName = "B-One", Release = second });
|
||||
@@ -333,7 +333,7 @@ public class MediumWritePathTests
|
||||
var repo = CreateRepository();
|
||||
ITrackService manager = CreateManager(repo);
|
||||
|
||||
var release = new ReleaseEntity { Title = "Live at the Vault", Artist = "Artist A", Medium = ReleaseMedium.Session };
|
||||
var release = new ReleaseEntity { EntryKey = "rk-peek", Title = "Live at the Vault", Artist = "Artist A", Medium = ReleaseMedium.Session };
|
||||
_context.Tracks.Add(new TrackEntity { EntryKey = "ek-1", TrackName = "Track One", Release = release });
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
@@ -370,7 +370,7 @@ public class MediumWritePathTests
|
||||
var repo = CreateRepository();
|
||||
ITrackService manager = CreateManager(repo);
|
||||
|
||||
var release = new ReleaseEntity { Title = "Live at the Vault", Artist = "Artist A", Medium = ReleaseMedium.Session };
|
||||
var release = new ReleaseEntity { EntryKey = "rk-cardses", Title = "Live at the Vault", Artist = "Artist A", Medium = ReleaseMedium.Session };
|
||||
_context.Tracks.Add(new TrackEntity { EntryKey = "ek-1", TrackName = "Track One", Release = release });
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
@@ -387,7 +387,7 @@ public class MediumWritePathTests
|
||||
var repo = CreateRepository();
|
||||
ITrackService manager = CreateManager(repo);
|
||||
|
||||
var release = new ReleaseEntity { Title = "Sunset Set", Artist = "DJ B", Medium = ReleaseMedium.Mix };
|
||||
var release = new ReleaseEntity { EntryKey = "rk-cardmix", Title = "Sunset Set", Artist = "DJ B", Medium = ReleaseMedium.Mix };
|
||||
_context.Tracks.Add(new TrackEntity { EntryKey = "ek-1", TrackName = "The Set", Release = release });
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
@@ -404,7 +404,7 @@ public class MediumWritePathTests
|
||||
var repo = CreateRepository();
|
||||
ITrackService manager = CreateManager(repo);
|
||||
|
||||
var release = new ReleaseEntity { Title = "Studio Album", Artist = "Artist C", Medium = ReleaseMedium.Cut };
|
||||
var release = new ReleaseEntity { EntryKey = "rk-cardcut", Title = "Studio Album", Artist = "Artist C", Medium = ReleaseMedium.Cut };
|
||||
_context.Tracks.AddRange(
|
||||
new TrackEntity { EntryKey = "c1", TrackName = "One", Release = release },
|
||||
new TrackEntity { EntryKey = "c2", TrackName = "Two", Release = release },
|
||||
|
||||
@@ -46,6 +46,7 @@ public class ReleaseBrowseQueryTests
|
||||
string title, string artist, ReleaseMedium medium = ReleaseMedium.Cut, string? genre = null)
|
||||
=> new()
|
||||
{
|
||||
EntryKey = Guid.NewGuid().ToString("N"),
|
||||
Title = title,
|
||||
Artist = artist,
|
||||
Medium = medium,
|
||||
|
||||
@@ -6,31 +6,35 @@ namespace DeepDrftTests;
|
||||
|
||||
/// <summary>
|
||||
/// The medium→detail-route table is the single source of truth for release navigation (Phase 11
|
||||
/// §2): Archive cards, AlbumsView cards, the player-bar title, and the /tracks/{id} redirect page
|
||||
/// all resolve through <see cref="ReleaseRoutes.DetailHref(long, ReleaseMedium)"/>. These tests pin
|
||||
/// each medium to its dedicated route and confirm the DTO overload (the call shape used everywhere
|
||||
/// but the redirect page) agrees with the primitive overload (the shape the redirect page uses
|
||||
/// after fetching the release by id).
|
||||
/// §2, §3e): Archive cards, AlbumsView cards, the player-bar title, and the /tracks/{entryKey}
|
||||
/// redirect page all resolve through <see cref="ReleaseRoutes.DetailHref(string, ReleaseMedium)"/>.
|
||||
/// The route now carries the release's opaque public EntryKey (a GUID string), never the int PK.
|
||||
/// These tests pin each medium to its dedicated route and confirm the DTO overload (the call shape
|
||||
/// used everywhere but the redirect page) agrees with the primitive overload (the shape the redirect
|
||||
/// page uses after fetching the release by EntryKey).
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class ReleaseRoutesTests
|
||||
{
|
||||
[TestCase(ReleaseMedium.Cut, "/cuts/42")]
|
||||
[TestCase(ReleaseMedium.Session, "/sessions/42")]
|
||||
[TestCase(ReleaseMedium.Mix, "/mixes/42")]
|
||||
private const string Key = "9f8a3c2e-key";
|
||||
|
||||
[TestCase(ReleaseMedium.Cut, "/cuts/9f8a3c2e-key")]
|
||||
[TestCase(ReleaseMedium.Session, "/sessions/9f8a3c2e-key")]
|
||||
[TestCase(ReleaseMedium.Mix, "/mixes/9f8a3c2e-key")]
|
||||
public void DetailHref_ResolvesEachMediumToItsDedicatedRoute(ReleaseMedium medium, string expected)
|
||||
{
|
||||
Assert.That(ReleaseRoutes.DetailHref(42, medium), Is.EqualTo(expected));
|
||||
Assert.That(ReleaseRoutes.DetailHref(Key, medium), Is.EqualTo(expected));
|
||||
}
|
||||
|
||||
[TestCase(ReleaseMedium.Cut, "/cuts/7")]
|
||||
[TestCase(ReleaseMedium.Session, "/sessions/7")]
|
||||
[TestCase(ReleaseMedium.Mix, "/mixes/7")]
|
||||
[TestCase(ReleaseMedium.Cut, "/cuts/9f8a3c2e-key")]
|
||||
[TestCase(ReleaseMedium.Session, "/sessions/9f8a3c2e-key")]
|
||||
[TestCase(ReleaseMedium.Mix, "/mixes/9f8a3c2e-key")]
|
||||
public void DetailHref_DtoOverload_AgreesWithPrimitiveOverload(ReleaseMedium medium, string expected)
|
||||
{
|
||||
// The redirect page resolves a fetched ReleaseDto through this overload; every other call
|
||||
// site does too. It must produce the same route as the (id, medium) primitive.
|
||||
var release = new ReleaseDto { Id = 7, Medium = medium };
|
||||
// site does too. It must read EntryKey and produce the same route as the (entryKey, medium)
|
||||
// primitive — a regression to release.Id here would re-expose the transparent int (§3e).
|
||||
var release = new ReleaseDto { EntryKey = Key, Medium = medium };
|
||||
|
||||
Assert.That(ReleaseRoutes.DetailHref(release), Is.EqualTo(expected));
|
||||
}
|
||||
@@ -43,7 +47,7 @@ public class ReleaseRoutesTests
|
||||
// route — a fourth medium lacking a route arm fails here rather than mis-routing to /cuts.
|
||||
foreach (var medium in Enum.GetValues<ReleaseMedium>().Where(m => m != ReleaseMedium.Cut))
|
||||
{
|
||||
var href = ReleaseRoutes.DetailHref(1, medium);
|
||||
var href = ReleaseRoutes.DetailHref(Key, medium);
|
||||
Assert.That(href, Does.Not.StartWith("/cuts/"),
|
||||
$"Medium {medium} fell through to the Cut default arm ('{href}') — it needs its own route.");
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ public class TrackFilterQueryTests
|
||||
string title, string artist, string? genre = null, string? image = null)
|
||||
=> new()
|
||||
{
|
||||
EntryKey = Guid.NewGuid().ToString("N"),
|
||||
Title = title,
|
||||
Artist = artist,
|
||||
Genre = genre,
|
||||
|
||||
Reference in New Issue
Block a user