summaryrefslogtreecommitdiff
path: root/internal/serve/roundtrip_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/serve/roundtrip_test.go')
-rw-r--r--internal/serve/roundtrip_test.go102
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
+}