diff options
| author | historia <[not public]> | 2026-09-15 20:44:31 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-15 20:44:31 -0400 |
| commit | 61881fd0c1a7066293f552be1a747143f70d03ff (patch) | |
| tree | 74256e16848fd3dbf66ebd8596395be5fa0f8136 /internal/admin/api_rooms_bulk_delete_test.go | |
| parent | fcb806c3445ce56f2e57e208de773b4dac957c8d (diff) | |
| download | thehouseoficarus-61881fd0c1a7066293f552be1a747143f70d03ff.tar.gz | |
fix: registration overwrite, recovery chip/payout edge cases, bulk-delete rollback
Diffstat (limited to 'internal/admin/api_rooms_bulk_delete_test.go')
| -rw-r--r-- | internal/admin/api_rooms_bulk_delete_test.go | 70 |
1 files changed, 70 insertions, 0 deletions
diff --git a/internal/admin/api_rooms_bulk_delete_test.go b/internal/admin/api_rooms_bulk_delete_test.go new file mode 100644 index 0000000..083b890 --- /dev/null +++ b/internal/admin/api_rooms_bulk_delete_test.go @@ -0,0 +1,70 @@ +package admin + +// Bulk-delete regression tests: duplicate IDs in one request are rejected +// before any file is touched, and a mid-plan delete failure rolls back +// every room it already removed (no rooms stranded without an undo record). + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "strings" + "testing" +) + +func postBulkDelete(t *testing.T, s *AdminServer, body string) (map[string]any, int) { + t.Helper() + req := httptest.NewRequest(http.MethodPost, "/api/rooms/bulk-delete", strings.NewReader(body)) + rec := httptest.NewRecorder() + s.handleBulkDelete(rec, req) + var resp map[string]any + _ = json.Unmarshal(rec.Body.Bytes(), &resp) + return resp, rec.Code +} + +func TestBulkDeleteRejectsDuplicateIDs(t *testing.T) { + s := newTestAdminServer(t, map[int]string{1: "id: 1\nname: Room\n"}) + path, _ := s.world.GetRoomPath(1) + resp, _ := postBulkDelete(t, s, `{"ids":[1,1]}`) + if errMsg, _ := resp["error"].(string); !strings.Contains(errMsg, "duplicate room id 1") { + t.Fatalf("duplicate IDs not rejected: %v", resp) + } + if _, err := os.Stat(path); err != nil { + t.Fatalf("rejected bulk-delete removed the room: %v", err) + } +} + +// TestBulkDeleteFailureRollsBackDeletions: a delete failure mid-plan +// restores the rooms already removed, so a failed plan leaves the world +// untouched. The failure is staged at the DELETE phase (not the earlier +// snapshot phase) by making room 3's parent directory unwritable after its +// file snapshot was taken. +func TestBulkDeleteFailureRollsBackDeletions(t *testing.T) { + s := newTestAdminServer(t, map[int]string{1: "id: 1\nname: One\n"}) + sub := filepath.Join(s.dataDir, "rooms", "sub") + if err := os.MkdirAll(sub, 0o755); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(sub, "3.yaml"), []byte("id: 3\nname: Three\n"), 0o644); err != nil { + t.Fatal(err) + } + s.world.RebuildRoomIndex(s.dataDir) + + // Room 3's snapshot succeeds (the file is readable), but its removal + // fails because the parent directory is not writable. + if err := os.Chmod(sub, 0o555); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = os.Chmod(sub, 0o755); os.RemoveAll(sub) }) + + path1, _ := s.world.GetRoomPath(1) + resp, _ := postBulkDelete(t, s, `{"ids":[1,3]}`) + if errMsg, _ := resp["error"].(string); !strings.Contains(errMsg, "delete room 3") { + t.Fatalf("expected room-3 delete failure: %v", resp) + } + if _, err := os.Stat(path1); err != nil { + t.Fatalf("failed bulk delete stranded room 1 without an undo record: %v", err) + } +} |
