Files
Enterprise-Knowledge-Base/internal/im/session_title_test.go
T
nullkey 5851c64ba1 feat(im): IM session improvements — titles, in-flight recovery, classification
Three related improvements to the IM-session experience:

- Auto-generated titles: a new IM session starts untitled and gets a
  content-based title from its first message via the same GenerateTitleAsync
  flow web sessions use, instead of an identity title like "user · dm". Falls
  back to the identity title when the first message has no text. All platforms.

- In-flight reply recovery (web chat): opening a session whose reply is still
  being generated on an IM channel made the web client attach via
  continue-stream and fail (the IM answer never streams through this server),
  flashing a "stream failed" toast and forcing a manual refresh. Treat a failed
  attach to an IM reply (channel === 'im') as recoverable — poll until it
  completes, then reload the thread; on timeout surface the standard error.
  Web/api replies keep the original behaviour, untouched.

- Source-filter classification: the sidebar filter treats "web" as sessions
  with no IM mapping, but the list join excluded soft-deleted mappings, so
  /clear'd IM sessions leaked into the user's own web chats. Include soft-deleted
  mappings so a session ever bound to an IM channel stays under its platform.
  Adds a repository regression test.
2026-06-12 17:01:28 +08:00

156 lines
3.4 KiB
Go

package im
import "testing"
func TestShortID(t *testing.T) {
tests := []struct {
name string
in string
want string
}{
{"empty", "", ""},
{"shorter than 8", "abc", "abc"},
{"exactly 8", "12345678", "12345678"},
{"longer than 8 keeps suffix", "aaaaaaaaXXXXXXXX", "XXXXXXXX"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := shortID(tt.in); got != tt.want {
t.Errorf("shortID(%q) = %q, want %q", tt.in, got, tt.want)
}
})
}
}
func TestBuildUserSessionTitle(t *testing.T) {
tests := []struct {
name string
msg *IncomingMessage
want string
}{
{
name: "group with username",
msg: &IncomingMessage{
Platform: "feishu",
UserName: "李四",
ChatType: ChatTypeGroup,
ChatID: "oc_aaaaaaaaaaaaaaaa",
},
want: "李四 · group aaaaaaaa",
},
{
name: "direct message with username",
msg: &IncomingMessage{
Platform: "feishu",
UserName: "李四",
ChatType: ChatTypeDirect,
},
want: "李四 · dm",
},
{
name: "group without username falls back to user id",
msg: &IncomingMessage{
Platform: "wecom",
UserID: "WeCom_ZhangSan",
ChatType: ChatTypeGroup,
ChatID: "wc_group_1234",
},
want: "user ZhangSan · group oup_1234",
},
{
name: "no user identity at all",
msg: &IncomingMessage{
Platform: "slack",
ChatType: ChatTypeDirect,
},
want: "user · dm",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := buildUserSessionTitle(tt.msg); got != tt.want {
t.Errorf("buildUserSessionTitle() = %q, want %q", got, tt.want)
}
})
}
}
func TestBuildThreadSessionTitle(t *testing.T) {
tests := []struct {
name string
msg *IncomingMessage
want string
}{
{
name: "chat + thread",
msg: &IncomingMessage{
Platform: "slack",
ChatID: "C0123456789",
ThreadID: "1700000000.111000",
},
want: "chat 23456789 · thread 0.111000",
},
{
name: "thread without chat id",
msg: &IncomingMessage{
Platform: "feishu",
ThreadID: "om_thread_abcdefgh",
},
want: "thread abcdefgh",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := buildThreadSessionTitle(tt.msg); got != tt.want {
t.Errorf("buildThreadSessionTitle() = %q, want %q", got, tt.want)
}
})
}
}
func TestIMInitialSessionTitle(t *testing.T) {
tests := []struct {
name string
msg *IncomingMessage
want string
}{
{
name: "text message starts untitled so it gets a content-based title later",
msg: &IncomingMessage{
Platform: "feishu",
UserName: "李四",
ChatType: ChatTypeDirect,
Content: "如何配置单点登录?",
},
want: "",
},
{
name: "whitespace-only content falls back to the identity title",
msg: &IncomingMessage{
Platform: "feishu",
UserName: "李四",
ChatType: ChatTypeDirect,
Content: " ",
},
want: "李四 · dm",
},
{
name: "non-text message (no content) falls back to the identity title",
msg: &IncomingMessage{
Platform: "wecom",
UserID: "WeCom_ZhangSan",
ChatType: ChatTypeGroup,
ChatID: "wc_group_1234",
},
want: "user ZhangSan · group oup_1234",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := imInitialSessionTitle(tt.msg, buildUserSessionTitle); got != tt.want {
t.Errorf("imInitialSessionTitle() = %q, want %q", got, tt.want)
}
})
}
}