fix: preserve committed room results

This commit is contained in:
Simo committed 2026-09-05 10:49:22 +02:00
1 parent 184052fbce
commit d0190bc11f
2 files changed
+95 -18

No files matched your search

+65
View File
@@ -236,6 +236,7 @@ beforeEach(() => {
testState.auditEntries = [];
vi.clearAllMocks();
testState.rconSend.mockResolvedValue(true);
testState.revalidate.mockImplementation(() => undefined);
});
describe("legacy room actions through the Hotel mutation service", () => {
@@ -449,6 +450,70 @@ describe("legacy room actions through the Hotel mutation service", () => {
expect(revalidatePath).toHaveBeenCalledWith("/admin/rooms");
});
it.each([
[
"item update",
() => {
testState.selectRows = [[itemRow()]];
return updateRoomItem({ roomId: 7, itemId: 11, x: 4 });
},
"/admin/rooms/7/furni",
],
[
"item delete",
() => {
testState.selectRows = [[itemRow()]];
return deleteRoomItem({ roomId: 7, itemId: 11 });
},
"/admin/rooms/7/furni",
],
[
"bulk item delete",
() => {
testState.selectRows = [[itemRow()]];
return bulkDeleteRoomItems({ roomId: 7, itemIds: [11] });
},
"/admin/rooms/7/furni",
],
[
"room update",
() => {
testState.selectRows = [[roomRow()]];
return updateRoom({ id: 7, name: "After" });
},
"/admin/rooms/7",
],
[
"room delete",
() => {
testState.selectRows = [[roomRow()]];
return deleteRoom({ id: 7 });
},
"/admin/rooms",
],
])(
"reports committed %s with refresh-needed context when revalidation fails",
async (_name, invoke, path) => {
testState.revalidate.mockImplementation(() => {
throw new Error("cache unavailable");
});
const result = await invoke();
expect(result).toMatchObject({
ok: true,
data: {
committed: true,
refreshNeeded: true,
warning: expect.stringMatching(/saved.*refresh/i),
},
});
expect(testState.committedWrites).toHaveLength(1);
expect(testState.auditEntries).toHaveLength(1);
expect(revalidatePath).toHaveBeenCalledWith(path);
},
);
it("rejects invalid runtime actions before RCON delivery", async () => {
const result = await roomRconAction({ roomId: 7, action: "explode" });
expectFailure(result, /invalid/i);
+30 -18
View File
@@ -58,17 +58,32 @@ function notifyBestEffort(payload: Parameters<typeof notify>[0]): void {
}
}
function revalidateCommittedPath(path: string): ActionResult {
try {
revalidatePath(path);
return actionOk();
} catch {
return actionOk({
committed: true,
refreshNeeded: true,
warning:
"Changes were saved, but the page could not refresh automatically. Refresh the page before trying the action again.",
});
}
}
export async function updateRoomItem(
payload: Record<string, unknown>,
): Promise<ActionResult> {
const staff = await requirePermission(PERMS.ROOMS_EDIT);
try {
await executeLegacyHotelMutation(staff, "room-item.update", payload);
revalidatePath(`/admin/rooms/${String(payload.roomId)}/furni`);
return actionOk();
} catch (error) {
return actionFailure(error);
}
return revalidateCommittedPath(
`/admin/rooms/${String(payload.roomId)}/furni`,
);
}
export async function bulkDeleteRoomItems(payload: {
@@ -78,11 +93,10 @@ export async function bulkDeleteRoomItems(payload: {
const staff = await requirePermission(PERMS.ROOMS_EDIT);
try {
await executeLegacyHotelMutation(staff, "room-item.bulk-delete", payload);
revalidatePath(`/admin/rooms/${payload.roomId}/furni`);
return actionOk();
} catch (error) {
return actionFailure(error);
}
return revalidateCommittedPath(`/admin/rooms/${payload.roomId}/furni`);
}
export async function deleteRoomItem(payload: {
@@ -92,11 +106,10 @@ export async function deleteRoomItem(payload: {
const staff = await requirePermission(PERMS.ROOMS_EDIT);
try {
await executeLegacyHotelMutation(staff, "room-item.delete", payload);
revalidatePath(`/admin/rooms/${payload.roomId}/furni`);
return actionOk();
} catch (error) {
return actionFailure(error);
}
return revalidateCommittedPath(`/admin/rooms/${payload.roomId}/furni`);
}
export async function roomRconAction(payload: {
@@ -124,25 +137,25 @@ export async function deleteRoom(payload: {
id: number;
}): Promise<ActionResult> {
const staff = await requirePermission(PERMS.ROOMS_DELETE);
let target = `#${payload.id}`;
try {
const snapshot = await executeLegacyHotelMutation(
staff,
"room.delete",
payload,
);
notifyBestEffort({
action: "room_delete",
actor: staff.username,
target:
typeof snapshot.before?.name === "string"
? snapshot.before.name
: `#${payload.id}`,
});
revalidatePath("/admin/rooms");
return actionOk();
if (typeof snapshot.before?.name === "string") {
target = snapshot.before.name;
}
} catch (error) {
return actionFailure(error);
}
notifyBestEffort({
action: "room_delete",
actor: staff.username,
target,
});
return revalidateCommittedPath("/admin/rooms");
}
export async function updateRoom(payload: {
@@ -155,9 +168,8 @@ export async function updateRoom(payload: {
const staff = await requirePermission(PERMS.ROOMS_EDIT);
try {
await executeLegacyHotelMutation(staff, "room.update", payload);
revalidatePath(`/admin/rooms/${payload.id}`);
return actionOk();
} catch (error) {
return actionFailure(error);
}
return revalidateCommittedPath(`/admin/rooms/${payload.id}`);
}