internal/httpd/routes_test.go
70 lines · 2257 bytes
1package httpd
2
3import (
4 "strings"
5 "testing"
6
7 "github.com/krazywarez/forge/internal/config"
8 "github.com/krazywarez/forge/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 POSTs allowed are the git transport endpoints: a pure
24 // read (upload-pack) and a static refusal (receive-pack).
25 if r.Method != "GET" && !strings.Contains(r.Pattern, "git-upload-pack") && !strings.Contains(r.Pattern, "git-receive-pack") {
26 t.Errorf("view_only route table contains non-GET route %s %s", r.Method, r.Pattern)
27 }
28 for _, word := range []string{"login", "logout", "register", "edit", "new", "settings"} {
29 if strings.Contains(r.Pattern, "/"+word) {
30 t.Errorf("view_only route table contains account-mode pattern %s %s", r.Method, r.Pattern)
31 }
32 }
33 }
34}
35
36// TestAccountsModeHasLoginRoute is the positive counterpart: switching the
37// mode on registers the session routes.
38func TestAccountsModeHasLoginRoute(t *testing.T) {
39 cfg := config.Default()
40 cfg.Web.Mode = "accounts"
41 s := New(cfg, nil)
42 found := false
43 for _, r := range s.Routes() {
44 if r.Pattern == "/login" {
45 found = true
46 }
47 }
48 if !found {
49 t.Fatal("accounts mode is missing the /login route")
50 }
51}
52
53// TestTopLevelRouteWordsAreReserved keeps the route table and the reserved
54// username list in agreement: every literal first path segment must be an
55// unclaimable username.
56func TestTopLevelRouteWordsAreReserved(t *testing.T) {
57 cfg := config.Default()
58 cfg.Web.Mode = "accounts" // superset of routes
59 s := New(cfg, nil)
60 for _, r := range s.Routes() {
61 seg := strings.TrimPrefix(r.Pattern, "/")
62 seg, _, _ = strings.Cut(seg, "/")
63 if seg == "" || strings.HasPrefix(seg, "{") {
64 continue // wildcard or root
65 }
66 if !policy.Reserved(seg) {
67 t.Errorf("top-level route word %q is not in the reserved username list", seg)
68 }
69 }
70}