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
|
package admin
import (
"net/http"
"net/http/httptest"
"path/filepath"
"strings"
"testing"
"thehouseoficarus/internal/config"
)
// The production router must not leave file-only mutations or undo/redo on
// the HTTP goroutine. A rejecting dispatcher prevents ALL API handlers from
// touching files, including future routes and read endpoints.
func TestAPIRoutesRequireOwnerAdmission(t *testing.T) {
h, _ := newEditorHarness(t)
tasks := 0
s, err := NewServer(config.Default(), false, h.g.AccountStore, h.g.World, h.g.ItemStore, h.g.ObjectStore, h.g.MobStore, h.dir,
h.g.CourseStore.ReloadAll, h.g.MigrateRoomIDsOnOwner, h.g.EvictCasinoRoom, h.g.RebuildCraftIndex, filepath.Join(h.dir, "config.yaml"),
func(func()) bool { tasks++; return false })
if err != nil {
t.Fatal(err)
}
cookieRec := httptest.NewRecorder()
s.setAuthCookie(cookieRec, "alice")
cookie := cookieRec.Result().Cookies()[0]
routes := []string{
"GET /api/map", "GET /api/dashboard", "GET /api/undo/state",
"POST /api/rooms", "PUT /api/rooms/2", "DELETE /api/rooms/2",
"POST /api/rooms/new-empty", "POST /api/rooms/link", "DELETE /api/rooms/link",
"POST /api/rooms/insert", "POST /api/rooms/remove", "POST /api/rooms/move",
"POST /api/rooms/rename", "POST /api/rooms/swap", "POST /api/rooms/bulk-edit",
"POST /api/rooms/bulk-delete", "POST /api/rooms/resolve-duplicate",
"POST /api/room-dirs/create", "POST /api/room-dirs/rename",
"POST /api/courses", "PUT /api/courses/test", "DELETE /api/courses/test",
"POST /api/triggers", "PUT /api/triggers/test", "DELETE /api/triggers/test",
"PUT /api/files?path=test.yaml", "POST /api/files?path=test.yaml",
"DELETE /api/files?path=test.yaml", "POST /api/files/rename",
"POST /api/colors", "POST /api/colors/reset",
"POST /api/undo/undo", "POST /api/undo/redo",
}
for _, entity := range []string{"objects", "items", "mobs", "drops", "hazards", "techs", "modules"} {
for _, route := range []string{"POST /api/%s", "PUT /api/%s/test", "DELETE /api/%s/test", "POST /api/%s/rename", "POST /api/%s/move", "POST /api/%s/dirs"} {
routes = append(routes, strings.ReplaceAll(route, "%s", entity))
}
}
for _, route := range routes {
t.Run(route, func(t *testing.T) {
method, path, _ := strings.Cut(route, " ")
r := httptest.NewRequest(method, path, strings.NewReader(`{}`))
r.AddCookie(cookie)
rec := httptest.NewRecorder()
before := tasks
s.ServeHTTP(rec, r)
if tasks != before+1 || rec.Code != http.StatusServiceUnavailable {
t.Fatalf("dispatcher calls=%d status=%d", tasks-before, rec.Code)
}
})
}
before := tasks
rec := httptest.NewRecorder()
s.ServeHTTP(rec, httptest.NewRequest("POST", "/api/rooms", nil))
if tasks != before || rec.Code != http.StatusUnauthorized {
t.Fatal("unauthenticated request reached owner")
}
}
|