1d16362ca6
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>
163 lines
4.8 KiB
Go
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
|
|
}
|