This repository has been archived on 2026-03-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Timmy-time-dashboard/tests/timmy/test_tools_gitea.py
Trip T 7163b15300 feat: add Gitea issue creation — Timmy's self-improvement channel
Give Timmy the ability to file Gitea issues when he notices bugs,
stale state, or improvement opportunities in his own codebase.

Components:
- GiteaHand async API client (infrastructure/hands/gitea.py)
  - Token auth with ~/.config/gitea/token fallback
  - Create/list/close issues, dedup by title similarity
  - Graceful degradation when Gitea unreachable
- Tool functions (timmy/tools_gitea.py)
  - create_gitea_issue: file issues with dedup + work order bridge
  - list_gitea_issues: check existing backlog
  - Classified as SAFE (no confirmation needed)
- Thinking post-hook (_maybe_file_issues in thinking.py)
  - Every 20 thoughts, LLM classifies recent thoughts for actionable items
  - Auto-files bugs/improvements to Gitea with dedup
  - Bridges to local work order system for dashboard tracking
- Config: gitea_url, gitea_token, gitea_repo, gitea_enabled,
  gitea_timeout, thinking_issue_every

All 1426 tests pass, 74.17% coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:36:06 -04:00

216 lines
6.7 KiB
Python

"""Tests for Gitea tool functions.
Covers:
- create_gitea_issue tool (success, dedup skip, unavailable)
- list_gitea_issues tool (success, empty, unavailable)
- Work order bridge
- Tool safety classification
"""
from unittest.mock import AsyncMock, MagicMock, patch
# ---------------------------------------------------------------------------
# Tool safety classification
# ---------------------------------------------------------------------------
def test_gitea_tools_are_safe():
"""Gitea tools should be classified as safe (no confirmation needed)."""
from timmy.tool_safety import requires_confirmation
assert requires_confirmation("create_gitea_issue") is False
assert requires_confirmation("list_gitea_issues") is False
# ---------------------------------------------------------------------------
# create_gitea_issue
# ---------------------------------------------------------------------------
# All patches target infrastructure.hands.gitea.gitea_hand because
# tools_gitea.py uses deferred imports inside function bodies.
def test_create_issue_unavailable():
"""Should return message when Gitea is not configured."""
mock_hand = MagicMock()
mock_hand.available = False
with patch("infrastructure.hands.gitea.gitea_hand", mock_hand):
from timmy.tools_gitea import create_gitea_issue
result = create_gitea_issue("Test issue", "Body")
assert "not configured" in result
def test_create_issue_success():
"""Should create issue and return confirmation."""
from infrastructure.hands.gitea import GiteaResult
mock_hand = MagicMock()
mock_hand.available = True
mock_hand.find_duplicate = AsyncMock(return_value=None)
mock_hand.create_issue = AsyncMock(
return_value=GiteaResult(
operation="POST",
success=True,
data={"number": 42, "html_url": "http://localhost:3000/issues/42"},
)
)
with (
patch("infrastructure.hands.gitea.gitea_hand", mock_hand),
patch("timmy.tools_gitea._bridge_to_work_order"),
):
from timmy.tools_gitea import create_gitea_issue
result = create_gitea_issue("Test bug", "Bug description", "bug")
assert "#42" in result
assert "Test bug" in result
def test_create_issue_dedup_skip():
"""Should skip when similar issue already exists."""
mock_hand = MagicMock()
mock_hand.available = True
mock_hand.find_duplicate = AsyncMock(
return_value={"number": 10, "html_url": "http://localhost:3000/issues/10"}
)
with patch("infrastructure.hands.gitea.gitea_hand", mock_hand):
from timmy.tools_gitea import create_gitea_issue
result = create_gitea_issue("Existing issue")
assert "Skipped" in result
assert "#10" in result
def test_create_issue_api_failure():
"""Should return error message on API failure."""
from infrastructure.hands.gitea import GiteaResult
mock_hand = MagicMock()
mock_hand.available = True
mock_hand.find_duplicate = AsyncMock(return_value=None)
mock_hand.create_issue = AsyncMock(
return_value=GiteaResult(
operation="POST",
success=False,
error="HTTP 500: Internal Server Error",
)
)
with (
patch("infrastructure.hands.gitea.gitea_hand", mock_hand),
patch("timmy.tools_gitea._bridge_to_work_order"),
):
from timmy.tools_gitea import create_gitea_issue
result = create_gitea_issue("Test issue")
assert "Failed" in result
# ---------------------------------------------------------------------------
# list_gitea_issues
# ---------------------------------------------------------------------------
def test_list_issues_unavailable():
"""Should return message when Gitea is not configured."""
mock_hand = MagicMock()
mock_hand.available = False
with patch("infrastructure.hands.gitea.gitea_hand", mock_hand):
from timmy.tools_gitea import list_gitea_issues
result = list_gitea_issues()
assert "not configured" in result
def test_list_issues_success():
"""Should return formatted issue list."""
from infrastructure.hands.gitea import GiteaResult
mock_hand = MagicMock()
mock_hand.available = True
mock_hand.list_issues = AsyncMock(
return_value=GiteaResult(
operation="GET",
success=True,
data=[
{"number": 1, "title": "Bug fix", "labels": [{"name": "bug"}]},
{"number": 2, "title": "Feature request", "labels": []},
],
)
)
with patch("infrastructure.hands.gitea.gitea_hand", mock_hand):
from timmy.tools_gitea import list_gitea_issues
result = list_gitea_issues("open")
assert "#1" in result
assert "Bug fix" in result
assert "[bug]" in result
assert "#2" in result
def test_list_issues_empty():
"""Should return empty message when no issues."""
from infrastructure.hands.gitea import GiteaResult
mock_hand = MagicMock()
mock_hand.available = True
mock_hand.list_issues = AsyncMock(
return_value=GiteaResult(
operation="GET",
success=True,
data=[],
)
)
with patch("infrastructure.hands.gitea.gitea_hand", mock_hand):
from timmy.tools_gitea import list_gitea_issues
result = list_gitea_issues()
assert "No open issues" in result
# ---------------------------------------------------------------------------
# Work order bridge
# ---------------------------------------------------------------------------
def test_bridge_to_work_order(tmp_path):
"""Should create a work order in the local database."""
from timmy.tools_gitea import _bridge_to_work_order
# Point to a "data" subdir inside tmp_path so the code creates it
data_dir = tmp_path / "data"
data_dir.mkdir()
db_path = data_dir / "work_orders.db"
with patch("timmy.tools_gitea.settings") as mock_settings:
mock_settings.repo_root = str(tmp_path)
_bridge_to_work_order("Test WO", "Description", "bug")
import sqlite3
conn = sqlite3.connect(str(db_path))
conn.row_factory = sqlite3.Row
rows = conn.execute("SELECT * FROM work_orders").fetchall()
conn.close()
assert len(rows) == 1
assert rows[0]["title"] == "Test WO"
assert rows[0]["submitter"] == "timmy-thinking"
assert rows[0]["category"] == "bug"
def test_bridge_to_work_order_graceful_failure():
"""Should not raise when bridge fails."""
from timmy.tools_gitea import _bridge_to_work_order
with patch("timmy.tools_gitea.settings") as mock_settings:
mock_settings.repo_root = "/nonexistent/path/that/cannot/exist"
# Should not raise
_bridge_to_work_order("Test", "Desc", "bug")