181 lines
5.3 KiB
Go
181 lines
5.3 KiB
Go
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)
|
|
}
|
|
}
|