diff --git a/backend/internal/session/memory_test.go b/backend/internal/session/memory_test.go index eb37b73..ba670f0 100644 --- a/backend/internal/session/memory_test.go +++ b/backend/internal/session/memory_test.go @@ -284,3 +284,198 @@ func TestActiveCount(t *testing.T) { t.Errorf("ActiveCount = %d, want 2", m.ActiveCount()) } } + +func TestCreateWithUserID(t *testing.T) { + m := NewMemoryManager(30*time.Minute, 20) + defer m.Stop() + ctx := context.Background() + + id, err := m.Create(ctx, "user-123", models.DefaultConfig()) + if err != nil { + t.Fatalf("Create: %v", err) + } + + sess, err := m.Get(ctx, id) + if err != nil { + t.Fatalf("Get: %v", err) + } + if sess.UserID != "user-123" { + t.Errorf("UserID = %q, want %q", sess.UserID, "user-123") + } + if sess.Title != models.DefaultSessionTitle { + t.Errorf("Title = %q, want %q", sess.Title, models.DefaultSessionTitle) + } + if sess.UpdatedAt.IsZero() { + t.Error("UpdatedAt should not be zero") + } +} + +func TestUpdateTitle(t *testing.T) { + m := NewMemoryManager(30*time.Minute, 20) + defer m.Stop() + ctx := context.Background() + + id, _ := m.Create(ctx, "user-1", models.DefaultConfig()) + + if err := m.UpdateTitle(ctx, id, "自定义标题"); err != nil { + t.Fatalf("UpdateTitle: %v", err) + } + + sess, _ := m.Get(ctx, id) + if sess.Title != "自定义标题" { + t.Errorf("Title = %q, want %q", sess.Title, "自定义标题") + } +} + +func TestUpdateTitleNotFound(t *testing.T) { + m := NewMemoryManager(30*time.Minute, 20) + defer m.Stop() + ctx := context.Background() + + err := m.UpdateTitle(ctx, "nonexistent", "标题") + if err != ErrSessionNotFound { + t.Errorf("UpdateTitle nonexistent: err = %v, want ErrSessionNotFound", err) + } +} + +func TestAutoTitleOnFirstMessage(t *testing.T) { + m := NewMemoryManager(30*time.Minute, 20) + defer m.Stop() + ctx := context.Background() + + id, _ := m.Create(ctx, "user-1", models.DefaultConfig()) + + // 首条 user 消息应自动更新标题 + m.AppendMessage(ctx, id, models.Message{Role: "user", Content: "你好世界"}) + + sess, _ := m.Get(ctx, id) + if sess.Title != "你好世界" { + t.Errorf("Title = %q, want %q", sess.Title, "你好世界") + } +} + +func TestAutoTitleLongMessage(t *testing.T) { + m := NewMemoryManager(30*time.Minute, 20) + defer m.Stop() + ctx := context.Background() + + id, _ := m.Create(ctx, "user-1", models.DefaultConfig()) + + // 超过 20 字符的消息应截断 + longMsg := "这是一条很长很长很长很长很长很长很长很长的消息" + m.AppendMessage(ctx, id, models.Message{Role: "user", Content: longMsg}) + + sess, _ := m.Get(ctx, id) + expected := string([]rune(longMsg)[:20]) + "…" + if sess.Title != expected { + t.Errorf("Title = %q, want %q", sess.Title, expected) + } +} + +func TestAutoTitleNotOverwritten(t *testing.T) { + m := NewMemoryManager(30*time.Minute, 20) + defer m.Stop() + ctx := context.Background() + + id, _ := m.Create(ctx, "user-1", models.DefaultConfig()) + + // 首条消息设置标题 + m.AppendMessage(ctx, id, models.Message{Role: "user", Content: "第一条消息"}) + // 第二条消息不应覆盖已有的标题 + m.AppendMessage(ctx, id, models.Message{Role: "user", Content: "第二条消息"}) + + sess, _ := m.Get(ctx, id) + if sess.Title != "第一条消息" { + t.Errorf("Title = %q, want %q", sess.Title, "第一条消息") + } +} + +func TestListByUser(t *testing.T) { + m := NewMemoryManager(30*time.Minute, 20) + defer m.Stop() + ctx := context.Background() + + // 创建两个用户的不同会话 + id1, _ := m.Create(ctx, "user-1", models.DefaultConfig()) + m.AppendMessage(ctx, id1, models.Message{Role: "user", Content: "会话1"}) + id2, _ := m.Create(ctx, "user-1", models.DefaultConfig()) + m.AppendMessage(ctx, id2, models.Message{Role: "user", Content: "会话2"}) + m.Create(ctx, "user-2", models.DefaultConfig()) // 其他用户的会话 + + list, total, err := m.ListByUser(ctx, "user-1", 1, 10) + if err != nil { + t.Fatalf("ListByUser: %v", err) + } + if total != 2 { + t.Errorf("total = %d, want 2", total) + } + if len(list) != 2 { + t.Fatalf("len = %d, want 2", len(list)) + } + // 按 UpdatedAt 降序,id2 应在前 + if list[0].ID != id2 { + t.Errorf("list[0].ID = %q, want %q", list[0].ID, id2) + } + if list[0].Title != "会话2" { + t.Errorf("list[0].Title = %q, want %q", list[0].Title, "会话2") + } + if list[0].LastMessage != "会话2" { + t.Errorf("list[0].LastMessage = %q, want %q", list[0].LastMessage, "会话2") + } +} + +func TestListByUserPagination(t *testing.T) { + m := NewMemoryManager(30*time.Minute, 20) + defer m.Stop() + ctx := context.Background() + + // 创建 5 个会话 + for i := 0; i < 5; i++ { + id, _ := m.Create(ctx, "user-1", models.DefaultConfig()) + m.AppendMessage(ctx, id, models.Message{Role: "user", Content: "msg"}) + } + + // 第 1 页,每页 2 条 + list, total, _ := m.ListByUser(ctx, "user-1", 1, 2) + if total != 5 { + t.Errorf("total = %d, want 5", total) + } + if len(list) != 2 { + t.Errorf("page 1 len = %d, want 2", len(list)) + } + + // 第 2 页 + list, _, _ = m.ListByUser(ctx, "user-1", 2, 2) + if len(list) != 2 { + t.Errorf("page 2 len = %d, want 2", len(list)) + } + + // 第 3 页(最后一页) + list, _, _ = m.ListByUser(ctx, "user-1", 3, 2) + if len(list) != 1 { + t.Errorf("page 3 len = %d, want 1", len(list)) + } + + // 超出范围的页 + list, _, _ = m.ListByUser(ctx, "user-1", 10, 2) + if len(list) != 0 { + t.Errorf("out of range page len = %d, want 0", len(list)) + } +} + +func TestListByUserEmpty(t *testing.T) { + m := NewMemoryManager(30*time.Minute, 20) + defer m.Stop() + ctx := context.Background() + + list, total, err := m.ListByUser(ctx, "no-such-user", 1, 10) + if err != nil { + t.Fatalf("ListByUser: %v", err) + } + if total != 0 { + t.Errorf("total = %d, want 0", total) + } + if len(list) != 0 { + t.Errorf("len = %d, want 0", len(list)) + } +}