package devagents import ( "context" "os" "path/filepath" "strings" "testing" "time" "github.com/enmanuel/agents/internal/config" "github.com/enmanuel/agents/pkg/decision" "github.com/enmanuel/agents/tools" "log/slog" ) // newMetricsTestAgent creates a minimal Agent for testing the !metrics command. // Does NOT connect to Matrix or LLM. func newMetricsTestAgent(logDir string) *Agent { cfg := &config.AgentConfig{ Agent: config.AgentMeta{ ID: "test-bot", Name: "Test Bot", }, } return &Agent{ cfg: cfg, logDir: logDir, toolReg: tools.NewRegistry(slog.Default()), logger: slog.Default(), startTime: time.Now(), commands: make(map[string]CommandHandler), cmdAliases: make(map[string]string), } } func TestCmdMetrics_NoLogDir(t *testing.T) { a := newMetricsTestAgent("") result := a.cmdMetrics(context.Background(), decision.MessageContext{}) if !strings.Contains(result, "no configurado") { t.Errorf("expected 'no configurado' message, got: %s", result) } } func TestCmdMetrics_NoLogsToday(t *testing.T) { dir := t.TempDir() // Create the agent subdirectory but with no log files agentDir := filepath.Join(dir, "test-bot") os.MkdirAll(agentDir, 0o755) a := newMetricsTestAgent(dir) result := a.cmdMetrics(context.Background(), decision.MessageContext{}) if !strings.Contains(result, "No hay logs") { t.Errorf("expected 'No hay logs' message, got: %s", result) } } func TestCmdMetrics_AggregatesCorrectly(t *testing.T) { dir := t.TempDir() agentDir := filepath.Join(dir, "test-bot") os.MkdirAll(agentDir, 0o755) // Create a JSONL log file for today today := time.Now().UTC().Format("2006-01-02") logFile := filepath.Join(agentDir, today+".jsonl") lines := []string{ `{"time":"2026-04-09T10:00:00Z","level":"DEBUG","msg":"handling event","agent_id":"test-bot","sender":"@user:example.com"}`, `{"time":"2026-04-09T10:00:01Z","level":"INFO","msg":"command_received","agent_id":"test-bot","command":"help"}`, `{"time":"2026-04-09T10:00:02Z","level":"DEBUG","msg":"handling event","agent_id":"test-bot","sender":"@user2:example.com"}`, `{"time":"2026-04-09T10:01:00Z","level":"DEBUG","msg":"LLM responded","agent_id":"test-bot","content_len":100,"duration_ms":500}`, `{"time":"2026-04-09T10:01:01Z","level":"DEBUG","msg":"LLM responded","agent_id":"test-bot","content_len":200,"duration_ms":300,"tokens_used":150}`, `{"time":"2026-04-09T10:02:00Z","level":"INFO","msg":"tool_exec_end","agent_id":"test-bot","tool":"current_time","duration_ms":5}`, `{"time":"2026-04-09T10:02:01Z","level":"WARN","msg":"tool_exec_error","agent_id":"test-bot","tool":"ssh_command","err":"timeout"}`, `{"time":"2026-04-09T10:03:00Z","level":"ERROR","msg":"something_failed","agent_id":"test-bot"}`, } content := strings.Join(lines, "\n") + "\n" if err := os.WriteFile(logFile, []byte(content), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } a := newMetricsTestAgent(dir) result := a.cmdMetrics(context.Background(), decision.MessageContext{}) // Verify the output contains expected metrics checks := map[string]string{ "messages": "| Mensajes recibidos | 2 |", "commands": "| Comandos ejecutados | 1 |", "llm_calls": "| Llamadas LLM | 2 |", "llm_tokens": "| Tokens LLM (total) | 150 |", "tool_calls": "| Llamadas a tools | 2 |", "tool_errors": "| Errores de tools | 1 |", "errors": "| Errores totales | 1 |", "entries": "| Entradas de log | 8 |", } for name, expected := range checks { if !strings.Contains(result, expected) { t.Errorf("missing %s: expected %q in output:\n%s", name, expected, result) } } } func TestCmdMetrics_LLMLatencyAverage(t *testing.T) { dir := t.TempDir() agentDir := filepath.Join(dir, "test-bot") os.MkdirAll(agentDir, 0o755) today := time.Now().UTC().Format("2006-01-02") logFile := filepath.Join(agentDir, today+".jsonl") lines := []string{ `{"time":"2026-04-09T10:01:00Z","level":"DEBUG","msg":"LLM responded","duration_ms":400}`, `{"time":"2026-04-09T10:01:01Z","level":"DEBUG","msg":"LLM responded","duration_ms":600}`, } content := strings.Join(lines, "\n") + "\n" os.WriteFile(logFile, []byte(content), 0o644) a := newMetricsTestAgent(dir) result := a.cmdMetrics(context.Background(), decision.MessageContext{}) // Average of 400 and 600 = 500 if !strings.Contains(result, "| Latencia LLM (media) | 500 ms |") { t.Errorf("expected average latency 500 ms in output:\n%s", result) } }