package e2e import ( "context" "testing" "time" "github.com/fujin/music-agregator/testing/e2e/testutil" ) // TestRefreshArtist_Flow covers section 1.2 of FLOWS.md: // 1. Manual or scheduled trigger // 2. Re-fetches artist + albums from MetadataService // 3. Upserts new/changed albums, keeps existing // 4. Updates artist_metadata.updated_at func TestRefreshArtist_Flow(t *testing.T) { env := testutil.NewTestEnv(t) defer env.Close() ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() artistName := "Depeche Mode" if err := env.CleanupArtistByName(ctx, artistName); err != nil { t.Fatalf("cleanup failed: %v", err) } syncResp, err := env.POST("/api/sync", map[string]any{ "artist": artistName, "store": true, "download": false, }) if err != nil { t.Fatalf("initial sync failed: %v", err) } syncResp.AssertStatus(t, 200) var syncResult struct { ArtistID string `json:"artist_id"` ArtistName string `json:"artist_name"` } if err := syncResp.DecodeJSON(&syncResult); err != nil { t.Fatalf("failed to decode sync response: %v", err) } artistID := syncResult.ArtistID actualArtistName := syncResult.ArtistName initialUpdatedAt, err := env.GetArtistUpdatedAt(ctx, artistID) if err != nil { t.Fatalf("failed to get initial updated_at: %v", err) } initialAlbumCount, err := env.CountAlbumsByArtist(ctx, artistID) if err != nil { t.Fatalf("failed to count initial albums: %v", err) } time.Sleep(1 * time.Second) t.Run("ManualRefreshTrigger", func(t *testing.T) { refreshResp, err := env.POST("/api/artists/"+artistID+"/refresh", nil) if err != nil { t.Fatalf("refresh request failed: %v", err) } refreshResp.AssertStatus(t, 200) var refreshResult struct { ArtistID string `json:"artist_id"` ArtistName string `json:"artist_name"` AlbumsUpdated int `json:"albums_updated"` AlbumsAdded int `json:"albums_added"` } if err := refreshResp.DecodeJSON(&refreshResult); err != nil { t.Fatalf("failed to decode refresh response: %v", err) } if refreshResult.ArtistID != artistID { t.Errorf("expected artist_id=%q, got %q", artistID, refreshResult.ArtistID) } if refreshResult.ArtistName != actualArtistName { t.Errorf("expected artist_name=%q, got %q", actualArtistName, refreshResult.ArtistName) } }) t.Run("UpdatedAtIncreased", func(t *testing.T) { newUpdatedAt, err := env.GetArtistUpdatedAt(ctx, artistID) if err != nil { t.Fatalf("failed to get new updated_at: %v", err) } if !newUpdatedAt.After(initialUpdatedAt) { t.Errorf("expected updated_at to increase: was %v, now %v", initialUpdatedAt, newUpdatedAt) } }) t.Run("AlbumsPreserved", func(t *testing.T) { newAlbumCount, err := env.CountAlbumsByArtist(ctx, artistID) if err != nil { t.Fatalf("failed to count albums after refresh: %v", err) } if newAlbumCount < initialAlbumCount { t.Errorf("expected albums to be preserved: was %d, now %d", initialAlbumCount, newAlbumCount) } }) t.Cleanup(func() { env.CleanupArtistByName(context.Background(), actualArtistName) }) } func TestRefreshArtist_NotFound(t *testing.T) { env := testutil.NewTestEnv(t) defer env.Close() refreshResp, err := env.POST("/api/artists/nonexistent-artist-id-12345/refresh", nil) if err != nil { t.Fatalf("refresh request failed: %v", err) } refreshResp.AssertStatus(t, 404) var errorResp struct { Error string `json:"error"` } if err := refreshResp.DecodeJSON(&errorResp); err != nil { t.Fatalf("failed to decode error response: %v", err) } if errorResp.Error == "" { t.Error("expected error message in response") } } func TestRefreshArtist_UpsertsNewAlbums(t *testing.T) { env := testutil.NewTestEnv(t) defer env.Close() ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() artistName := "Massive Attack" if err := env.CleanupArtistByName(ctx, artistName); err != nil { t.Fatalf("cleanup failed: %v", err) } syncResp, err := env.POST("/api/sync", map[string]any{ "artist": artistName, "album": "Mezzanine", "store": true, "download": false, }) if err != nil { t.Fatalf("initial sync failed: %v", err) } syncResp.AssertStatus(t, 200) var syncResult struct { ArtistID string `json:"artist_id"` ArtistName string `json:"artist_name"` TotalAlbums int `json:"total_albums"` } if err := syncResp.DecodeJSON(&syncResult); err != nil { t.Fatalf("failed to decode sync response: %v", err) } artistID := syncResult.ArtistID actualArtistName := syncResult.ArtistName initialAlbumCount, err := env.CountAlbumsByArtist(ctx, artistID) if err != nil { t.Fatalf("failed to count initial albums: %v", err) } refreshResp, err := env.POST("/api/artists/"+artistID+"/refresh", nil) if err != nil { t.Fatalf("refresh request failed: %v", err) } refreshResp.AssertStatus(t, 200) var refreshResult struct { AlbumsAdded int `json:"albums_added"` } if err := refreshResp.DecodeJSON(&refreshResult); err != nil { t.Fatalf("failed to decode refresh response: %v", err) } finalAlbumCount, err := env.CountAlbumsByArtist(ctx, artistID) if err != nil { t.Fatalf("failed to count final albums: %v", err) } if finalAlbumCount <= initialAlbumCount { t.Logf("no new albums added (initial sync had filtered subset)") } if refreshResult.AlbumsAdded > 0 && finalAlbumCount != initialAlbumCount+int64(refreshResult.AlbumsAdded) { t.Errorf("album count mismatch: initial=%d, added=%d, final=%d", initialAlbumCount, refreshResult.AlbumsAdded, finalAlbumCount) } t.Cleanup(func() { env.CleanupArtistByName(context.Background(), actualArtistName) }) } func TestRefreshArtist_IdempotentRefresh(t *testing.T) { env := testutil.NewTestEnv(t) defer env.Close() ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) defer cancel() artistName := "Portishead" if err := env.CleanupArtistByName(ctx, artistName); err != nil { t.Fatalf("cleanup failed: %v", err) } syncResp, err := env.POST("/api/sync", map[string]any{ "artist": artistName, "store": true, "download": false, }) if err != nil { t.Fatalf("initial sync failed: %v", err) } syncResp.AssertStatus(t, 200) var syncResult struct { ArtistID string `json:"artist_id"` ArtistName string `json:"artist_name"` } syncResp.DecodeJSON(&syncResult) artistID := syncResult.ArtistID actualArtistName := syncResult.ArtistName firstRefresh, err := env.POST("/api/artists/"+artistID+"/refresh", nil) if err != nil { t.Fatalf("first refresh failed: %v", err) } firstRefresh.AssertStatus(t, 200) countAfterFirst, _ := env.CountAlbumsByArtist(ctx, artistID) secondRefresh, err := env.POST("/api/artists/"+artistID+"/refresh", nil) if err != nil { t.Fatalf("second refresh failed: %v", err) } secondRefresh.AssertStatus(t, 200) countAfterSecond, _ := env.CountAlbumsByArtist(ctx, artistID) if countAfterSecond != countAfterFirst { t.Errorf("expected idempotent refresh: album count was %d after first, %d after second", countAfterFirst, countAfterSecond) } t.Cleanup(func() { env.CleanupArtistByName(context.Background(), actualArtistName) }) }