summaryrefslogtreecommitdiff
path: root/internal/serve/roundtrip_test.go
blob: c18de79cb0a50836d1bee1292ac2851eed5f6dd0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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
}