Files
MeBox/internal/handler/discover_extra_test.go
T

184 lines
6.1 KiB
Go

package handler
import (
"context"
"errors"
"testing"
"time"
"github.com/glebarez/sqlite"
"go.uber.org/zap"
"go.uber.org/zap/zaptest/observer"
"gorm.io/gorm"
"github.com/ShukeBta/MediaStationGo/internal/model"
"github.com/ShukeBta/MediaStationGo/internal/repository"
"github.com/ShukeBta/MediaStationGo/internal/service"
)
func TestDiscoverProviderEnabledHonorsAPIConfigToggle(t *testing.T) {
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
if err := db.AutoMigrate(&model.APIConfig{}); err != nil {
t.Fatal(err)
}
repos := repository.New(db)
apiConfig := service.NewAPIConfigService(zap.NewNop(), repos, service.NewCryptoService("", zap.NewNop()))
enabled := false
if _, err := apiConfig.Update(t.Context(), "douban", service.APIConfigPatch{Enabled: &enabled}); err != nil {
t.Fatal(err)
}
svc := &service.Container{APIConfig: apiConfig}
if discoverProviderEnabled(t.Context(), svc, "douban") {
t.Fatal("disabled API config should disable discover provider")
}
if !discoverProviderEnabled(t.Context(), svc, "missing-provider") {
t.Fatal("missing API config should keep discover provider available")
}
}
func TestDiscoverFetchFailureLogIncludesDiagnostics(t *testing.T) {
core, observed := observer.New(zap.WarnLevel)
logger := zap.New(core)
logDiscoverFetchFailed(
&service.Container{Log: logger},
"tmdb_latest_movie",
2,
1500*time.Millisecond,
discoverSectionTimeout("tmdb_latest_movie"),
context.DeadlineExceeded,
)
entries := observed.FilterMessage("discover section fetch failed").All()
if len(entries) != 1 {
t.Fatalf("expected one failure log entry, got %d", len(entries))
}
fields := entries[0].ContextMap()
if fields["section"] != "tmdb_latest_movie" || fields["provider"] != "tmdb" {
t.Fatalf("unexpected section/provider fields: %#v", fields)
}
if fields["page"] != int64(2) && fields["page"] != 2 {
t.Fatalf("page field missing or wrong: %#v", fields["page"])
}
if fields["duration_ms"] != int64(1500) && fields["duration_ms"] != 1500 {
t.Fatalf("duration_ms field missing or wrong: %#v", fields["duration_ms"])
}
if _, ok := fields["timeout"]; !ok {
t.Fatalf("timeout field missing: %#v", fields)
}
}
func TestDiscoverSectionTimeoutRaisesBangumiBudget(t *testing.T) {
if got := discoverSectionTimeout("bangumi_calendar"); got != discoverFeedBangumiTimeout {
t.Fatalf("bangumi timeout = %s, want %s", got, discoverFeedBangumiTimeout)
}
if got := discoverSectionTimeout("tmdb_latest_movie"); got != discoverFeedSectionTimeout {
t.Fatalf("tmdb timeout = %s, want %s", got, discoverFeedSectionTimeout)
}
}
func TestDiscoverSlowFetchLogIncludesSectionTiming(t *testing.T) {
core, observed := observer.New(zap.InfoLevel)
logger := zap.New(core)
logDiscoverFetchSlow(&service.Container{Log: logger}, "douban_hot_movie", 1, discoverFeedSlowSectionThreshold-time.Millisecond, 24)
if got := observed.FilterMessage("discover section fetch slow").Len(); got != 0 {
t.Fatalf("fast section should not log, got %d entries", got)
}
logDiscoverFetchSlow(&service.Container{Log: logger}, "douban_hot_movie", 1, discoverFeedSlowSectionThreshold, 24)
entries := observed.FilterMessage("discover section fetch slow").All()
if len(entries) != 1 {
t.Fatalf("expected one slow log entry, got %d", len(entries))
}
fields := entries[0].ContextMap()
if fields["section"] != "douban_hot_movie" || fields["provider"] != "douban" {
t.Fatalf("unexpected section/provider fields: %#v", fields)
}
if fields["items"] != int64(24) && fields["items"] != 24 {
t.Fatalf("items field missing or wrong: %#v", fields["items"])
}
if _, ok := fields["duration_ms"]; !ok {
t.Fatalf("duration_ms field missing: %#v", fields)
}
if _, ok := fields["slow_threshold"]; !ok {
t.Fatalf("slow_threshold field missing: %#v", fields)
}
}
func TestDiscoverFeedErrorMessageHidesTechnicalTimeout(t *testing.T) {
for _, err := range []error{
context.DeadlineExceeded,
errors.New("timeout of 30000ms exceeded"),
} {
got := discoverFeedErrorMessage(err)
if got != "推荐源响应超时,已跳过本次加载" {
t.Fatalf("message for %q = %q", err, got)
}
}
if got := discoverFeedErrorMessage(errors.New("upstream 503")); got != "推荐源暂时不可用,已跳过本次加载" {
t.Fatalf("generic message = %q", got)
}
}
func TestDefaultDiscoverSectionKeysSkipDisabledProviders(t *testing.T) {
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
if err := db.AutoMigrate(&model.APIConfig{}); err != nil {
t.Fatal(err)
}
repos := repository.New(db)
apiConfig := service.NewAPIConfigService(zap.NewNop(), repos, service.NewCryptoService("", zap.NewNop()))
disabled := false
for _, provider := range []string{"douban", "bangumi"} {
if _, err := apiConfig.Update(t.Context(), provider, service.APIConfigPatch{Enabled: &disabled}); err != nil {
t.Fatal(err)
}
}
svc := &service.Container{APIConfig: apiConfig}
keys := defaultDiscoverSectionKeys(t.Context(), svc)
for _, key := range keys {
switch discoverSectionProvider(key) {
case "douban", "bangumi":
t.Fatalf("disabled provider key %q should not be selected by default; keys=%v", key, keys)
}
}
if len(keys) == 0 {
t.Fatal("default keys should keep enabled providers")
}
}
func TestDefaultDiscoverSectionKeysIncludeLatestTMDbRails(t *testing.T) {
keys := defaultDiscoverSectionKeys(t.Context(), &service.Container{})
keySet := map[string]struct{}{}
for _, key := range keys {
keySet[key] = struct{}{}
}
for _, key := range []string{"tmdb_latest_movie", "tmdb_latest_tv"} {
if _, ok := keySet[key]; !ok {
t.Fatalf("default discover keys should include %q: %v", key, keys)
}
}
}
func TestFallbackDiscoverSectionKeyUsesTMDbForDoubanRails(t *testing.T) {
cases := map[string]string{
"douban_hot_movie": "tmdb_popular_movie",
"douban_hot_tv": "tmdb_popular_tv",
"douban_top_movie": "tmdb_top_rated_movie",
"tmdb_latest_tv": "",
}
for key, want := range cases {
if got := fallbackDiscoverSectionKey(key); got != want {
t.Fatalf("fallbackDiscoverSectionKey(%q) = %q, want %q", key, got, want)
}
}
}