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:
@@ -9,8 +9,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
|
||||||
@@ -64,10 +66,15 @@ func (s *Service) Flag(ctx context.Context, userID, trackID pgtype.UUID, reason
|
|||||||
Notes: notesPtr,
|
Notes: notesPtr,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// FK violation on track_id surfaces here as a postgres error;
|
// FK violation on track_id (no such track) → ErrTrackNotFound so the
|
||||||
// callers can treat any error as "track doesn't exist or DB issue."
|
// handler can return 404 instead of a generic 500. Code 23503 is the
|
||||||
// Distinguishing FK violation specifically isn't worth the extra
|
// Postgres SQLSTATE for foreign_key_violation; the constraint check
|
||||||
// dependency on pgconn just for one branch.
|
// 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 dbq.LidarrQuarantine{}, fmt.Errorf("upsert: %w", err)
|
||||||
}
|
}
|
||||||
return row, nil
|
return row, nil
|
||||||
@@ -102,7 +109,7 @@ type AdminQueueRow struct {
|
|||||||
TrackID pgtype.UUID
|
TrackID pgtype.UUID
|
||||||
TrackTitle string
|
TrackTitle string
|
||||||
ArtistName string
|
ArtistName string
|
||||||
AlbumTitle *string
|
AlbumTitle string
|
||||||
AlbumID pgtype.UUID
|
AlbumID pgtype.UUID
|
||||||
LidarrAlbumMBID *string
|
LidarrAlbumMBID *string
|
||||||
ReportCount int32
|
ReportCount int32
|
||||||
@@ -149,16 +156,11 @@ func (s *Service) ListAdminQueue(ctx context.Context) ([]AdminQueueRow, error) {
|
|||||||
CreatedAt: rep.CreatedAt,
|
CreatedAt: rep.CreatedAt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
var albumTitle *string
|
|
||||||
if r.AlbumTitle != "" {
|
|
||||||
t := r.AlbumTitle
|
|
||||||
albumTitle = &t
|
|
||||||
}
|
|
||||||
out = append(out, AdminQueueRow{
|
out = append(out, AdminQueueRow{
|
||||||
TrackID: r.TrackID,
|
TrackID: r.TrackID,
|
||||||
TrackTitle: r.TrackTitle,
|
TrackTitle: r.TrackTitle,
|
||||||
ArtistName: r.ArtistName,
|
ArtistName: r.ArtistName,
|
||||||
AlbumTitle: albumTitle,
|
AlbumTitle: r.AlbumTitle,
|
||||||
AlbumID: r.AlbumID,
|
AlbumID: r.AlbumID,
|
||||||
LidarrAlbumMBID: r.LidarrAlbumMbid,
|
LidarrAlbumMBID: r.LidarrAlbumMbid,
|
||||||
ReportCount: r.ReportCount,
|
ReportCount: r.ReportCount,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
|
||||||
"git.fabledsword.com/bvandeusen/minstrel/internal/db"
|
"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) {
|
func TestFlag_BadReasonRejected(t *testing.T) {
|
||||||
pool := newPool(t)
|
pool := newPool(t)
|
||||||
user := seedUser(t, pool, "alice")
|
user := seedUser(t, pool, "alice")
|
||||||
|
|||||||
Reference in New Issue
Block a user