package serve import ( "net/http" "net/http/httptest" "os" "path/filepath" "testing" "time" "podkit/internal/feed" ) // A podcast client fetches the feed, then fetches each enclosure as a bare GET // carrying nothing from the first request. If the server wants a token and the // enclosure URLs do not have one, the feed loads, lists every episode, and not // one of them will ever download — a failure that looks like success right up // until playback. // // Found live: a tokened server produced enclosure URLs without the token, and // every episode 404'd. func TestTokenedFeedEnclosuresAreFetchable(t *testing.T) { const token = "s3cret" dir := t.TempDir() for _, name := range []string{"01 - April.mp3", "10 - Probability & Uncertainty.mp3"} { if err := os.WriteFile(filepath.Join(dir, name), []byte("audio"), 0o644); err != nil { t.Fatal(err) } } srv := httptest.NewServer(Handler(Options{Dir: dir, Token: token})) defer srv.Close() // Build the feed exactly as the server would, pointed at itself. rss, err := feed.Build(feed.Options{ Dir: dir, BaseURL: srv.URL + "/audio/", Title: "Test", Token: token, Epoch: time.Date(2000, time.January, 1, 12, 0, 0, 0, time.UTC), }, nil) if err != nil { t.Fatal(err) } if len(rss.Channel.Items) != 2 { t.Fatalf("got %d items", len(rss.Channel.Items)) } for _, item := range rss.Channel.Items { resp, err := http.Get(item.Enclosure.URL) if err != nil { t.Fatalf("GET %s: %v", item.Enclosure.URL, err) } resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("enclosure %s returned %d — a podcast client could not download this episode", item.Enclosure.URL, resp.StatusCode) } } } // The converse: without a token configured anywhere, enclosures must still // resolve, and must not carry a stray empty k= parameter. func TestUntokenedFeedEnclosuresAreFetchable(t *testing.T) { dir := t.TempDir() if err := os.WriteFile(filepath.Join(dir, "01 - April.mp3"), []byte("audio"), 0o644); err != nil { t.Fatal(err) } srv := httptest.NewServer(Handler(Options{Dir: dir})) defer srv.Close() rss, err := feed.Build(feed.Options{ Dir: dir, BaseURL: srv.URL + "/audio/", Title: "Test", }, nil) if err != nil { t.Fatal(err) } url := rss.Channel.Items[0].Enclosure.URL if got := rss.Channel.Items[0].Enclosure.URL; len(got) > 0 && containsQ(got, "k=") { t.Errorf("enclosure %s carries a token parameter when none was set", got) } resp, err := http.Get(url) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("enclosure %s returned %d", url, resp.StatusCode) } } func containsQ(h, n string) bool { for i := 0; i+len(n) <= len(h); i++ { if h[i:i+len(n)] == n { return true } } return false }