package api import ( "context" "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/jackc/pgx/v5/pgtype" ) // seedMissingWant marks a track's file missing for two days and puts its // album on the wanted list, held at heldDaysAgo (0 for an open want) and // last searched with nothing found. func seedMissingWant(t *testing.T, h *handlers, trackID, albumID pgtype.UUID, group string, heldDaysAgo int) string { t.Helper() ctx := context.Background() if _, err := h.pool.Exec(ctx, `UPDATE tracks SET missing_since = now() - interval '2 days' WHERE id = $1`, trackID); err != nil { t.Fatalf("mark missing: %v", err) } if _, err := h.pool.Exec(ctx, `DELETE FROM wants WHERE release_group_mbid = $1`, group); err != nil { t.Fatalf("clear want: %v", err) } var id pgtype.UUID if err := h.pool.QueryRow(ctx, `INSERT INTO wants (release_group_mbid, artist_mbid, artist_name, title, reason, album_id, wanted_at, fulfilled_at, searched_at, search_outcome) VALUES ($1, 'artist-'||$1, 'Artist', 'Album '||$1, 'library', $2, now() - interval '30 days', CASE WHEN $3::int > 0 THEN now() - make_interval(days => $3::int) END, now() - interval '1 hour', 'nothing') RETURNING id`, group, albumID, heldDaysAgo).Scan(&id); err != nil { t.Fatalf("insert want: %v", err) } return uuidToString(id) } // A folder's album that was held before its files went is not wanted back // yet (every library album is on the list, held, since #5567); an open want // comes with its search, so the Missing page can show it and act on it (M504). func TestListMissingTracks_WantState(t *testing.T) { h, pool := testHandlers(t) truncateLibrary(t, pool) admin := seedUser(t, pool, "admin", "pw", true) heldTrack, _ := seedTrackOnDisk(t, h, "missheld") openTrack, _ := seedTrackOnDisk(t, h, "missopen") seedMissingWant(t, h, heldTrack.ID, heldTrack.AlbumID, "rg-missing-held", 10) openID := seedMissingWant(t, h, openTrack.ID, openTrack.AlbumID, "rg-missing-open", 0) req := withUser(httptest.NewRequest(http.MethodGet, "/api/admin/library/missing", nil), admin) w := httptest.NewRecorder() h.handleListMissingTracks(w, req) if w.Code != http.StatusOK { t.Fatalf("status %d: %s", w.Code, w.Body.String()) } var resp adminMissingResponse if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil { t.Fatalf("decode: %v", err) } if len(resp.Groups) != 2 { t.Fatalf("groups = %d, want 2", len(resp.Groups)) } byTitle := map[string]missingGroupView{} for _, g := range resp.Groups { byTitle[g.Tracks[0].Title] = g } if r := byTitle["AdmQ Track missheld"].Reacquisition; r != nil { t.Errorf("held before the loss: reacquisition = %+v, want none", r) } r := byTitle["AdmQ Track missopen"].Reacquisition if r == nil || r.WantedAt == nil || r.HeldAt != nil { t.Fatalf("open want: reacquisition = %+v, want wanted and not held", r) } if r.Want == nil || r.Want.ID != openID { t.Fatalf("open want: want = %+v, want id %s", r.Want, openID) } if r.Want.Search == nil || r.Want.Search.Outcome != "nothing" { t.Errorf("open want: search = %+v, want outcome nothing", r.Want.Search) } }