13 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-queue-config-player-tests | 01 | execute | 1 |
|
true |
|
|
Purpose: Queue tests are the highest-priority safety net — Phase 7 (PERF-01) will change queue persistence from full table rewrite to incremental INSERT/DELETE. These tests must catch any data loss or index corruption during that refactoring.
Output: 3 test files with ~15-20 tests covering SetQueue, Next, Previous, shuffle, repeat modes, Add/Insert/Move/Remove, and full persistence round-trip.
<execution_context> @/home/caleb/.config/opencode/get-shit-done/workflows/execute-plan.md @/home/caleb/.config/opencode/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-test-infrastructure/03-01-SUMMARY.mdFrom backend/queue/queue.go:
type RepeatMode string
const (
RepeatOff RepeatMode = "off"
RepeatAll RepeatMode = "all"
RepeatOne RepeatMode = "one"
)
type Track struct {
ID int64 `json:"id"`
AudioFileID int64 `json:"audioFileId"`
FilePath string `json:"filePath"`
Position int64 `json:"position"`
Title string `json:"title"`
Artist string `json:"artist"`
}
type State struct {
Tracks []Track `json:"tracks"`
CurrentIndex int `json:"currentIndex"`
ShuffleMode bool `json:"shuffleMode"`
RepeatMode RepeatMode `json:"repeatMode"`
SourcePlaylistID int64 `json:"sourcePlaylistId"`
}
type TrackLoader interface {
LoadFile(filePath string) error
Play() error
IsPlaying() bool
CurrentPositionSeconds() (int, error)
UnloadTrack()
}
// Queue struct (unexported fields — accessible from same package tests):
type Queue struct {
ctx context.Context
logger *slog.Logger
db *database.DB
player TrackLoader
mu sync.Mutex
tracks []Track
currentIndex int
shuffleMode bool
repeatMode RepeatMode
shuffleOrder []int
sourcePlaylistID int64
setQueueGen atomic.Int64
}
func NewQueue(logger *slog.Logger, db *database.DB) *Queue
func (q *Queue) SetPlayer(player TrackLoader)
func (q *Queue) SetQueue(filePaths []string, startIndex int, shuffleStart bool)
func (q *Queue) AddTrack(filePath string)
func (q *Queue) AddTracks(filePaths []string)
func (q *Queue) InsertNext(filePath string)
func (q *Queue) InsertTracksAt(filePaths []string, index int)
func (q *Queue) MoveQueueTracks(fromIndices []int, toIndex int)
func (q *Queue) RemoveTrack(position int)
func (q *Queue) RemoveTracks(positions []int)
func (q *Queue) Next()
func (q *Queue) Previous()
func (q *Queue) PlayIndex(index int)
func (q *Queue) ToggleShuffle()
func (q *Queue) CycleRepeat()
func (q *Queue) GetState() State
func (q *Queue) Clear()
func (q *Queue) SaveState()
func (q *Queue) RestoreState()
From backend/database/testhelper.go:
func NewTestDB(t *testing.T) *DB
FK dependency chain for test data setup:
-- file_types is pre-seeded (0=.mp3, 1=.flac, 2=.ogg, 3=.wav)
-- queue row pre-seeded (id=1)
-- Insert chain:
INSERT INTO artist_credit (id, text) VALUES (1, 'Test Artist');
INSERT INTO recordings (id, name, artist_credit_id) VALUES (1, 'Test Track', 1);
INSERT INTO audio_files (id, file_path, length_milliseconds, file_type_id, recording_id)
VALUES (1, '/test/track1.mp3', 180000, 0, 1);
-- Then queue_tracks can reference audio_file_id
queue_test.go — Core operation tests (~10-12 tests):
-
Define a
mockTrackLoaderstruct satisfyingTrackLoaderinterface at top of file. All methods are no-ops:LoadFilereturns nil,Playreturns nil,IsPlayingreturns false,CurrentPositionSecondsreturns (0, nil),UnloadTrackis empty. Add aloadedFile stringfield to track which file was loaded. -
Define a
setupTestQueue(t *testing.T) (*Queue, *database.DB)helper that:- Calls
database.NewTestDB(t)to get isolated DB - Creates
NewQueue(slog.Default(), db) - Sets a
&mockTrackLoader{}viaSetPlayer - Returns queue and db
- Calls
-
Define a
seedAudioFiles(t *testing.T, db *database.DB, count int) []stringhelper that:- Inserts
countaudio_file rows with FK chain (1 shared artist_credit, 1 shared recording per file, audio_files with file_path/test/trackN.mp3) - Uses
db.ExecContext()for raw SQL inserts - Returns the file paths as a string slice
- Uses
t.Helper()
- Inserts
-
Write these test functions (all with t.Parallel()):
TestSetQueue_PopulatesTracks— SetQueue with 5 file paths at startIndex 0, verify GetState returns correct track count and currentIndexTestSetQueue_WithStartIndex— SetQueue at startIndex 2, verify currentIndex is 2TestSetQueue_WithShuffleStart— SetQueue with shuffleStart=true, verify shuffleMode is true and shuffleOrder is populatedTestAddTrack_AppendsToQueue— SetQueue with 3 tracks, AddTrack a 4th, verify 4 tracks total and the new track is lastTestInsertTracksAt_BeforeCurrentIndex— SetQueue 5 tracks at index 2, InsertTracksAt index 1, verify currentIndex shifted by inserted countTestInsertTracksAt_AfterCurrentIndex— same but insert at index 3, verify currentIndex unchangedTestMoveQueueTracks_ForwardMove— SetQueue 5 tracks, move track from index 1 to index 3, verify order and currentIndex adjustmentTestMoveQueueTracks_BackwardMove— move from index 3 to index 1, verify orderTestMoveQueueTracks_MoveCurrentTrack— move the current track, verify currentIndex follows itTestRemoveTrack_RemovesCorrectTrack— SetQueue 5 tracks, remove at index 2, verify 4 tracks remain and correct track removedTestRemoveTrack_RemoveCurrentTrack— remove at currentIndex, verify index adjustsTestClear_EmptiesQueue— SetQueue, Clear, verify empty stateTestToggleShuffle_TogglesMode— verify shuffle toggles on/off and shuffleOrder populates/clearsTestCycleRepeat_CyclesThroughModes— verify off→all→one→off cycle
navigation_test.go — Navigation edge case tests (~6-8 tests):
Use direct field manipulation (same package) to set up queue state without DB:
- Create queue with
&Queue{logger: slog.Default()}, settracks,currentIndex,shuffleMode,repeatMode,shuffleOrderdirectly
Tests (all t.Parallel()):
TestNextIndex_NormalMode_AdvancesToNextTrack— 5 tracks, index 2, repeatOff → returns 3TestNextIndex_NormalMode_EndOfQueue_RepeatOff— index at last track, repeatOff → returns -1TestNextIndex_NormalMode_EndOfQueue_RepeatAll— index at last track, repeatAll → returns 0 (wraps)TestNextIndex_RepeatOne— any index, repeatOne → returns same indexTestPreviousIndex_NormalMode_GoesBack— index 3, repeatOff → returns 2TestPreviousIndex_AtStart_RepeatOff— index 0, repeatOff → returns -1TestPreviousIndex_AtStart_RepeatAll— index 0, repeatAll → returns last indexTestGenerateShuffleOrder_Properties— table-driven test verifying: all indices present, no duplicates, current track at shuffleOrder[0], length matches tracks length. Test with 1, 5, and 20 tracks.TestNextIndex_ShuffleMode— set shuffleOrder, verify navigation follows shuffle order not track order
Use the established codebase test conventions: t.Parallel(), t.Helper() on helpers, t.Errorf with "got X, want Y" format, no assertion libraries. cd backend && go test -race -count=1 -run "TestSetQueue|TestAdd|TestInsert|TestMove|TestRemove|TestClear|TestToggle|TestCycle|TestNext|TestPrevious|TestGenerate" ./queue/ -v 2>&1 | tail -30 queue_test.go has ~12 tests for core operations (SetQueue, Add, Insert, Move, Remove, Clear, ToggleShuffle, CycleRepeat); navigation_test.go has ~8 tests for Next/Previous in all modes + shuffle order properties. All pass with -race.
Task 2: Queue persistence round-trip tests backend/queue/persistence_test.go Create persistence_test.go in the queue package (internal, package queue).Reuse the setupTestQueue and seedAudioFiles helpers from queue_test.go (same package, accessible).
Write these test functions (all t.Parallel()):
-
TestSaveState_RestoreState_Roundtrip— The critical safety net test:- Setup queue with DB, seed 5 audio files
- SetQueue with 5 file paths at startIndex 2
- CycleRepeat to "all"
- ToggleShuffle
- SaveState
- Create a NEW Queue instance with same DB:
q2 := NewQueue(slog.Default(), db); q2.SetPlayer(&mockTrackLoader{}) - RestoreState on q2
- Verify ALL fields match: tracks length, each track's FilePath/Title/Artist, currentIndex, shuffleMode, repeatMode, shuffleOrder
-
TestSaveState_RestoreState_EmptyQueue— SaveState with no tracks, RestoreState, verify empty state -
TestSaveState_RestoreState_SingleTrack— Verify edge case with 1 track -
TestSaveState_RestoreState_PreservesTrackOrder— SetQueue with 10 tracks, verify exact order after restore (not just count) -
TestRestoreState_NoSavedState— RestoreState on fresh DB with no prior SaveState, verify queue stays empty (no panic, no error) -
TestSaveState_OverwritesPreviousState— SaveState with 5 tracks, then SaveState with 3 different tracks, RestoreState should get the 3 tracks
These tests are the highest-priority safety net for Phase 7 (PERF-01). The roundtrip test verifies ALL queue state fields survive serialization, which is essential before changing persistence from full-table-rewrite to incremental. cd backend && go test -race -count=1 -run "TestSaveState|TestRestoreState" ./queue/ -v 2>&1 | tail -20 persistence_test.go has ~6 tests covering full round-trip fidelity, empty/single edge cases, and overwrite behavior. All pass with -race.
```bash cd backend && go test -race -count=1 ./queue/ -v ``` All queue tests pass with -race flag. Expected ~18-20 tests total.<success_criteria>
- backend/queue/queue_test.go exists with ~12 tests for core operations
- backend/queue/navigation_test.go exists with ~8 tests for navigation + shuffle
- backend/queue/persistence_test.go exists with ~6 tests for state persistence
- All tests pass with
go test -race ./queue/ - SaveState/RestoreState roundtrip preserves all state fields
- Edge cases covered: empty queue, single track, boundary indices, repeat mode wrapping </success_criteria>