Antonio Mika
·
2026-10-11
1package prose
2
3import (
4 "log/slog"
5 "net/http"
6 "net/http/httptest"
7 "strings"
8 "testing"
9
10 "github.com/picosh/pico/pkg/db"
11 "github.com/picosh/pico/pkg/db/stub"
12 "github.com/picosh/pico/pkg/postfs"
13 "github.com/picosh/pico/pkg/shared/router"
14 "github.com/picosh/pico/pkg/storage"
15)
16
17func TestRoutes(t *testing.T) {
18 t.Chdir("../../..")
19 t.Setenv("PICO_PIPE_ENABLED", "false")
20 logger := slog.New(slog.DiscardHandler)
21 user := &db.User{ID: "user-1", Name: "tester"}
22 posts := stub.NewPostsDB(logger, user)
23 cfg := NewConfig("prose-test")
24 cfg.Logger = logger
25 for _, name := range []string{"hello", "raw"} {
26 post := postfs.NewPost(name+".md", []byte("# "+name))
27 post.UserID, post.Space, post.Title = user.ID, cfg.Space, name
28 if _, err := posts.InsertPost(post); err != nil {
29 t.Fatal(err)
30 }
31 }
32 st, err := storage.NewStorageMemory(map[string]map[string]string{})
33 if err != nil {
34 t.Fatal(err)
35 }
36 web := &webRouter{cfg: cfg, db: posts, storage: st}
37 handler := router.NewHostRouter(cfg.Domain, cfg.Space, web.mainMux(), web.sitesMux())
38
39 site := "https://tester." + cfg.Domain
40 for _, tt := range []struct {
41 url string
42 status int
43 // body is what the response holds, "" for anything.
44 body string
45 // exact means the response is body and nothing else.
46 exact bool
47 }{
48 {url: site + "/hello", status: http.StatusOK, body: "<html"},
49 {url: site + "/hello.html", status: http.StatusOK, body: "<html"},
50 {url: site + "/hello.md", status: http.StatusOK, body: "# hello"},
51 {url: site + "/hello.lxt", status: http.StatusOK, body: "# hello"},
52 {url: site + "/raw/hello", status: http.StatusOK, body: "# hello"},
53 {url: site + "/raw", status: http.StatusOK, body: "<html"},
54 {url: site + "/raw.md", status: http.StatusOK, body: "# raw"},
55 {url: site + "/raw/raw", status: http.StatusOK, body: "# raw"},
56 {url: site + "/missing", status: http.StatusNotFound},
57 // Images are served from the user's bucket, which they don't have.
58 {url: site + "/cat.png", status: http.StatusUnprocessableEntity, body: "bucket does not exist"},
59 {url: site + "/cat.png/s:500", status: http.StatusUnprocessableEntity, body: "bucket does not exist"},
60 // A site serves its own robots.txt, which this user hasn't uploaded.
61 {url: site + "/robots.txt", status: http.StatusOK, exact: true},
62 {url: "https://" + cfg.Domain + "/robots.txt", status: http.StatusOK, body: "User-agent"},
63 {url: site + "/read.css", status: http.StatusOK},
64 {url: "https://" + cfg.Domain + "/read.css", status: http.StatusOK},
65 {url: "https://" + cfg.Domain + "/hello", status: http.StatusNotFound},
66 } {
67 res := httptest.NewRecorder()
68 handler.ServeHTTP(res, httptest.NewRequest(http.MethodGet, tt.url, nil))
69 got := res.Body.String()
70 if res.Code != tt.status || !strings.Contains(got, tt.body) || (tt.exact && got != tt.body) {
71 t.Errorf("%s = %d %.80q, want %d %q", tt.url, res.Code, got, tt.status, tt.body)
72 }
73 }
74}