chore: complete v1.0 Consolidation milestone
Archive milestone artifacts: - milestones/v1.0-ROADMAP.md (full roadmap archive) - milestones/v1.0-REQUIREMENTS.md (26/26 requirements complete) - milestones/v1.0-phases/ (8 phase directories with plans, summaries, verifications) Updated: - PROJECT.md: full evolution review, all consolidation requirements validated - ROADMAP.md: collapsed to milestone summary with archive link - STATE.md: reset for next milestone - MILESTONES.md: created with stats and accomplishments - RETROSPECTIVE.md: created with lessons learned Deleted: - REQUIREMENTS.md (archived, fresh for next milestone) 8 phases, 17 plans, 34 tasks, 84 tests added, 6 days
This commit is contained in:
@@ -0,0 +1,276 @@
|
||||
---
|
||||
phase: 04-queue-config-player-tests
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- backend/queue/queue_test.go
|
||||
- backend/queue/navigation_test.go
|
||||
- backend/queue/persistence_test.go
|
||||
autonomous: true
|
||||
requirements: [TEST-02]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Queue navigation (Next/Previous) works correctly in all repeat modes (off, one, all) for both normal and shuffle playback"
|
||||
- "Queue mutations (Add, Insert, Move, Remove) correctly update tracks and adjust currentIndex"
|
||||
- "Queue state persists across SaveState/RestoreState cycles without data loss"
|
||||
- "Shuffle order contains all indices, has current track at position 0, and has no duplicates"
|
||||
- "All queue tests pass with -race flag"
|
||||
artifacts:
|
||||
- path: "backend/queue/queue_test.go"
|
||||
provides: "Tests for SetQueue, Add/Insert/Move/Remove, ToggleShuffle, CycleRepeat, Clear, mock TrackLoader"
|
||||
min_lines: 200
|
||||
- path: "backend/queue/navigation_test.go"
|
||||
provides: "Tests for Next/Previous in all modes, edge cases (empty, single, boundary)"
|
||||
min_lines: 150
|
||||
- path: "backend/queue/persistence_test.go"
|
||||
provides: "Tests for SaveState/RestoreState roundtrip fidelity"
|
||||
min_lines: 100
|
||||
key_links:
|
||||
- from: "backend/queue/queue_test.go"
|
||||
to: "backend/database/testhelper.go"
|
||||
via: "database.NewTestDB(t)"
|
||||
pattern: "database\\.NewTestDB"
|
||||
- from: "backend/queue/persistence_test.go"
|
||||
to: "backend/queue/persistence.go"
|
||||
via: "SaveState/RestoreState roundtrip"
|
||||
pattern: "SaveState|RestoreState"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Write comprehensive unit tests for the queue package covering core operations, navigation logic, and state persistence.
|
||||
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<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>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/03-test-infrastructure/03-01-SUMMARY.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Key types and contracts the executor needs. Extracted from codebase. -->
|
||||
|
||||
From backend/queue/queue.go:
|
||||
```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:
|
||||
```go
|
||||
func NewTestDB(t *testing.T) *DB
|
||||
```
|
||||
|
||||
FK dependency chain for test data setup:
|
||||
```sql
|
||||
-- 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
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Queue core operations and navigation tests</name>
|
||||
<files>backend/queue/queue_test.go, backend/queue/navigation_test.go</files>
|
||||
<action>
|
||||
Create two test files for the queue package using internal tests (package queue, not queue_test).
|
||||
|
||||
**queue_test.go** — Core operation tests (~10-12 tests):
|
||||
|
||||
1. Define a `mockTrackLoader` struct satisfying `TrackLoader` interface at top of file. All methods are no-ops: `LoadFile` returns nil, `Play` returns nil, `IsPlaying` returns false, `CurrentPositionSeconds` returns (0, nil), `UnloadTrack` is empty. Add a `loadedFile string` field to track which file was loaded.
|
||||
|
||||
2. 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{}` via `SetPlayer`
|
||||
- Returns queue and db
|
||||
|
||||
3. Define a `seedAudioFiles(t *testing.T, db *database.DB, count int) []string` helper that:
|
||||
- Inserts `count` audio_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()`
|
||||
|
||||
4. 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 currentIndex
|
||||
- `TestSetQueue_WithStartIndex` — SetQueue at startIndex 2, verify currentIndex is 2
|
||||
- `TestSetQueue_WithShuffleStart` — SetQueue with shuffleStart=true, verify shuffleMode is true and shuffleOrder is populated
|
||||
- `TestAddTrack_AppendsToQueue` — SetQueue with 3 tracks, AddTrack a 4th, verify 4 tracks total and the new track is last
|
||||
- `TestInsertTracksAt_BeforeCurrentIndex` — SetQueue 5 tracks at index 2, InsertTracksAt index 1, verify currentIndex shifted by inserted count
|
||||
- `TestInsertTracksAt_AfterCurrentIndex` — same but insert at index 3, verify currentIndex unchanged
|
||||
- `TestMoveQueueTracks_ForwardMove` — SetQueue 5 tracks, move track from index 1 to index 3, verify order and currentIndex adjustment
|
||||
- `TestMoveQueueTracks_BackwardMove` — move from index 3 to index 1, verify order
|
||||
- `TestMoveQueueTracks_MoveCurrentTrack` — move the current track, verify currentIndex follows it
|
||||
- `TestRemoveTrack_RemovesCorrectTrack` — SetQueue 5 tracks, remove at index 2, verify 4 tracks remain and correct track removed
|
||||
- `TestRemoveTrack_RemoveCurrentTrack` — remove at currentIndex, verify index adjusts
|
||||
- `TestClear_EmptiesQueue` — SetQueue, Clear, verify empty state
|
||||
- `TestToggleShuffle_TogglesMode` — verify shuffle toggles on/off and shuffleOrder populates/clears
|
||||
- `TestCycleRepeat_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()}`, set `tracks`, `currentIndex`, `shuffleMode`, `repeatMode`, `shuffleOrder` directly
|
||||
|
||||
Tests (all t.Parallel()):
|
||||
- `TestNextIndex_NormalMode_AdvancesToNextTrack` — 5 tracks, index 2, repeatOff → returns 3
|
||||
- `TestNextIndex_NormalMode_EndOfQueue_RepeatOff` — index at last track, repeatOff → returns -1
|
||||
- `TestNextIndex_NormalMode_EndOfQueue_RepeatAll` — index at last track, repeatAll → returns 0 (wraps)
|
||||
- `TestNextIndex_RepeatOne` — any index, repeatOne → returns same index
|
||||
- `TestPreviousIndex_NormalMode_GoesBack` — index 3, repeatOff → returns 2
|
||||
- `TestPreviousIndex_AtStart_RepeatOff` — index 0, repeatOff → returns -1
|
||||
- `TestPreviousIndex_AtStart_RepeatAll` — index 0, repeatAll → returns last index
|
||||
- `TestGenerateShuffleOrder_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.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>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</automated>
|
||||
</verify>
|
||||
<done>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.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Queue persistence round-trip tests</name>
|
||||
<files>backend/queue/persistence_test.go</files>
|
||||
<action>
|
||||
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:
|
||||
1. Setup queue with DB, seed 5 audio files
|
||||
2. SetQueue with 5 file paths at startIndex 2
|
||||
3. CycleRepeat to "all"
|
||||
4. ToggleShuffle
|
||||
5. SaveState
|
||||
6. Create a NEW Queue instance with same DB: `q2 := NewQueue(slog.Default(), db); q2.SetPlayer(&mockTrackLoader{})`
|
||||
7. RestoreState on q2
|
||||
8. 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.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd backend && go test -race -count=1 -run "TestSaveState|TestRestoreState" ./queue/ -v 2>&1 | tail -20</automated>
|
||||
</verify>
|
||||
<done>persistence_test.go has ~6 tests covering full round-trip fidelity, empty/single edge cases, and overwrite behavior. All pass with -race.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
```bash
|
||||
cd backend && go test -race -count=1 ./queue/ -v
|
||||
```
|
||||
All queue tests pass with -race flag. Expected ~18-20 tests total.
|
||||
</verification>
|
||||
|
||||
<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>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/04-queue-config-player-tests/04-01-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,99 @@
|
||||
---
|
||||
phase: 04-queue-config-player-tests
|
||||
plan: 01
|
||||
subsystem: testing
|
||||
tags: [queue, sqlite, unit-tests, shuffle, repeat, persistence]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 03-test-infrastructure
|
||||
provides: "NewTestDB(t) helper for in-memory SQLite test databases"
|
||||
provides:
|
||||
- "29 queue tests covering core ops, navigation, and persistence roundtrip"
|
||||
- "Mock TrackLoader and seedAudioFiles test helpers in queue package"
|
||||
- "Safety net for Phase 7 (PERF-01) queue persistence refactoring"
|
||||
affects: [07-performance-optimization]
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: ["internal package tests (package queue, not queue_test)", "direct field manipulation for pure logic tests (no DB)", "seedAudioFiles helper with FK chain for DB-backed tests"]
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- backend/queue/queue_test.go
|
||||
- backend/queue/navigation_test.go
|
||||
- backend/queue/persistence_test.go
|
||||
modified: []
|
||||
|
||||
key-decisions:
|
||||
- "Internal tests (package queue) to access unexported fields like shuffleOrder, mu"
|
||||
- "Navigation tests use direct struct construction (no DB) for fast pure-logic testing"
|
||||
- "Persistence roundtrip test verifies ALL state fields including shuffleOrder JSON"
|
||||
|
||||
patterns-established:
|
||||
- "mockTrackLoader pattern: no-op TrackLoader with loadedFile tracking"
|
||||
- "seedAudioFiles helper: creates FK chain (artist_credit → recordings → audio_files) for N tracks"
|
||||
- "newTestQueueDirect: direct Queue construction for navigation/logic tests without DB"
|
||||
|
||||
requirements-completed: [TEST-02]
|
||||
|
||||
# Metrics
|
||||
duration: 3min
|
||||
completed: 2026-03-03
|
||||
---
|
||||
|
||||
# Phase 04 Plan 01: Queue Unit Tests Summary
|
||||
|
||||
**29 unit tests for queue core operations (SetQueue, Add, Insert, Move, Remove, Shuffle, Repeat), navigation logic (Next/Previous in all modes), and SaveState/RestoreState persistence roundtrip**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 3 min
|
||||
- **Started:** 2026-03-03T21:57:38Z
|
||||
- **Completed:** 2026-03-03T22:00:46Z
|
||||
- **Tasks:** 2
|
||||
- **Files modified:** 3
|
||||
|
||||
## Accomplishments
|
||||
- 14 core operation tests: SetQueue (3 variants), AddTrack, InsertTracksAt (before/after current), MoveQueueTracks (forward/backward/current), RemoveTrack (normal/current), Clear, ToggleShuffle, CycleRepeat
|
||||
- 9 navigation tests: nextIndex/previousIndex in RepeatOff/RepeatAll/RepeatOne modes, shuffle navigation, generateShuffleOrder property validation (all indices, no duplicates, current at [0])
|
||||
- 6 persistence roundtrip tests: full state fidelity, empty/single/10-track edge cases, overwrite semantics, no-prior-save safety
|
||||
- All 29 tests pass with `-race` flag
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically:
|
||||
|
||||
1. **Task 1: Queue core operations and navigation tests** - `8d60dc0` (test)
|
||||
2. **Task 2: Queue persistence round-trip tests** - `77cc993` (test)
|
||||
|
||||
## Files Created/Modified
|
||||
- `backend/queue/queue_test.go` - Core operation tests + mock TrackLoader + setupTestQueue/seedAudioFiles helpers
|
||||
- `backend/queue/navigation_test.go` - Navigation edge case tests + shuffle order property tests
|
||||
- `backend/queue/persistence_test.go` - SaveState/RestoreState roundtrip fidelity tests
|
||||
|
||||
## Decisions Made
|
||||
- Used internal tests (`package queue`) to access unexported fields (shuffleOrder, mu) — necessary for shuffle verification and roundtrip assertions
|
||||
- Navigation tests bypass DB entirely using direct struct construction for fast, focused tests
|
||||
- Roundtrip test asserts on shuffleOrder (JSON-serialized) to ensure Phase 7 refactoring won't silently lose shuffle state
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None - plan executed exactly as written.
|
||||
|
||||
## Issues Encountered
|
||||
None
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None - no external service configuration required.
|
||||
|
||||
## Next Phase Readiness
|
||||
- Queue test safety net complete — ready for Phase 7 (PERF-01) incremental persistence refactoring
|
||||
- Test helpers (mockTrackLoader, seedAudioFiles) available for reuse in Plan 04-02 (config/player tests)
|
||||
- Ready for Plan 04-02 execution
|
||||
|
||||
---
|
||||
*Phase: 04-queue-config-player-tests*
|
||||
*Completed: 2026-03-03*
|
||||
@@ -0,0 +1,300 @@
|
||||
---
|
||||
phase: 04-queue-config-player-tests
|
||||
plan: 02
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- backend/config/config_test.go
|
||||
- backend/theme/config_test.go
|
||||
- backend/tracklist/config_test.go
|
||||
- backend/favorites/config_test.go
|
||||
- backend/library/config_test.go
|
||||
- backend/player/volume_test.go
|
||||
autonomous: true
|
||||
requirements: [TEST-04, TEST-05]
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "Config load/save roundtrip preserves all fields without data loss"
|
||||
- "Sub-config validators reject invalid values and accept valid ones"
|
||||
- "Missing config file is handled gracefully (created with defaults)"
|
||||
- "UserVolume↔Volume conversion is mathematically correct at all boundary values"
|
||||
- "stateToMediaControls maps all player states correctly"
|
||||
- "All config and player tests pass with -race flag"
|
||||
artifacts:
|
||||
- path: "backend/config/config_test.go"
|
||||
provides: "Tests for Load/Save roundtrip, Validate composition, missing file handling, defaults"
|
||||
min_lines: 80
|
||||
- path: "backend/theme/config_test.go"
|
||||
provides: "Tests for theme validation (hex color, background shade)"
|
||||
min_lines: 40
|
||||
- path: "backend/tracklist/config_test.go"
|
||||
provides: "Tests for tracklist validation (valid/invalid/duplicate columns)"
|
||||
min_lines: 40
|
||||
- path: "backend/favorites/config_test.go"
|
||||
provides: "Tests for favorites validation (icon style)"
|
||||
min_lines: 30
|
||||
- path: "backend/library/config_test.go"
|
||||
provides: "Tests for library validation (directory existence, scan concurrency)"
|
||||
min_lines: 40
|
||||
- path: "backend/player/volume_test.go"
|
||||
provides: "Tests for volume conversion, clamp, state mapping"
|
||||
min_lines: 60
|
||||
key_links:
|
||||
- from: "backend/config/config_test.go"
|
||||
to: "backend/config/config.go"
|
||||
via: "Load/Save roundtrip with t.TempDir()"
|
||||
pattern: "Save|Load"
|
||||
- from: "backend/player/volume_test.go"
|
||||
to: "backend/player/volume.go"
|
||||
via: "ToVolume/ToUserVolume conversion"
|
||||
pattern: "ToVolume|ToUserVolume"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Write unit tests for the config package (including all sub-config validators) and player pure logic (volume conversion, state mapping).
|
||||
|
||||
Purpose: Config tests verify roundtrip fidelity and validation rules, which are essential before any config format changes. Player logic tests characterize the volume conversion math and state mapping as a safety net for any future player refactoring.
|
||||
|
||||
Output: 6 test files — 5 for config/sub-configs (~8-10 tests) and 1 for player (~5-6 tests).
|
||||
</objective>
|
||||
|
||||
<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>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Key types and contracts the executor needs. Extracted from codebase. -->
|
||||
|
||||
From backend/config/config.go:
|
||||
```go
|
||||
type Config struct {
|
||||
ctx context.Context // unexported
|
||||
logger *slog.Logger // unexported
|
||||
filePath string // unexported — set by NewConfig or manually for tests
|
||||
Library *library.Config `toml:"Library"`
|
||||
Theme *theme.Config `toml:"Theme"`
|
||||
Window *WindowConfig `toml:"Window"`
|
||||
TrackList *tracklist.Config `toml:"TrackList"`
|
||||
Favorites *favorites.Config `toml:"Favorites"`
|
||||
}
|
||||
|
||||
func NewConfig(logger *slog.Logger) (*Config, error) // reads from system config dir — NOT usable in tests
|
||||
func (c *Config) Validate() error // delegates to sub-configs
|
||||
func (c *Config) Load() error // reads from c.filePath
|
||||
func (c *Config) Save() error // writes to c.filePath with 0o644
|
||||
func (c *Config) applyDefaults() // unexported — fills nil sub-configs
|
||||
```
|
||||
|
||||
From backend/theme/config.go:
|
||||
```go
|
||||
type BackgroundShade string // "darker", "dark", "light"
|
||||
type Config struct { AccentColor string; BackgroundShade BackgroundShade }
|
||||
func (c *Config) ApplyDefaults()
|
||||
func (c *Config) Validate() error // checks hex color regex + shade enum
|
||||
const DefaultAccentColor = "#ffd43b"
|
||||
const DefaultBackgroundShade = BackgroundDark
|
||||
```
|
||||
|
||||
From backend/tracklist/config.go:
|
||||
```go
|
||||
type ColumnID string // 16 valid values
|
||||
type Column struct { ID ColumnID }
|
||||
type Config struct { Columns []Column }
|
||||
func (c *Config) ApplyDefaults()
|
||||
func (c *Config) Validate() error // checks valid IDs + no duplicates
|
||||
var DefaultColumns = []Column{{ColTrackName}, {ColArtistName}, {ColTrackLength}}
|
||||
```
|
||||
|
||||
From backend/favorites/config.go:
|
||||
```go
|
||||
type IconStyle string // "heart", "star"
|
||||
type Config struct { PlaylistID int64; IconStyle; PinDefault bool }
|
||||
func (c *Config) ApplyDefaults()
|
||||
func (c *Config) Validate() error // checks icon style enum
|
||||
const DefaultIconStyle = IconHeart
|
||||
```
|
||||
|
||||
From backend/library/config.go:
|
||||
```go
|
||||
type ScanConcurrency string // "auto", "ssd", "hdd"
|
||||
type Directory string
|
||||
type Config struct { DirectoryPath Directory; ScanConcurrency }
|
||||
func (c *Config) Validate() error // checks dir exists on filesystem + mode enum
|
||||
const DefaultScanConcurrency = ScanConcurrencyAuto
|
||||
```
|
||||
|
||||
From backend/player/volume.go:
|
||||
```go
|
||||
type UserVolume int // 0-100
|
||||
type Volume float64 // -5 to 0
|
||||
const MinUserVol UserVolume = 0, MaxUserVol = 100, DefaultUserVol = 50
|
||||
const MinVol Volume = -5, MaxVol = 0
|
||||
func (uv UserVolume) ToVolume() Volume
|
||||
func (v Volume) ToUserVolume() UserVolume
|
||||
func clampVolume(v UserVolume) UserVolume // unexported
|
||||
```
|
||||
|
||||
From backend/player/player.go:
|
||||
```go
|
||||
type State string
|
||||
const Playing State = "playing", Paused = "paused", Stopped = "stopped"
|
||||
func stateToMediaControls(s State) mediacontrols.PlaybackState // unexported
|
||||
```
|
||||
|
||||
From backend/mediacontrols/mediacontrols.go:
|
||||
```go
|
||||
type PlaybackState int
|
||||
const StateStopped PlaybackState = 0, StatePlaying = 1, StatePaused = 2
|
||||
```
|
||||
|
||||
From backend/config/window.go:
|
||||
```go
|
||||
type WindowConfig struct { Width int; Height int }
|
||||
func NewDefaultWindowConfig() *WindowConfig // returns &WindowConfig{Width: 1024, Height: 768}
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Config and sub-config validation tests</name>
|
||||
<files>backend/config/config_test.go, backend/theme/config_test.go, backend/tracklist/config_test.go, backend/favorites/config_test.go, backend/library/config_test.go</files>
|
||||
<action>
|
||||
Create 5 test files for config and all sub-config packages. All use internal test packages (same package name). Follow established conventions: t.Parallel(), table-driven subtests, stdlib testing only, no assertion libraries, t.Helper() on helpers.
|
||||
|
||||
**backend/theme/config_test.go** (package theme) — ~3 tests:
|
||||
- `TestThemeConfig_Validate_ValidValues` — table-driven: valid hex colors ("#fff", "#ffd43b", "#000000") with valid shades ("darker", "dark", "light") all pass
|
||||
- `TestThemeConfig_Validate_InvalidHexColor` — table-driven: invalid colors ("fff", "#gg0000", "#12345", "red", "") all return error containing "invalid hex color"
|
||||
- `TestThemeConfig_Validate_InvalidBackgroundShade` — shade "neon" returns error containing "unknown background shade"
|
||||
- `TestThemeConfig_ApplyDefaults` — verify zero-value Config gets DefaultAccentColor and DefaultBackgroundShade
|
||||
|
||||
**backend/tracklist/config_test.go** (package tracklist) — ~3 tests:
|
||||
- `TestTrackListConfig_Validate_ValidColumns` — valid column IDs pass
|
||||
- `TestTrackListConfig_Validate_UnknownColumnID` — unknown ID returns error containing "unknown track-list column ID"
|
||||
- `TestTrackListConfig_Validate_DuplicateColumn` — duplicate ID returns error containing "duplicate column ID"
|
||||
- `TestTrackListConfig_ApplyDefaults` — verify zero-value Config gets DefaultColumns
|
||||
|
||||
**backend/favorites/config_test.go** (package favorites) — ~2-3 tests:
|
||||
- `TestFavoritesConfig_Validate_ValidIconStyles` — table-driven: "heart", "star" both pass
|
||||
- `TestFavoritesConfig_Validate_InvalidIconStyle` — "diamond" returns error containing "unknown favorites icon style"
|
||||
- `TestFavoritesConfig_ApplyDefaults` — verify zero-value gets DefaultIconStyle
|
||||
|
||||
**backend/library/config_test.go** (package library) — ~3-4 tests:
|
||||
- `TestLibraryConfig_Validate_ValidDirectory` — use t.TempDir() as directory, all scan concurrency modes ("auto", "ssd", "hdd") pass
|
||||
- `TestLibraryConfig_Validate_NonexistentDirectory` — "/nonexistent/path/xyz" returns error
|
||||
- `TestLibraryConfig_Validate_InvalidScanConcurrency` — "turbo" returns error containing "unknown scan concurrency"
|
||||
- `TestLibraryConfig_Validate_EmptyDirectory` — empty DirectoryPath with valid scan concurrency passes (no dir check when empty)
|
||||
- `TestLibraryConfig_ApplyDefaults` — verify zero-value ScanConcurrency gets DefaultScanConcurrency
|
||||
|
||||
**backend/config/config_test.go** (package config) — ~3-4 tests:
|
||||
- `TestConfig_LoadSave_Roundtrip` — The critical roundtrip test:
|
||||
1. Create Config struct directly with `filePath` set to `filepath.Join(t.TempDir(), "config.toml")`
|
||||
2. Set all sub-configs to non-default values: theme accent "#ff0000", shade "light", tracklist columns with 5 columns, favorites icon "star", library directory set to a second t.TempDir(), library scan concurrency "ssd", window 800x600
|
||||
3. Call applyDefaults() then Save()
|
||||
4. Create NEW Config struct with same filePath, call Load()
|
||||
5. Verify ALL fields match the original values
|
||||
Note: Set `logger` to `slog.Default()` on the Config struct for both instances.
|
||||
|
||||
- `TestConfig_Load_MissingFile` — Config with filePath pointing to nonexistent file. Load() should create the file with defaults (current behavior). Verify file exists after Load().
|
||||
|
||||
- `TestConfig_Validate_ComposesSubConfigErrors` — Config with invalid theme (bad hex) AND invalid tracklist (unknown column) returns an error. Verify both error messages are present (errors.Join behavior).
|
||||
|
||||
- `TestConfig_ApplyDefaults_NilSubConfigs` — Config with all nil sub-configs, call applyDefaults(), verify all sub-configs are non-nil with sensible defaults.
|
||||
|
||||
For the roundtrip test, import sub-config packages: theme, tracklist, favorites, library. Access unexported fields (filePath, logger) directly since this is an internal test (package config).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd backend && go test -race -count=1 ./config/ ./theme/ ./tracklist/ ./favorites/ ./library/ -run "TestTheme|TestTrackList|TestFavorites|TestLibrary|TestConfig" -v 2>&1 | tail -40</automated>
|
||||
</verify>
|
||||
<done>5 test files exist covering: theme hex+shade validation, tracklist column validation, favorites icon validation, library dir+concurrency validation, and config load/save roundtrip. All pass with -race.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Player volume and state mapping tests</name>
|
||||
<files>backend/player/volume_test.go</files>
|
||||
<action>
|
||||
Create volume_test.go in the player package (internal, package player). Follow established conventions: t.Parallel(), table-driven subtests, stdlib testing only.
|
||||
|
||||
Write these test functions:
|
||||
|
||||
- `TestUserVolume_ToVolume` — table-driven with cases:
|
||||
| UserVolume | Expected Volume |
|
||||
|------------|-----------------|
|
||||
| 0 (MinUserVol) | -5.0 (MinVol) |
|
||||
| 100 (MaxUserVol) | 0.0 (MaxVol) |
|
||||
| 50 (DefaultUserVol) | -2.5 (midpoint) |
|
||||
| 25 | -3.75 |
|
||||
| 75 | -1.25 |
|
||||
For each: verify `uv.ToVolume()` matches expected within a tolerance of 0.001 (use math.Abs for float comparison).
|
||||
|
||||
- `TestVolume_ToUserVolume` — table-driven with inverse cases:
|
||||
| Volume | Expected UserVolume |
|
||||
|--------|---------------------|
|
||||
| -5.0 (MinVol) | 0 (MinUserVol) |
|
||||
| 0.0 (MaxVol) | 100 (MaxUserVol) |
|
||||
| -2.5 | 50 |
|
||||
| -3.75 | 25 |
|
||||
| -1.25 | 75 |
|
||||
For each: verify `v.ToUserVolume()` matches expected exactly (int comparison).
|
||||
|
||||
- `TestUserVolume_ToVolume_OutOfRange` — table-driven: values outside [0,100] like -1, 101, 200, -50. Verify ToVolume() returns zero-value Volume (0.0) per current implementation (the `if` guard fails, returns uninitialized `newVol`).
|
||||
|
||||
- `TestVolume_ToUserVolume_OutOfRange` — values outside [-5,0] like -6.0, 1.0, -10.0. Verify ToUserVolume() returns zero-value UserVolume (0) per current implementation.
|
||||
|
||||
- `TestUserVolume_ToVolume_Roundtrip` — for every UserVolume from 0 to 100, convert to Volume and back. Verify roundtrip matches original value. This is the characterization test — if the math changes, this breaks.
|
||||
|
||||
- `TestClampVolume` — table-driven:
|
||||
| Input | Expected |
|
||||
|-------|----------|
|
||||
| -10 | 0 (MinUserVol) |
|
||||
| 0 | 0 |
|
||||
| 50 | 50 |
|
||||
| 100 | 100 |
|
||||
| 150 | 100 (MaxUserVol) |
|
||||
|
||||
- `TestStateToMediaControls` — table-driven:
|
||||
| State | Expected PlaybackState |
|
||||
|-------|------------------------|
|
||||
| Playing | mediacontrols.StatePlaying (1) |
|
||||
| Paused | mediacontrols.StatePaused (2) |
|
||||
| Stopped | mediacontrols.StateStopped (0) |
|
||||
| State("unknown") | mediacontrols.StateStopped (0) — default case |
|
||||
|
||||
Import "yellowjacket/backend/mediacontrols" for the PlaybackState constants. Use `math` for float comparison tolerance.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd backend && go test -race -count=1 -run "TestUserVolume|TestVolume|TestClamp|TestState" ./player/ -v 2>&1 | tail -20</automated>
|
||||
</verify>
|
||||
<done>volume_test.go has ~7 tests covering ToVolume/ToUserVolume conversion at all boundaries, out-of-range behavior, full roundtrip 0-100, clamp, and state mapping. All pass with -race.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
```bash
|
||||
cd backend && go test -race -count=1 ./config/ ./theme/ ./tracklist/ ./favorites/ ./library/ ./player/ -v
|
||||
```
|
||||
All config and player tests pass with -race flag. Expected ~15-17 tests total.
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 5 config test files exist covering all sub-config validators + composed Config
|
||||
- Config load/save roundtrip preserves all non-default values
|
||||
- Missing config file handled gracefully
|
||||
- volume_test.go exists with ~7 tests for volume conversion + state mapping
|
||||
- ToVolume/ToUserVolume roundtrip is verified for all values 0-100
|
||||
- All tests pass with `go test -race`
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/04-queue-config-player-tests/04-02-SUMMARY.md`
|
||||
</output>
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
phase: 04-queue-config-player-tests
|
||||
plan: 02
|
||||
subsystem: testing
|
||||
tags: [config, theme, tracklist, favorites, library, player, volume, validation, table-driven-tests]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 03-test-infrastructure
|
||||
provides: Test infrastructure conventions (t.Parallel, table-driven, stdlib only)
|
||||
provides:
|
||||
- Config roundtrip and validation tests for all sub-configs
|
||||
- Player volume conversion characterization tests
|
||||
- State mapping coverage for mediacontrols integration
|
||||
affects: [05-database-query-tests, 06-sql-consolidation]
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "Internal package tests (same package) for unexported access"
|
||||
- "Float comparison with math.Abs tolerance for volume tests"
|
||||
- "Characterization roundtrip with ±1 tolerance for int-truncated conversions"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- backend/config/config_test.go
|
||||
- backend/theme/config_test.go
|
||||
- backend/tracklist/config_test.go
|
||||
- backend/favorites/config_test.go
|
||||
- backend/library/config_test.go
|
||||
- backend/player/volume_test.go
|
||||
modified: []
|
||||
|
||||
key-decisions:
|
||||
- "Roundtrip test uses ±1 tolerance: ToVolume/ToUserVolume uses int truncation not rounding, causing up to 1 unit drift"
|
||||
- "Empty AccentColor not tested as invalid: Validate() calls ApplyDefaults() first, filling in the default value"
|
||||
|
||||
patterns-established:
|
||||
- "Config validation tests: table-driven subtests for valid/invalid enum values"
|
||||
- "Volume characterization: boundary values exact, full-range roundtrip within tolerance"
|
||||
|
||||
requirements-completed: [TEST-04, TEST-05]
|
||||
|
||||
# Metrics
|
||||
duration: 4min
|
||||
completed: 2026-03-03
|
||||
---
|
||||
|
||||
# Phase 04 Plan 02: Config & Player Tests Summary
|
||||
|
||||
**Unit tests for config load/save roundtrip, all sub-config validators (theme/tracklist/favorites/library), and player volume conversion + state mapping — 27 test cases across 6 packages, all passing with -race**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 4 min
|
||||
- **Started:** 2026-03-03T21:57:19Z
|
||||
- **Completed:** 2026-03-03T22:02:12Z
|
||||
- **Tasks:** 2
|
||||
- **Files modified:** 6
|
||||
|
||||
## Accomplishments
|
||||
- Config load/save roundtrip test verifies all fields survive TOML serialization
|
||||
- All 4 sub-config validators (theme, tracklist, favorites, library) tested for valid values, invalid values, and defaults
|
||||
- Player volume conversion tested at all boundaries with full 0-100 roundtrip characterization
|
||||
- stateToMediaControls mapping verified for all states including unknown fallback
|
||||
- All tests pass with `-race` flag
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically:
|
||||
|
||||
1. **Task 1: Config and sub-config validation tests** - `f9b2ad9` (test)
|
||||
2. **Task 2: Player volume and state mapping tests** - `294b629` (test)
|
||||
|
||||
## Files Created/Modified
|
||||
- `backend/config/config_test.go` - Load/Save roundtrip, missing file, composed errors, nil defaults (227 lines)
|
||||
- `backend/theme/config_test.go` - Hex color regex + background shade enum validation (83 lines)
|
||||
- `backend/tracklist/config_test.go` - Column ID recognition + duplicate detection (73 lines)
|
||||
- `backend/favorites/config_test.go` - Icon style enum validation (49 lines)
|
||||
- `backend/library/config_test.go` - Directory existence + scan concurrency mode validation (83 lines)
|
||||
- `backend/player/volume_test.go` - Volume conversion, clamp, state mapping (198 lines)
|
||||
|
||||
## Decisions Made
|
||||
- **Roundtrip tolerance:** The `ToVolume`/`ToUserVolume` conversion uses `int()` truncation (not `math.Round`), so some values lose 1 unit in the roundtrip. The characterization test documents this with a ±1 tolerance, while verifying boundary values (0, 50, 100) are exact.
|
||||
- **Empty AccentColor not invalid:** `Validate()` calls `ApplyDefaults()` first, which fills empty accent color with `#ffd43b`, so empty string is handled gracefully rather than being an error case.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 1 - Bug] Removed empty-string hex color from invalid test cases**
|
||||
- **Found during:** Task 1 (theme validation tests)
|
||||
- **Issue:** Plan listed empty string as invalid hex color, but `Validate()` calls `ApplyDefaults()` first which fills in the default color
|
||||
- **Fix:** Removed empty string from invalid test cases — it's valid behavior by design
|
||||
- **Files modified:** backend/theme/config_test.go
|
||||
- **Verification:** All theme tests pass
|
||||
- **Committed in:** f9b2ad9 (Task 1 commit)
|
||||
|
||||
**2. [Rule 1 - Bug] Changed roundtrip test from exact to ±1 tolerance**
|
||||
- **Found during:** Task 2 (volume roundtrip test)
|
||||
- **Issue:** Plan specified exact roundtrip match for all 0-100 values, but `ToUserVolume()` uses `int()` truncation causing up to 1 unit drift
|
||||
- **Fix:** Changed to ±1 tolerance with separate exact checks for boundary values (0, 50, 100)
|
||||
- **Files modified:** backend/player/volume_test.go
|
||||
- **Verification:** All player tests pass with -race
|
||||
- **Committed in:** 294b629 (Task 2 commit)
|
||||
|
||||
---
|
||||
|
||||
**Total deviations:** 2 auto-fixed (2 bugs — plan assumptions didn't match actual code behavior)
|
||||
**Impact on plan:** Both fixes accurately characterize existing behavior rather than imposing incorrect expectations. No scope creep.
|
||||
|
||||
## Issues Encountered
|
||||
None
|
||||
|
||||
## User Setup Required
|
||||
None - no external service configuration required.
|
||||
|
||||
## Next Phase Readiness
|
||||
- Config and player pure logic fully characterized with tests
|
||||
- Ready for remaining Phase 4 plans (queue tests) or Phase 5 (database query tests)
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
All 6 created files verified on disk. Both commits (f9b2ad9, 294b629) verified in git log.
|
||||
|
||||
---
|
||||
*Phase: 04-queue-config-player-tests*
|
||||
*Completed: 2026-03-03*
|
||||
@@ -0,0 +1,73 @@
|
||||
# Phase 4: Queue, Config & Player Tests - Context
|
||||
|
||||
**Gathered:** 2026-03-03
|
||||
**Status:** Ready for planning
|
||||
|
||||
<domain>
|
||||
## Phase Boundary
|
||||
|
||||
Write unit tests for three packages: queue operations (SetQueue, Next, Previous, shuffle, repeat, persistence), config roundtrip (load/save, validation, defaults), and player pure logic (volume conversion, state mapping). These tests characterize current behavior and serve as a safety net for Phase 6-7 refactoring. No production code changes except adding test files.
|
||||
|
||||
</domain>
|
||||
|
||||
<decisions>
|
||||
## Implementation Decisions
|
||||
|
||||
### Test fixture strategy
|
||||
- Per-test inline setup for queue — each test creates its own audio_file FK rows with minimal fields. Verbose but self-contained; a test failure tells you everything.
|
||||
- t.TempDir() for config filesystem tests — real filesystem via Go's test temp dirs, auto-cleaned, tests actual TOML read/write.
|
||||
- Simple mock TrackLoader struct defined locally in queue_test.go — only queue tests need it, keep it local.
|
||||
- Player tests are pure logic only — no NewTestDB, no persistence round-trips. Volume conversion, clamp, state mapping only. Player persistence deferred to integration tests.
|
||||
|
||||
### Player logic extraction
|
||||
- Test existing pure logic in place — volume.go (UserVolume, Volume, clampVolume) is already cleanly separated. Write volume_test.go against it. No extraction from player.go.
|
||||
- Include stateToMediaControls() — it's pure and trivial but documents the state mapping. Characterization value.
|
||||
- Format detection tested in metadata package, not player — the code lives in metadata/decoder.go, tests belong there (decoder_test.go or similar).
|
||||
- Do NOT extract anything new from player.go — lock-sensitive code must not be touched. Test what's already pure.
|
||||
|
||||
### Coverage depth vs breadth
|
||||
- Queue: edge cases first — empty queue, single track, last track, first track, remove current track. These are where bugs hide and refactoring breaks.
|
||||
- Queue: dedicated move test cases — move forward, move backward, move current track, move to boundaries, move multiple tracks. MoveQueueTracks has the most complex index arithmetic.
|
||||
- Queue: test InsertTracksAt index shifts — insert before/at/after current index, verify currentIndex adjusts correctly. Common off-by-one bug source.
|
||||
- Queue: verify generateShuffleOrder() properties — all indices present, current track at index 0, no duplicates. Property-based validation.
|
||||
- Queue: full persistence round-trip — SaveState → new Queue → RestoreState → verify all fields match (shuffle order, repeat mode, current index, track list). Critical for Phase 7 optimization safety.
|
||||
- Config: test both sub-config validators independently AND the composed Config.Validate(). Pinpoints failures to specific validators.
|
||||
- Config: include library.Config.Validate() path with t.TempDir() — test both valid directory (real temp dir) and invalid directory (nonexistent path).
|
||||
- Player: 5-6 tests is sufficient — volume roundtrip, boundary values, clamp, state mapping. Quality over quantity.
|
||||
|
||||
### Test organization
|
||||
- Internal test packages (package queue, package config, package player) — queue tests need access to unexported fields (shuffleOrder, currentIndex, tracks) for setup and assertions.
|
||||
- Mirror source file names — navigation_test.go tests navigation.go, persistence_test.go tests persistence.go, queue_test.go tests queue.go. Easy to find tests for any function.
|
||||
- Sub-config tests in their respective packages — theme/config_test.go, tracklist/config_test.go, favorites/config_test.go, library/config_test.go. Config package tests the composed Config.
|
||||
- t.Parallel() everywhere — NewTestDB gives isolated DB instances, pure logic tests have no shared state. Matches existing coverart/metadata convention.
|
||||
|
||||
### Claude's Discretion
|
||||
- Exact test case names and table-driven subtest structure
|
||||
- How to organize table-driven tests vs individual test functions (per complexity)
|
||||
- Specific assertion messages and error formatting
|
||||
- Whether to use subtests within a single Test function or separate Test functions per behavior
|
||||
|
||||
</decisions>
|
||||
|
||||
<specifics>
|
||||
## Specific Ideas
|
||||
|
||||
- Queue persistence round-trip is 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.
|
||||
- Mock TrackLoader should be minimal — just enough to satisfy the interface. LoadFile/Play/UnloadTrack can be no-ops, IsPlaying returns false, CurrentPositionSeconds returns 0.
|
||||
- Queue tests need to insert audio_file rows before queue_tracks (FK constraint). Also need file_type rows since audio_files FKs to file_types.
|
||||
- Player's existing player_test.go is an integration test guarded by YELLOWJACKET_INTEGRATION env var — new unit tests are separate and should always run.
|
||||
- Existing test conventions: table-driven subtests with t.Run(), t.Parallel(), standard library testing only (no testify), no assertion libraries.
|
||||
|
||||
</specifics>
|
||||
|
||||
<deferred>
|
||||
## Deferred Ideas
|
||||
|
||||
None — discussion stayed within phase scope.
|
||||
|
||||
</deferred>
|
||||
|
||||
---
|
||||
|
||||
*Phase: 04-queue-config-player-tests*
|
||||
*Context gathered: 2026-03-03*
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
phase: 04-queue-config-player-tests
|
||||
verified: 2026-03-03T17:10:00Z
|
||||
status: passed
|
||||
score: 11/11 must-haves verified
|
||||
re_verification: false
|
||||
---
|
||||
|
||||
# Phase 04: Queue, Config & Player Tests Verification Report
|
||||
|
||||
**Phase Goal:** The queue, config, and player packages have comprehensive unit tests that characterize current behavior and serve as a safety net for later refactoring
|
||||
**Verified:** 2026-03-03T17:10:00Z
|
||||
**Status:** passed
|
||||
**Re-verification:** No — initial verification
|
||||
|
||||
## Goal Achievement
|
||||
|
||||
### Observable Truths
|
||||
|
||||
| # | Truth | Status | Evidence |
|
||||
|---|-------|--------|----------|
|
||||
| 1 | Queue navigation (Next/Previous) works correctly in all repeat modes (off, one, all) for both normal and shuffle playback | ✓ VERIFIED | 9 tests in navigation_test.go: nextIndex/previousIndex for RepeatOff, RepeatAll, RepeatOne, shuffle mode, plus generateShuffleOrder property validation |
|
||||
| 2 | Queue mutations (Add, Insert, Move, Remove) correctly update tracks and adjust currentIndex | ✓ VERIFIED | 8 tests in queue_test.go: AddTrack, InsertTracksAt before/after, MoveQueueTracks forward/backward/current, RemoveTrack normal/current |
|
||||
| 3 | Queue state persists across SaveState/RestoreState cycles without data loss | ✓ VERIFIED | 6 tests in persistence_test.go: full roundtrip (all fields including shuffleOrder), empty queue, single track, 10-track order, no-prior-save safety, overwrite semantics |
|
||||
| 4 | Shuffle order contains all indices, has current track at position 0, and has no duplicates | ✓ VERIFIED | TestGenerateShuffleOrder_Properties with table-driven subtests for 1, 5, and 20 tracks — checks length, [0] == currentIndex, all-unique, all-in-range |
|
||||
| 5 | All queue tests pass with -race flag | ✓ VERIFIED | `go test -race -count=1 ./queue/ -v` — 29 tests PASS, 0 failures, 0 data races |
|
||||
| 6 | Config load/save roundtrip preserves all fields without data loss | ✓ VERIFIED | TestConfig_LoadSave_Roundtrip verifies theme, tracklist, favorites, library, window all survive TOML serialization |
|
||||
| 7 | Sub-config validators reject invalid values and accept valid ones | ✓ VERIFIED | 16 tests across theme (4), tracklist (4), favorites (3), library (5) — valid values pass, invalid hex/shade/column/icon/dir/concurrency rejected |
|
||||
| 8 | Missing config file is handled gracefully (created with defaults) | ✓ VERIFIED | TestConfig_Load_MissingFile verifies Load() on nonexistent file succeeds and creates file |
|
||||
| 9 | UserVolume↔Volume conversion is mathematically correct at all boundary values | ✓ VERIFIED | 5 tests: ToVolume (5 cases), ToUserVolume (5 cases), out-of-range (4+3 cases), full 0-100 roundtrip with ±1 tolerance, exact boundaries |
|
||||
| 10 | stateToMediaControls maps all player states correctly | ✓ VERIFIED | TestStateToMediaControls: Playing→StatePlaying, Paused→StatePaused, Stopped→StateStopped, unknown→StateStopped |
|
||||
| 11 | All config and player tests pass with -race flag | ✓ VERIFIED | `go test -race -count=1 ./config/ ./theme/ ./tracklist/ ./favorites/ ./library/ ./player/ -v` — 27 tests PASS, 0 failures |
|
||||
|
||||
**Score:** 11/11 truths verified
|
||||
|
||||
### Required Artifacts
|
||||
|
||||
| Artifact | Expected | Status | Details |
|
||||
|----------|----------|--------|---------|
|
||||
| `backend/queue/queue_test.go` | Core ops tests + mock + helpers (min 200 lines) | ✓ VERIFIED | 395 lines, 14 test functions, mockTrackLoader, setupTestQueue, seedAudioFiles |
|
||||
| `backend/queue/navigation_test.go` | Navigation tests (min 150 lines) | ✓ VERIFIED | 198 lines, 9 test functions covering all repeat+shuffle modes |
|
||||
| `backend/queue/persistence_test.go` | Persistence roundtrip tests (min 100 lines) | ✓ VERIFIED | 199 lines, 6 test functions covering full roundtrip fidelity |
|
||||
| `backend/config/config_test.go` | Config load/save + defaults (min 80 lines) | ✓ VERIFIED | 228 lines, 4 test functions |
|
||||
| `backend/theme/config_test.go` | Theme validation (min 40 lines) | ✓ VERIFIED | 84 lines, 4 test functions |
|
||||
| `backend/tracklist/config_test.go` | Tracklist validation (min 40 lines) | ✓ VERIFIED | 74 lines, 4 test functions |
|
||||
| `backend/favorites/config_test.go` | Favorites validation (min 30 lines) | ✓ VERIFIED | 50 lines, 3 test functions |
|
||||
| `backend/library/config_test.go` | Library validation (min 40 lines) | ✓ VERIFIED | 84 lines, 5 test functions |
|
||||
| `backend/player/volume_test.go` | Volume conversion + state mapping (min 60 lines) | ✓ VERIFIED | 199 lines, 7 test functions |
|
||||
|
||||
### Key Link Verification
|
||||
|
||||
| From | To | Via | Status | Details |
|
||||
|------|----|-----|--------|---------|
|
||||
| `queue/queue_test.go` | `database/testhelper.go` | `database.NewTestDB(t)` | ✓ WIRED | Line 34: `db := database.NewTestDB(t)` — called in setupTestQueue helper, used by all DB-backed queue tests |
|
||||
| `queue/persistence_test.go` | `queue/persistence.go` | `SaveState/RestoreState roundtrip` | ✓ WIRED | 19 references: SaveState() called in 5 tests, RestoreState() in 6 tests, full state verification after each |
|
||||
| `config/config_test.go` | `config/config.go` | `Load/Save roundtrip with t.TempDir()` | ✓ WIRED | Save() + Load() called against temp file, all fields verified after roundtrip |
|
||||
| `player/volume_test.go` | `player/volume.go` | `ToVolume/ToUserVolume conversion` | ✓ WIRED | 17 references: ToVolume() called at all boundaries + out-of-range, ToUserVolume() inverse, full 0-100 roundtrip |
|
||||
|
||||
### Requirements Coverage
|
||||
|
||||
| Requirement | Source Plan | Description | Status | Evidence |
|
||||
|-------------|------------|-------------|--------|----------|
|
||||
| TEST-02 | 04-01-PLAN | Queue package has unit tests covering SetQueue, Next, Previous, shuffle mode, repeat modes, and state persistence (~15-20 tests) | ✓ SATISFIED | 29 queue tests (14 core + 9 navigation + 6 persistence), all passing with -race. Exceeds ~15-20 target. |
|
||||
| TEST-04 | 04-02-PLAN | Config package has unit tests covering load/save roundtrip, validation rules, default application, and behavior with missing/empty config files (~8-10 tests) | ✓ SATISFIED | 20 config tests (4 config + 4 theme + 4 tracklist + 3 favorites + 5 library), all passing with -race. Exceeds ~8-10 target. |
|
||||
| TEST-05 | 04-02-PLAN | Player pure logic (UserVolume-to-Volume conversion, state serialization, format detection) is extracted into testable functions with unit tests (~5-8 tests) | ✓ SATISFIED | 7 player tests covering volume conversion, out-of-range, roundtrip, clamp, and state mapping. Format detection lives in metadata package per CONTEXT decision — not a gap. |
|
||||
|
||||
### Anti-Patterns Found
|
||||
|
||||
| File | Line | Pattern | Severity | Impact |
|
||||
|------|------|---------|----------|--------|
|
||||
| — | — | None found | — | — |
|
||||
|
||||
No TODOs, FIXMEs, placeholders, empty implementations, or stub patterns detected in any of the 9 test files.
|
||||
|
||||
### Success Criteria Verification (from ROADMAP.md)
|
||||
|
||||
| # | Criterion | Status | Evidence |
|
||||
|---|-----------|--------|----------|
|
||||
| 1 | Queue package has ~15-20 tests covering SetQueue, Next, Previous, shuffle, repeat, persistence | ✓ VERIFIED | 29 tests (exceeds target): SetQueue (3), Next/Previous (7), shuffle (2+TestGenerateShuffleOrder), repeat (1 CycleRepeat), mutations (8), persistence (6) |
|
||||
| 2 | Config package has ~8-10 tests covering roundtrip, validation, defaults, missing files | ✓ VERIFIED | 20 tests (exceeds target): roundtrip (1), validation across 4 sub-configs (12), defaults (5), missing file (1), composed errors (1) |
|
||||
| 3 | Player pure logic extracted with ~5-8 unit tests | ✓ VERIFIED | 7 tests: ToVolume (1), ToUserVolume (1), OutOfRange (2), Roundtrip (1), Clamp (1), StateToMediaControls (1). Format detection in metadata package per design decision. |
|
||||
| 4 | All tests pass with `-race` flag | ✓ VERIFIED | 56 total tests (29 queue + 27 config/player) all PASS with `-race -count=1`, zero data races detected |
|
||||
|
||||
### Human Verification Required
|
||||
|
||||
None. All verification is automated via `go test -race`. Test correctness is observable from pass/fail results and code inspection.
|
||||
|
||||
### Gaps Summary
|
||||
|
||||
No gaps found. All 11 observable truths verified, all 9 artifacts exist and are substantive (1,502 total lines), all 4 key links wired and active, all 3 requirements satisfied, all 4 ROADMAP success criteria met. 56 tests pass with `-race` flag.
|
||||
|
||||
The phase goal — "comprehensive unit tests that characterize current behavior and serve as a safety net for later refactoring" — is achieved. The queue persistence roundtrip test (the highest-priority safety net for Phase 7 PERF-01) verifies all state fields including shuffleOrder JSON serialization.
|
||||
|
||||
---
|
||||
|
||||
_Verified: 2026-03-03T17:10:00Z_
|
||||
_Verifier: Claude (gsd-verifier)_
|
||||
Reference in New Issue
Block a user