package history import ( "context" "errors" "strings" "testing" ) // titleOf returns the stored title of a session, failing the test when it is absent. func titleOf(t *testing.T, st *Store, id string) string { 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.Title } } t.Fatalf("session %q not found in %+v", id, sessions) return "" } func TestEnsureTitleFromFirstUserMessage(t *testing.T) { t.Parallel() st := newStore(t) ctx := context.Background() // The main session starts nameless. if got := titleOf(t, st, MainSessionID); got != "" { t.Fatalf("initial main title = %q, want empty", got) } // The first user message names it, with whitespace runs collapsed. if _, err := st.AppendFull(ctx, Message{SessionID: MainSessionID, Role: RoleUser, Content: " 你好\n世界 "}); err != nil { t.Fatalf("append first user: %v", err) } if got := titleOf(t, st, MainSessionID); got != "你好 世界" { t.Fatalf("main title = %q, want 你好 世界", got) } // A later user message does not overwrite the established title. if _, err := st.AppendFull(ctx, Message{SessionID: MainSessionID, Role: RoleUser, Content: "second"}); err != nil { t.Fatalf("append second user: %v", err) } if got := titleOf(t, st, MainSessionID); got != "你好 世界" { t.Fatalf("main title after second = %q, want unchanged", got) } } func TestEnsureTitleTruncatesAndFallsBack(t *testing.T) { t.Parallel() st := newStore(t) ctx := context.Background() // A long first user message is truncated to 20 runes plus an ellipsis. sub, err := st.NewSubSession(ctx, "owner") if err != nil { t.Fatalf("new sub: %v", err) } long := strings.Repeat("字", autoTitleMaxRunes+5) if _, err := st.AppendFull(ctx, Message{SessionID: sub, Role: RoleUser, Content: long}); err != nil { t.Fatalf("append long: %v", err) } want := strings.Repeat("字", autoTitleMaxRunes) + "…" if got := titleOf(t, st, sub); got != want { t.Fatalf("sub title = %q, want %q", got, want) } // An all-whitespace first message (e.g. an image-only turn) falls back to the // session-type label, for both a sub and the main session. blank, err := st.NewSubSession(ctx, "owner") if err != nil { t.Fatalf("new blank sub: %v", err) } if _, err := st.AppendFull(ctx, Message{SessionID: blank, Role: RoleUser, Content: " \n\t "}); err != nil { t.Fatalf("append blank: %v", err) } if got := titleOf(t, st, blank); got != titleSubFallback { t.Fatalf("blank sub title = %q, want %q", got, titleSubFallback) } st2 := newStore(t) if _, err := st2.AppendFull(ctx, Message{SessionID: MainSessionID, Role: RoleUser, Content: " "}); err != nil { t.Fatalf("append blank main: %v", err) } if got := titleOf(t, st2, MainSessionID); got != titleMainFallback { t.Fatalf("blank main title = %q, want %q", got, titleMainFallback) } } func TestEnsureTitleOnlyFromFirstUserMessage(t *testing.T) { t.Parallel() st := newStore(t) ctx := context.Background() // An assistant message first does not name the session. sub, err := st.NewSubSession(ctx, "owner") if err != nil { t.Fatalf("new sub: %v", err) } if _, err := st.AppendFull(ctx, Message{SessionID: sub, Role: RoleAssistant, Content: "hello"}); err != nil { t.Fatalf("append assistant: %v", err) } if got := titleOf(t, st, sub); got != "" { t.Fatalf("sub title after assistant = %q, want empty", got) } // The first user message afterwards still names it. if _, err := st.AppendFull(ctx, Message{SessionID: sub, Role: RoleUser, Content: "real question"}); err != nil { t.Fatalf("append user: %v", err) } if got := titleOf(t, st, sub); got != "real question" { t.Fatalf("sub title after user = %q, want real question", got) } } func TestRenameSession(t *testing.T) { t.Parallel() st := newStore(t) ctx := context.Background() // The fixed main session may be renamed (unlike deletion). if err := st.RenameSession(ctx, MainSessionID, "工作台"); err != nil { t.Fatalf("rename main: %v", err) } if got := titleOf(t, st, MainSessionID); got != "工作台" { t.Fatalf("main title = %q, want 工作台", got) } // A sub session may be renamed, and a rename overwrites an auto title. sub, err := st.NewSubSession(ctx, "owner") if err != nil { t.Fatalf("new sub: %v", err) } if _, err := st.AppendFull(ctx, Message{SessionID: sub, Role: RoleUser, Content: "auto"}); err != nil { t.Fatalf("append: %v", err) } if err := st.RenameSession(ctx, sub, "手动标题"); err != nil { t.Fatalf("rename sub: %v", err) } if got := titleOf(t, st, sub); got != "手动标题" { t.Fatalf("sub title = %q, want 手动标题", got) } // An unknown id is reported as not found. if err := st.RenameSession(ctx, "web:missing", "x"); !errors.Is(err, ErrSessionNotFound) { t.Fatalf("rename unknown err = %v, want ErrSessionNotFound", err) } }