package model import ( "context" "net/http" "net/http/httptest" "testing" "time" "github.com/stretchr/testify/assert" ) func TestLoadFeed_Success(t *testing.T) { // Mock RSS feed rssFeed := ` Test Podcast This is a test podcast http://example.com http://example.com/image.jpg Test Copyright en Episode 1 http://example.com/episode1 Episode 1 description ` // Create a test server server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(rssFeed)) })) defer server.Close() // Call LoadFeed podcast, err := LoadFeed(server.URL, "", 10*time.Second) // Assertions assert.NoError(t, err) assert.NotNil(t, podcast) assert.Equal(t, "Test Podcast", podcast.Title) assert.Equal(t, "This is a test podcast", podcast.Description) assert.Equal(t, "http://example.com", podcast.Link) assert.Equal(t, "http://example.com/image.jpg", podcast.Image) assert.Equal(t, "Test Copyright", podcast.Copyright) assert.Equal(t, "en", podcast.Language) assert.Len(t, podcast.Episodes, 1) assert.Equal(t, "Episode 1", podcast.Episodes[0].Title) } func TestLoadFeed_Timeout(t *testing.T) { // Create a test server that delays response server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { time.Sleep(2 * time.Second) w.Write([]byte("")) })) defer server.Close() // Call LoadFeed with a short timeout _, err := LoadFeed(server.URL, "", 1*time.Second) // Assertions assert.Error(t, err) assert.Contains(t, err.Error(), "context deadline exceeded") } func TestLoadFeed_InvalidURL(t *testing.T) { // Call LoadFeed with an invalid URL _, err := LoadFeed("http://invalid-url", "", 10*time.Second) // Assertions assert.Error(t, err) assert.Contains(t, err.Error(), "解析播客订阅失败") } func TestLoadFeed_EmptyFeed(t *testing.T) { // Create a test server with an empty feed server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("")) })) defer server.Close() // Call LoadFeed _, err := LoadFeed(server.URL, "", 10*time.Second) // Assertions assert.Error(t, err) assert.Contains(t, err.Error(), "解析播客订阅失败") } // UA 是这个库唯一会影响「能不能抓到」的可配项:实测有源站按 UA 黑名单直接 403 // (feed.xyzfm.space 对 gofeed 出厂的 "Gofeed/1.0" 返回 denied by UA ACL = blacklist), // 且无 UA 的请求比任何 UA 都更容易被拒。故这三条都必须钉死在测试里。 func TestLoadFeedWithOptions_UserAgent(t *testing.T) { cases := []struct { name string give string want string }{ {"显式指定的 UA 逐字节发出", "txpodcast/1.0", "txpodcast/1.0"}, {"空串回落默认而不是发空头", "", DefaultUserAgent}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { t.Parallel() // buffered chan 而非裸变量:handler 在另一个 goroutine 里写,-race 会告警。 got := make(chan string, 1) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { got <- r.Header.Get("User-Agent") _, _ = w.Write([]byte(`t`)) })) defer srv.Close() _, err := LoadFeedWithOptions(context.Background(), srv.URL, FeedOptions{UserAgent: tc.give, Timeout: 10 * time.Second}) assert.NoError(t, err) assert.Equal(t, tc.want, <-got) }) } } // 旧签名必须继续发默认 UA:它是 LoadFeedWithOptions 的包装,委托时若漏传就会退化成空头。 func TestLoadFeedContext_KeepsDefaultUserAgent(t *testing.T) { got := make(chan string, 1) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { got <- r.Header.Get("User-Agent") _, _ = w.Write([]byte(`t`)) })) defer srv.Close() _, err := LoadFeedContext(context.Background(), srv.URL, "", 10*time.Second) assert.NoError(t, err) assert.Equal(t, DefaultUserAgent, <-got) } // timeout <= 0 回落 30 秒的既有语义不能因为改走 FeedOptions 而丢失。 func TestLoadFeedWithOptions_ZeroTimeoutFallsBack(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(`t`)) })) defer srv.Close() // 0 若被原样当成超时,请求会立刻 context deadline exceeded。 _, err := LoadFeedWithOptions(context.Background(), srv.URL, FeedOptions{}) assert.NoError(t, err) }