aboutsummaryrefslogtreecommitdiff
path: root/internal/admin/bulk_edit_regression_test.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-15 19:55:21 -0400
committerhistoria <[not public]>2026-09-15 19:55:21 -0400
commit6f3c43aca62a7d33a19f14f6ad7c16891952673a (patch)
tree0b0665ef624ef442df87ec7131129aaecc3420bd /internal/admin/bulk_edit_regression_test.go
parentcd95de60364aba8f5bdf8ec86f7234a9903eacc8 (diff)
downloadthehouseoficarus-6f3c43aca62a7d33a19f14f6ad7c16891952673a.tar.gz
fix: outstanding-stake ledger, owner backpressure, editor state preservation
Diffstat (limited to 'internal/admin/bulk_edit_regression_test.go')
-rw-r--r--internal/admin/bulk_edit_regression_test.go125
1 files changed, 125 insertions, 0 deletions
diff --git a/internal/admin/bulk_edit_regression_test.go b/internal/admin/bulk_edit_regression_test.go
new file mode 100644
index 0000000..4828c93
--- /dev/null
+++ b/internal/admin/bulk_edit_regression_test.go
@@ -0,0 +1,125 @@
+package admin
+
+// Bulk-edit mutation-plan regressions: a same-directory "move" must never
+// delete the room, a failure anywhere must leave every room untouched (no
+// partial changes without an undo record), and a successful plan applies as
+// one unit with a single undo record.
+
+import (
+ "encoding/json"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func postBulkEdit(t *testing.T, s *AdminServer, body string) (map[string]any, int) {
+ t.Helper()
+ req := httptest.NewRequest(http.MethodPost, "/api/rooms/bulk-edit", strings.NewReader(body))
+ rec := httptest.NewRecorder()
+ s.handleBulkEdit(rec, req)
+ var resp map[string]any
+ _ = json.Unmarshal(rec.Body.Bytes(), &resp)
+ return resp, rec.Code
+}
+
+// TestBulkEditSameDirectoryMovePreservesRoom: moving a room to the
+// directory it already lives in is an in-place edit, not a delete. The old
+// implementation removed the file and reported success.
+func TestBulkEditSameDirectoryMovePreservesRoom(t *testing.T) {
+ s := newTestAdminServer(t, map[int]string{1: "id: 1\nname: Room\n"})
+ oldPath, _ := s.world.GetRoomPath(1)
+ dir := filepath.Join(s.dataDir, "rooms", "area")
+ if err := os.MkdirAll(dir, 0o755); err != nil {
+ t.Fatal(err)
+ }
+ dest := filepath.Join(dir, "1.yaml")
+ if err := os.Rename(oldPath, dest); err != nil {
+ t.Fatal(err)
+ }
+ s.world.RebuildRoomIndex(s.dataDir)
+
+ resp, code := postBulkEdit(t, s, `{"ids":[1],"patch":{"dir":"area"}}`)
+ if code != http.StatusOK {
+ t.Fatalf("bulk edit failed: %d %v", code, resp)
+ }
+ if _, err := os.Stat(dest); err != nil {
+ t.Fatalf("same-directory move deleted the room: %v", err)
+ }
+ if _, err := os.Stat(oldPath); !os.IsNotExist(err) {
+ t.Fatal("old path should not reappear for an in-place edit")
+ }
+}
+
+// TestBulkEditFailureLeavesNoPartialChanges: a parse failure on any selected
+// room aborts the whole plan before any file changes, so no room is mutated
+// without an undo record.
+func TestBulkEditFailureLeavesNoPartialChanges(t *testing.T) {
+ s := newTestAdminServer(t, map[int]string{
+ 1: "id: 1\nname: Room\n",
+ 2: "invalid: [\n",
+ })
+ path, _ := s.world.GetRoomPath(1)
+ before, err := os.ReadFile(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ resp, code := postBulkEdit(t, s, `{"ids":[1,2],"patch":{"color":"red"}}`)
+ if code == http.StatusOK && resp["error"] == nil {
+ t.Fatalf("bulk edit unexpectedly succeeded: %v", resp)
+ }
+ if resp["error"] == nil {
+ t.Fatalf("expected an error response, got: %v", resp)
+ }
+ after, err := os.ReadFile(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if string(after) != string(before) {
+ t.Fatalf("failed bulk edit mutated room 1: before=%q after=%q", before, after)
+ }
+ if s.undoStack.Info().StackSize != 0 {
+ t.Fatal("failed bulk edit pushed an undo record")
+ }
+}
+
+// TestBulkEditSuccessSingleUndoRecord: a valid plan applies every patch and
+// pushes exactly one undo record whose undo restores every file.
+func TestBulkEditSuccessSingleUndoRecord(t *testing.T) {
+ s := newTestAdminServer(t, map[int]string{
+ 1: "id: 1\nname: Room\n",
+ 2: "id: 2\nname: Other\n",
+ })
+
+ resp, code := postBulkEdit(t, s, `{"ids":[1,2],"patch":{"color":"red"}}`)
+ if code != http.StatusOK {
+ t.Fatalf("bulk edit failed: %d %v", code, resp)
+ }
+ for _, id := range []int{1, 2} {
+ path, _ := s.world.GetRoomPath(id)
+ data, err := os.ReadFile(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !strings.Contains(string(data), "color: red") {
+ t.Fatalf("room %d not patched: %q", id, data)
+ }
+ }
+ if info := s.undoStack.Info(); info.StackSize != 1 {
+ t.Fatalf("undo stack size = %d, want exactly 1 plan", info.StackSize)
+ }
+ if s.undoStack.Undo() == nil {
+ t.Fatal("undo failed")
+ }
+ path, _ := s.world.GetRoomPath(1)
+ data, err := os.ReadFile(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if strings.Contains(string(data), "color: red") {
+ t.Fatalf("undo did not restore room 1: %q", data)
+ }
+}