internal/httpd/routes_test.go
92 lines · 2848 bytes
1package httpd
2
3import (
4 "strings"
5 "testing"
6
7 "gitbay.org/gitbay/internal/config"
8 "gitbay.org/gitbay/internal/policy"
9)
10
11// TestViewOnlyHasNoMutatingRoutes is the structural guarantee from the plan:
12// under web.mode = "view_only" the route table must contain no mutating
13// route — not hidden ones, none at all.
14func TestViewOnlyHasNoMutatingRoutes(t *testing.T) {
15 cfg := config.Default()
16 cfg.Web.Mode = "view_only"
17 s := New(cfg, nil)
18
19 for _, r := range s.Routes() {
20 if r.Mutating {
21 t.Errorf("view_only route table contains mutating route %s %s", r.Method, r.Pattern)
22 }
23 // The only non-GETs allowed are transport endpoints, which never
24 // authenticate by web session: git upload-pack (a pure read), the
25 // receive-pack static refusal, and LFS (SSH-minted tokens).
26 if r.Method != "GET" && !strings.Contains(r.Pattern, "git-upload-pack") &&
27 !strings.Contains(r.Pattern, "git-receive-pack") && !strings.Contains(r.Pattern, "/info/lfs/") {
28 t.Errorf("view_only route table contains non-GET route %s %s", r.Method, r.Pattern)
29 }
30 for _, word := range []string{"login", "logout", "register", "edit", "new", "settings"} {
31 if strings.Contains(r.Pattern, "/"+word) {
32 t.Errorf("view_only route table contains account-mode pattern %s %s", r.Method, r.Pattern)
33 }
34 }
35 }
36}
37
38// TestAPIRouteGating: the API route exists only when [api] enabled = true.
39func TestAPIRouteGating(t *testing.T) {
40 has := func(cfg config.Config) bool {
41 for _, r := range New(cfg, nil).Routes() {
42 if r.Pattern == "/api/v1/cmd" {
43 return true
44 }
45 }
46 return false
47 }
48 if has(config.Default()) {
49 t.Fatal("API route present with api disabled (the default)")
50 }
51 cfg := config.Default()
52 cfg.API.Enabled = true
53 if !has(cfg) {
54 t.Fatal("API route missing with api enabled")
55 }
56}
57
58// TestAccountsModeHasLoginRoute is the positive counterpart: switching the
59// mode on registers the session routes.
60func TestAccountsModeHasLoginRoute(t *testing.T) {
61 cfg := config.Default()
62 cfg.Web.Mode = "accounts"
63 s := New(cfg, nil)
64 found := false
65 for _, r := range s.Routes() {
66 if r.Pattern == "/login" {
67 found = true
68 }
69 }
70 if !found {
71 t.Fatal("accounts mode is missing the /login route")
72 }
73}
74
75// TestTopLevelRouteWordsAreReserved keeps the route table and the reserved
76// username list in agreement: every literal first path segment must be an
77// unclaimable username.
78func TestTopLevelRouteWordsAreReserved(t *testing.T) {
79 cfg := config.Default()
80 cfg.Web.Mode = "accounts" // superset of routes
81 s := New(cfg, nil)
82 for _, r := range s.Routes() {
83 seg := strings.TrimPrefix(r.Pattern, "/")
84 seg, _, _ = strings.Cut(seg, "/")
85 if seg == "" || strings.HasPrefix(seg, "{") {
86 continue // wildcard or root
87 }
88 if !policy.Reserved(seg) {
89 t.Errorf("top-level route word %q is not in the reserved username list", seg)
90 }
91 }
92}