release / govulncheck (push) Successful in 16s
release / web (push) Successful in 1m19s
release / go (push) Successful in 1m37s
release / integration (push) Successful in 5m28s
release / android (push) Successful in 5m44s
release / Build signed APK (releases and dev) (push) Successful in 5m58s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 25s
release / Verify release artifacts (tag releases only) (push) Skipped
The one-time listing took only albums whose tags already named their release group and artist, and left 340 off the list. The rest are now matched: the release their tags name, else the artist's release groups called what the album is called, else a MusicBrainz search by artist and title. A group found by name is taken only when its tracks agree with the album's (by disc and number, then title; length and title, or recording), so a partial copy still matches and a same-titled other album does not. library_album_matches records each album the listing decided, listed or unmatched with why, so an album taken off the list is never re-listed. The albums no match was found for are named on the Held tab. Album rows with no tracks are no longer counted. The length and title checks move from the importer to release, shared by both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
466 lines
17 KiB
Go
466 lines
17 KiB
Go
package wanted
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/catalogue"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/db"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/dbtest"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/musicbrainz"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/tags"
|
|
)
|
|
|
|
func TestPasses_PrimaryListedAndEverySecondaryAllowed(t *testing.T) {
|
|
standard := dbq.ReleaseTypeProfile{PrimaryTypes: []string{"Album", "EP", "Single"}, SecondaryTypes: []string{}}
|
|
withLive := dbq.ReleaseTypeProfile{PrimaryTypes: []string{"Album"}, SecondaryTypes: []string{"Live"}}
|
|
for _, c := range []struct {
|
|
profile dbq.ReleaseTypeProfile
|
|
primary string
|
|
secondary []string
|
|
want bool
|
|
}{
|
|
{standard, "Album", nil, true},
|
|
{standard, "single", nil, true}, // MusicBrainz's case is not relied on
|
|
{standard, "Album", []string{"Live"}, false},
|
|
{standard, "Broadcast", nil, false},
|
|
{withLive, "Album", []string{"Live"}, true},
|
|
{withLive, "Album", []string{"Live", "Compilation"}, false},
|
|
{withLive, "EP", nil, false},
|
|
} {
|
|
if got := Passes(c.profile, c.primary, c.secondary); got != c.want {
|
|
t.Errorf("Passes(%v/%v, %s %v) = %v, want %v", c.profile.PrimaryTypes, c.profile.SecondaryTypes,
|
|
c.primary, c.secondary, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestProfileInput_NormalizesToMusicBrainzTypes(t *testing.T) {
|
|
in, err := ProfileInput{Name: " Live too ", PrimaryTypes: []string{"single", "Album"}, SecondaryTypes: []string{"live"}}.normalize()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if in.Name != "Live too" || fmt.Sprint(in.PrimaryTypes) != "[Album Single]" || fmt.Sprint(in.SecondaryTypes) != "[Live]" {
|
|
t.Errorf("normalized = %+v", in)
|
|
}
|
|
for name, bad := range map[string]ProfileInput{
|
|
"no name": {PrimaryTypes: []string{"Album"}},
|
|
"no primary type": {Name: "x"},
|
|
"unknown type": {Name: "x", PrimaryTypes: []string{"Mixtape"}},
|
|
} {
|
|
if _, err := bad.normalize(); !errors.Is(err, ErrProfileInvalid) {
|
|
t.Errorf("%s: err = %v", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRecent_ReadsAPartialDateAsItsLastDay(t *testing.T) {
|
|
s := &Service{}
|
|
baseline := time.Date(2026, 10, 10, 0, 0, 0, 0, time.UTC)
|
|
for date, want := range map[string]bool{
|
|
"": true, // not yet known: an announced release
|
|
"2026-11-01": true,
|
|
"2026-09-15": true, // inside the grace before the follow
|
|
"2026-08-01": false,
|
|
"2026": true, // could be December
|
|
"2025": false,
|
|
"2026-09": true,
|
|
"1998-04-01": false,
|
|
} {
|
|
if got := s.recent(date, baseline); got != want {
|
|
t.Errorf("recent(%q) = %v, want %v", date, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestShortest_PrefersOfficialEditions(t *testing.T) {
|
|
rel := func(status string, n int32) dbq.MbRelease { return dbq.MbRelease{Status: status, TrackCount: n} }
|
|
for _, c := range []struct {
|
|
releases []dbq.MbRelease
|
|
want int
|
|
}{
|
|
{nil, 0},
|
|
{[]dbq.MbRelease{rel("Official", 14), rel("Official", 12), rel("Promotion", 3)}, 12},
|
|
{[]dbq.MbRelease{rel("Bootleg", 9), rel("", 11)}, 9},
|
|
{[]dbq.MbRelease{rel("Official", 0), rel("Bootleg", 9)}, 9},
|
|
} {
|
|
if got := Shortest(c.releases); got != c.want {
|
|
t.Errorf("Shortest(%v) = %d, want %d", c.releases, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestAnswers(t *testing.T) {
|
|
album := pgtype.UUID{Bytes: [16]byte{1}, Valid: true}
|
|
other := pgtype.UUID{Bytes: [16]byte{2}, Valid: true}
|
|
missing := dbq.Want{Reason: ReasonMissing, AlbumID: album}
|
|
request := dbq.Want{Reason: ReasonRequest}
|
|
for name, c := range map[string]struct {
|
|
w dbq.Want
|
|
h dbq.WantHoldingsRow
|
|
shortest int
|
|
want bool
|
|
}{
|
|
"nothing on disk": {request, dbq.WantHoldingsRow{AlbumID: other, ArrivedSince: true}, 10, false},
|
|
"a full edition": {request, dbq.WantHoldingsRow{AlbumID: other, Present: 10}, 10, true},
|
|
"short of every edition": {request, dbq.WantHoldingsRow{AlbumID: other, Present: 9, ArrivedSince: true}, 10, false},
|
|
"no editions known, an arrival": {request, dbq.WantHoldingsRow{AlbumID: other, Present: 1, ArrivedSince: true, MissingBefore: true}, 0, true},
|
|
"no editions known, still missing": {request, dbq.WantHoldingsRow{AlbumID: other, Present: 5, MissingBefore: true}, 0, false},
|
|
"the lost album, a gap remains": {missing, dbq.WantHoldingsRow{AlbumID: album, Present: 12, UncoveredMissing: 1}, 10, false},
|
|
"the lost album, every gap filled": {missing, dbq.WantHoldingsRow{AlbumID: album, Present: 9, UncoveredMissing: 0}, 10, true},
|
|
"another copy of the lost album": {missing, dbq.WantHoldingsRow{AlbumID: other, Present: 10}, 10, true},
|
|
} {
|
|
if got := Answers(c.w, c.h, c.shortest); got != c.want {
|
|
t.Errorf("%s: Answers = %v, want %v", name, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFollowable(t *testing.T) {
|
|
for _, c := range []struct {
|
|
mbid, name string
|
|
want bool
|
|
}{
|
|
{"a", "Gorillaz", true},
|
|
{variousArtists, "Various Artists", false},
|
|
{"b", "[unknown]", false},
|
|
{"", "Nameless", false},
|
|
{"c", "[ ]Bracketed Band", true},
|
|
} {
|
|
if got := Followable(c.mbid, c.name); got != c.want {
|
|
t.Errorf("Followable(%q, %q) = %v", c.mbid, c.name, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// --- integration ---
|
|
|
|
func newPool(t *testing.T) *pgxpool.Pool {
|
|
t.Helper()
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test in -short mode")
|
|
}
|
|
dsn := os.Getenv("MINSTREL_TEST_DATABASE_URL")
|
|
if dsn == "" {
|
|
t.Skip("MINSTREL_TEST_DATABASE_URL not set")
|
|
}
|
|
if err := db.Migrate(dsn, slog.New(slog.NewTextHandler(io.Discard, nil))); err != nil {
|
|
t.Fatalf("migrate: %v", err)
|
|
}
|
|
pool, err := pgxpool.New(context.Background(), dsn)
|
|
if err != nil {
|
|
t.Fatalf("pool: %v", err)
|
|
}
|
|
t.Cleanup(pool.Close)
|
|
dbtest.ResetDB(t, pool)
|
|
return pool
|
|
}
|
|
|
|
const (
|
|
blur = "ba853904-ae25-4ebb-89d6-c44cfbd71bd2"
|
|
parklife = "11111111-0000-4000-8000-000000000001" // 1994 album, held
|
|
liveAtWemb = "11111111-0000-4000-8000-000000000002" // 2009 live album
|
|
theBallad = "11111111-0000-4000-8000-000000000003" // 2023 album, not held
|
|
newSingle = "11111111-0000-4000-8000-000000000004" // appears after the baseline
|
|
oldSingle = "11111111-0000-4000-8000-000000000005" // 1997 single, catalogued late
|
|
parkRelease = "22222222-0000-4000-8000-000000000001"
|
|
)
|
|
|
|
// fakeMB is a MusicBrainz holding Blur's discography; added groups appear in
|
|
// it after the baseline, and down makes every request a 503.
|
|
type fakeMB struct {
|
|
srv *httptest.Server
|
|
added atomic.Bool
|
|
down atomic.Bool
|
|
}
|
|
|
|
func newFakeMB(t *testing.T) *fakeMB {
|
|
t.Helper()
|
|
f := &fakeMB{}
|
|
group := func(id, title, primary, secondary, date string) string {
|
|
return fmt.Sprintf(`{"id": %q, "title": %q, "primary-type": %q, "secondary-types": [%s], "first-release-date": %q,
|
|
"artist-credit": [{"name": "Blur", "artist": {"id": %q}}]}`, id, title, primary, secondary, date, blur)
|
|
}
|
|
f.srv = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if f.down.Load() {
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
return
|
|
}
|
|
switch {
|
|
case r.URL.Path == "/artist/"+blur:
|
|
_, _ = fmt.Fprintf(w, `{"id": %q, "name": "Blur", "type": "Group", "country": "GB"}`, blur)
|
|
case r.URL.Path == "/release-group" && r.URL.Query().Get("artist") == blur:
|
|
groups := group(parklife, "Parklife", "Album", "", "1994-04-25") + "," +
|
|
group(liveAtWemb, "Live at Wembley Stadium", "Album", `"Live"`, "2009-07-02") + "," +
|
|
group(theBallad, "The Ballad of Darren", "Album", "", "2023-07-21")
|
|
n := 3
|
|
if f.added.Load() {
|
|
groups += "," + group(newSingle, "New Single", "Single", "", "") +
|
|
"," + group(oldSingle, "Old Single", "Single", "", "1997-02-03")
|
|
n = 5
|
|
}
|
|
_, _ = fmt.Fprintf(w, `{"release-group-count": %d, "release-groups": [%s]}`, n, groups)
|
|
case r.URL.Path == "/release-group/"+theBallad:
|
|
_, _ = fmt.Fprint(w, group(theBallad, "The Ballad of Darren", "Album", "", "2023-07-21"))
|
|
case r.URL.Path == "/release" && r.URL.Query().Get("release-group") == theBallad:
|
|
_, _ = fmt.Fprint(w, `{"release-count": 2, "releases": [
|
|
{"id": "r-ballad-1", "title": "The Ballad of Darren", "status": "Official", "media": [{"position": 1,
|
|
"tracks": [{"position": 1, "number": "1", "title": "The Ballad", "recording": {"id": "rec-1"}},
|
|
{"position": 2, "number": "2", "title": "St. Charles Square", "recording": {"id": "rec-2"}}]}]},
|
|
{"id": "r-ballad-2", "title": "The Ballad of Darren", "status": "Official", "media": [{"position": 1,
|
|
"tracks": [{"position": 1, "number": "1", "title": "The Ballad", "recording": {"id": "rec-1"}},
|
|
{"position": 2, "number": "2", "title": "St. Charles Square", "recording": {"id": "rec-2"}},
|
|
{"position": 3, "number": "3", "title": "Bonus", "recording": {"id": "rec-3"}}]}]}]}`)
|
|
case r.URL.Path == "/release":
|
|
_, _ = fmt.Fprint(w, `{"release-count": 0, "releases": []}`)
|
|
case r.URL.Path == "/release-group" && strings.Contains(r.URL.Query().Get("query"), "releasegroup:"):
|
|
// The library listing's search by artist and title (#5569).
|
|
found := ""
|
|
if strings.Contains(r.URL.Query().Get("query"), "Ballad") {
|
|
found = group(theBallad, "The Ballad of Darren", "Album", "", "2023-07-21")
|
|
}
|
|
_, _ = fmt.Fprintf(w, `{"release-groups": [%s]}`, found)
|
|
default:
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
}))
|
|
t.Cleanup(f.srv.Close)
|
|
return f
|
|
}
|
|
|
|
func newService(t *testing.T, pool *pgxpool.Pool, f *fakeMB) *Service {
|
|
t.Helper()
|
|
mb := &musicbrainz.Client{BaseURL: f.srv.URL, Pace: &musicbrainz.Pacer{}, Retries: -1}
|
|
s := New(pool, catalogue.New(pool, mb, nil), slog.New(slog.NewTextHandler(io.Discard, nil)))
|
|
// Never the real MusicBrainz from a test.
|
|
s.releaseGroup = func(context.Context, string) (string, error) { return "", tags.ErrNotFound }
|
|
return s
|
|
}
|
|
|
|
// seedHeld puts an album of Blur's in the library with n tracks on disk.
|
|
func seedHeld(t *testing.T, q *dbq.Queries, title, release, group string, n int) dbq.Album {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
mbid := blur
|
|
artist, err := q.UpsertArtist(ctx, dbq.UpsertArtistParams{Name: "Blur", SortName: "Blur", Mbid: &mbid})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
params := dbq.UpsertAlbumParams{Title: title, SortTitle: title, ArtistID: artist.ID, Mbid: &release}
|
|
if group != "" {
|
|
params.ReleaseGroupMbid = &group
|
|
}
|
|
album, err := q.UpsertAlbum(ctx, params)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for i := range n {
|
|
num := int32(i + 1)
|
|
if _, err := q.UpsertTrack(ctx, dbq.UpsertTrackParams{
|
|
Title: fmt.Sprintf("Track %d", i+1), AlbumID: album.ID, ArtistID: artist.ID, TrackNumber: &num,
|
|
DurationMs: 180000, FilePath: fmt.Sprintf("/music/%s/%02d.flac", release, i+1), FileSize: 1, FileFormat: "flac",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return album
|
|
}
|
|
|
|
func TestFollow_BaselineThenOnlyNewReleasesThatPass_Integration(t *testing.T) {
|
|
pool := newPool(t)
|
|
q := dbq.New(pool)
|
|
ctx := context.Background()
|
|
f := newFakeMB(t)
|
|
s := newService(t, pool, f)
|
|
_ = seedHeld(t, q, "Parklife", parkRelease, parklife, 2)
|
|
|
|
if _, err := s.Follow(ctx, FollowParams{ArtistMBID: blur, ArtistName: "Blur"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
res, err := s.Pass(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.Baselined != 1 || res.Wanted != 0 {
|
|
t.Fatalf("baseline pass = %+v: a follow wants nothing it already lists", res)
|
|
}
|
|
|
|
// MusicBrainz gains two singles: one new, one from 1997. A day later the
|
|
// follow reads the discography again.
|
|
f.added.Store(true)
|
|
if _, err := pool.Exec(ctx, `
|
|
UPDATE mb_artists SET discography_refreshed_at = now() - interval '2 days';
|
|
UPDATE follows SET checked_at = now() - interval '2 days'`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res, err = s.Pass(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if res.Wanted != 1 {
|
|
t.Errorf("second pass wanted %d, want 1 (the new single)", res.Wanted)
|
|
}
|
|
if w, err := q.GetWantByReleaseGroup(ctx, newSingle); err != nil || w.Reason != ReasonFollow || w.PrimaryType != "Single" {
|
|
t.Errorf("new single: %+v, %v", w, err)
|
|
}
|
|
if _, err := q.GetWantByReleaseGroup(ctx, oldSingle); err == nil {
|
|
t.Error("a 1997 single catalogued late is not a new release")
|
|
}
|
|
}
|
|
|
|
func TestFollow_WantExistingWantsWhatPassesAndIsNotHeld_Integration(t *testing.T) {
|
|
pool := newPool(t)
|
|
q := dbq.New(pool)
|
|
ctx := context.Background()
|
|
f := newFakeMB(t)
|
|
s := newService(t, pool, f)
|
|
_ = seedHeld(t, q, "Parklife", parkRelease, parklife, 2)
|
|
|
|
if _, err := s.Follow(ctx, FollowParams{ArtistMBID: blur, ArtistName: "Blur", WantExisting: true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.Pass(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := q.GetWantByReleaseGroup(ctx, theBallad); err != nil {
|
|
t.Errorf("the studio album not held should be wanted: %v", err)
|
|
}
|
|
for group, why := range map[string]string{parklife: "held", liveAtWemb: "live, and Standard is studio only"} {
|
|
if _, err := q.GetWantByReleaseGroup(ctx, group); err == nil {
|
|
t.Errorf("%s wanted, but it is %s", group, why)
|
|
}
|
|
}
|
|
if fol, _ := q.GetFollow(ctx, blur); fol.WantExisting || !fol.BaselineAt.Valid {
|
|
t.Errorf("follow = %+v: want_existing applied once, then cleared", fol)
|
|
}
|
|
|
|
// A profile that allows live releases, applied with "want what exists".
|
|
live, err := s.CreateProfile(ctx, ProfileInput{Name: "With live", PrimaryTypes: []string{"Album"}, SecondaryTypes: []string{"Live"}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.Follow(ctx, FollowParams{ArtistMBID: blur, ArtistName: "Blur", ProfileID: live.ID, WantExisting: true}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.Pass(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := q.GetWantByReleaseGroup(ctx, liveAtWemb); err != nil {
|
|
t.Errorf("with live allowed, the live album is wanted: %v", err)
|
|
}
|
|
if err := s.DeleteProfile(ctx, live.ID); !errors.Is(err, ErrProfileInUse) {
|
|
t.Errorf("deleting a profile in use: err = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestSettle_HeldOnceAFullEditionIsOnDisk_Integration(t *testing.T) {
|
|
pool := newPool(t)
|
|
q := dbq.New(pool)
|
|
ctx := context.Background()
|
|
s := newService(t, pool, newFakeMB(t))
|
|
|
|
if _, err := WantWith(ctx, q, WantParams{
|
|
GroupMBID: theBallad, ArtistMBID: blur, ArtistName: "Blur", Title: "The Ballad of Darren", Reason: ReasonRequest,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
album := seedHeld(t, q, "The Ballad of Darren", "r-ballad-1", theBallad, 1)
|
|
if res, err := s.Pass(ctx); err != nil || res.Held != 0 {
|
|
t.Fatalf("one track of a two-track edition: %+v, %v", res, err)
|
|
}
|
|
_ = seedHeld(t, q, "The Ballad of Darren", "r-ballad-1", theBallad, 2)
|
|
if res, err := s.Pass(ctx); err != nil || res.Held != 1 {
|
|
t.Fatalf("the shortest edition complete: %+v, %v", res, err)
|
|
}
|
|
w, _ := q.GetWantByReleaseGroup(ctx, theBallad)
|
|
if !w.FulfilledAt.Valid || w.AlbumID != album.ID {
|
|
t.Errorf("want = %+v: held by the album", w)
|
|
}
|
|
}
|
|
|
|
func TestSettle_MovesAWantNamedByReleaseIdToItsGroup_Integration(t *testing.T) {
|
|
pool := newPool(t)
|
|
q := dbq.New(pool)
|
|
ctx := context.Background()
|
|
s := newService(t, pool, newFakeMB(t))
|
|
s.releaseGroup = func(_ context.Context, release string) (string, error) {
|
|
if release == "r-ballad-1" {
|
|
return theBallad, nil
|
|
}
|
|
return "", tags.ErrNotFound
|
|
}
|
|
if _, err := WantWith(ctx, q, WantParams{
|
|
GroupMBID: "r-ballad-1", ArtistMBID: blur, ArtistName: "Blur", Title: "The Ballad of Darren", Reason: ReasonRequest,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
group := "r-ballad-1"
|
|
if _, err := q.CreateRequest(ctx, dbq.CreateRequestParams{
|
|
UserID: seedUser(t, pool), Kind: dbq.RequestKindAlbum, ArtistMbid: blur, AlbumMbid: &group, ArtistName: "Blur",
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := s.Pass(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := q.GetWantByReleaseGroup(ctx, theBallad); err != nil {
|
|
t.Errorf("the want should be under its group now: %v", err)
|
|
}
|
|
var moved string
|
|
if err := pool.QueryRow(ctx, "SELECT album_mbid FROM requests").Scan(&moved); err != nil || moved != theBallad {
|
|
t.Errorf("the request should follow its want: %q, %v", moved, err)
|
|
}
|
|
}
|
|
|
|
func TestPass_MusicBrainzDownStopsFollowsButStillSettles_Integration(t *testing.T) {
|
|
pool := newPool(t)
|
|
q := dbq.New(pool)
|
|
ctx := context.Background()
|
|
f := newFakeMB(t)
|
|
s := newService(t, pool, f)
|
|
if _, err := s.Follow(ctx, FollowParams{ArtistMBID: blur, ArtistName: "Blur"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := WantWith(ctx, q, WantParams{
|
|
GroupMBID: "some-group", ArtistMBID: blur, ArtistName: "Blur", Title: "Some", Reason: ReasonRequest,
|
|
}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = seedHeld(t, q, "Some", "some-release", "some-group", 3)
|
|
f.down.Store(true)
|
|
res, err := s.Pass(ctx)
|
|
if !errors.Is(err, musicbrainz.ErrUnreachable) {
|
|
t.Errorf("err = %v, want ErrUnreachable", err)
|
|
}
|
|
if res.Held != 1 {
|
|
t.Errorf("held = %d: with no editions known, an album that arrived since the want answers it", res.Held)
|
|
}
|
|
if fol, _ := q.GetFollow(ctx, blur); fol.BaselineAt.Valid {
|
|
t.Error("a follow is not baselined without reading its discography")
|
|
}
|
|
}
|
|
|
|
func seedUser(t *testing.T, pool *pgxpool.Pool) pgtype.UUID {
|
|
t.Helper()
|
|
u, err := dbq.New(pool).CreateUser(context.Background(), dbq.CreateUserParams{
|
|
Username: dbtest.TestUserPrefix + "wanter", PasswordHash: "x", ApiTokenHash: "x",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return u.ID
|
|
}
|