diff options
Diffstat (limited to 'internal/serve/roundtrip_test.go')
| -rw-r--r-- | internal/serve/roundtrip_test.go | 102 |
1 files changed, 102 insertions, 0 deletions
diff --git a/internal/serve/roundtrip_test.go b/internal/serve/roundtrip_test.go new file mode 100644 index 0000000..c18de79 --- /dev/null +++ b/internal/serve/roundtrip_test.go @@ -0,0 +1,102 @@ +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 +} |
