package admin // Regression: admin create/link grid prechecks seeded the layout from the // configured starting room. A detached in-progress area (not yet reachable // from the start room) was therefore never laid out, so a link that introduced // a grid twist was accepted, and auto-detecting a direction rejected two // adjacent detached rooms as "not reachable from the seed room". The prechecks // now seed from the room being edited. import ( "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "thehouseoficarus/internal/config" ) // newDetachedAdminServer builds a server whose configured starting room (999) // is deliberately outside every room under test. func newDetachedAdminServer(t *testing.T, rooms map[int]string) *AdminServer { t.Helper() s := newTestAdminServer(t, rooms) s.cfg = config.Default() s.cfg.StartingRoom = 999 return s } func postLink(t *testing.T, s *AdminServer, body string) (map[string]any, int) { t.Helper() req := httptest.NewRequest(http.MethodPost, "/api/rooms/link", strings.NewReader(body)) req.Header.Set("Content-Type", "application/json") rr := httptest.NewRecorder() s.handleRoomLink(rr, req) var resp map[string]any _ = json.Unmarshal(rr.Body.Bytes(), &resp) return resp, rr.Code } func TestLinkConflictDetectedInDetachedArea(t *testing.T) { s := newDetachedAdminServer(t, map[int]string{ 10: "id: 10\nname: A\nexits:\n north:\n room: 11\n", 11: "id: 11\nname: B\nexits:\n south:\n room: 10\n east:\n room: 12\n", 12: "id: 12\nname: C\n", // Starting room outside the component under edit. 999: "id: 999\nname: Start\n", }) // 10-e north->11 and 11-east->12 place 12 at (1,-1); adding 10-east->12 // would also place it at (1,0) — a twist the detached precheck must catch. if resp, code := postLink(t, s, `{"from":10,"to":12,"dir":"east"}`); code != http.StatusConflict { t.Fatalf("detached link that creates a grid twist: status = %d (%v), want %d", code, resp, http.StatusConflict) } } func TestAutoDirectionWorksInDetachedArea(t *testing.T) { s := newDetachedAdminServer(t, map[int]string{ 10: "id: 10\nname: A\nexits:\n east:\n room: 11\n", 11: "id: 11\nname: B\nexits:\n west:\n room: 10\n", 999: "id: 999\nname: Start\n", }) resp, code := postLink(t, s, `{"from":10,"to":11}`) if code != http.StatusOK { t.Fatalf("auto-direction in a detached area: status = %d (%v), want 200", code, resp) } if got := roomExitTarget(s, 10, "east"); got != 11 { t.Fatalf("room 10 east exit = %d, want 11", got) } }