package web import ( "net/http" "net/http/httptest" "strings" "testing" "naviwatcher/internal/config" "naviwatcher/internal/database" ) // seedArtist inserts an artist_settings row and returns its ID. func seedArtist(t *testing.T, db *database.DB, id, name, mbid string, monitored bool) { t.Helper() if err := database.SaveArtistSettings(db, &database.ArtistSettings{ ID: id, Name: name, MBID: mbid, Monitored: monitored, }); err != nil { t.Fatalf("seed artist %s: %v", id, err) } } // seedLocalAlbum inserts a local_albums row. func seedLocalAlbum(t *testing.T, db *database.DB, id, artistID, title string) { t.Helper() if err := database.SaveLocalAlbum(db, &database.LocalAlbum{ID: id, ArtistID: artistID, Title: title}); err != nil { t.Fatalf("seed local album %s: %v", id, err) } } // seedExternalRelease inserts an external_releases row (not ignored). func seedExternalRelease(t *testing.T, db *database.DB, rgid, artistID, title string) { t.Helper() if err := database.SaveExternalRelease(db, &database.ExternalRelease{ RGID: rgid, ArtistID: artistID, Title: title, }); err != nil { t.Fatalf("seed external release %s: %v", rgid, err) } } func newServer(t *testing.T, user, pass string) (*Server, *database.DB) { t.Helper() db, err := database.New(":memory:") if err != nil { t.Fatalf("open db: %v", err) } cfg := &config.ServerConfig{Host: "0.0.0.0", Port: 8080, Username: user, Password: pass} s := NewServer(cfg, db, "http://ui.example") return s, db } func TestDashboard_Authenticated200(t *testing.T) { s, db := newServer(t, "admin", "secret") seedArtist(t, db, "a1", "Radiohead", "mbid-1", true) seedLocalAlbum(t, db, "l1", "a1", "OK Computer") // One missing release (no local album matches "Kid A"). seedExternalRelease(t, db, "r1", "a1", "Kid A") req := httptest.NewRequest(http.MethodGet, "/", nil) req.SetBasicAuth("admin", "secret") rec := httptest.NewRecorder() s.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rec.Code) } body := rec.Body.String() if !strings.Contains(body, "Radiohead") { t.Errorf("expected artist name in body, got:\n%s", body) } if !strings.Contains(body, "1") { t.Errorf("expected missing count rendered, got:\n%s", body) } if !strings.Contains(body, "mbid-1") { t.Errorf("expected MBID rendered, got:\n%s", body) } } func TestDashboard_Unauthenticated401(t *testing.T) { s, _ := newServer(t, "admin", "secret") req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() s.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusUnauthorized { t.Fatalf("expected 401, got %d", rec.Code) } if !strings.Contains(rec.Header().Get("WWW-Authenticate"), "Basic") { t.Errorf("expected Basic auth challenge, got headers: %v", rec.Header()) } } func TestDashboard_WrongPassword401(t *testing.T) { s, _ := newServer(t, "admin", "secret") req := httptest.NewRequest(http.MethodGet, "/", nil) req.SetBasicAuth("admin", "wrong") rec := httptest.NewRecorder() s.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusUnauthorized { t.Fatalf("expected 401 for wrong password, got %d", rec.Code) } } func TestDashboard_NoAuthWhenDisabled(t *testing.T) { // When username or password is empty, auth is bypassed. s, db := newServer(t, "", "") seedArtist(t, db, "a1", "Boards of Canada", "", true) req := httptest.NewRequest(http.MethodGet, "/", nil) rec := httptest.NewRecorder() s.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200 when auth disabled, got %d", rec.Code) } if !strings.Contains(rec.Body.String(), "Boards of Canada") { t.Errorf("expected artist rendered, got:\n%s", rec.Body.String()) } } func TestDashboard_SkipsUnmonitored(t *testing.T) { s, db := newServer(t, "admin", "secret") seedArtist(t, db, "mon", "Monitored Artist", "", true) seedArtist(t, db, "unmon", "Unmonitored Artist", "", false) seedExternalRelease(t, db, "r1", "unmon", "Should not appear") seedExternalRelease(t, db, "r2", "mon", "Missing here") req := httptest.NewRequest(http.MethodGet, "/", nil) req.SetBasicAuth("admin", "secret") rec := httptest.NewRecorder() s.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rec.Code) } body := rec.Body.String() if strings.Contains(body, "Unmonitored Artist") { t.Errorf("unmonitored artist should not appear, got:\n%s", body) } if !strings.Contains(body, "Monitored Artist") { t.Errorf("monitored artist should appear, got:\n%s", body) } } func TestDashboard_EmptyState(t *testing.T) { s, _ := newServer(t, "admin", "secret") req := httptest.NewRequest(http.MethodGet, "/", nil) req.SetBasicAuth("admin", "secret") rec := httptest.NewRecorder() s.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200, got %d", rec.Code) } if !strings.Contains(rec.Body.String(), "No monitored artists") { t.Errorf("expected empty-state message, got:\n%s", rec.Body.String()) } } func TestDashboard_NotFoundForOtherPaths(t *testing.T) { s, _ := newServer(t, "admin", "secret") req := httptest.NewRequest(http.MethodGet, "/favicon.ico", nil) req.SetBasicAuth("admin", "secret") rec := httptest.NewRecorder() s.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusNotFound { t.Fatalf("expected 404 for non-root path, got %d", rec.Code) } }