package main import ( "net/http" "net/http/httptest" "path/filepath" "testing" "pete/internal/config" "pete/internal/storage" ) func setupTestDB(t *testing.T) string { t.Helper() dbPath := filepath.Join(t.TempDir(), "test.db") if err := storage.Init(dbPath); err != nil { t.Fatal(err) } t.Cleanup(func() { storage.Close() }) return dbPath } // serveFeed returns a test HTTP server that serves a minimal RSS feed. func serveFeed(t *testing.T, items int) *httptest.Server { t.Helper() return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/rss+xml") w.WriteHeader(200) w.Write([]byte(`Test Feed`)) for i := 0; i < items; i++ { guid := "https://example.com/story-" + string(rune('a'+i)) w.Write([]byte(`Story ` + string(rune('A'+i)) + `` + guid + `` + guid + `Lede for story.`)) } w.Write([]byte(``)) })) } func TestTimeNow(t *testing.T) { a := timeNow() b := timeNow() if b < a { t.Errorf("timeNow() went backwards: %d then %d", a, b) } if b-a > 1 { t.Errorf("timeNow() gap too large: %d", b-a) } } func TestRunSeed_IngestsStories(t *testing.T) { setupTestDB(t) ts := serveFeed(t, 3) defer ts.Close() cfg := &config.Config{ Sources: []config.SourceConfig{ { Name: "Test Feed", FeedURL: ts.URL, Tier: 1, PollIntervalMinutes: 20, DirectRoute: "tech", Enabled: true, }, }, } runSeed(cfg) // All 3 stories should be marked as seen and classified for _, c := range []rune{'a', 'b', 'c'} { guid := "https://example.com/story-" + string(c) if !storage.IsGUIDSeen(guid) { t.Errorf("expected GUID %q to be seen after seed", guid) } } // Seed marks rows classified=1; confirm none remain unclassified. var unclassified int storage.Get().QueryRow(`SELECT COUNT(*) FROM stories WHERE classified = 0`).Scan(&unclassified) if unclassified != 0 { t.Errorf("expected 0 unclassified after seed, got %d", unclassified) } } func TestRunSeed_Idempotent(t *testing.T) { setupTestDB(t) ts := serveFeed(t, 2) defer ts.Close() cfg := &config.Config{ Sources: []config.SourceConfig{ { Name: "Test Feed", FeedURL: ts.URL, Tier: 1, PollIntervalMinutes: 20, DirectRoute: "tech", Enabled: true, }, }, } // Run twice — should not error or duplicate runSeed(cfg) runSeed(cfg) var count int storage.Get().QueryRow(`SELECT COUNT(*) FROM stories`).Scan(&count) if count != 2 { t.Errorf("expected 2 stories after double seed, got %d", count) } } func TestRunSeed_SkipsDisabledSources(t *testing.T) { setupTestDB(t) ts := serveFeed(t, 3) defer ts.Close() cfg := &config.Config{ Sources: []config.SourceConfig{ { Name: "Disabled Feed", FeedURL: ts.URL, Tier: 1, PollIntervalMinutes: 20, Enabled: false, }, }, } runSeed(cfg) var count int storage.Get().QueryRow(`SELECT COUNT(*) FROM stories`).Scan(&count) if count != 0 { t.Errorf("expected 0 stories for disabled source, got %d", count) } } func TestRunSeed_BadFeedURL(t *testing.T) { setupTestDB(t) cfg := &config.Config{ Sources: []config.SourceConfig{ { Name: "Bad Feed", FeedURL: "http://127.0.0.1:1/nonexistent", Tier: 1, PollIntervalMinutes: 20, Enabled: true, }, }, } // Should not panic — just log and skip runSeed(cfg) var count int storage.Get().QueryRow(`SELECT COUNT(*) FROM stories`).Scan(&count) if count != 0 { t.Errorf("expected 0 stories for bad feed, got %d", count) } }