Files
fusion/backend/internal/pull/puller_test.go
T
Yuan 62301ea572 feat(backend): migrate feed runtime pull state to fetch_state
Move pull metadata to feed_fetch_state and unify scheduler decisions around next_check_at with a global FUSION_PULL_MAX_BACKOFF cap. This keeps feed core data stable while making retry/cache-aware pulling behavior explicit across API, docs, and frontend types.
2026-02-14 15:25:43 +08:00

147 lines
4.1 KiB
Go

package pull
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"path/filepath"
"sync/atomic"
"testing"
"time"
"github.com/0x2E/fusion/internal/config"
"github.com/0x2E/fusion/internal/store"
)
func TestRefreshFeedPreservesValidatorsWhen304OmitHeaders(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "test.db")
st, err := store.New(dbPath)
if err != nil {
t.Fatalf("create store: %v", err)
}
defer st.Close()
var requestCount int32
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
count := atomic.AddInt32(&requestCount, 1)
if count == 1 {
w.Header().Set("Content-Type", "application/rss+xml")
w.Header().Set("ETag", `"etag-v1"`)
w.Header().Set("Last-Modified", "Mon, 02 Jan 2006 15:04:05 GMT")
w.Header().Set("Cache-Control", "max-age=86400")
w.Header().Set("Expires", "Tue, 03 Jan 2006 15:04:05 GMT")
_, _ = fmt.Fprint(w, `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"><channel><title>Demo</title><link>https://example.com</link>
<item><guid>g1</guid><title>Item</title><link>https://example.com/1</link></item>
</channel></rss>`)
return
}
w.WriteHeader(http.StatusNotModified)
}))
defer server.Close()
feed, err := st.CreateFeed(1, "Feed A", server.URL, "", "")
if err != nil {
t.Fatalf("create feed: %v", err)
}
p := New(st, &config.Config{
PullInterval: 1800,
PullTimeout: 5,
PullConcurrency: 1,
PullMaxBackoff: 604800,
AllowPrivateFeeds: true,
})
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := p.RefreshFeed(ctx, feed.ID); err != nil {
t.Fatalf("first refresh: %v", err)
}
if err := p.RefreshFeed(ctx, feed.ID); err != nil {
t.Fatalf("second refresh: %v", err)
}
updatedFeed, err := st.GetFeed(feed.ID)
if err != nil {
t.Fatalf("get feed: %v", err)
}
if updatedFeed.FetchState.ETag != `"etag-v1"` {
t.Fatalf("etag = %q, want %q", updatedFeed.FetchState.ETag, `"etag-v1"`)
}
if updatedFeed.FetchState.LastModified != "Mon, 02 Jan 2006 15:04:05 GMT" {
t.Fatalf("last_modified = %q, want %q", updatedFeed.FetchState.LastModified, "Mon, 02 Jan 2006 15:04:05 GMT")
}
if updatedFeed.FetchState.CacheControl != "max-age=86400" {
t.Fatalf("cache_control = %q, want %q", updatedFeed.FetchState.CacheControl, "max-age=86400")
}
expires, err := http.ParseTime("Tue, 03 Jan 2006 15:04:05 GMT")
if err != nil {
t.Fatalf("parse expires: %v", err)
}
if updatedFeed.FetchState.ExpiresAt != expires.Unix() {
t.Fatalf("expires_at = %d, want %d", updatedFeed.FetchState.ExpiresAt, expires.Unix())
}
}
func TestRefreshAllWaitsForRunningJobs(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "test.db")
st, err := store.New(dbPath)
if err != nil {
t.Fatalf("create store: %v", err)
}
defer st.Close()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(200 * time.Millisecond)
w.Header().Set("Content-Type", "application/rss+xml")
_, _ = fmt.Fprintf(w, `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"><channel><title>Demo</title><link>https://example.com</link>
<item><guid>%s</guid><title>Item</title><link>https://example.com%s</link></item>
</channel></rss>`, r.URL.Path, r.URL.Path)
}))
defer server.Close()
if _, err := st.CreateFeed(1, "Feed A", server.URL+"/a", "", ""); err != nil {
t.Fatalf("create feed A: %v", err)
}
if _, err := st.CreateFeed(1, "Feed B", server.URL+"/b", "", ""); err != nil {
t.Fatalf("create feed B: %v", err)
}
p := New(st, &config.Config{
PullInterval: 1800,
PullTimeout: 5,
PullConcurrency: 1,
PullMaxBackoff: 604800,
AllowPrivateFeeds: true,
})
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
count, err := p.RefreshAll(ctx)
if err != nil {
t.Fatalf("refresh all: %v", err)
}
if count != 2 {
t.Fatalf("refresh count = %d, want 2", count)
}
feeds, err := st.ListFeeds()
if err != nil {
t.Fatalf("list feeds: %v", err)
}
for _, feed := range feeds {
if feed.FetchState.LastSuccessAt <= 0 {
t.Fatalf("feed %d last_success_at = %d, want > 0", feed.ID, feed.FetchState.LastSuccessAt)
}
}
}