aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/api_rooms_create_test.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-16 21:05:39 -0400
committerhistoria <[not public]>2026-09-16 21:05:39 -0400
commit35ddd0ad2215760bb1d3dc7724480f179ea33fa6 (patch)
tree3e9acf5e8a690544ecee1ec03af888ee28dd05b8 /internal/admin/api_rooms_create_test.go
parent6ef40a5739f1723ec26cc32dfc48fe764f776dca (diff)
downloadthehouseoficarus-35ddd0ad2215760bb1d3dc7724480f179ea33fa6.tar.gz
fix: login account race, casino chip double-refund, room-create rollback, rename collision, fatal transport errors
Diffstat (limited to 'internal/admin/api_rooms_create_test.go')
-rw-r--r--internal/admin/api_rooms_create_test.go78
1 files changed, 78 insertions, 0 deletions
diff --git a/internal/admin/api_rooms_create_test.go b/internal/admin/api_rooms_create_test.go
new file mode 100644
index 0000000..38d6fae
--- /dev/null
+++ b/internal/admin/api_rooms_create_test.go
@@ -0,0 +1,78 @@
+package admin
+
+// Regression: a failed room create (POST /api/rooms with link_from) must
+// not leave the source room with a dangling exit pointing at a room that
+// was never written, and must not push an undo entry for work that never
+// committed. The destination is committed before the source exit so the
+// failure happens before any source-side mutation.
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+
+ "thehouseoficarus/internal/config"
+ "thehouseoficarus/internal/world"
+)
+
+func TestFailedRoomCreateRollsBackSourceExit(t *testing.T) {
+ s := newTestAdminServer(t, map[int]string{1: "id: 1\nname: Origin\n"})
+ s.cfg = config.Default()
+ s.cfg.StartingRoom = 1
+ // A directory at the destination path makes the destination file write
+ // fail deterministically while the source room is still readable.
+ if err := os.Mkdir(filepath.Join(s.dataDir, "rooms", "2.yaml"), 0o755); err != nil {
+ t.Fatal(err)
+ }
+
+ rec := httptest.NewRecorder()
+ s.handleRooms(rec, httptest.NewRequest(http.MethodPost, "/api/rooms",
+ strings.NewReader(`{"name":"Next","link_from":1,"link_dir":"north"}`)))
+ if !strings.Contains(rec.Body.String(), "error") {
+ t.Fatalf("expected the create to fail; got %s", rec.Body.String())
+ }
+
+ src, err := s.world.LoadRoom(1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if exit, ok := src.Exits[world.North]; ok {
+ t.Fatalf("failed create left a dangling exit to nonexistent room %d", exit.Room)
+ }
+ if info := s.undoStack.Info(); info.StackSize != 0 {
+ t.Fatalf("failed create pushed an undo entry: %+v", info)
+ }
+}
+
+// The reciprocal exit still lands in the written destination file when the
+// create succeeds.
+func TestSuccessfulRoomCreateWritesReciprocalExit(t *testing.T) {
+ s := newTestAdminServer(t, map[int]string{1: "id: 1\nname: Origin\n"})
+ s.cfg = config.Default()
+ s.cfg.StartingRoom = 1
+
+ rec := httptest.NewRecorder()
+ s.handleRooms(rec, httptest.NewRequest(http.MethodPost, "/api/rooms",
+ strings.NewReader(`{"name":"Next","link_from":1,"link_dir":"north"}`)))
+ if strings.Contains(rec.Body.String(), "error") {
+ t.Fatalf("create failed: %s", rec.Body.String())
+ }
+
+ dest, err := s.world.LoadRoom(2)
+ if err != nil {
+ t.Fatalf("destination room not loadable: %v", err)
+ }
+ if dest.Exits[world.South].Room != 1 {
+ t.Fatalf("destination room missing its reciprocal exit south: %+v", dest.Exits)
+ }
+ src, err := s.world.LoadRoom(1)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if src.Exits[world.North].Room != 2 {
+ t.Fatalf("source room missing its north exit: %+v", src.Exits)
+ }
+}