mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-13 07:05:47 +00:00
137a986d4f
* feat(channels): add Slack channel via Socket Mode (#37) Implement Slack integration using Socket Mode (xapp-/xoxb- tokens): - Event-driven messaging via app_mention + message events - Policy checks: open, pairing, allowlist, disabled (DM + group) - Thread participation with configurable TTL - Markdown-to-mrkdwn formatting pipeline - Streaming support (edit-in-place + native ChatStreamer) - SSRF-protected file downloads - Debounce, dedup, reactions, group history context - 170 unit tests (format, helpers, stream, SSRF) Fix BaseChannel.HandleMessage allowlist to also check chatID, enabling group allowlist with channel IDs across all channels. Closes #37 * feat(slack): add file/media support and edit-to-mention handling - Wire inbound file download into handleMessage (images, audio, documents) - Add media.go with resolveMedia, classifyMime, buildMediaTags - Extract shared ExtractDocumentContent to channels/media_utils.go (DRY with Telegram) - Support file_share and message_changed subtypes - Handle edit-to-mention: respond when user edits old message to add @bot - Add MediaMaxBytes config field (default 20MB) - Fix debounce media accumulation (was silently dropping files) - Add 60s HTTP client timeout on file downloads - Refactor downloadFile signature for slack.File compatibility
122 lines
2.3 KiB
Go
122 lines
2.3 KiB
Go
package slack
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func TestExtractChannelID(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
localKey string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "plain channel id",
|
|
localKey: "C123456",
|
|
expected: "C123456",
|
|
},
|
|
{
|
|
name: "threaded message",
|
|
localKey: "C123456:thread:1234.5678",
|
|
expected: "C123456",
|
|
},
|
|
{
|
|
name: "threaded with different ts format",
|
|
localKey: "C999:thread:999999.999999",
|
|
expected: "C999",
|
|
},
|
|
{
|
|
name: "no thread marker",
|
|
localKey: "C123456789",
|
|
expected: "C123456789",
|
|
},
|
|
{
|
|
name: "empty string",
|
|
localKey: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "only thread marker",
|
|
localKey: ":thread:1234.5678",
|
|
expected: ":thread:1234.5678",
|
|
},
|
|
{
|
|
name: "colon but no thread text",
|
|
localKey: "C123:thread:",
|
|
expected: "C123",
|
|
},
|
|
{
|
|
name: "multiple colons",
|
|
localKey: "C123:thread:1234:5678",
|
|
expected: "C123",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := extractChannelID(tt.localKey)
|
|
if got != tt.expected {
|
|
t.Errorf("extractChannelID(%q) = %q, want %q", tt.localKey, got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestExtractThreadTS(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
localKey string
|
|
expected string
|
|
}{
|
|
{
|
|
name: "threaded message",
|
|
localKey: "C123456:thread:1234.5678",
|
|
expected: "1234.5678",
|
|
},
|
|
{
|
|
name: "plain channel id",
|
|
localKey: "C123456",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "empty string",
|
|
localKey: "",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "only thread marker (no channel id)",
|
|
localKey: ":thread:1234.5678",
|
|
expected: "", // idx must be > 0, so this returns ""
|
|
},
|
|
{
|
|
name: "thread marker but empty ts",
|
|
localKey: "C123:thread:",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "multiple colons after thread",
|
|
localKey: "C123:thread:1234:5678:extra",
|
|
expected: "1234:5678:extra",
|
|
},
|
|
{
|
|
name: "thread marker not found",
|
|
localKey: "C123:other:1234.5678",
|
|
expected: "",
|
|
},
|
|
{
|
|
name: "thread marker case sensitive",
|
|
localKey: "C123:THREAD:1234.5678",
|
|
expected: "",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := extractThreadTS(tt.localKey)
|
|
if got != tt.expected {
|
|
t.Errorf("extractThreadTS(%q) = %q, want %q", tt.localKey, got, tt.expected)
|
|
}
|
|
})
|
|
}
|
|
}
|