feat(FN-4739): complete Step 2 — add room message clear in chat store

Fusion-Task-Id: FN-4739
Fusion-Task-Lineage: fc120dfa-d1b2-4065-9595-d0bc6304d2af
This commit is contained in:
Fusion (runfusion.ai)
2026-05-16 09:30:39 -07:00
committed by gsxdsm
parent 477f79f3eb
commit 356f49588a
2 changed files with 64 additions and 0 deletions

View File

@@ -178,6 +178,34 @@ describe("ChatStore — rooms (FN-3805..FN-3811 contract)", () => {
expect(store.getRoomMessages(roomB.id).map((m) => m.content)).toEqual(["b1"]);
expect(store.getMessages(session.id).map((m) => m.content)).toEqual(["direct"]);
});
it("clears all room messages while preserving room and advancing updatedAt", async () => {
const room = store.createRoom({ name: "clear-room" });
store.addRoomMessage(room.id, { role: "user", content: "one" });
store.addRoomMessage(room.id, { role: "assistant", content: "two" });
const before = store.getRoom(room.id)?.updatedAt;
await new Promise((r) => setTimeout(r, 5));
const deletedCount = store.clearRoomMessages(room.id);
expect(deletedCount).toBe(2);
expect(store.getRoomMessages(room.id)).toEqual([]);
expect(store.getRoom(room.id)).toBeDefined();
expect(store.getRoom(room.id)?.updatedAt > (before ?? "")).toBe(true);
});
it("returns 0 when clearing a non-existent room", () => {
expect(store.clearRoomMessages("room-missing")).toBe(0);
});
it("returns 0 and does not emit clear event when clearing an empty room", () => {
const room = store.createRoom({ name: "empty-clear" });
const cleared = vi.fn();
store.on("chat:room:messages:cleared", cleared);
expect(store.clearRoomMessages(room.id)).toBe(0);
expect(cleared).not.toHaveBeenCalled();
});
});
describe("Room events", () => {
@@ -190,6 +218,7 @@ describe("ChatStore — rooms (FN-3805..FN-3811 contract)", () => {
const messageAdded = vi.fn();
const messageUpdated = vi.fn();
const messageDeleted = vi.fn();
const messagesCleared = vi.fn();
store.on("chat:room:created", created);
store.on("chat:room:updated", updated);
@@ -199,6 +228,7 @@ describe("ChatStore — rooms (FN-3805..FN-3811 contract)", () => {
store.on("chat:room:message:added", messageAdded);
store.on("chat:room:message:updated", messageUpdated);
store.on("chat:room:message:deleted", messageDeleted);
store.on("chat:room:messages:cleared", messagesCleared);
const room = store.createRoom({ name: "events", createdBy: "agent-1", memberAgentIds: ["agent-1"] });
const roomUpdate = store.updateRoom(room.id, { description: "updated" });
@@ -212,8 +242,11 @@ describe("ChatStore — rooms (FN-3805..FN-3811 contract)", () => {
size: 1,
createdAt: new Date().toISOString(),
});
store.addRoomMessage(room.id, { role: "user", content: "clear-me" });
store.removeRoomMember(room.id, "agent-2");
store.deleteRoomMessage(message.id);
const clearedCount = store.clearRoomMessages(room.id);
const refreshedRoom = store.getRoom(room.id);
store.deleteRoom(room.id);
expect(created).toHaveBeenCalledWith(room);
@@ -223,6 +256,10 @@ describe("ChatStore — rooms (FN-3805..FN-3811 contract)", () => {
expect(messageUpdated).toHaveBeenCalledWith(msgUpdate);
expect(memberRemoved).toHaveBeenCalledWith({ roomId: room.id, agentId: "agent-2" });
expect(messageDeleted).toHaveBeenCalledWith(message.id);
expect(clearedCount).toBe(1);
expect(messagesCleared).toHaveBeenCalledTimes(1);
expect(messagesCleared).toHaveBeenCalledWith({ roomId: room.id, deletedCount: 1 });
expect(updated).toHaveBeenCalledWith(refreshedRoom);
expect(deleted).toHaveBeenCalledWith(room.id);
});
});

View File

@@ -67,6 +67,8 @@ export interface ChatStoreEvents {
"chat:room:message:updated": [message: ChatRoomMessage];
/** Emitted when a room message is deleted */
"chat:room:message:deleted": [messageId: string];
/** Emitted when all room messages are cleared */
"chat:room:messages:cleared": [payload: { roomId: string; deletedCount: number }];
}
// ── Row Interfaces ───────────────────────────────────────────────────
@@ -1027,6 +1029,31 @@ export class ChatStore extends EventEmitter<ChatStoreEvents> {
return true;
}
clearRoomMessages(roomId: string): number {
const room = this.getRoom(roomId);
if (!room) {
return 0;
}
const deleted = this.db.prepare("DELETE FROM chat_room_messages WHERE roomId = ?").run(roomId);
const deletedCount = deleted.changes;
if (deletedCount <= 0) {
return 0;
}
const now = new Date().toISOString();
this.db.prepare("UPDATE chat_rooms SET updatedAt = ? WHERE id = ?").run(now, roomId);
this.db.bumpLastModified();
this.emit("chat:room:messages:cleared", { roomId, deletedCount });
const updatedRoom = this.getRoom(roomId);
if (updatedRoom) {
this.emit("chat:room:updated", updatedRoom);
}
return deletedCount;
}
addRoomMessageAttachment(roomId: string, messageId: string, attachment: ChatAttachment): ChatRoomMessage {
const message = this.getRoomMessage(messageId);
if (!message || message.roomId !== roomId) {