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
103
104
105
106
107
108
109
110
111
112
113
114
115 |
package server
import (
"encoding/json"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"strings"
"testing"
"git.pablomurad.com/pablo/patchwork/internal/config"
"git.pablomurad.com/pablo/patchwork/internal/render"
)
func testTree() *render.Tree {
cfg := config.Default()
m := &render.Model{Cfg: cfg, Versions: map[string][]*render.Version{}}
m.Pages = []render.PageDetail{{PageSummary: render.PageSummary{Slug: "notes/deep", Title: "Deep"}}}
m.Versions["notes/deep"] = []*render.Version{{Number: 1, LatestNumber: 1, Baseline: true, Title: "Deep", BodyMarkdown: "Hello."}}
t, err := m.Tree()
if err != nil {
panic(err)
}
return t
}
func newServer() *Server {
s := New(slog.New(slog.NewTextHandler(io.Discard, nil)))
s.Swap(testTree())
return s
}
func get(s *Server, path string, hdr ...string) *httptest.ResponseRecorder {
req := httptest.NewRequest(http.MethodGet, path, nil)
for i := 0; i+1 < len(hdr); i += 2 {
req.Header.Set(hdr[i], hdr[i+1])
}
rec := httptest.NewRecorder()
s.ServeHTTP(rec, req)
return rec
}
func TestRoutesAndTypes(t *testing.T) {
s := newServer()
cases := map[string]string{
"/api/v1/pages": "application/json",
"/api/v1/pages.json": "application/json",
"/api/v1/revisions": "application/json",
"/api/v1/pages/notes/deep": "application/json",
"/api/v1/pages/notes/deep.json": "application/json",
"/api/v1/pages/notes/deep/versions/1": "application/json",
"/feeds/revisions.atom": "application/atom+xml",
"/feeds/revisions.json": "application/feed+json",
"/feeds/pages/notes/deep.atom": "application/atom+xml",
"/text/revisions": "text/plain",
"/text/pages/notes/deep/history": "text/plain",
"/text/pages/notes/deep/versions/1": "text/plain",
"/healthz": "text/plain",
}
for path, ctype := range cases {
rec := get(s, path)
if rec.Code != http.StatusOK || !strings.HasPrefix(rec.Header().Get("Content-Type"), ctype) {
t.Errorf("%s: %d %s", path, rec.Code, rec.Header().Get("Content-Type"))
}
}
}
func TestNotFoundShape(t *testing.T) {
s := newServer()
for _, path := range []string{"/nope", "/api/v1/pages/missing", "/api/v1/pages/notes/deep/versions/9", "/text/pages/notes/deep/versions/x"} {
rec := get(s, path)
var e render.Error
if rec.Code != http.StatusNotFound || json.Unmarshal(rec.Body.Bytes(), &e) != nil || e.Error.Code != "not_found" {
t.Errorf("%s: %d %s", path, rec.Code, rec.Body)
}
}
req := httptest.NewRequest(http.MethodPost, "/api/v1/pages", nil)
rec := httptest.NewRecorder()
s.ServeHTTP(rec, req)
if rec.Code != http.StatusMethodNotAllowed {
t.Errorf("POST: %d", rec.Code)
}
}
func TestETag(t *testing.T) {
s := newServer()
first := get(s, "/feeds/revisions.atom")
etag := first.Header().Get("ETag")
if etag == "" {
t.Fatal("no ETag")
}
if rec := get(s, "/feeds/revisions.atom", "If-None-Match", etag); rec.Code != http.StatusNotModified || rec.Body.Len() != 0 {
t.Fatalf("conditional GET: %d", rec.Code)
}
if rec := get(s, "/feeds/revisions.atom", "If-None-Match", `"other", W/`+etag); rec.Code != http.StatusNotModified {
t.Fatalf("weak/list match: %d", rec.Code)
}
}
func TestNotReady(t *testing.T) {
s := New(slog.New(slog.NewTextHandler(io.Discard, nil)))
if rec := get(s, "/healthz"); rec.Code != http.StatusServiceUnavailable {
t.Fatalf("healthz before first build: %d", rec.Code)
}
}
func TestTextOptions(t *testing.T) {
s := newServer()
colored := get(s, "/text/revisions").Body.String()
plain := get(s, "/text/revisions?plain&w=60").Body.String()
if !strings.Contains(colored, "\x1b[") || strings.Contains(plain, "\x1b[") {
t.Fatal("?plain should drop ANSI")
}
}
|