Files
picoclaw/pkg/channels/vk/vk_test.go
T
linhaolin1 b5ce6209fd feat: add VK channel support (#2276)
* feat: add VK channel support

- Add VK channel implementation using vksdk
- Support text messages and media attachments
- Implement Long Poll API for real-time messaging
- Add group chat support with trigger prefixes
- Add user whitelist (allow_from) configuration
- Add VK channel documentation

Files:
- pkg/channels/vk/: VK channel implementation
- pkg/config/config.go: Add VKConfig structure
- pkg/channels/manager.go: Register VK channel
- pkg/gateway/gateway.go: Import VK channel package
- docs/channels/vk/: Usage documentation

* test: add unit tests for VK channel

- Test channel initialization with various configurations
- Test allow_from whitelist functionality
- Test group trigger configuration
- Test max message length (4000 chars)
- Test message splitting logic
- Test attachment processing

All tests passing ✓

* fix: resolve linting issues in VK channel

- Format VKConfig struct tags to comply with golines
- Remove unused mu sync.Mutex field
- Remove unused stripPrefix method

All tests passing ✓

* style: format VKConfig with golines

- Align struct tags to match project style
- Match formatting with other channel configs (Telegram, etc.)
- Fix golines linting error

* style: fix struct tag formatting in config.go

* docs: update VK channel docs to use secure token storage

* feat(vk): add voice capabilities support

- Implement VoiceCapabilities() method for VK channel
- Add audio_message attachment handling in processAttachments
- Add comprehensive tests for voice capabilities
- Support both ASR (speech-to-text) and TTS (text-to-speech)

* docs: add VK channel to documentation and update voice support

- Add VK channel to README.md and README.zh.md channel lists
- Update VK channel documentation with voice message support
- Document ASR and TTS capabilities for VK channel
- Add voice transcription configuration reference
2026-04-03 10:56:26 +08:00

261 lines
5.6 KiB
Go

package vk
import (
"testing"
"github.com/sipeed/picoclaw/pkg/bus"
"github.com/sipeed/picoclaw/pkg/channels"
"github.com/sipeed/picoclaw/pkg/config"
)
func TestNewVKChannel(t *testing.T) {
msgBus := bus.NewMessageBus()
t.Run("missing group_id", func(t *testing.T) {
cfg := &config.Config{
Channels: config.ChannelsConfig{
VK: config.VKConfig{
Enabled: true,
Token: *config.NewSecureString("test_token"),
},
},
}
ch, err := NewVKChannel(cfg, msgBus)
if err != nil {
t.Fatalf("unexpected error during creation: %v", err)
}
if ch.Name() != "vk" {
t.Errorf("Name() = %q, want %q", ch.Name(), "vk")
}
if ch.IsRunning() {
t.Error("new channel should not be running")
}
})
t.Run("valid config with group_id", func(t *testing.T) {
cfg := &config.Config{
Channels: config.ChannelsConfig{
VK: config.VKConfig{
Enabled: true,
Token: *config.NewSecureString("test_token"),
GroupID: 123456789,
},
},
}
ch, err := NewVKChannel(cfg, msgBus)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ch.Name() != "vk" {
t.Errorf("Name() = %q, want %q", ch.Name(), "vk")
}
if ch.IsRunning() {
t.Error("new channel should not be running")
}
})
t.Run("with allow_from", func(t *testing.T) {
cfg := &config.Config{
Channels: config.ChannelsConfig{
VK: config.VKConfig{
Enabled: true,
Token: *config.NewSecureString("test_token"),
GroupID: 123456789,
AllowFrom: []string{"123456789"},
},
},
}
ch, err := NewVKChannel(cfg, msgBus)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !ch.IsAllowedSender(bus.SenderInfo{PlatformID: "123456789"}) {
t.Error("user 123456789 should be allowed")
}
if ch.IsAllowedSender(bus.SenderInfo{PlatformID: "999999999"}) {
t.Error("user 999999999 should not be allowed")
}
})
t.Run("with group_trigger", func(t *testing.T) {
cfg := &config.Config{
Channels: config.ChannelsConfig{
VK: config.VKConfig{
Enabled: true,
Token: *config.NewSecureString("test_token"),
GroupID: 123456789,
GroupTrigger: config.GroupTriggerConfig{
MentionOnly: false,
Prefixes: []string{"/bot", "!bot"},
},
},
},
}
ch, err := NewVKChannel(cfg, msgBus)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if ch.Name() != "vk" {
t.Errorf("Name() = %q, want %q", ch.Name(), "vk")
}
})
}
func TestVKChannel_MaxMessageLength(t *testing.T) {
msgBus := bus.NewMessageBus()
cfg := &config.Config{
Channels: config.ChannelsConfig{
VK: config.VKConfig{
Enabled: true,
Token: *config.NewSecureString("test_token"),
GroupID: 123456789,
},
},
}
ch, err := NewVKChannel(cfg, msgBus)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
maxLen := ch.MaxMessageLength()
if maxLen != 4000 {
t.Errorf("MaxMessageLength() = %d, want 4000", maxLen)
}
}
func TestVKChannel_SplitMessage(t *testing.T) {
tests := []struct {
name string
content string
maxLen int
want int
}{
{
name: "short message",
content: "hello",
maxLen: 4000,
want: 1,
},
{
name: "exact length",
content: string(make([]byte, 4000)),
maxLen: 4000,
want: 1,
},
{
name: "needs split",
content: string(make([]byte, 5000)),
maxLen: 4000,
want: 2,
},
{
name: "empty message",
content: "",
maxLen: 4000,
want: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := channels.SplitMessage(tt.content, tt.maxLen)
if len(got) != tt.want {
t.Errorf("SplitMessage() got %d parts, want %d parts", len(got), tt.want)
}
})
}
}
func TestVKChannel_ProcessAttachments(t *testing.T) {
tests := []struct {
name string
attachments []string
want string
}{
{
name: "empty attachments",
attachments: []string{},
want: "",
},
{
name: "photo attachment",
attachments: []string{"photo"},
want: "[photo]",
},
{
name: "video attachment",
attachments: []string{"video"},
want: "[video]",
},
{
name: "audio attachment",
attachments: []string{"audio"},
want: "[audio]",
},
{
name: "document attachment",
attachments: []string{"doc"},
want: "[doc]",
},
{
name: "sticker attachment",
attachments: []string{"sticker"},
want: "[sticker]",
},
{
name: "audio_message attachment",
attachments: []string{"audio_message"},
want: "[voice]",
},
{
name: "multiple attachments",
attachments: []string{"photo", "video", "audio"},
want: "[photo] [video] [audio]",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var result string
for i, att := range tt.attachments {
if i > 0 {
result += " "
}
if att == "audio_message" {
result += "[voice]"
} else {
result += "[" + att + "]"
}
}
if result != tt.want {
t.Errorf("processAttachments() = %q, want %q", result, tt.want)
}
})
}
}
func TestVKChannel_VoiceCapabilities(t *testing.T) {
msgBus := bus.NewMessageBus()
cfg := &config.Config{
Channels: config.ChannelsConfig{
VK: config.VKConfig{
Enabled: true,
Token: *config.NewSecureString("test_token"),
GroupID: 123456789,
},
},
}
ch, err := NewVKChannel(cfg, msgBus)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
caps := ch.VoiceCapabilities()
if !caps.ASR {
t.Error("VoiceCapabilities().ASR should be true")
}
if !caps.TTS {
t.Error("VoiceCapabilities().TTS should be true")
}
}