- Honor ignore_singles/ignore_compilations at scanner read time so toggles take effect immediately on the dashboard, artist page, and digest instead of waiting for the MusicBrainz cache to expire and prune rows. - Run notifier notify synchronously in the scheduler loop to avoid overlapping read-send-mark runs double-sending the digest. - Show artist name (with ID fallback) on the archive page instead of raw IDs. - Select last_synced in GetAllArtistSettings for contract consistency. - Fix stale startPeriodicSync comment and remove redundant error var. - Remove dead ignored-branch from the artist template (never rendered). - Add tests: CSRF sameOrigin, ArtistCacheFresh, secondary_types round-trip, and scanner type-toggle filtering. - Update Specification.md schema/config to reflect mbid, last_synced, secondary_types, sync.interval, and server.public_url.
550 lines
17 KiB
Go
550 lines
17 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"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", 0)
|
|
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)
|
|
}
|
|
// The dashboard must link each artist to its detail page, otherwise the
|
|
// ignore/restore/singles actions on that page are unreachable via normal UI
|
|
// navigation.
|
|
if !strings.Contains(body, `href="/artist/a1"`) {
|
|
t.Errorf("expected link to artist detail page, 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)
|
|
}
|
|
}
|
|
|
|
func TestArtistDetail_RendersLocalAndMissing(t *testing.T) {
|
|
s, db := newServer(t, "admin", "secret")
|
|
seedArtist(t, db, "a1", "Radiohead", "mbid-1", true)
|
|
seedLocalAlbum(t, db, "l1", "a1", "OK Computer")
|
|
seedLocalAlbum(t, db, "l2", "a1", "The Bends")
|
|
// One missing release (no local album matches "Kid A").
|
|
seedExternalRelease(t, db, "r1", "a1", "Kid A")
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/artist/a1", 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()
|
|
for _, want := range []string{"Radiohead", "OK Computer", "The Bends", "Kid A", "mbid-1"} {
|
|
if !strings.Contains(body, want) {
|
|
t.Errorf("expected %q in artist page, got:\n%s", want, body)
|
|
}
|
|
}
|
|
if !strings.Contains(body, `name="rgid" value="r1"`) {
|
|
t.Errorf("expected ignore form for r1, got:\n%s", body)
|
|
}
|
|
}
|
|
|
|
func TestArtistDetail_UnknownArtist404(t *testing.T) {
|
|
s, _ := newServer(t, "admin", "secret")
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/artist/does-not-exist", nil)
|
|
req.SetBasicAuth("admin", "secret")
|
|
rec := httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusNotFound {
|
|
t.Fatalf("expected 404 for unknown artist, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestArtistDetail_BarePath404(t *testing.T) {
|
|
s, _ := newServer(t, "admin", "secret")
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/artist/", nil)
|
|
req.SetBasicAuth("admin", "secret")
|
|
rec := httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusNotFound {
|
|
t.Fatalf("expected 404 for bare /artist/, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestArtistDetail_RequiresGet(t *testing.T) {
|
|
s, _ := newServer(t, "admin", "secret")
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/artist/a1", nil)
|
|
req.SetBasicAuth("admin", "secret")
|
|
rec := httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusMethodNotAllowed {
|
|
t.Fatalf("expected 405 for POST on detail, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestArchive_RendersIgnoredReleases(t *testing.T) {
|
|
s, db := newServer(t, "admin", "secret")
|
|
seedArtist(t, db, "a1", "Radiohead", "", true)
|
|
// An ignored external release.
|
|
if err := database.SaveExternalRelease(db, &database.ExternalRelease{
|
|
RGID: "r-ignored",
|
|
ArtistID: "a1",
|
|
Title: "Ignored Album",
|
|
IsIgnored: true,
|
|
}); err != nil {
|
|
t.Fatalf("seed ignored release: %v", err)
|
|
}
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/archive", 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, "Ignored Album") {
|
|
t.Errorf("expected ignored release in archive, got:\n%s", body)
|
|
}
|
|
if !strings.Contains(body, `name="rgid" value="r-ignored"`) {
|
|
t.Errorf("expected restore form for r-ignored, got:\n%s", body)
|
|
}
|
|
}
|
|
|
|
func TestArchive_EmptyState(t *testing.T) {
|
|
s, _ := newServer(t, "admin", "secret")
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "/archive", 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 ignored releases") {
|
|
t.Errorf("expected empty archive message, got:\n%s", rec.Body.String())
|
|
}
|
|
}
|
|
|
|
func TestArchive_RequiresGet(t *testing.T) {
|
|
s, _ := newServer(t, "admin", "secret")
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/archive", nil)
|
|
req.SetBasicAuth("admin", "secret")
|
|
rec := httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusMethodNotAllowed {
|
|
t.Fatalf("expected 405 for POST on archive, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
func TestIgnoreAction_SetsFlagAndRemovesFromDashboard(t *testing.T) {
|
|
s, db := newServer(t, "admin", "secret")
|
|
seedArtist(t, db, "a1", "Radiohead", "", true)
|
|
seedExternalRelease(t, db, "r1", "a1", "Kid A")
|
|
|
|
// Before ignore: dashboard shows 1 missing.
|
|
before := dashboardMissingCount(t, s, "Radiohead")
|
|
if before != 1 {
|
|
t.Fatalf("expected 1 missing before ignore, got %d", before)
|
|
}
|
|
|
|
// POST ignore.
|
|
form := strings.NewReader("rgid=r1")
|
|
req := httptest.NewRequest(http.MethodPost, "/artist/a1/ignore", form)
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
req.SetBasicAuth("admin", "secret")
|
|
rec := httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusSeeOther {
|
|
t.Fatalf("expected 303 redirect, got %d", rec.Code)
|
|
}
|
|
if loc := rec.Header().Get("Location"); loc != "/artist/a1" {
|
|
t.Errorf("expected redirect to /artist/a1, got %q", loc)
|
|
}
|
|
|
|
// Flag persisted.
|
|
rel, err := database.GetExternalRelease(db, "r1")
|
|
if err != nil {
|
|
t.Fatalf("get release: %v", err)
|
|
}
|
|
if !rel.IsIgnored {
|
|
t.Errorf("expected r1 to be ignored")
|
|
}
|
|
|
|
// Dashboard missing count drops to 0.
|
|
after := dashboardMissingCount(t, s, "Radiohead")
|
|
if after != 0 {
|
|
t.Fatalf("expected 0 missing after ignore, got %d", after)
|
|
}
|
|
}
|
|
|
|
func TestIgnoreAction_RequiresAuth(t *testing.T) {
|
|
s, db := newServer(t, "admin", "secret")
|
|
seedArtist(t, db, "a1", "Radiohead", "", true)
|
|
seedExternalRelease(t, db, "r1", "a1", "Kid A")
|
|
|
|
form := strings.NewReader("rgid=r1")
|
|
req := httptest.NewRequest(http.MethodPost, "/artist/a1/ignore", form)
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
rec := httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusUnauthorized {
|
|
t.Fatalf("expected 401 without auth, got %d", rec.Code)
|
|
}
|
|
// Flag must remain unset.
|
|
rel, err := database.GetExternalRelease(db, "r1")
|
|
if err != nil {
|
|
t.Fatalf("get release: %v", err)
|
|
}
|
|
if rel.IsIgnored {
|
|
t.Errorf("release must not be ignored without auth")
|
|
}
|
|
}
|
|
|
|
func TestRestoreAction_ClearsFlag(t *testing.T) {
|
|
s, db := newServer(t, "admin", "secret")
|
|
seedArtist(t, db, "a1", "Radiohead", "", true)
|
|
if err := database.SaveExternalRelease(db, &database.ExternalRelease{
|
|
RGID: "r1",
|
|
ArtistID: "a1",
|
|
Title: "Kid A",
|
|
IsIgnored: true,
|
|
}); err != nil {
|
|
t.Fatalf("seed ignored release: %v", err)
|
|
}
|
|
|
|
form := strings.NewReader("rgid=r1")
|
|
req := httptest.NewRequest(http.MethodPost, "/artist/a1/restore", form)
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
req.SetBasicAuth("admin", "secret")
|
|
rec := httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusSeeOther {
|
|
t.Fatalf("expected 303 redirect, got %d", rec.Code)
|
|
}
|
|
rel, err := database.GetExternalRelease(db, "r1")
|
|
if err != nil {
|
|
t.Fatalf("get release: %v", err)
|
|
}
|
|
if rel.IsIgnored {
|
|
t.Errorf("expected r1 to be restored (not ignored)")
|
|
}
|
|
}
|
|
|
|
func TestIgnoreSingles_TogglesFlag(t *testing.T) {
|
|
s, db := newServer(t, "admin", "secret")
|
|
seedArtist(t, db, "a1", "Radiohead", "", true)
|
|
|
|
// Toggle on.
|
|
req := httptest.NewRequest(http.MethodPost, "/artist/a1/ignore-singles", nil)
|
|
req.SetBasicAuth("admin", "secret")
|
|
rec := httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
if rec.Code != http.StatusSeeOther {
|
|
t.Fatalf("expected 303 on toggle, got %d", rec.Code)
|
|
}
|
|
settings, err := database.GetArtistSettings(db, "a1")
|
|
if err != nil {
|
|
t.Fatalf("get settings: %v", err)
|
|
}
|
|
if !settings.IgnoreSingles {
|
|
t.Errorf("expected ignore_singles = true after first toggle")
|
|
}
|
|
|
|
// Toggle off.
|
|
req2 := httptest.NewRequest(http.MethodPost, "/artist/a1/ignore-singles", nil)
|
|
req2.SetBasicAuth("admin", "secret")
|
|
rec2 := httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec2, req2)
|
|
settings, err = database.GetArtistSettings(db, "a1")
|
|
if err != nil {
|
|
t.Fatalf("get settings: %v", err)
|
|
}
|
|
if settings.IgnoreSingles {
|
|
t.Errorf("expected ignore_singles = false after second toggle")
|
|
}
|
|
}
|
|
|
|
func TestIgnoreSingles_RequiresAuth(t *testing.T) {
|
|
s, _ := newServer(t, "admin", "secret")
|
|
|
|
req := httptest.NewRequest(http.MethodPost, "/artist/a1/ignore-singles", nil)
|
|
rec := httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusUnauthorized {
|
|
t.Fatalf("expected 401 without auth, got %d", rec.Code)
|
|
}
|
|
}
|
|
|
|
// dashboardMissingCount returns the missing-release count for the artist with
|
|
// the given name from the dashboard view model (0 if the artist is absent).
|
|
func dashboardMissingCount(t *testing.T, s *Server, artistName string) int {
|
|
t.Helper()
|
|
data, err := s.buildDashboardData(context.Background(), 0)
|
|
if err != nil {
|
|
t.Fatalf("build dashboard data: %v", err)
|
|
}
|
|
for _, a := range data.Artists {
|
|
if a.Name == artistName {
|
|
return a.MissingCount
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// postStateChanging issues a state-changing POST to the given route with the
|
|
// provided Origin/Referer header and basic auth, returning the response code.
|
|
func postStateChanging(t *testing.T, s *Server, path, originHeader string) int {
|
|
t.Helper()
|
|
form := strings.NewReader("rgid=r1")
|
|
req := httptest.NewRequest(http.MethodPost, path, form)
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
if originHeader != "" {
|
|
req.Header.Set("Origin", originHeader)
|
|
}
|
|
req.SetBasicAuth("admin", "secret")
|
|
rec := httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
return rec.Code
|
|
}
|
|
|
|
func TestStateChangingEnforcesSameOrigin(t *testing.T) {
|
|
served := "http://0.0.0.0:8080" // matches the server's Addr()
|
|
|
|
tests := []struct {
|
|
name string
|
|
route string
|
|
origin string
|
|
wantCode int
|
|
}{
|
|
{"same-origin Origin allowed", "/artist/a1/ignore", served, http.StatusSeeOther},
|
|
{"no Origin header allowed (same-origin form post)", "/artist/a1/ignore", "", http.StatusSeeOther},
|
|
{"cross-origin Origin rejected", "/artist/a1/ignore", "http://evil.example", http.StatusForbidden},
|
|
{"cross-origin Referer rejected", "/artist/a1/ignore", "", http.StatusForbidden},
|
|
{"cross-origin on toggle rejected", "/artist/a1/ignore-singles", "http://evil.example", http.StatusForbidden},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
s, db := newServer(t, "admin", "secret")
|
|
seedArtist(t, db, "a1", "Radiohead", "", true)
|
|
seedExternalRelease(t, db, "r1", "a1", "Kid A")
|
|
|
|
// For the cross-origin Referer case, use Referer instead of Origin.
|
|
var code int
|
|
if tt.name == "cross-origin Referer rejected" {
|
|
form := strings.NewReader("rgid=r1")
|
|
req := httptest.NewRequest(http.MethodPost, tt.route, form)
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
req.Header.Set("Referer", "http://evil.example/artist/a1")
|
|
req.SetBasicAuth("admin", "secret")
|
|
rec := httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
code = rec.Code
|
|
} else {
|
|
code = postStateChanging(t, s, tt.route, tt.origin)
|
|
}
|
|
|
|
if code != tt.wantCode {
|
|
t.Fatalf("route %s origin %q: got %d, want %d", tt.route, tt.origin, code, tt.wantCode)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStateChanging_MalformedOriginRejected(t *testing.T) {
|
|
s, db := newServer(t, "admin", "secret")
|
|
seedArtist(t, db, "a1", "Radiohead", "", true)
|
|
seedExternalRelease(t, db, "r1", "a1", "Kid A")
|
|
|
|
form := strings.NewReader("rgid=r1")
|
|
req := httptest.NewRequest(http.MethodPost, "/artist/a1/ignore", form)
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
// An Origin that does not parse as a valid URL with a host.
|
|
req.Header.Set("Origin", "http://")
|
|
req.SetBasicAuth("admin", "secret")
|
|
rec := httptest.NewRecorder()
|
|
s.Handler().ServeHTTP(rec, req)
|
|
|
|
if rec.Code != http.StatusForbidden {
|
|
t.Fatalf("expected 403 for malformed origin, got %d", rec.Code)
|
|
}
|
|
}
|