feat(FN-2012): merge fusion/fn-2012
This commit is contained in:
5
.changeset/fix-chat-delete-message.md
Normal file
5
.changeset/fix-chat-delete-message.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@gsxdsm/fusion": patch
|
||||
---
|
||||
|
||||
Fix DELETE /api/chat/sessions/:id/messages/:messageId to actually delete the message from the database instead of returning success without any action.
|
||||
@@ -511,6 +511,51 @@ describe("ChatStore", () => {
|
||||
expect(result.has(session.id)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("deleteMessage", () => {
|
||||
it("deletes an existing message and returns true", () => {
|
||||
const session = createTestSession(store);
|
||||
const message = store.addMessage(session.id, { role: "user", content: "Hello" });
|
||||
|
||||
expect(store.getMessage(message.id)).toBeDefined();
|
||||
|
||||
const result = store.deleteMessage(message.id);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(store.getMessage(message.id)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("returns false for non-existent message", () => {
|
||||
const result = store.deleteMessage("msg-nonexistent");
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it("removes message from session's message list", () => {
|
||||
const session = createTestSession(store);
|
||||
store.addMessage(session.id, { role: "user", content: "Hello" });
|
||||
const msg2 = store.addMessage(session.id, { role: "assistant", content: "Hi" });
|
||||
|
||||
expect(store.getMessages(session.id)).toHaveLength(2);
|
||||
|
||||
store.deleteMessage(msg2.id);
|
||||
|
||||
expect(store.getMessages(session.id)).toHaveLength(1);
|
||||
expect(store.getMessages(session.id)[0].content).toBe("Hello");
|
||||
});
|
||||
|
||||
it("does not delete messages from other sessions", () => {
|
||||
const session1 = createTestSession(store);
|
||||
const session2 = createTestSession(store);
|
||||
const msg1 = store.addMessage(session1.id, { role: "user", content: "Session 1" });
|
||||
store.addMessage(session2.id, { role: "user", content: "Session 2" });
|
||||
|
||||
store.deleteMessage(msg1.id);
|
||||
|
||||
expect(store.getMessages(session1.id)).toHaveLength(0);
|
||||
expect(store.getMessages(session2.id)).toHaveLength(1);
|
||||
expect(store.getMessages(session2.id)[0].content).toBe("Session 2");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ── Event Emission Tests ─────────────────────────────────────────
|
||||
@@ -568,6 +613,29 @@ describe("ChatStore", () => {
|
||||
expect(handler).toHaveBeenCalledWith(message);
|
||||
});
|
||||
|
||||
it("deleteMessage emits chat:message:deleted", () => {
|
||||
const handler = vi.fn();
|
||||
store.on("chat:message:deleted", handler);
|
||||
|
||||
const session = createTestSession(store);
|
||||
const message = store.addMessage(session.id, { role: "user", content: "Hello" });
|
||||
handler.mockClear();
|
||||
|
||||
store.deleteMessage(message.id);
|
||||
|
||||
expect(handler).toHaveBeenCalledTimes(1);
|
||||
expect(handler).toHaveBeenCalledWith(message.id);
|
||||
});
|
||||
|
||||
it("deleteMessage does NOT emit for non-existent message", () => {
|
||||
const handler = vi.fn();
|
||||
store.on("chat:message:deleted", handler);
|
||||
|
||||
store.deleteMessage("msg-nonexistent");
|
||||
|
||||
expect(handler).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("archiveSession emits chat:session:updated", () => {
|
||||
const handler = vi.fn();
|
||||
store.on("chat:session:updated", handler);
|
||||
|
||||
@@ -36,6 +36,8 @@ export interface ChatStoreEvents {
|
||||
"chat:session:deleted": [sessionId: string];
|
||||
/** Emitted when a message is added to a session */
|
||||
"chat:message:added": [message: ChatMessage];
|
||||
/** Emitted when a message is deleted from a session */
|
||||
"chat:message:deleted": [messageId: string];
|
||||
}
|
||||
|
||||
// ── ChatStore Class ─────────────────────────────────────────────────
|
||||
@@ -374,4 +376,20 @@ export class ChatStore extends EventEmitter<ChatStoreEvents> {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a message by ID.
|
||||
*
|
||||
* @param id - Message ID
|
||||
* @returns true if deleted, false if not found
|
||||
*/
|
||||
deleteMessage(id: string): boolean {
|
||||
const existing = this.getMessage(id);
|
||||
if (!existing) return false;
|
||||
|
||||
this.db.prepare("DELETE FROM chat_messages WHERE id = ?").run(id);
|
||||
this.db.bumpLastModified();
|
||||
this.emit("chat:message:deleted", id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,6 +192,7 @@ const mockAddMessage = vi.fn();
|
||||
const mockGetMessages = vi.fn();
|
||||
const mockGetMessage = vi.fn();
|
||||
const mockGetLastMessageForSessions = vi.fn().mockReturnValue(new Map());
|
||||
const mockDeleteMessage = vi.fn();
|
||||
|
||||
// Mock AgentStore
|
||||
const mockAgentStoreInit = vi.fn().mockResolvedValue(undefined);
|
||||
@@ -211,6 +212,7 @@ vi.mock("@fusion/core", () => {
|
||||
getMessages = mockGetMessages;
|
||||
getMessage = mockGetMessage;
|
||||
getLastMessageForSessions = mockGetLastMessageForSessions;
|
||||
deleteMessage = mockDeleteMessage;
|
||||
},
|
||||
AgentStore: class MockAgentStore {
|
||||
init = mockAgentStoreInit;
|
||||
@@ -315,6 +317,7 @@ const mockChatStoreInstance = {
|
||||
getMessages: mockGetMessages,
|
||||
getMessage: mockGetMessage,
|
||||
getLastMessageForSessions: mockGetLastMessageForSessions,
|
||||
deleteMessage: mockDeleteMessage,
|
||||
emit: vi.fn(),
|
||||
on: vi.fn(),
|
||||
off: vi.fn(),
|
||||
@@ -366,6 +369,7 @@ describe("Chat API Routes", () => {
|
||||
mockGetMessages.mockReset();
|
||||
mockGetMessage.mockReset();
|
||||
mockGetLastMessageForSessions.mockReset();
|
||||
mockDeleteMessage.mockReset();
|
||||
mockSendMessage.mockReset();
|
||||
mockAgentStoreInit.mockResolvedValue(undefined);
|
||||
mockAgentStoreGetAgent.mockReset();
|
||||
@@ -887,6 +891,7 @@ describe("Chat API Routes", () => {
|
||||
it("deletes message when session exists", async () => {
|
||||
mockGetSession.mockReturnValue(sampleSession);
|
||||
mockGetMessage.mockReturnValue(sampleMessage);
|
||||
mockDeleteMessage.mockReturnValue(true);
|
||||
|
||||
const response = await request(
|
||||
app,
|
||||
@@ -896,6 +901,7 @@ describe("Chat API Routes", () => {
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect((response.body as any).success).toBe(true);
|
||||
expect(mockDeleteMessage).toHaveBeenCalledWith("msg-xyz789");
|
||||
});
|
||||
|
||||
it("returns 404 when session not found", async () => {
|
||||
|
||||
@@ -9176,9 +9176,11 @@ export function createApiRoutes(store: TaskStore, options?: ServerOptions): Rout
|
||||
throw notFound(`Message ${messageId} not found`);
|
||||
}
|
||||
|
||||
// Note: ChatStore currently doesn't have deleteMessage, but we can add it
|
||||
// For now, return success if session exists (the message check is a bonus)
|
||||
// TODO: Add deleteMessage to ChatStore if not already present
|
||||
// Delete the message
|
||||
const deleted = chatStore.deleteMessage(messageId);
|
||||
if (!deleted) {
|
||||
throw notFound(`Message ${messageId} not found`);
|
||||
}
|
||||
res.json({ success: true });
|
||||
} catch (err: unknown) {
|
||||
if (err instanceof ApiError) {
|
||||
|
||||
Reference in New Issue
Block a user