package bot import ( "net/http" "net/http/httptest" "net/url" "strings" "testing" ) func TestFeedLinksFromHTML(t *testing.T) { base, _ := url.Parse("https://news.example/section/home") html := `
` feeds := feedLinksFromHTML(html, base) want := []string{ "https://news.example/feeds/all.xml", "https://news.example/atom", "https://news.example/section/also-relative.rss", // resolved against the page URL } if len(feeds) != len(want) { t.Fatalf("got %v, want %v", feeds, want) } for i := range want { if feeds[i] != want[i] { t.Errorf("feed %d: got %q, want %q", i, feeds[i], want[i]) } } if got := feedLinksFromHTML("no links here
", base); len(got) != 0 { t.Errorf("linkless page: got %v", got) } } func TestDiscoverFeeds(t *testing.T) { t.Setenv("LCHAT_BOT_ALLOW_PRIVATE_FETCH", "1") srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") w.Write([]byte(``)) })) defer srv.Close() feeds, err := discoverFeeds(srv.URL) if err != nil { t.Fatalf("discover: %v", err) } if len(feeds) != 1 || feeds[0] != srv.URL+"/rss.xml" { t.Fatalf("got %v", feeds) } // The tool wrapper formats for a tool_result and surfaces failures as text. if out := runDiscoverFeeds(map[string]any{"site": srv.URL}); !strings.Contains(out, "/rss.xml") { t.Fatalf("tool output: %q", out) } if out := runDiscoverFeeds(map[string]any{"site": ""}); !strings.Contains(out, "discovery failed") { t.Fatalf("empty site should fail as text: %q", out) } } // Without the test escape hatch, discovery refuses non-https and (via the // dialer guard) private addresses — the member named a site, not our LAN. func TestDiscoverFeedsBlocksPrivate(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("secret")) })) defer srv.Close() if _, err := discoverFeeds(srv.URL); err == nil { t.Fatal("loopback http discovery must be refused outside test mode") } } func TestSameSiteHost(t *testing.T) { ok := [][2]string{{"bbc.com", "bbc.com"}, {"www.bbc.com", "bbc.com"}, {"bbc.com", "www.bbc.com"}} for _, p := range ok { if !sameSiteHost(p[0], p[1]) { t.Errorf("%s vs %s should be the same site", p[0], p[1]) } } bad := [][2]string{{"bbc.com", "bbc.co.uk"}, {"www.bbc.com", "www.npr.org"}, {"evil-bbc.com", "bbc.com"}} for _, p := range bad { if sameSiteHost(p[0], p[1]) { t.Errorf("%s vs %s should NOT be the same site", p[0], p[1]) } } } func TestHostLabelAndJoinAnd(t *testing.T) { for in, want := range map[string]string{ "bbc.com": "bbc.com", "https://www.bbc.com/": "bbc.com", "http://npr.org/feeds?x=1": "npr.org", " theguardian.com/uk ": "theguardian.com", } { if got := hostLabel(in); got != want { t.Errorf("hostLabel(%q) = %q, want %q", in, got, want) } } if got := joinAnd([]string{"a"}); got != "a" { t.Errorf("joinAnd one: %q", got) } if got := joinAnd([]string{"a", "b"}); got != "a and b" { t.Errorf("joinAnd two: %q", got) } if got := joinAnd([]string{"a", "b", "c"}); got != "a, b and c" { t.Errorf("joinAnd three: %q", got) } }