package main import ( "encoding/xml" "io" "net/http" "net/http/httptest" "os" "path/filepath" "strings" "testing" ) func newTestServer(t *testing.T, files map[string]string) (*httptest.Server, string) { t.Helper() dir := t.TempDir() for name, src := range files { writeFile(t, dir, name, src) } a, err := newApp(dir, "https://posts.example.com/") if err != nil { t.Fatal(err) } srv := httptest.NewServer(a.routes()) t.Cleanup(srv.Close) return srv, dir } func writeFile(t *testing.T, dir, name, src string) { t.Helper() if err := os.WriteFile(filepath.Join(dir, name), []byte(src), 0o644); err != nil { t.Fatal(err) } } func get(t *testing.T, srv *httptest.Server, path string) (int, http.Header, string) { t.Helper() resp, err := http.Get(srv.URL + path) if err != nil { t.Fatal(err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { t.Fatal(err) } return resp.StatusCode, resp.Header, string(body) } var sample = map[string]string{ "2026-01-01-oldest.md": "# Oldest\n", "2026-02-01-middle.md": "---\ntitle: Middle\nsummary: The middle one.\n---\nMiddle body\n", "2026-03-01-newest.md": "# Newest\n", "2026-04-01-draft.md": "---\ndraft: true\n---\n# Secret draft\n", "Bad Name.md": "# Broken\n", "notes.txt": "not a post", } func TestIndex(t *testing.T) { srv, _ := newTestServer(t, sample) status, h, body := get(t, srv, "/") if status != 200 { t.Fatalf("status = %d", status) } if h.Get("Content-Security-Policy") == "" || h.Get("X-Content-Type-Options") != "nosniff" { t.Errorf("missing security headers: %v", h) } n, m, o := strings.Index(body, "Newest"), strings.Index(body, "Middle"), strings.Index(body, "Oldest") if n < 0 || m < 0 || o < 0 || !(n < m && m < o) { t.Errorf("posts missing or out of order:\n%s", body) } if !strings.Contains(body, "The middle one.") { t.Error("summary not shown") } for _, hidden := range []string{"Secret draft", "Broken"} { if strings.Contains(body, hidden) { t.Errorf("%q should not be listed", hidden) } } } func TestEmptyIndex(t *testing.T) { srv, _ := newTestServer(t, nil) if status, _, body := get(t, srv, "/"); status != 200 || !strings.Contains(body, "No posts yet") { t.Errorf("status = %d, body:\n%s", status, body) } } func TestPostPage(t *testing.T) { srv, _ := newTestServer(t, sample) status, _, body := get(t, srv, "/posts/middle") if status != 200 { t.Fatalf("status = %d", status) } for _, want := range []string{ "
Middle body
", ``, ``, } { if !strings.Contains(body, want) { t.Errorf("missing %q in:\n%s", want, body) } } _, _, newest := get(t, srv, "/posts/newest") if strings.Contains(newest, `class="newer"`) { t.Error("newest post should have no newer link") } _, _, oldest := get(t, srv, "/posts/oldest") if strings.Contains(oldest, `class="older"`) { t.Error("oldest post should have no older link") } } func TestNotFound(t *testing.T) { srv, _ := newTestServer(t, sample) for _, path := range []string{ "/posts/nope", "/posts/draft", "/posts/2026-01-01-oldest", "/posts/..%2f..%2fetc%2fpasswd", "/posts/", "/static/", "/static/missing.css", "/whatever", } { status, _, body := get(t, srv, path) if status != 404 { t.Errorf("%s: status = %d, want 404", path, status) } if path != "/static/missing.css" && !strings.Contains(body, "