Files
agents_and_robots/shell/effects/progress_integration_test.go
T
egutierrez 4ccc052f5b test: tests de integracion para streaming + ProgressReporter
Agrega tests de integracion end-to-end que validan el pipeline completo:
streaming events → ProgressReporter → mock sender → Matrix messages.

- TestIntegration_StreamToProgressReporter: simula sesion completa con
  init, 3 tool_use, text, result y finalize — verifica 1 send + 4 edits
- TestIntegration_NoStreamingNoSideEffects: verifica que streaming=false
  no genera ningun side effect (regression test)
- TestIntegration_ProgressReporterWithSendError: verifica que errores
  de envio no causan panic y se manejan gracefully

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 22:59:27 +00:00

163 lines
4.7 KiB
Go

package effects
import (
"context"
"log/slog"
"strings"
"testing"
coretypes "github.com/enmanuel/agents/pkg/llm"
)
// TestIntegration_StreamToProgressReporter simulates the full flow:
// parseStreamLine produces events → ProgressReporter consumes them → mock sender records calls.
// This validates the complete pipeline from raw JSON lines to Matrix messages.
func TestIntegration_StreamToProgressReporter(t *testing.T) {
sender := &mockProgressSender{}
pr := NewProgressReporter(sender, "!room:integration", slog.Default())
pr.minInterval = 0 // disable rate limiting for test
fn := pr.StreamFunc()
// Simulate a realistic stream-json session:
// 1. Init event
fn(coretypes.StreamEvent{Kind: coretypes.StreamInit})
// 2. Tool use: Bash
fn(coretypes.StreamEvent{
Kind: coretypes.StreamToolUse,
ToolName: "Bash",
ToolInput: "git status",
})
// 3. Tool use: Read
fn(coretypes.StreamEvent{
Kind: coretypes.StreamToolUse,
ToolName: "Read",
ToolInput: "/home/user/project/main.go",
})
// 4. Tool use: Edit
fn(coretypes.StreamEvent{
Kind: coretypes.StreamToolUse,
ToolName: "Edit",
ToolInput: "/home/user/project/main.go",
})
// 5. Text event (intermediate, should be ignored)
fn(coretypes.StreamEvent{
Kind: coretypes.StreamText,
Content: "I've made the changes...",
})
// 6. Result event (should be ignored by progress reporter)
fn(coretypes.StreamEvent{
Kind: coretypes.StreamResult,
Content: "Here is the final answer with all changes applied.",
})
// Verify sends: only 1 initial send
if len(sender.sends) != 1 {
t.Errorf("expected 1 send (init), got %d", len(sender.sends))
}
if !strings.Contains(sender.sends[0], "Procesando") {
t.Errorf("init message should contain 'Procesando', got %q", sender.sends[0])
}
// Verify edits: 3 tool use events
if len(sender.edits) != 3 {
t.Fatalf("expected 3 edits (tool uses), got %d", len(sender.edits))
}
// First edit: Bash
if !strings.Contains(sender.edits[0], "Bash") {
t.Errorf("edit[0] should mention Bash, got %q", sender.edits[0])
}
if !strings.Contains(sender.edits[0], "git status") {
t.Errorf("edit[0] should show input, got %q", sender.edits[0])
}
// Second edit: Read
if !strings.Contains(sender.edits[1], "Read") {
t.Errorf("edit[1] should mention Read, got %q", sender.edits[1])
}
// Third edit: Edit
if !strings.Contains(sender.edits[2], "Edit") {
t.Errorf("edit[2] should mention Edit, got %q", sender.edits[2])
}
// All edits should target the same event ID
for i, target := range sender.editTargets {
if target != "$progress_msg_1" {
t.Errorf("editTarget[%d] = %q, want %q", i, target, "$progress_msg_1")
}
}
// Finalize
pr.Finalize("\u2705 *Completado*")
if len(sender.edits) != 4 {
t.Fatalf("expected 4 edits (3 tools + 1 finalize), got %d", len(sender.edits))
}
if !strings.Contains(sender.edits[3], "Completado") {
t.Errorf("finalize edit should contain 'Completado', got %q", sender.edits[3])
}
}
// TestIntegration_NoStreamingNoSideEffects verifies that when streaming is
// not enabled, no ProgressReporter is created and no Matrix side effects occur.
// This is a regression test for the streaming=false default behavior.
func TestIntegration_NoStreamingNoSideEffects(t *testing.T) {
sender := &mockProgressSender{}
// Simulate the handler check: streaming disabled → progress is nil
var progress *ProgressReporter // nil, because streaming is disabled
if progress != nil {
t.Error("progress reporter should be nil when streaming is disabled")
}
// Verify no sends or edits happened
if len(sender.sends) != 0 {
t.Errorf("expected 0 sends, got %d", len(sender.sends))
}
if len(sender.edits) != 0 {
t.Errorf("expected 0 edits, got %d", len(sender.edits))
}
}
// TestIntegration_ProgressReporterWithSendError verifies that the reporter
// handles send errors gracefully without panicking.
func TestIntegration_ProgressReporterWithSendError(t *testing.T) {
sender := &errorSender{}
pr := NewProgressReporter(sender, "!room:test", slog.Default())
pr.minInterval = 0
fn := pr.StreamFunc()
// Should not panic even when send fails
fn(coretypes.StreamEvent{Kind: coretypes.StreamInit})
// EventID should be empty since send failed
if pr.EventID() != "" {
t.Errorf("expected empty EventID after send error, got %q", pr.EventID())
}
// Finalize should be a no-op since no message was sent
pr.Finalize("Done")
}
// errorSender always returns errors.
type errorSender struct {
fakeMatrixSender
}
func (e *errorSender) SendMarkdownGetID(_ context.Context, _, _ string) (string, error) {
return "", context.DeadlineExceeded
}
func (e *errorSender) EditMessage(_ context.Context, _, _, _ string) error {
return context.DeadlineExceeded
}