feat(stats): flip home Plays card live (Phase 16.5)

Add TotalPlays + UniqueListeners to HomeStatsDto, composed at
StatsController from IEventService (no migration). Card reads via
existing persistent-state-bridged round-trip.
This commit is contained in:
daniel-c-harvey
2026-06-19 15:26:07 -04:00
parent cfcc2693f2
commit be1a55fd37
7 changed files with 123 additions and 13 deletions
+39
View File
@@ -159,6 +159,45 @@ public class PlayEventQueryTests
Assert.That(await _context.PlayCounters.AnyAsync(c => c.TrackId == track.Id), Is.False);
}
// --- Site-wide total plays (§5 — the home Plays card's primary figure) ---
// An empty counter table sums to zero rather than throwing — the card's reading until the
// telemetry migration is applied and the first play lands.
[Test]
public async Task CountTotalPlaysAsync_NoCounters_IsZero()
{
Assert.That(await CreateRepository().CountTotalPlaysAsync(), Is.EqualTo(0L));
}
// Total plays sums all three bucket columns of a single track's counter.
[Test]
public async Task CountTotalPlaysAsync_SumsAllBucketsOfOneCounter()
{
await SeedTrackAsync("track-1");
var repo = CreateRepository();
await repo.RecordPlayAsync("track-1", PlayBucket.Partial, null);
await repo.RecordPlayAsync("track-1", PlayBucket.Sampled, null);
await repo.RecordPlayAsync("track-1", PlayBucket.Sampled, null);
await repo.RecordPlayAsync("track-1", PlayBucket.Complete, null);
Assert.That(await repo.CountTotalPlaysAsync(), Is.EqualTo(4L));
}
// Total plays is site-wide: it sums across every track's counter, not one track's.
[Test]
public async Task CountTotalPlaysAsync_SumsAcrossAllTracks()
{
await SeedTrackAsync("track-1");
await SeedTrackAsync("track-2");
var repo = CreateRepository();
await repo.RecordPlayAsync("track-1", PlayBucket.Complete, null);
await repo.RecordPlayAsync("track-1", PlayBucket.Partial, null);
await repo.RecordPlayAsync("track-2", PlayBucket.Sampled, null);
Assert.That(await repo.CountTotalPlaysAsync(), Is.EqualTo(3L),
"site-wide total spans every track's counter");
}
// A share append writes one row with the target, channel, and a null anonId.
[Test]
public async Task RecordShareAsync_AppendsRow()