Files
agents_and_robots/shell/effects/progress_integration_test.go
egutierrez 1d16362ca6 test: tests para deteccion de pasos del pipeline en ProgressReporter
Tests unitarios para formatToolEvent con todos los pipeline hints:
create-full.sh, health-check.sh, notify-developer.sh, restart.sh,
start.sh, go build, go test, Edit, Read, Glob, Grep, y fallback
generico. Incluye tests para el contador de pasos y truncateInput.

Actualiza test de integracion existente para el nuevo formato de
mensajes (step counter + nombres legibles vs raw tool names).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:16:52 +00:00

163 lines
4.8 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 with "git status" (generic command, no pipeline hint)
if !strings.Contains(sender.edits[0], "Paso 1") {
t.Errorf("edit[0] should have step 1, 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 → "Leyendo"
if !strings.Contains(sender.edits[1], "Leyendo") {
t.Errorf("edit[1] should contain 'Leyendo', got %q", sender.edits[1])
}
// Third edit: Edit → "Editando"
if !strings.Contains(sender.edits[2], "Editando") {
t.Errorf("edit[2] should contain 'Editando', 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
}