import { describe, expect, test } from "bun:test"; import type { Conversation, ConversationSummary } from "../domain/conversation"; import type { ConversationRepository } from "../ports/conversation-repository"; import { ConversationService } from "./conversation-service"; class MemoryConversationRepository implements ConversationRepository { readonly values = new Map(); async listRecent(): Promise { return [...this.values.values()]; } async listByProject(projectId: string) { return [...this.values.values()].filter( (value) => value.projectId === projectId, ); } async deleteByProject(projectId: string) { for (const [id, value] of this.values) if (value.projectId === projectId) this.values.delete(id); } async getById(id: string): Promise { return this.values.get(id) ?? null; } async create(conversation: Conversation): Promise { this.values.set(conversation.id, conversation); } async appendMessage( id: string, message: Conversation["messages"][number], ): Promise { const current = this.values.get(id); if (!current) throw new Error("not found"); const next = { ...current, messages: [...current.messages, message], updatedAt: message.createdAt, }; this.values.set(id, next); return next; } } describe("ConversationService", () => { test("首条有效消息才创建普通会话", async () => { const conversations = new MemoryConversationRepository(); let nextId = 0; const service = new ConversationService({ conversations, clock: { now: () => new Date("2026-08-12T00:00:00.000Z") }, ids: { create: () => `id_${++nextId}` }, }); const created = await service.createWithFirstMessage( " 第一次对话 ", "run_1", ); expect(created.projectId).toBeNull(); expect(created.messages[0]?.content).toBe("第一次对话"); expect(created.messages[0]?.runId).toBe("run_1"); expect(await service.listRecent()).toHaveLength(1); }); test("拒绝空消息且不创建会话", async () => { const conversations = new MemoryConversationRepository(); const service = new ConversationService({ conversations, clock: { now: () => new Date() }, ids: { create: () => "unused" }, }); expect( service.createWithFirstMessage(" \n ", "run_1"), ).rejects.toMatchObject({ code: "MESSAGE_EMPTY" }); expect(conversations.values.size).toBe(0); }); });