fix: close TOCTOU in CREATE path; add anti-forgery, loose-track, and case-sensitivity tests

FindOrCreateRelease now returns (ReleaseDto, bool WasCreated); the CREATE path in UploadAsync
rejects WasCreated=false as a duplicate rather than silently attaching on a lost race.
This commit is contained in:
daniel-c-harvey
2026-06-19 15:55:08 -04:00
parent bd85507308
commit 558ff4b4c6
5 changed files with 90 additions and 27 deletions
+9 -9
View File
@@ -60,9 +60,9 @@ public class MediumWritePathTests
"Live at the Vault", "Artist A", ReleaseData("Live at the Vault", "Artist A", ReleaseMedium.Session));
Assert.That(result.Success, Is.True);
Assert.That(result.Value!.Medium, Is.EqualTo(ReleaseMedium.Session));
Assert.That(result.Value.Release.Medium, Is.EqualTo(ReleaseMedium.Session));
var stored = await CreateRepository().GetReleaseByIdAsync(result.Value.Id);
var stored = await CreateRepository().GetReleaseByIdAsync(result.Value.Release.Id);
Assert.That(stored!.Medium, Is.EqualTo(ReleaseMedium.Session));
}
@@ -75,7 +75,7 @@ public class MediumWritePathTests
var result = await manager.FindOrCreateRelease(
"Sunset Set", "DJ B", ReleaseData("Sunset Set", "DJ B", ReleaseMedium.Mix));
Assert.That(result.Value!.Medium, Is.EqualTo(ReleaseMedium.Mix));
Assert.That(result.Value.Release.Medium, Is.EqualTo(ReleaseMedium.Mix));
}
// 9.5.A — a Cut upload (the default) creates a release carrying Medium == Cut.
@@ -87,7 +87,7 @@ public class MediumWritePathTests
var result = await manager.FindOrCreateRelease(
"Studio Album", "Artist C", ReleaseData("Studio Album", "Artist C", ReleaseMedium.Cut));
Assert.That(result.Value!.Medium, Is.EqualTo(ReleaseMedium.Cut));
Assert.That(result.Value.Release.Medium, Is.EqualTo(ReleaseMedium.Cut));
}
// 9.5.A — a second upload to an existing release does NOT mutate the stored medium. The first
@@ -105,10 +105,10 @@ public class MediumWritePathTests
var found = await manager.FindOrCreateRelease(
"Live at the Vault", "Artist A", ReleaseData("Live at the Vault", "Artist A", ReleaseMedium.Cut));
Assert.That(found.Value!.Id, Is.EqualTo(created.Value!.Id), "same release row is returned");
Assert.That(found.Value.Medium, Is.EqualTo(ReleaseMedium.Session), "medium stays as first set");
Assert.That(found.Value.Release.Id, Is.EqualTo(created.Value.Release.Id), "same release row is returned");
Assert.That(found.Value.Release.Medium, Is.EqualTo(ReleaseMedium.Session), "medium stays as first set");
var stored = await CreateRepository().GetReleaseByIdAsync(created.Value.Id);
var stored = await CreateRepository().GetReleaseByIdAsync(created.Value.Release.Id);
Assert.That(stored!.Medium, Is.EqualTo(ReleaseMedium.Session), "DB row unchanged");
}
@@ -207,9 +207,9 @@ public class MediumWritePathTests
var result = await manager.FindOrCreateRelease("Studio Album", "Artist C", data);
Assert.That(result.Success, Is.True);
Assert.That(result.Value!.Description, Is.EqualTo(prose));
Assert.That(result.Value.Release.Description, Is.EqualTo(prose));
var stored = await CreateRepository().GetReleaseByIdAsync(result.Value.Id);
var stored = await CreateRepository().GetReleaseByIdAsync(result.Value.Release.Id);
Assert.That(stored!.Description, Is.EqualTo(prose));
}