yao/agent/robot/events/integrations/feishu/e2e_test.go
Max 33efd3e890 Add Feishu and DingTalk integration support in robot lifecycle</message>
<message>
- Introduce Feishu and DingTalk adapters in the robot lifecycle for enhanced integration capabilities.
- Update the integration dispatcher to include new adapters for handling events from Feishu and DingTalk.
- Modify the configuration structure to support settings for both Feishu and DingTalk integrations.
- Enhance the integration parsing logic to recognize and process configurations for Feishu and DingTalk.
2026-03-02 07:36:34 +08:00

205 lines
5 KiB
Go

package feishu
import (
"context"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
robottypes "github.com/yaoapp/yao/agent/robot/types"
fsapi "github.com/yaoapp/yao/integrations/feishu"
)
var (
fsAppID string
fsAppSecret string
)
func TestMain(m *testing.M) {
fsAppID = os.Getenv("FEISHU_TEST_APP_ID")
fsAppSecret = os.Getenv("FEISHU_TEST_APP_SECRET")
os.Exit(m.Run())
}
func skipIfNoCreds(t *testing.T) {
t.Helper()
if fsAppID == "" || fsAppSecret == "" {
t.Skip("FEISHU_TEST_APP_ID or FEISHU_TEST_APP_SECRET not set")
}
}
// TestE2E_Adapter_Apply verifies that Apply correctly registers a bot.
func TestE2E_Adapter_Apply(t *testing.T) {
skipIfNoCreds(t)
a := &Adapter{
bots: make(map[string]*botEntry),
appIdx: make(map[string]string),
dedup: newDedupStore(),
stopCh: make(chan struct{}),
}
defer close(a.stopCh)
robot := &robottypes.Robot{
MemberID: "robot_e2e_feishu_adapter",
TeamID: "team_e2e_fs",
Config: &robottypes.Config{
Integrations: &robottypes.Integrations{
Feishu: &robottypes.FeishuConfig{
Enabled: true,
AppID: fsAppID,
AppSecret: fsAppSecret,
},
},
},
}
a.Apply(context.Background(), robot)
a.mu.RLock()
entry, ok := a.bots["robot_e2e_feishu_adapter"]
a.mu.RUnlock()
require.True(t, ok, "bot should be registered")
assert.Equal(t, fsAppID, entry.appID)
assert.NotNil(t, entry.bot)
t.Logf("OK Apply: feishu bot registered robot=%s app=%s", robot.MemberID, entry.appID)
}
// TestE2E_Adapter_Apply_Update verifies re-Apply with same appID is a no-op.
func TestE2E_Adapter_Apply_Update(t *testing.T) {
skipIfNoCreds(t)
a := &Adapter{
bots: make(map[string]*botEntry),
appIdx: make(map[string]string),
dedup: newDedupStore(),
stopCh: make(chan struct{}),
}
defer close(a.stopCh)
robot := &robottypes.Robot{
MemberID: "robot_e2e_feishu_update",
TeamID: "team_e2e_fs",
Config: &robottypes.Config{
Integrations: &robottypes.Integrations{
Feishu: &robottypes.FeishuConfig{
Enabled: true,
AppID: fsAppID,
AppSecret: fsAppSecret,
},
},
},
}
a.Apply(context.Background(), robot)
a.mu.RLock()
_, ok := a.bots["robot_e2e_feishu_update"]
a.mu.RUnlock()
require.True(t, ok)
// Apply again — should be no-op
a.Apply(context.Background(), robot)
a.mu.RLock()
assert.Len(t, a.bots, 1)
a.mu.RUnlock()
// Remove
a.Remove(context.Background(), "robot_e2e_feishu_update")
a.mu.RLock()
_, ok = a.bots["robot_e2e_feishu_update"]
a.mu.RUnlock()
assert.False(t, ok, "bot should be removed")
t.Log("OK Apply/Remove lifecycle verified")
}
// TestE2E_Adapter_Dedup verifies deduplication works.
func TestE2E_Adapter_Dedup(t *testing.T) {
a := &Adapter{
bots: make(map[string]*botEntry),
appIdx: make(map[string]string),
dedup: newDedupStore(),
stopCh: make(chan struct{}),
}
defer close(a.stopCh)
key := "fs:test-robot:msg-12345"
assert.True(t, a.dedup.markSeen(key), "first time should return true")
assert.False(t, a.dedup.markSeen(key), "second time should return false (dedup)")
t.Log("OK dedup working correctly")
}
// TestE2E_Adapter_HandleMessages verifies message handling through the adapter.
func TestE2E_Adapter_HandleMessages(t *testing.T) {
skipIfNoCreds(t)
a := &Adapter{
bots: make(map[string]*botEntry),
appIdx: make(map[string]string),
dedup: newDedupStore(),
stopCh: make(chan struct{}),
}
defer close(a.stopCh)
entry := &botEntry{
robotID: "robot_e2e_feishu_handle",
appID: fsAppID,
bot: fsapi.NewBot(fsAppID, fsAppSecret),
}
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cms := []*fsapi.ConvertedMessage{
{
MessageID: "test_msg_1",
ChatID: "test_chat_1",
ChatType: "p2p",
SenderID: "test_sender_1",
Text: "Hello from E2E test",
},
}
// This should not panic even without event bus running
a.handleMessages(ctx, entry, cms)
// Verify dedup: should be marked as seen
assert.False(t, a.dedup.markSeen("fs:robot_e2e_feishu_handle:test_msg_1"),
"message should be marked as seen after handleMessages")
t.Log("OK handleMessages processed 1 message")
}
// TestE2E_Adapter_ApplyDisabled verifies Apply removes bot when disabled.
func TestE2E_Adapter_ApplyDisabled(t *testing.T) {
a := &Adapter{
bots: make(map[string]*botEntry),
appIdx: make(map[string]string),
dedup: newDedupStore(),
stopCh: make(chan struct{}),
}
defer close(a.stopCh)
robot := &robottypes.Robot{
MemberID: "robot_e2e_feishu_disabled",
TeamID: "team_e2e_fs",
Config: &robottypes.Config{
Integrations: &robottypes.Integrations{
Feishu: &robottypes.FeishuConfig{
Enabled: false,
AppID: "some_app",
AppSecret: "some_secret",
},
},
},
}
a.Apply(context.Background(), robot)
a.mu.RLock()
_, ok := a.bots["robot_e2e_feishu_disabled"]
a.mu.RUnlock()
assert.False(t, ok, "disabled bot should not be registered")
t.Log("OK disabled config not registered")
}