package api import ( "net/url" "testing" "github.com/jackc/pgx/v5/pgtype" "git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq" ) func TestUUIDToString(t *testing.T) { var u pgtype.UUID if err := u.Scan("00112233-4455-6677-8899-aabbccddeeff"); err != nil { t.Fatalf("scan: %v", err) } got := uuidToString(u) want := "00112233-4455-6677-8899-aabbccddeeff" if got != want { t.Errorf("uuidToString = %q, want %q", got, want) } var zero pgtype.UUID if s := uuidToString(zero); s != "" { t.Errorf("uuidToString(invalid) = %q, want empty", s) } } func TestParseUUID(t *testing.T) { u, ok := parseUUID("00112233-4455-6677-8899-aabbccddeeff") if !ok || !u.Valid { t.Fatalf("parseUUID valid: ok=%v valid=%v", ok, u.Valid) } if _, ok := parseUUID("not-a-uuid"); ok { t.Error("parseUUID accepted garbage") } if _, ok := parseUUID(""); ok { t.Error("parseUUID accepted empty string") } } func TestYearFromDate(t *testing.T) { var d pgtype.Date if y := yearFromDate(d); y != 0 { t.Errorf("yearFromDate(invalid) = %d, want 0", y) } } func TestDurationMsToSec(t *testing.T) { cases := []struct { ms int32 want int }{ {0, 0}, {500, 1}, // rounds up {499, 0}, // rounds down {1500, 2}, // rounds up {60000, 60}, } for _, c := range cases { if got := durationMsToSec(c.ms); got != c.want { t.Errorf("durationMsToSec(%d) = %d, want %d", c.ms, got, c.want) } } } func TestCoverURLAndStreamURL(t *testing.T) { var u pgtype.UUID if err := u.Scan("00112233-4455-6677-8899-aabbccddeeff"); err != nil { t.Fatalf("scan: %v", err) } if got := coverURL(u); got != "/api/albums/00112233-4455-6677-8899-aabbccddeeff/cover" { t.Errorf("coverURL = %q", got) } if got := streamURL(u); got != "/api/tracks/00112233-4455-6677-8899-aabbccddeeff/stream" { t.Errorf("streamURL = %q", got) } } func TestArtistRefFrom(t *testing.T) { var id pgtype.UUID _ = id.Scan("00112233-4455-6677-8899-aabbccddeeff") a := dbq.Artist{ID: id, Name: "Beatles", SortName: "Beatles"} got := artistRefFrom(a, 7) if got.ID != "00112233-4455-6677-8899-aabbccddeeff" { t.Errorf("ID = %q", got.ID) } if got.Name != "Beatles" || got.AlbumCount != 7 { t.Errorf("ref = %+v", got) } } func TestAlbumRefFrom(t *testing.T) { var aid, artID pgtype.UUID _ = aid.Scan("00000000-0000-0000-0000-000000000001") _ = artID.Scan("00000000-0000-0000-0000-000000000002") var rel pgtype.Date _ = rel.Scan("1969-09-26") a := dbq.Album{ID: aid, Title: "Abbey Road", ArtistID: artID, ReleaseDate: rel} got := albumRefFrom(a, "Beatles", 17, 2820) if got.Title != "Abbey Road" || got.ArtistName != "Beatles" { t.Errorf("ref = %+v", got) } if got.TrackCount != 17 || got.DurationSec != 2820 { t.Errorf("counts = %+v", got) } if got.Year != 1969 { t.Errorf("year = %d", got.Year) } if got.CoverURL != "/api/albums/00000000-0000-0000-0000-000000000001/cover" { t.Errorf("cover_url = %q", got.CoverURL) } } func TestTrackRefFrom(t *testing.T) { var tid, aid, artID pgtype.UUID _ = tid.Scan("00000000-0000-0000-0000-000000000010") _ = aid.Scan("00000000-0000-0000-0000-000000000001") _ = artID.Scan("00000000-0000-0000-0000-000000000002") trackNum := int32(3) discNum := int32(1) t2 := dbq.Track{ ID: tid, Title: "Something", AlbumID: aid, ArtistID: artID, TrackNumber: &trackNum, DiscNumber: &discNum, DurationMs: 183_000, } got := trackRefFrom(t2, "Abbey Road", "Beatles") if got.Title != "Something" || got.AlbumTitle != "Abbey Road" || got.ArtistName != "Beatles" { t.Errorf("ref = %+v", got) } if got.TrackNumber != 3 || got.DiscNumber != 1 { t.Errorf("positions = %+v", got) } if got.DurationSec != 183 { t.Errorf("duration = %d, want 183", got.DurationSec) } if got.StreamURL != "/api/tracks/00000000-0000-0000-0000-000000000010/stream" { t.Errorf("stream_url = %q", got.StreamURL) } } func TestTrackRefFromNilPositions(t *testing.T) { var tid, aid, artID pgtype.UUID _ = tid.Scan("00000000-0000-0000-0000-000000000010") _ = aid.Scan("00000000-0000-0000-0000-000000000001") _ = artID.Scan("00000000-0000-0000-0000-000000000002") t2 := dbq.Track{ID: tid, AlbumID: aid, ArtistID: artID, DurationMs: 1000} got := trackRefFrom(t2, "A", "B") if got.TrackNumber != 0 || got.DiscNumber != 0 { t.Errorf("nil positions should be 0, got %+v", got) } } func TestParsePaging(t *testing.T) { tests := []struct { name string query url.Values wantLimit int wantOffset int wantErr bool }{ { name: "default (empty values)", query: url.Values{}, wantLimit: 50, wantOffset: 0, wantErr: false, }, { name: "limit=0 clamps to 1", query: url.Values{"limit": {"0"}}, wantLimit: 1, wantOffset: 0, wantErr: false, }, { name: "limit=500 clamps to 200", query: url.Values{"limit": {"500"}}, wantLimit: 200, wantOffset: 0, wantErr: false, }, { name: "offset=-5 clamps to 0", query: url.Values{"offset": {"-5"}}, wantLimit: 50, wantOffset: 0, wantErr: false, }, { name: "limit=abc errors", query: url.Values{"limit": {"abc"}}, wantLimit: 0, wantOffset: 0, wantErr: true, }, { name: "offset=xyz errors", query: url.Values{"offset": {"xyz"}}, wantLimit: 0, wantOffset: 0, wantErr: true, }, { name: "normal values", query: url.Values{"limit": {"25"}, "offset": {"100"}}, wantLimit: 25, wantOffset: 100, wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { gotLimit, gotOffset, err := parsePaging(tt.query) if (err != nil) != tt.wantErr { t.Errorf("parsePaging() err = %v, wantErr %v", err, tt.wantErr) return } if gotLimit != tt.wantLimit { t.Errorf("parsePaging() limit = %d, want %d", gotLimit, tt.wantLimit) } if gotOffset != tt.wantOffset { t.Errorf("parsePaging() offset = %d, want %d", gotOffset, tt.wantOffset) } }) } }