初始化仓库
This commit is contained in:
@@ -0,0 +1,497 @@
|
||||
//go:build integration
|
||||
|
||||
// Package integration contains integration tests for cc-connect.
|
||||
// These tests verify component interactions and require specific setup.
|
||||
// Run with: go test -tags=integration ./tests/integration/...
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/chenhg5/cc-connect/core"
|
||||
"github.com/chenhg5/cc-connect/tests/mocks"
|
||||
"github.com/chenhg5/cc-connect/tests/mocks/fake"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// T-300: Engine + Platform Integration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestIntegration_EnginePlatformMessageFlow(t *testing.T) {
|
||||
// This test verifies the message flow between platform and engine
|
||||
// using mock components to simulate real behavior
|
||||
|
||||
// Create mock platform
|
||||
platform := new(mocks.MockPlatform)
|
||||
platform.On("Name").Return("test-integration")
|
||||
platform.On("Start", mock.Anything).Return(nil)
|
||||
platform.On("Stop").Return(nil)
|
||||
|
||||
// Create message handler to capture messages
|
||||
var receivedMessages []*core.Message
|
||||
var mu sync.Mutex
|
||||
|
||||
handler := func(p core.Platform, msg *core.Message) {
|
||||
mu.Lock()
|
||||
receivedMessages = append(receivedMessages, msg)
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
// Start platform
|
||||
err := platform.Start(handler)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Simulate platform receiving a message
|
||||
testMsg := fake.TestMessageWithContent("Integration test message")
|
||||
handler(platform, testMsg)
|
||||
|
||||
// Verify message was captured
|
||||
mu.Lock()
|
||||
require.Len(t, receivedMessages, 1)
|
||||
assert.Equal(t, "Integration test message", receivedMessages[0].Content)
|
||||
mu.Unlock()
|
||||
|
||||
// Stop platform
|
||||
platform.Stop()
|
||||
|
||||
t.Log("Engine-Platform message flow: PASS")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// T-301: Multi-Agent Session Coordination
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestIntegration_MultiAgentSessionCoordination(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create fake agents
|
||||
agent1 := fake.NewFakeAgent("agent-1")
|
||||
agent2 := fake.NewFakeAgent("agent-2")
|
||||
|
||||
// Start sessions on both agents
|
||||
sess1, err := agent1.StartSession(ctx, "coord-session-1")
|
||||
require.NoError(t, err)
|
||||
|
||||
sess2, err := agent2.StartSession(ctx, "coord-session-2")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify both sessions are independent
|
||||
assert.NotEqual(t, sess1.CurrentSessionID(), sess2.CurrentSessionID())
|
||||
assert.True(t, sess1.Alive())
|
||||
assert.True(t, sess2.Alive())
|
||||
|
||||
// Send message to agent 1
|
||||
err = sess1.Send("Message to agent 1", nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Send message to agent 2
|
||||
err = sess2.Send("Message to agent 2", nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify both agents received their messages
|
||||
prompts1 := agent1.GetSession().GetPrompts()
|
||||
prompts2 := agent2.GetSession().GetPrompts()
|
||||
|
||||
assert.Len(t, prompts1, 1)
|
||||
assert.Contains(t, prompts1[0], "Message to agent 1")
|
||||
assert.Len(t, prompts2, 1)
|
||||
assert.Contains(t, prompts2[0], "Message to agent 2")
|
||||
|
||||
t.Log("Multi-agent session coordination: PASS")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// T-302: Session Persistence Simulation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestIntegration_SessionPersistenceSimulation(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
agent := fake.NewFakeAgent("persist-agent")
|
||||
|
||||
// Start session
|
||||
sess1, err := agent.StartSession(ctx, "persist-session")
|
||||
require.NoError(t, err)
|
||||
originalSessionID := sess1.CurrentSessionID()
|
||||
|
||||
// Simulate session close (as would happen on restart)
|
||||
err = sess1.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Simulate new session with same ID (as would happen on restore)
|
||||
sess2, err := agent.StartSession(ctx, "persist-session")
|
||||
require.NoError(t, err)
|
||||
restoredSessionID := sess2.CurrentSessionID()
|
||||
|
||||
// Verify session was "restored"
|
||||
assert.Equal(t, originalSessionID, restoredSessionID)
|
||||
assert.True(t, sess2.Alive())
|
||||
|
||||
t.Log("Session persistence simulation: PASS")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// T-303: Rate Limiter Integration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestIntegration_RateLimiterIntegration(t *testing.T) {
|
||||
// Create rate limiter: 2 messages per second
|
||||
rl := core.NewRateLimiter(2, time.Second)
|
||||
defer rl.Stop()
|
||||
|
||||
// First two should succeed
|
||||
assert.True(t, rl.Allow("user-rate"))
|
||||
assert.True(t, rl.Allow("user-rate"))
|
||||
|
||||
// Third should fail
|
||||
assert.False(t, rl.Allow("user-rate"))
|
||||
|
||||
// Different user should succeed
|
||||
assert.True(t, rl.Allow("other-user"))
|
||||
|
||||
// Wait for window to reset
|
||||
time.Sleep(1100 * time.Millisecond)
|
||||
|
||||
// Should be allowed again
|
||||
assert.True(t, rl.Allow("user-rate"))
|
||||
|
||||
t.Log("Rate limiter integration: PASS")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// T-304: Message Dedup Integration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestIntegration_MessageDedupIntegration(t *testing.T) {
|
||||
dedup := &core.MessageDedup{}
|
||||
|
||||
// Process a message
|
||||
msgID := "dedup-test-msg-001"
|
||||
isDup := dedup.IsDuplicate(msgID)
|
||||
assert.False(t, isDup, "first occurrence should not be duplicate")
|
||||
|
||||
// Same message again should be duplicate
|
||||
isDup = dedup.IsDuplicate(msgID)
|
||||
assert.True(t, isDup, "second occurrence should be duplicate")
|
||||
|
||||
// Different message should not be duplicate
|
||||
isDup = dedup.IsDuplicate("different-msg")
|
||||
assert.False(t, isDup)
|
||||
|
||||
// Empty should never be duplicate
|
||||
isDup = dedup.IsDuplicate("")
|
||||
assert.False(t, isDup)
|
||||
|
||||
t.Log("Message dedup integration: PASS")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// T-305: Command Registry Integration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestIntegration_CommandRegistryIntegration(t *testing.T) {
|
||||
registry := core.NewCommandRegistry()
|
||||
|
||||
// Add multiple commands
|
||||
registry.Add("start", "Start the process", "Starting...", "", "", "builtin")
|
||||
registry.Add("stop", "Stop the process", "Stopping...", "", "", "builtin")
|
||||
registry.Add("status", "Check status", "Status: running", "", "", "builtin")
|
||||
|
||||
// Verify all commands are registered
|
||||
commands := registry.ListAll()
|
||||
assert.Len(t, commands, 3)
|
||||
|
||||
// Resolve each command
|
||||
cmd, ok := registry.Resolve("start")
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "start", cmd.Name)
|
||||
|
||||
cmd, ok = registry.Resolve("stop")
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "stop", cmd.Name)
|
||||
|
||||
cmd, ok = registry.Resolve("status")
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "status", cmd.Name)
|
||||
|
||||
// Hyphen/underscore normalization
|
||||
registry.Add("my-cmd", "My command", "Running...", "", "", "builtin")
|
||||
cmd, ok = registry.Resolve("my_cmd") // Telegram sanitizes hyphens to underscores
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "my-cmd", cmd.Name)
|
||||
|
||||
t.Log("Command registry integration: PASS")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// T-306: Role Manager Integration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestIntegration_RoleManagerIntegration(t *testing.T) {
|
||||
manager := core.NewUserRoleManager()
|
||||
|
||||
manager.Configure("viewer", []core.RoleInput{
|
||||
{
|
||||
Name: "admin",
|
||||
UserIDs: []string{"admin1", "admin2"},
|
||||
},
|
||||
{
|
||||
Name: "developer",
|
||||
UserIDs: []string{"dev1", "dev2"},
|
||||
},
|
||||
{
|
||||
Name: "viewer",
|
||||
UserIDs: []string{"*"}, // wildcard - everyone else
|
||||
},
|
||||
})
|
||||
|
||||
// Admin users
|
||||
assert.Equal(t, "admin", manager.ResolveRole("admin1").Name)
|
||||
assert.Equal(t, "admin", manager.ResolveRole("admin2").Name)
|
||||
|
||||
// Developer users
|
||||
assert.Equal(t, "developer", manager.ResolveRole("dev1").Name)
|
||||
assert.Equal(t, "developer", manager.ResolveRole("dev2").Name)
|
||||
|
||||
// Unknown user gets viewer (wildcard)
|
||||
assert.Equal(t, "viewer", manager.ResolveRole("random-user").Name)
|
||||
|
||||
t.Log("Role manager integration: PASS")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// T-307: Platform Reply Integration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestIntegration_PlatformReplyIntegration(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
platform := new(mocks.MockPlatform)
|
||||
|
||||
// Expect specific reply
|
||||
expectedContent := "This is a reply from the platform"
|
||||
platform.On("Reply", ctx, mock.Anything, expectedContent).Return(nil)
|
||||
|
||||
// Simulate engine sending reply
|
||||
err := platform.Reply(ctx, nil, expectedContent)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify reply was called correctly
|
||||
platform.AssertExpectations(t)
|
||||
|
||||
t.Log("Platform reply integration: PASS")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// T-308: Agent Permission Flow
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestIntegration_AgentPermissionFlow(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create mock agent session
|
||||
session := fake.NewFakeAgentSession("perm-session")
|
||||
session.AddPermissionRequest("req-001", "Bash", "rm -rf /")
|
||||
session.AddResultEvent("Request handled")
|
||||
|
||||
agent := fake.NewFakeAgentWithSession("perm-agent", "perm-session", session)
|
||||
|
||||
// Start session
|
||||
sess, err := agent.StartSession(ctx, "perm-session")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Send message
|
||||
err = sess.Send("Run dangerous command", nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Collect events
|
||||
var events []core.Event
|
||||
for e := range sess.Events() {
|
||||
events = append(events, e)
|
||||
if e.Done {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Should have permission request and result
|
||||
assert.GreaterOrEqual(t, len(events), 2)
|
||||
|
||||
// Find permission request event
|
||||
var permRequest core.Event
|
||||
for _, e := range events {
|
||||
if e.Type == core.EventPermissionRequest {
|
||||
permRequest = e
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.Equal(t, "Bash", permRequest.ToolName)
|
||||
assert.Equal(t, "req-001", permRequest.RequestID)
|
||||
|
||||
t.Log("Agent permission flow: PASS")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// T-310: Cron Store Integration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestIntegration_CronStoreIntegration(t *testing.T) {
|
||||
store, err := core.NewCronStore(t.TempDir())
|
||||
require.NoError(t, err)
|
||||
|
||||
// Add cron job
|
||||
job := &core.CronJob{
|
||||
ID: "integration-cron",
|
||||
Description: "Integration test cron",
|
||||
Prompt: "Run test",
|
||||
CronExpr: "0 9 * * *",
|
||||
Enabled: true,
|
||||
}
|
||||
err = store.Add(job)
|
||||
require.NoError(t, err)
|
||||
|
||||
// List jobs
|
||||
jobs := store.List()
|
||||
assert.Len(t, jobs, 1)
|
||||
|
||||
// Disable job
|
||||
ok := store.SetEnabled("integration-cron", false)
|
||||
assert.True(t, ok)
|
||||
|
||||
// Verify disabled
|
||||
job = store.Get("integration-cron")
|
||||
assert.False(t, job.Enabled)
|
||||
|
||||
// Toggle mute
|
||||
store.SetMute("integration-cron", true)
|
||||
muted, ok := store.ToggleMute("integration-cron")
|
||||
assert.True(t, ok)
|
||||
assert.False(t, muted) // toggled from true to false
|
||||
|
||||
// Remove job
|
||||
ok = store.Remove("integration-cron")
|
||||
assert.True(t, ok)
|
||||
|
||||
jobs = store.List()
|
||||
assert.Empty(t, jobs)
|
||||
|
||||
t.Log("Cron store integration: PASS")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// T-311: Card Rendering Integration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestIntegration_CardRenderingIntegration(t *testing.T) {
|
||||
// Create a complex card
|
||||
card := core.NewCard().
|
||||
Title("Integration Test Card", "green").
|
||||
Markdown("## Welcome\nThis is a **test** card.").
|
||||
Markdown("- Item 1\n- Item 2\n- Item 3").
|
||||
Divider().
|
||||
Buttons(
|
||||
core.PrimaryBtn("Confirm", "act:/confirm"),
|
||||
core.DefaultBtn("Cancel", "act:/cancel"),
|
||||
core.DangerBtn("Delete", "act:/delete"),
|
||||
).
|
||||
Note("This is a footnote").
|
||||
Build()
|
||||
|
||||
// Verify card structure
|
||||
assert.NotNil(t, card)
|
||||
assert.NotNil(t, card.Header)
|
||||
assert.Equal(t, "Integration Test Card", card.Header.Title)
|
||||
assert.Equal(t, "green", card.Header.Color)
|
||||
assert.GreaterOrEqual(t, len(card.Elements), 5) // markdown + markdown + divider + buttons + note
|
||||
|
||||
// Verify text fallback
|
||||
text := card.RenderText()
|
||||
assert.Contains(t, text, "Integration Test Card")
|
||||
assert.Contains(t, text, "Welcome")
|
||||
assert.Contains(t, text, "Confirm")
|
||||
assert.Contains(t, text, "Delete")
|
||||
|
||||
// Verify button collection
|
||||
buttons := card.CollectButtons()
|
||||
assert.Len(t, buttons, 1)
|
||||
assert.Len(t, buttons[0], 3)
|
||||
|
||||
// Verify HasButtons
|
||||
assert.True(t, card.HasButtons())
|
||||
|
||||
t.Log("Card rendering integration: PASS")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// T-312: Env Merge Integration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestIntegration_EnvMergeIntegration(t *testing.T) {
|
||||
base := []string{"PATH=/usr/bin", "HOME=/home/user", "VAR=value"}
|
||||
|
||||
// Merge with overlapping keys
|
||||
extra := []string{"VAR=newvalue", "ADD=new", "HOME=/different"}
|
||||
merged := core.MergeEnv(base, extra)
|
||||
|
||||
// Should have: PATH (from base), HOME (overridden), VAR (overridden), ADD (from extra)
|
||||
assert.Len(t, merged, 4)
|
||||
|
||||
// Find VAR - should be new value
|
||||
for _, env := range merged {
|
||||
if len(env) > 3 && env[:3] == "VAR" {
|
||||
assert.Equal(t, "VAR=newvalue", env)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Find ADD - should be new
|
||||
found := false
|
||||
for _, env := range merged {
|
||||
if len(env) > 3 && env[:3] == "ADD" {
|
||||
assert.Equal(t, "ADD=new", env)
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.True(t, found)
|
||||
|
||||
t.Log("Env merge integration: PASS")
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// T-313: Message Attachment Handling
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestIntegration_MessageAttachmentHandling(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create session
|
||||
session := fake.NewFakeAgentSession("attach-session")
|
||||
agent := fake.NewFakeAgentWithSession("attach-agent", "attach-session", session)
|
||||
|
||||
sess, err := agent.StartSession(ctx, "attach-session")
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create message with attachments
|
||||
images := []core.ImageAttachment{
|
||||
{MimeType: "image/png", Data: []byte("fake png data"), FileName: "screenshot.png"},
|
||||
}
|
||||
files := []core.FileAttachment{
|
||||
{MimeType: "application/pdf", Data: []byte("fake pdf data"), FileName: "doc.pdf"},
|
||||
}
|
||||
|
||||
// Send with attachments
|
||||
err = sess.Send("Analyze this", images, files)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Verify prompts captured
|
||||
prompts := session.GetPrompts()
|
||||
assert.Len(t, prompts, 1)
|
||||
|
||||
t.Log("Message attachment handling: PASS")
|
||||
}
|
||||
Reference in New Issue
Block a user