package service import ( "context" "net/http" "net/http/httptest" "strings" "testing" "go.uber.org/zap" "github.com/ShukeBta/MediaStationGo/internal/model" "github.com/ShukeBta/MediaStationGo/internal/repository" ) func TestSiteUpdateKeepsSecretsWhenPatchIsBlank(t *testing.T) { db := newServiceTestDB(t, &model.Site{}) svc := NewSiteService(zap.NewNop(), &repository.Container{DB: db}, "") site := &model.Site{ Name: "M-Team", Type: "mteam", URL: "https://api.m-team.cc", AuthType: "api_key", APIKey: "token-123", Enabled: true, } if err := svc.Create(context.Background(), site); err != nil { t.Fatal(err) } if err := svc.Update(context.Background(), site.ID, map[string]any{ "url": "https://api.m-team.cc/", "api_key": "", "cookie": "", }); err != nil { t.Fatal(err) } got, err := svc.FindByID(context.Background(), site.ID) if err != nil { t.Fatal(err) } if got.APIKey != "token-123" { t.Fatalf("APIKey = %q, want original token", got.APIKey) } if got.URL != "https://api.m-team.cc" { t.Fatalf("URL = %q, want trimmed URL", got.URL) } } func TestYemaPTTestConnectionDoesNotFallbackAfterAuthFailure(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _, _ = w.Write([]byte(`{"success":false,"errorCode":403,"errorMessage":"need api auth"}`)) })) defer server.Close() db := newServiceTestDB(t, &model.Site{}) repos := repository.New(db) svc := NewSiteService(zap.NewNop(), repos, "") site := &model.Site{ Name: "YemaPT", Type: "yemapt", URL: server.URL, AuthType: "api_key", APIKey: "bad-auth", Enabled: true, } if err := svc.Create(context.Background(), site); err != nil { t.Fatal(err) } ok, msg, err := svc.TestConnection(context.Background(), site.ID) if err != nil { t.Fatal(err) } if ok { t.Fatal("TestConnection succeeded after YemaPT auth failure") } if !strings.Contains(msg, "need api auth") { t.Fatalf("message = %q, want need api auth", msg) } } func TestRedactSensitiveDownloadURL(t *testing.T) { tests := []struct { name string raw string want string }{ { name: "query secrets", raw: "https://pt.example/download.php?id=123&passkey=secret#frag", want: "https://pt.example/download.php", }, { name: "magnet", raw: "magnet:?xt=urn:btih:abc&dn=movie", want: "magnet:?xt=***", }, { name: "invalid", raw: "not a url", want: "[redacted-download-url]", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := redactSensitiveDownloadURL(tt.raw); got != tt.want { t.Fatalf("redactSensitiveDownloadURL() = %q, want %q", got, tt.want) } }) } } func TestSiteSearchReturnsErrorWhenAllEnabledSitesFail(t *testing.T) { upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.Error(w, "upstream timeout simulation", http.StatusGatewayTimeout) })) defer upstream.Close() db := newServiceTestDB(t, &model.Site{}) repos := repository.New(db) svc := NewSiteService(zap.NewNop(), repos, "") site := &model.Site{ Name: "馒头", Type: "mteam", URL: upstream.URL, AuthType: "api_key", APIKey: "token-123", Enabled: true, Timeout: 5, } if err := svc.Create(context.Background(), site); err != nil { t.Fatal(err) } results, err := svc.Search(context.Background(), "南部档案 2026") if err == nil { t.Fatalf("Search error = nil, want all-sites-failed error; results=%#v", results) } if len(results) != 0 { t.Fatalf("results = %#v, want none on all-sites failure", results) } if !strings.Contains(err.Error(), "all enabled sites failed") || !strings.Contains(err.Error(), "馒头") { t.Fatalf("error = %q, want site failure context", err.Error()) } }