From 40c4677add8a6f250b9b56cb197cb3387ce0560a Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Fri, 19 Dec 2025 16:05:30 -0800 Subject: [PATCH 1/3] Fix unlisted premium library tracks --- api/v1_users_library_tracks.go | 4 +- api/v1_users_library_tracks_test.go | 201 ++++++++++++++++++++++++++++ 2 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 api/v1_users_library_tracks_test.go diff --git a/api/v1_users_library_tracks.go b/api/v1_users_library_tracks.go index 3e492647..ff082060 100644 --- a/api/v1_users_library_tracks.go +++ b/api/v1_users_library_tracks.go @@ -55,7 +55,7 @@ func (app *ApiServer) v1UsersLibraryTracks(c *fiber.Ctx) error { } trackFilters := []string{ - "(is_unlisted = false OR is_purchase = true)", + "is_unlisted = false", } if params.Query != "" { @@ -104,7 +104,7 @@ func (app *ApiServer) v1UsersLibraryTracks(c *fiber.Ctx) error { bool_or(is_purchase) as is_purchase FROM library_items JOIN tracks ON track_id = item_id - WHERE is_unlisted = false OR is_purchase = true + WHERE is_unlisted = false GROUP BY item_id ) SELECT diff --git a/api/v1_users_library_tracks_test.go b/api/v1_users_library_tracks_test.go new file mode 100644 index 00000000..9b8b89f1 --- /dev/null +++ b/api/v1_users_library_tracks_test.go @@ -0,0 +1,201 @@ +package api + +import ( + "testing" + + "api.audius.co/database" + "api.audius.co/trashid" + "github.com/stretchr/testify/assert" +) + +func TestUsersLibraryTracks(t *testing.T) { + app := testAppWithFixtures(t) + var response struct { + Data []struct { + Class string `json:"class"` + ItemType string `json:"item_type"` + ItemID int32 `json:"item_id"` + Timestamp string `json:"timestamp"` + Item struct { + ID string `json:"id"` + Title string `json:"title"` + } `json:"item"` + } `json:"data"` + } + + // User 1 has saved track 100 (T1) and reposted track 200 (Culca Canyon) + user1Id := trashid.MustEncodeHashID(1) + + // Test all library tracks + status, body := testGet(t, app, "/v1/full/users/"+user1Id+"/library/tracks?type=all", &response) + assert.Equal(t, 200, status) + assert.GreaterOrEqual(t, len(response.Data), 2, "Should have at least saved and reposted tracks") + + jsonAssert(t, body, map[string]any{ + "data.0.class": "track_activity_full", + "data.0.item_type": "track", + }) + + // Test favorite tracks only + status, body = testGet(t, app, "/v1/full/users/"+user1Id+"/library/tracks?type=favorite", &response) + assert.Equal(t, 200, status) + assert.GreaterOrEqual(t, len(response.Data), 1, "Should have at least one favorite track") + + jsonAssert(t, body, map[string]any{ + "data.0.class": "track_activity_full", + "data.0.item_type": "track", + "data.0.item_id": 100, // Track 100 (T1) is saved + }) + + // Test repost tracks only + status, body = testGet(t, app, "/v1/full/users/"+user1Id+"/library/tracks?type=repost", &response) + assert.Equal(t, 200, status) + assert.GreaterOrEqual(t, len(response.Data), 1, "Should have at least one reposted track") + + jsonAssert(t, body, map[string]any{ + "data.0.class": "track_activity_full", + "data.0.item_type": "track", + "data.0.item_id": 200, // Track 200 (Culca Canyon) is reposted + }) + + // Test purchase tracks only (user 11 has purchased track 303) + user11Id := trashid.MustEncodeHashID(11) + status, body = testGet(t, app, "/v1/full/users/"+user11Id+"/library/tracks?type=purchase", &response) + assert.Equal(t, 200, status) + assert.GreaterOrEqual(t, len(response.Data), 1, "Should have at least one purchased track") + + jsonAssert(t, body, map[string]any{ + "data.0.class": "track_activity_full", + "data.0.item_type": "track", + "data.0.item_id": 303, // Track 303 (Pay Gated Stream) is purchased + }) +} + +func TestUsersLibraryTracksUnlistedFiltered(t *testing.T) { + app := emptyTestApp(t) + + fixtures := database.FixtureMap{ + "users": []map[string]any{ + { + "user_id": 1, + "handle": "user1", + }, + }, + "tracks": []map[string]any{ + { + "track_id": 100, + "title": "Public Track", + "owner_id": 1, + "is_unlisted": false, + }, + { + "track_id": 201, + "title": "Unlisted Track", + "owner_id": 1, + "is_unlisted": true, + }, + }, + "saves": []map[string]any{ + { + "user_id": 1, + "save_item_id": 100, + "save_type": "track", + }, + { + "user_id": 1, + "save_item_id": 201, // Save an unlisted track + "save_type": "track", + }, + }, + "usdc_purchases": []map[string]any{ + { + "signature": "test1", + "buyer_user_id": 1, + "seller_user_id": 1, + "content_id": 201, // Purchase an unlisted track + "content_type": "track", + "amount": 100, + }, + }, + } + + database.Seed(app.pool.Replicas[0], fixtures) + user1Id := trashid.MustEncodeHashID(1) + + var response struct { + Data []struct { + ItemID int32 `json:"item_id"` + } `json:"data"` + } + + // Test that unlisted tracks are filtered out from favorites + status, _ := testGet(t, app, "/v1/full/users/"+user1Id+"/library/tracks?type=favorite", &response) + assert.Equal(t, 200, status) + assert.Equal(t, 1, len(response.Data), "Should only have public track") + assert.Equal(t, int32(100), response.Data[0].ItemID, "Should only return public track") + + // Test that unlisted purchased tracks are filtered out + status, _ = testGet(t, app, "/v1/full/users/"+user1Id+"/library/tracks?type=purchase", &response) + assert.Equal(t, 200, status) + assert.Equal(t, 0, len(response.Data), "Should not return unlisted purchased tracks") + + // Test that unlisted tracks are filtered out from all + status, _ = testGet(t, app, "/v1/full/users/"+user1Id+"/library/tracks?type=all", &response) + assert.Equal(t, 200, status) + assert.Equal(t, 1, len(response.Data), "Should only have public track") + assert.Equal(t, int32(100), response.Data[0].ItemID, "Should only return public track") +} + +func TestUsersLibraryTracksSorting(t *testing.T) { + app := testAppWithFixtures(t) + user1Id := trashid.MustEncodeHashID(1) + + var response struct { + Data []struct { + ItemID int32 `json:"item_id"` + Item struct { + Title string `json:"title"` + } `json:"item"` + } `json:"data"` + } + + // Test sorting by title + status, _ := testGet(t, app, "/v1/full/users/"+user1Id+"/library/tracks?type=all&sort_method=title&sort_direction=asc", &response) + assert.Equal(t, 200, status) + assert.GreaterOrEqual(t, len(response.Data), 2, "Should have multiple tracks") + + // Verify tracks are sorted by title ascending + if len(response.Data) >= 2 { + assert.LessOrEqual(t, response.Data[0].Item.Title, response.Data[1].Item.Title, + "Tracks should be sorted by title ascending") + } + + // Test sorting by added_date (default) + status, _ = testGet(t, app, "/v1/full/users/"+user1Id+"/library/tracks?type=all&sort_method=added_date&sort_direction=desc", &response) + assert.Equal(t, 200, status) + assert.GreaterOrEqual(t, len(response.Data), 2, "Should have multiple tracks") +} + +func TestUsersLibraryTracksQuery(t *testing.T) { + app := testAppWithFixtures(t) + user1Id := trashid.MustEncodeHashID(1) + + var response struct { + Data []struct { + ItemID int32 `json:"item_id"` + Item struct { + Title string `json:"title"` + } `json:"item"` + } `json:"data"` + } + + // Test query filtering by track title + status, _ := testGet(t, app, "/v1/full/users/"+user1Id+"/library/tracks?type=all&query=T1", &response) + assert.Equal(t, 200, status) + assert.GreaterOrEqual(t, len(response.Data), 1, "Should find tracks matching query") + + // Verify all results match the query + for _, item := range response.Data { + assert.Contains(t, item.Item.Title, "T1", "All results should match query") + } +} From 19458c19b89195d93d949a2bfcdf525225c196fb Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Fri, 19 Dec 2025 17:28:04 -0800 Subject: [PATCH 2/3] Update to allow unlisted tracks --- api/v1_users_library_tracks.go | 7 ++++--- api/v1_users_library_tracks_test.go | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/api/v1_users_library_tracks.go b/api/v1_users_library_tracks.go index ff082060..e37ede9e 100644 --- a/api/v1_users_library_tracks.go +++ b/api/v1_users_library_tracks.go @@ -156,11 +156,12 @@ func (app *ApiServer) v1UsersLibraryTracks(c *fiber.Ctx) error { trackIds = append(trackIds, i.ItemID) } - // get tracks + // get tracks - include unlisted tracks since they may be in the library (e.g., purchases) tracks, err := app.queries.FullTracksKeyed(c.Context(), dbv1.FullTracksParams{ GetTracksParams: dbv1.GetTracksParams{ - Ids: trackIds, - MyID: myId, + Ids: trackIds, + MyID: myId, + IncludeUnlisted: true, }, }) if err != nil { diff --git a/api/v1_users_library_tracks_test.go b/api/v1_users_library_tracks_test.go index 9b8b89f1..c1880e8b 100644 --- a/api/v1_users_library_tracks_test.go +++ b/api/v1_users_library_tracks_test.go @@ -199,3 +199,25 @@ func TestUsersLibraryTracksQuery(t *testing.T) { assert.Contains(t, item.Item.Title, "T1", "All results should match query") } } + +func TestUsersLibraryTracksMetadataNotNull(t *testing.T) { + app := testAppWithFixtures(t) + user1Id := trashid.MustEncodeHashID(1) + + var response struct { + Data []struct { + ItemID int32 `json:"item_id"` + Item any `json:"item"` + } `json:"data"` + } + + // Test that all returned tracks have non-null metadata + status, _ := testGet(t, app, "/v1/full/users/"+user1Id+"/library/tracks?type=all", &response) + assert.Equal(t, 200, status) + assert.GreaterOrEqual(t, len(response.Data), 1, "Should have at least one track") + + // Verify all items have non-null metadata + for _, item := range response.Data { + assert.NotNil(t, item.Item, "Track metadata should not be null for item_id %d", item.ItemID) + } +} From 75910922f9cf42bfc8ef4ef86b9085b640734590 Mon Sep 17 00:00:00 2001 From: Dylan Jeffers Date: Fri, 19 Dec 2025 17:30:40 -0800 Subject: [PATCH 3/3] Revert sql --- api/v1_users_library_tracks.go | 4 ++-- api/v1_users_library_tracks_test.go | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/api/v1_users_library_tracks.go b/api/v1_users_library_tracks.go index e37ede9e..a3a18853 100644 --- a/api/v1_users_library_tracks.go +++ b/api/v1_users_library_tracks.go @@ -55,7 +55,7 @@ func (app *ApiServer) v1UsersLibraryTracks(c *fiber.Ctx) error { } trackFilters := []string{ - "is_unlisted = false", + "(is_unlisted = false OR is_purchase = true)", } if params.Query != "" { @@ -104,7 +104,7 @@ func (app *ApiServer) v1UsersLibraryTracks(c *fiber.Ctx) error { bool_or(is_purchase) as is_purchase FROM library_items JOIN tracks ON track_id = item_id - WHERE is_unlisted = false + WHERE is_unlisted = false OR is_purchase = true GROUP BY item_id ) SELECT diff --git a/api/v1_users_library_tracks_test.go b/api/v1_users_library_tracks_test.go index c1880e8b..35085236 100644 --- a/api/v1_users_library_tracks_test.go +++ b/api/v1_users_library_tracks_test.go @@ -125,25 +125,32 @@ func TestUsersLibraryTracksUnlistedFiltered(t *testing.T) { var response struct { Data []struct { ItemID int32 `json:"item_id"` + Item any `json:"item"` } `json:"data"` } - // Test that unlisted tracks are filtered out from favorites + // Test that unlisted tracks saved (not purchased) are filtered out from favorites status, _ := testGet(t, app, "/v1/full/users/"+user1Id+"/library/tracks?type=favorite", &response) assert.Equal(t, 200, status) assert.Equal(t, 1, len(response.Data), "Should only have public track") assert.Equal(t, int32(100), response.Data[0].ItemID, "Should only return public track") + assert.NotNil(t, response.Data[0].Item, "Track metadata should not be null") - // Test that unlisted purchased tracks are filtered out + // Test that unlisted purchased tracks ARE included and have metadata status, _ = testGet(t, app, "/v1/full/users/"+user1Id+"/library/tracks?type=purchase", &response) assert.Equal(t, 200, status) - assert.Equal(t, 0, len(response.Data), "Should not return unlisted purchased tracks") + assert.Equal(t, 1, len(response.Data), "Should return unlisted purchased track") + assert.Equal(t, int32(201), response.Data[0].ItemID, "Should return unlisted purchased track") + assert.NotNil(t, response.Data[0].Item, "Unlisted purchased track metadata should not be null") - // Test that unlisted tracks are filtered out from all + // Test that unlisted purchased tracks are included in all status, _ = testGet(t, app, "/v1/full/users/"+user1Id+"/library/tracks?type=all", &response) assert.Equal(t, 200, status) - assert.Equal(t, 1, len(response.Data), "Should only have public track") - assert.Equal(t, int32(100), response.Data[0].ItemID, "Should only return public track") + assert.Equal(t, 2, len(response.Data), "Should have both public and unlisted purchased tracks") + // Verify both tracks have metadata + for _, item := range response.Data { + assert.NotNil(t, item.Item, "Track metadata should not be null for item_id %d", item.ItemID) + } } func TestUsersLibraryTracksSorting(t *testing.T) {