package history import ( "context" "strings" "testing" ) // sessionOf returns the SessionMeta of one session from ListSessions, failing the test // when the session is absent. func sessionOf(t *testing.T, st *Store, id string) SessionMeta { t.Helper() sessions, err := st.ListSessions(context.Background()) if err != nil { t.Fatalf("list sessions: %v", err) } for _, m := range sessions { if m.ID == id { return m } } t.Fatalf("session %q not found in %+v", id, sessions) return SessionMeta{} } func TestListSessionsLastMessagePreview(t *testing.T) { t.Parallel() st := newStore(t) ctx := context.Background() // The empty main session has no preview. if got := sessionOf(t, st, MainSessionID); got.LastMessage != nil { t.Fatalf("empty main LastMessage = %+v, want nil", got.LastMessage) } sub, err := st.NewSubSession(ctx, "owner") if err != nil { t.Fatalf("new sub: %v", err) } if err := st.Append(ctx, Message{SessionID: MainSessionID, Role: RoleUser, Content: "first", CreatedAt: 1000}); err != nil { t.Fatalf("append main user: %v", err) } if err := st.Append(ctx, Message{SessionID: MainSessionID, Role: RoleAssistant, Content: "latest reply", CreatedAt: 2000}); err != nil { t.Fatalf("append main assistant: %v", err) } if err := st.Append(ctx, Message{SessionID: sub, Role: RoleUser, Content: "sub question", CreatedAt: 1500}); err != nil { t.Fatalf("append sub: %v", err) } // Main stays first and carries its newest message, not the first one. sessions, err := st.ListSessions(ctx) if err != nil { t.Fatalf("list sessions: %v", err) } if len(sessions) != 2 || sessions[0].ID != MainSessionID { t.Fatalf("sessions = %+v, want main first", sessions) } main := sessions[0].LastMessage if main == nil || main.Role != RoleAssistant || main.Content != "latest reply" || main.CreatedAt != 2000 { t.Fatalf("main LastMessage = %+v, want assistant/latest reply/2000", main) } subPreview := sessions[1].LastMessage if subPreview == nil || subPreview.Role != RoleUser || subPreview.Content != "sub question" || subPreview.CreatedAt != 1500 { t.Fatalf("sub LastMessage = %+v, want user/sub question/1500", subPreview) } // A sub session created after the main one but never written has no preview. empty, err := st.NewSubSession(ctx, "owner") if err != nil { t.Fatalf("new empty sub: %v", err) } if got := sessionOf(t, st, empty); got.LastMessage != nil { t.Fatalf("empty sub LastMessage = %+v, want nil", got.LastMessage) } } func TestListSessionsLastMessageTruncatesAndCollapses(t *testing.T) { t.Parallel() st := newStore(t) ctx := context.Background() long := strings.Repeat("字", lastMessagePreviewRunes+10) + "\n\n尾部" if err := st.Append(ctx, Message{SessionID: MainSessionID, Role: RoleUser, Content: long, CreatedAt: 1000}); err != nil { t.Fatalf("append long: %v", err) } want := strings.Repeat("字", lastMessagePreviewRunes) + "…" if got := sessionOf(t, st, MainSessionID).LastMessage; got == nil || got.Content != want { t.Fatalf("truncated preview = %+v, want %q", got, want) } // Whitespace runs (including newlines) collapse to single spaces, and a short // message is returned whole. sub, err := st.NewSubSession(ctx, "owner") if err != nil { t.Fatalf("new sub: %v", err) } if err := st.Append(ctx, Message{SessionID: sub, Role: RoleUser, Content: " 你好\n\t世界 ", CreatedAt: 1000}); err != nil { t.Fatalf("append spaced: %v", err) } if got := sessionOf(t, st, sub).LastMessage; got == nil || got.Content != "你好 世界" { t.Fatalf("collapsed preview = %+v, want %q", got, "你好 世界") } } func TestListSessionsLastMessageTieBreaksByInsertion(t *testing.T) { t.Parallel() st := newStore(t) ctx := context.Background() // Two messages sharing a millisecond timestamp: the later insert is the newest. for _, content := range []string{"older", "newer"} { if err := st.Append(ctx, Message{SessionID: MainSessionID, Role: RoleAssistant, Content: content, CreatedAt: 5000}); err != nil { t.Fatalf("append %s: %v", content, err) } } if got := sessionOf(t, st, MainSessionID).LastMessage; got == nil || got.Content != "newer" { t.Fatalf("tie-break preview = %+v, want newer", got) } } func TestPreviewContentBounds(t *testing.T) { t.Parallel() if got := previewContent(""); got != "" { t.Fatalf("empty preview = %q, want empty", got) } exact := strings.Repeat("a", lastMessagePreviewRunes) if got := previewContent(exact); got != exact { t.Fatalf("exact-length preview changed: %q", got) } over := strings.Repeat("a", lastMessagePreviewRunes+1) if got := previewContent(over); got != exact+"…" { t.Fatalf("over-length preview = %q, want ellipsis", got) } }