fix(lidarrquarantine): translate FK violation to ErrTrackNotFound + drop unneeded *string AlbumTitle

- Flag now detects Postgres SQLSTATE 23503 (foreign_key_violation) on
  UpsertQuarantine and surfaces ErrTrackNotFound instead of a wrapped
  generic. T8's handler can now return 404 track_not_found cleanly.
  Constraint-name guard ('track' substring) keeps a future user_id FK from
  mis-mapping to the same error.
- AdminQueueRow.AlbumTitle is now string (not *string). albums.title is
  NOT NULL upstream; the empty-string-to-nil dance was answering a
  nullability question that doesn't exist in the schema.
- Add TestFlag_NonexistentTrackReturnsErrTrackNotFound covering the new
  branch.
This commit is contained in:
2026-04-30 17:42:55 -04:00
parent b4fe224a67
commit 7a0bc1f815
2 changed files with 29 additions and 11 deletions
+13 -11
View File
@@ -9,8 +9,10 @@ import (
"context"
"errors"
"fmt"
"strings"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgxpool"
@@ -64,10 +66,15 @@ func (s *Service) Flag(ctx context.Context, userID, trackID pgtype.UUID, reason
Notes: notesPtr,
})
if err != nil {
// FK violation on track_id surfaces here as a postgres error;
// callers can treat any error as "track doesn't exist or DB issue."
// Distinguishing FK violation specifically isn't worth the extra
// dependency on pgconn just for one branch.
// FK violation on track_id (no such track) → ErrTrackNotFound so the
// handler can return 404 instead of a generic 500. Code 23503 is the
// Postgres SQLSTATE for foreign_key_violation; the constraint check
// guards against future FKs (e.g. user_id) firing the same code.
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == "23503" &&
(pgErr.ConstraintName == "" || strings.Contains(pgErr.ConstraintName, "track")) {
return dbq.LidarrQuarantine{}, ErrTrackNotFound
}
return dbq.LidarrQuarantine{}, fmt.Errorf("upsert: %w", err)
}
return row, nil
@@ -102,7 +109,7 @@ type AdminQueueRow struct {
TrackID pgtype.UUID
TrackTitle string
ArtistName string
AlbumTitle *string
AlbumTitle string
AlbumID pgtype.UUID
LidarrAlbumMBID *string
ReportCount int32
@@ -149,16 +156,11 @@ func (s *Service) ListAdminQueue(ctx context.Context) ([]AdminQueueRow, error) {
CreatedAt: rep.CreatedAt,
})
}
var albumTitle *string
if r.AlbumTitle != "" {
t := r.AlbumTitle
albumTitle = &t
}
out = append(out, AdminQueueRow{
TrackID: r.TrackID,
TrackTitle: r.TrackTitle,
ArtistName: r.ArtistName,
AlbumTitle: albumTitle,
AlbumTitle: r.AlbumTitle,
AlbumID: r.AlbumID,
LidarrAlbumMBID: r.LidarrAlbumMbid,
ReportCount: r.ReportCount,
+16
View File
@@ -9,6 +9,7 @@ import (
"path/filepath"
"testing"
"github.com/jackc/pgx/v5/pgtype"
"github.com/jackc/pgx/v5/pgxpool"
"git.fabledsword.com/bvandeusen/minstrel/internal/db"
@@ -117,6 +118,21 @@ func TestFlag_UpsertOnSecondFlag(t *testing.T) {
}
}
func TestFlag_NonexistentTrackReturnsErrTrackNotFound(t *testing.T) {
pool := newPool(t)
user := seedUser(t, pool, "alice")
var bogus pgtype.UUID
bogus.Bytes = [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
bogus.Valid = true
svc := NewService(pool, lidarrconfig.New(pool), nil)
_, err := svc.Flag(context.Background(), user.ID, bogus, "bad_rip", "")
if !errors.Is(err, ErrTrackNotFound) {
t.Errorf("err = %v, want ErrTrackNotFound", err)
}
}
func TestFlag_BadReasonRejected(t *testing.T) {
pool := newPool(t)
user := seedUser(t, pool, "alice")