4ccc052f5b
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>
163 lines
4.7 KiB
Go
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
|
|
}
|