Files
chenhg5-cc-connect/tests/integration/engine_platform_test.go
Claude db05dee804 feat(tests): add integration, performance tests and CI improvements
- Add 13 integration tests (T-300~T-313) covering engine-platform
  message flow, multi-agent coordination, session persistence,
  rate limiting, dedup, command registry, role manager, platform
  reply, agent permission flow, cron store, card rendering, env
  merge, and message attachments
- Add 14 performance benchmarks (T-400~T-413) for message latency,
  concurrent throughput, session switching, rate limiting,
  deduplication, command lookup, card rendering, cron store,
  role resolution, and multi-agent coordination
- Fix FakeAgentSession so NewFakeAgentWithSession properly
  preserves pre-configured session for first StartSession call
- Add performance-test Makefile target and update test-release
- Update CI to run performance benchmarks on release events

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 10:49:15 +08:00

498 lines
14 KiB
Go

//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")
}