From 9d0ce99a5d9ac4e274a9f70602f839b21f31af37 Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Fri, 19 Jun 2026 15:23:20 -0400 Subject: [PATCH] fix: PlayRelease always materialises a defensive copy so Items alias can't wipe the queue on jump; add aliasing regression test --- .../Services/QueueService.cs | 2 +- DeepDrftTests/QueueServiceTests.cs | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/DeepDrftPublic.Client/Services/QueueService.cs b/DeepDrftPublic.Client/Services/QueueService.cs index 8696ec1..aca4c80 100644 --- a/DeepDrftPublic.Client/Services/QueueService.cs +++ b/DeepDrftPublic.Client/Services/QueueService.cs @@ -56,7 +56,7 @@ public sealed class QueueService : IQueueService, IDisposable public async Task PlayRelease(IEnumerable tracks, int startIndex = 0) { - var list = tracks as IReadOnlyList ?? tracks.ToList(); + var list = tracks.ToList(); if (list.Count == 0) return; var start = Math.Clamp(startIndex, 0, list.Count - 1); diff --git a/DeepDrftTests/QueueServiceTests.cs b/DeepDrftTests/QueueServiceTests.cs index 5087c4b..f9bd8a3 100644 --- a/DeepDrftTests/QueueServiceTests.cs +++ b/DeepDrftTests/QueueServiceTests.cs @@ -144,6 +144,33 @@ public class QueueServiceTests }); } + [Test] + public async Task PlayRelease_ViaLiveQueueItems_PreservesTracksAndJumpsToIndex() + { + // Regression guard for the aliasing bug: OnQueueJump calls PlayRelease(QueueService.Items, index). + // Items returns the backing list directly; without a defensive copy, the cast + // "tracks as IReadOnlyList" aliases _items, so _items.Clear() also clears list, + // and _items.AddRange(list) adds nothing — wiping the queue and playing nothing. + await _queue.PlayRelease(Tracks(4)); // populate the live queue + + // Jump to index 2 via the live Items reference, exactly as OnQueueJump does. + await _queue.PlayRelease(_queue.Items, 2); + + Assert.Multiple(() => + { + // The queue must survive — all four tracks still present, in order. + Assert.That(_queue.Items, Has.Count.EqualTo(4)); + Assert.That(_queue.Items.Select(t => t.EntryKey), + Is.EqualTo(new[] { "track-1", "track-2", "track-3", "track-4" })); + // CurrentIndex must be the jumped-to slot. + Assert.That(_queue.CurrentIndex, Is.EqualTo(2)); + // Current must be the right track. + Assert.That(_queue.Current!.EntryKey, Is.EqualTo("track-3")); + // The player must have streamed the jumped-to track. + Assert.That(_player.SelectedTracks.Last().EntryKey, Is.EqualTo("track-3")); + }); + } + // --- Arm: prerender-safe load without streaming (release embed) --- [Test]