diff --git a/src/infrastructure/error_capture.py b/src/infrastructure/error_capture.py index dd1798a..ad89d43 100644 --- a/src/infrastructure/error_capture.py +++ b/src/infrastructure/error_capture.py @@ -149,6 +149,52 @@ def _log_error_event( logger.debug("Failed to log error event: %s", log_exc) +def _build_report_description( + exc: Exception, + source: str, + context: dict | None, + error_hash: str, + tb_str: str, + affected_file: str, + affected_line: int, + git_ctx: dict, +) -> str: + """Build the markdown description for a bug report task.""" + parts = [ + f"**Error:** {type(exc).__name__}: {str(exc)}", + f"**Source:** {source}", + f"**File:** {affected_file}:{affected_line}", + f"**Git:** {git_ctx.get('branch', '?')} @ {git_ctx.get('commit', '?')}", + f"**Time:** {datetime.now(UTC).isoformat()}", + f"**Hash:** {error_hash}", + ] + + if context: + ctx_str = ", ".join(f"{k}={v}" for k, v in context.items()) + parts.append(f"**Context:** {ctx_str}") + + parts.append(f"\n**Stack Trace:**\n```\n{tb_str[:2000]}\n```") + return "\n".join(parts) + + +def _log_bug_report_created(source: str, task_id: str, error_hash: str, title: str) -> None: + """Log a BUG_REPORT_CREATED event (best-effort).""" + try: + from swarm.event_log import EventType, log_event + + log_event( + EventType.BUG_REPORT_CREATED, + source=source, + task_id=task_id, + data={ + "error_hash": error_hash, + "title": title[:100], + }, + ) + except Exception as exc: + logger.warning("Bug report event log error: %s", exc) + + def _create_bug_report( exc: Exception, source: str, @@ -164,25 +210,20 @@ def _create_bug_report( from swarm.task_queue.models import create_task title = f"[BUG] {type(exc).__name__}: {str(exc)[:80]}" - - description_parts = [ - f"**Error:** {type(exc).__name__}: {str(exc)}", - f"**Source:** {source}", - f"**File:** {affected_file}:{affected_line}", - f"**Git:** {git_ctx.get('branch', '?')} @ {git_ctx.get('commit', '?')}", - f"**Time:** {datetime.now(UTC).isoformat()}", - f"**Hash:** {error_hash}", - ] - - if context: - ctx_str = ", ".join(f"{k}={v}" for k, v in context.items()) - description_parts.append(f"**Context:** {ctx_str}") - - description_parts.append(f"\n**Stack Trace:**\n```\n{tb_str[:2000]}\n```") + description = _build_report_description( + exc, + source, + context, + error_hash, + tb_str, + affected_file, + affected_line, + git_ctx, + ) task = create_task( title=title, - description="\n".join(description_parts), + description=description, assigned_to="default", created_by="system", priority="normal", @@ -190,24 +231,9 @@ def _create_bug_report( auto_approve=True, task_type="bug_report", ) - task_id = task.id - try: - from swarm.event_log import EventType, log_event - - log_event( - EventType.BUG_REPORT_CREATED, - source=source, - task_id=task_id, - data={ - "error_hash": error_hash, - "title": title[:100], - }, - ) - except Exception as exc: - logger.warning("Bug report screenshot error: %s", exc) - - return task_id + _log_bug_report_created(source, task.id, error_hash, title) + return task.id except Exception as task_exc: logger.debug("Failed to create bug report task: %s", task_exc) diff --git a/tests/infrastructure/test_error_capture.py b/tests/infrastructure/test_error_capture.py index ab47d2f..0481212 100644 --- a/tests/infrastructure/test_error_capture.py +++ b/tests/infrastructure/test_error_capture.py @@ -5,11 +5,13 @@ from datetime import UTC, datetime, timedelta from unittest.mock import patch from infrastructure.error_capture import ( + _build_report_description, _create_bug_report, _dedup_cache, _extract_traceback_info, _get_git_context, _is_duplicate, + _log_bug_report_created, _log_error_event, _notify_bug_report, _record_to_session, @@ -231,6 +233,68 @@ class TestLogErrorEvent: _log_error_event(e, "test", "abc123", "file.py", 42, {"branch": "main"}) +class TestBuildReportDescription: + """Test _build_report_description helper.""" + + def test_includes_error_info(self): + try: + raise RuntimeError("desc test") + except RuntimeError as e: + desc = _build_report_description( + e, + "test_src", + None, + "hash1", + "tb...", + "file.py", + 10, + {"branch": "main"}, + ) + assert "RuntimeError" in desc + assert "test_src" in desc + assert "file.py:10" in desc + assert "hash1" in desc + + def test_includes_context_when_provided(self): + try: + raise RuntimeError("ctx desc") + except RuntimeError as e: + desc = _build_report_description( + e, + "src", + {"path": "/api"}, + "h", + "tb", + "f.py", + 1, + {}, + ) + assert "path=/api" in desc + + def test_omits_context_when_none(self): + try: + raise RuntimeError("no ctx") + except RuntimeError as e: + desc = _build_report_description( + e, + "src", + None, + "h", + "tb", + "f.py", + 1, + {}, + ) + assert "**Context:**" not in desc + + +class TestLogBugReportCreated: + """Test _log_bug_report_created helper.""" + + def test_does_not_crash_on_missing_deps(self): + _log_bug_report_created("test", "task-1", "hash1", "title") + + class TestCreateBugReport: """Test _create_bug_report helper."""