From 1d16362ca66cece29c46d95153f50045edaa508f Mon Sep 17 00:00:00 2001 From: Enmanuel Date: Fri, 10 Apr 2026 23:16:52 +0000 Subject: [PATCH] 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) --- shell/effects/progress_integration_test.go | 18 +- shell/effects/progress_test.go | 187 ++++++++++++++++++++- 2 files changed, 194 insertions(+), 11 deletions(-) diff --git a/shell/effects/progress_integration_test.go b/shell/effects/progress_integration_test.go index 1d4685b..f1f7460 100644 --- a/shell/effects/progress_integration_test.go +++ b/shell/effects/progress_integration_test.go @@ -69,22 +69,22 @@ func TestIntegration_StreamToProgressReporter(t *testing.T) { 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]) + // 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 - if !strings.Contains(sender.edits[1], "Read") { - t.Errorf("edit[1] should mention Read, got %q", sender.edits[1]) + // 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 - if !strings.Contains(sender.edits[2], "Edit") { - t.Errorf("edit[2] should mention Edit, got %q", sender.edits[2]) + // 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 diff --git a/shell/effects/progress_test.go b/shell/effects/progress_test.go index 9be9338..42b7ae9 100644 --- a/shell/effects/progress_test.go +++ b/shell/effects/progress_test.go @@ -67,8 +67,9 @@ func TestProgressReporter_ToolUseEditsMessage(t *testing.T) { if len(sender.edits) != 1 { t.Fatalf("expected 1 edit, got %d", len(sender.edits)) } - if !strings.Contains(sender.edits[0], "Bash") { - t.Errorf("edit = %q, should contain tool name", sender.edits[0]) + // Should contain step number and the command + if !strings.Contains(sender.edits[0], "Paso 1") { + t.Errorf("edit = %q, should contain step number", sender.edits[0]) } if !strings.Contains(sender.edits[0], "ls -la") { t.Errorf("edit = %q, should contain tool input", sender.edits[0]) @@ -224,3 +225,185 @@ func TestProgressReporter_ToolInputTruncation(t *testing.T) { t.Error("truncated input should end with ...") } } + +// ── formatToolEvent unit tests ────────────────────────────────────────── + +func TestFormatToolEvent_PipelineHints(t *testing.T) { + tests := []struct { + name string + tool string + input string + wantSub string // substring that must be present + wantNot string // substring that must NOT be present (empty = skip) + }{ + { + name: "create-full.sh detected", + tool: "Bash", + input: "./dev-scripts/agent/create-full.sh hora-bot \"Hora Bot\"", + wantSub: "Creando agente", + }, + { + name: "health-check.sh detected", + tool: "Bash", + input: "./dev-scripts/agent/health-check.sh hora-bot", + wantSub: "health check", + }, + { + name: "notify-developer.sh detected", + tool: "Bash", + input: "./dev-scripts/agent/notify-developer.sh hora-bot agent \"Hora Bot\"", + wantSub: "bienvenida", + }, + { + name: "restart.sh detected", + tool: "Bash", + input: "./dev-scripts/server/restart.sh", + wantSub: "Reiniciando", + }, + { + name: "start.sh detected", + tool: "Bash", + input: "./dev-scripts/server/start.sh", + wantSub: "Arrancando", + }, + { + name: "go build detected", + tool: "Bash", + input: "go build -tags goolm ./...", + wantSub: "Compilando", + }, + { + name: "go test detected", + tool: "Bash", + input: "go test -tags goolm ./pkg/...", + wantSub: "tests", + }, + { + name: "generic Bash command", + tool: "Bash", + input: "cat /etc/hostname", + wantSub: "cat /etc/hostname", + }, + { + name: "Edit tool", + tool: "Edit", + input: "agents/hora-bot/config.yaml", + wantSub: "Editando", + }, + { + name: "Write tool", + tool: "Write", + input: "agents/hora-bot/prompts/system.md", + wantSub: "Editando", + }, + { + name: "Read tool", + tool: "Read", + input: "agents/hora-bot/agent.go", + wantSub: "Leyendo", + }, + { + name: "Glob tool", + tool: "Glob", + input: "agents/*/config.yaml", + wantSub: "Buscando", + }, + { + name: "Grep tool", + tool: "Grep", + input: "func Rules", + wantSub: "Buscando", + }, + { + name: "unknown tool with input", + tool: "CustomTool", + input: "some argument", + wantSub: "CustomTool", + }, + { + name: "unknown tool without input", + tool: "CustomTool", + input: "", + wantSub: "CustomTool", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := formatToolEvent(1, tt.tool, tt.input) + if !strings.Contains(got, tt.wantSub) { + t.Errorf("formatToolEvent(1, %q, %q) = %q, want substring %q", + tt.tool, tt.input, got, tt.wantSub) + } + if tt.wantNot != "" && strings.Contains(got, tt.wantNot) { + t.Errorf("formatToolEvent(1, %q, %q) = %q, should NOT contain %q", + tt.tool, tt.input, got, tt.wantNot) + } + // All outputs should have step prefix + if !strings.Contains(got, "Paso 1") { + t.Errorf("formatToolEvent output %q should contain step number", got) + } + }) + } +} + +func TestFormatToolEvent_StepCounter(t *testing.T) { + r1 := formatToolEvent(1, "Bash", "echo hello") + r5 := formatToolEvent(5, "Read", "file.go") + r12 := formatToolEvent(12, "Edit", "config.yaml") + + if !strings.Contains(r1, "Paso 1") { + t.Errorf("step 1: %q", r1) + } + if !strings.Contains(r5, "Paso 5") { + t.Errorf("step 5: %q", r5) + } + if !strings.Contains(r12, "Paso 12") { + t.Errorf("step 12: %q", r12) + } +} + +func TestProgressReporter_StepCounterIncrements(t *testing.T) { + sender := &mockProgressSender{} + pr := NewProgressReporter(sender, "!room:test", slog.Default()) + pr.minInterval = 0 + + fn := pr.StreamFunc() + fn(coretypes.StreamEvent{Kind: coretypes.StreamInit}) + fn(coretypes.StreamEvent{Kind: coretypes.StreamToolUse, ToolName: "Bash", ToolInput: "echo 1"}) + fn(coretypes.StreamEvent{Kind: coretypes.StreamToolUse, ToolName: "Read", ToolInput: "file.go"}) + fn(coretypes.StreamEvent{Kind: coretypes.StreamToolUse, ToolName: "Edit", ToolInput: "file.go"}) + + if len(sender.edits) != 3 { + t.Fatalf("expected 3 edits, got %d", len(sender.edits)) + } + if !strings.Contains(sender.edits[0], "Paso 1") { + t.Errorf("first edit should be Paso 1, got %q", sender.edits[0]) + } + if !strings.Contains(sender.edits[1], "Paso 2") { + t.Errorf("second edit should be Paso 2, got %q", sender.edits[1]) + } + if !strings.Contains(sender.edits[2], "Paso 3") { + t.Errorf("third edit should be Paso 3, got %q", sender.edits[2]) + } +} + +func TestTruncateInput(t *testing.T) { + tests := []struct { + input string + maxLen int + want string + }{ + {"short", 10, "short"}, + {"exactly10!", 10, "exactly10!"}, + {"this is longer than ten", 10, "this is..."}, + {"", 10, ""}, + } + + for _, tt := range tests { + got := truncateInput(tt.input, tt.maxLen) + if got != tt.want { + t.Errorf("truncateInput(%q, %d) = %q, want %q", tt.input, tt.maxLen, got, tt.want) + } + } +}