package main import ( "context" "net/http" "net/http/httptest" "strings" "testing" "naviwatcher/internal/config" "naviwatcher/internal/database" "naviwatcher/internal/notifier" "naviwatcher/internal/scanner" "naviwatcher/internal/web" ) // TestDataFlowSmoke seeds an in-memory DB with a monitored artist, one local // album, and a missing external release, then exercises the full // scan -> notify -> web pipeline end to end: // - ScanAll reports the missing release // - NotifyOnce (with a stub sender) sends exactly the missing release and // marks it as sent, so a second run reports nothing // - The Web UI dashboard requires auth and renders the artist + missing count, // and the artist detail page renders the missing release // // This is the acceptance smoke check for Task 9. func TestDataFlowSmoke(t *testing.T) { db, err := database.New(":memory:") if err != nil { t.Fatalf("open :memory: db: %v", err) } defer db.Close() ctx := context.Background() // Seed: one monitored artist with one local album ... if err := database.SaveArtistSettings(db, &database.ArtistSettings{ ID: "ar1", Name: "Pink Floyd", MBID: "abcdef", Monitored: true, }); err != nil { t.Fatalf("seed artist: %v", err) } if err := database.SaveLocalAlbum(db, &database.LocalAlbum{ ID: "al1", ArtistID: "ar1", Title: "The Wall", }); err != nil { t.Fatalf("seed local album: %v", err) } // ... and a missing external release (not present locally). if err := database.SaveExternalRelease(db, &database.ExternalRelease{ RGID: "rg-missing", ArtistID: "ar1", Title: "Animals", Type: "Album", ReleaseDate: "1977-01-01", }); err != nil { t.Fatalf("seed external release: %v", err) } // 1) ScanAll should surface exactly the missing release. missing, err := scanner.ScanAll(ctx, db, 0.85) if err != nil { t.Fatalf("ScanAll: %v", err) } if len(missing) != 1 { t.Fatalf("expected 1 missing release, got %d", len(missing)) } if missing[0].RGID != "rg-missing" || missing[0].Title != "Animals" { t.Fatalf("unexpected missing release: %+v", missing[0]) } // 2) Notifier: stub sender, first run notifies 1, second run notifies 0. var sent []string stub := stubSender{onSend: func(msg string) error { sent = append(sent, msg) return nil }} tgCfg := config.TelegramConfig{Enabled: true} n1, err := notifier.NotifyOnce(ctx, db, stub, tgCfg, "http://localhost:8080") if err != nil { t.Fatalf("NotifyOnce #1: %v", err) } if n1 != 1 { t.Fatalf("expected NotifyOnce to send 1, got %d", n1) } if len(sent) != 1 || !strings.Contains(sent[0], "Animals") { t.Fatalf("digest missing expected content: %v", sent) } n2, err := notifier.NotifyOnce(ctx, db, stub, tgCfg, "http://localhost:8080") if err != nil { t.Fatalf("NotifyOnce #2: %v", err) } if n2 != 0 { t.Fatalf("expected second NotifyOnce to send 0 (already sent), got %d", n2) } // 3) Web UI: dashboard requires basic auth and renders the artist. srvCfg := &config.ServerConfig{ Host: "localhost", Port: 0, Username: "admin", Password: "secret", } srv := web.NewServer(srvCfg, db, "http://localhost:8080", 0) // Unauthenticated -> 401. rec := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/", nil) srv.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusUnauthorized { t.Fatalf("expected 401 for unauthenticated dashboard, got %d", rec.Code) } // Authenticated -> 200 with the artist name and missing count. rec = httptest.NewRecorder() req = httptest.NewRequest(http.MethodGet, "/", nil) req.SetBasicAuth("admin", "secret") srv.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200 for authenticated dashboard, got %d", rec.Code) } body := rec.Body.String() if !strings.Contains(body, "Pink Floyd") { t.Fatalf("dashboard did not render artist name; body head:\n%s", body[:min(400, len(body))]) } // Artist detail page renders the missing release. rec = httptest.NewRecorder() req = httptest.NewRequest(http.MethodGet, "/artist/ar1", nil) req.SetBasicAuth("admin", "secret") srv.Handler().ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("expected 200 for artist page, got %d", rec.Code) } if !strings.Contains(rec.Body.String(), "Animals") { t.Fatalf("artist page did not render missing release 'Animals'") } } func min(a, b int) int { if a < b { return a } return b } // stubSender is a test double for notifier.Sender. type stubSender struct { onSend func(message string) error } func (s stubSender) Send(_ context.Context, message string) error { return s.onSend(message) }