diff options
| author | historia <[not public]> | 2026-09-15 19:55:21 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-15 19:55:21 -0400 |
| commit | 6f3c43aca62a7d33a19f14f6ad7c16891952673a (patch) | |
| tree | 0b0665ef624ef442df87ec7131129aaecc3420bd /internal/admin/bulk_edit_regression_test.go | |
| parent | cd95de60364aba8f5bdf8ec86f7234a9903eacc8 (diff) | |
| download | thehouseoficarus-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.go | 125 |
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) + } +} |
