test: Add unit tests for paperclip.py (Refs #1236)
Adds comprehensive unit tests for src/timmy/paperclip.py covering: - PaperclipTask dataclass - PaperclipClient (init, get_tasks, update_task_status) - ResearchOrchestrator (get_gitea_issue, post_gitea_comment, run_research_pipeline, run) - PaperclipPoller (init, poll, run_research_task) - start_paperclip_poller function All external dependencies (httpx, config.settings, research_tools, research_triage) are mocked. Refs #1236
This commit is contained in:
569
tests/unit/test_paperclip.py
Normal file
569
tests/unit/test_paperclip.py
Normal file
@@ -0,0 +1,569 @@
|
||||
"""Unit tests for src/timmy/paperclip.py.
|
||||
|
||||
Refs #1236
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from types import ModuleType
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
|
||||
# ── Stub serpapi before any import of paperclip (it imports research_tools) ───
|
||||
|
||||
_serpapi_stub = ModuleType("serpapi")
|
||||
_google_search_mock = MagicMock()
|
||||
_serpapi_stub.GoogleSearch = _google_search_mock
|
||||
sys.modules.setdefault("serpapi", _serpapi_stub)
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
# ── PaperclipTask ─────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestPaperclipTask:
|
||||
"""PaperclipTask dataclass holds task data."""
|
||||
|
||||
def test_task_creation(self):
|
||||
from timmy.paperclip import PaperclipTask
|
||||
|
||||
task = PaperclipTask(id="task-123", kind="research", context={"key": "value"})
|
||||
assert task.id == "task-123"
|
||||
assert task.kind == "research"
|
||||
assert task.context == {"key": "value"}
|
||||
|
||||
def test_task_creation_empty_context(self):
|
||||
from timmy.paperclip import PaperclipTask
|
||||
|
||||
task = PaperclipTask(id="task-456", kind="other", context={})
|
||||
assert task.id == "task-456"
|
||||
assert task.kind == "other"
|
||||
assert task.context == {}
|
||||
|
||||
|
||||
# ── PaperclipClient ───────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestPaperclipClient:
|
||||
"""PaperclipClient interacts with the Paperclip API."""
|
||||
|
||||
def test_init_uses_settings(self):
|
||||
from timmy.paperclip import PaperclipClient
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.paperclip_url = "http://test.example:3100"
|
||||
mock_settings.paperclip_api_key = "test-api-key"
|
||||
mock_settings.paperclip_agent_id = "agent-123"
|
||||
mock_settings.paperclip_company_id = "company-456"
|
||||
mock_settings.paperclip_timeout = 45
|
||||
|
||||
client = PaperclipClient()
|
||||
assert client.base_url == "http://test.example:3100"
|
||||
assert client.api_key == "test-api-key"
|
||||
assert client.agent_id == "agent-123"
|
||||
assert client.company_id == "company-456"
|
||||
assert client.timeout == 45
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_tasks_makes_correct_request(self):
|
||||
from timmy.paperclip import PaperclipClient
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.paperclip_url = "http://test.example:3100"
|
||||
mock_settings.paperclip_api_key = "test-api-key"
|
||||
mock_settings.paperclip_agent_id = "agent-123"
|
||||
mock_settings.paperclip_company_id = "company-456"
|
||||
mock_settings.paperclip_timeout = 30
|
||||
|
||||
client = PaperclipClient()
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = [
|
||||
{"id": "task-1", "kind": "research", "context": {"issue_number": 42}},
|
||||
{"id": "task-2", "kind": "other", "context": {}},
|
||||
]
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
||||
mock_client.__aexit__ = AsyncMock(return_value=False)
|
||||
mock_client.get = AsyncMock(return_value=mock_response)
|
||||
|
||||
with patch("httpx.AsyncClient", return_value=mock_client):
|
||||
tasks = await client.get_tasks()
|
||||
|
||||
mock_client.get.assert_called_once_with(
|
||||
"http://test.example:3100/api/tasks",
|
||||
headers={"Authorization": "Bearer test-api-key"},
|
||||
params={
|
||||
"agent_id": "agent-123",
|
||||
"company_id": "company-456",
|
||||
"status": "queued",
|
||||
},
|
||||
)
|
||||
mock_response.raise_for_status.assert_called_once()
|
||||
assert len(tasks) == 2
|
||||
assert tasks[0].id == "task-1"
|
||||
assert tasks[0].kind == "research"
|
||||
assert tasks[1].id == "task-2"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_tasks_empty_response(self):
|
||||
from timmy.paperclip import PaperclipClient
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.paperclip_url = "http://test.example:3100"
|
||||
mock_settings.paperclip_api_key = "test-api-key"
|
||||
mock_settings.paperclip_agent_id = "agent-123"
|
||||
mock_settings.paperclip_company_id = "company-456"
|
||||
mock_settings.paperclip_timeout = 30
|
||||
|
||||
client = PaperclipClient()
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = []
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
||||
mock_client.__aexit__ = AsyncMock(return_value=False)
|
||||
mock_client.get = AsyncMock(return_value=mock_response)
|
||||
|
||||
with patch("httpx.AsyncClient", return_value=mock_client):
|
||||
tasks = await client.get_tasks()
|
||||
|
||||
assert tasks == []
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_tasks_raises_on_http_error(self):
|
||||
from timmy.paperclip import PaperclipClient
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.paperclip_url = "http://test.example:3100"
|
||||
mock_settings.paperclip_api_key = "test-api-key"
|
||||
mock_settings.paperclip_agent_id = "agent-123"
|
||||
mock_settings.paperclip_company_id = "company-456"
|
||||
mock_settings.paperclip_timeout = 30
|
||||
|
||||
client = PaperclipClient()
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
||||
mock_client.__aexit__ = AsyncMock(return_value=False)
|
||||
mock_client.get = AsyncMock(side_effect=httpx.HTTPError("Connection failed"))
|
||||
|
||||
with patch("httpx.AsyncClient", return_value=mock_client):
|
||||
with pytest.raises(httpx.HTTPError):
|
||||
await client.get_tasks()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_task_status_makes_correct_request(self):
|
||||
from timmy.paperclip import PaperclipClient
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.paperclip_url = "http://test.example:3100"
|
||||
mock_settings.paperclip_api_key = "test-api-key"
|
||||
mock_settings.paperclip_timeout = 30
|
||||
|
||||
client = PaperclipClient()
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
||||
mock_client.__aexit__ = AsyncMock(return_value=False)
|
||||
mock_client.patch = AsyncMock(return_value=MagicMock())
|
||||
|
||||
with patch("httpx.AsyncClient", return_value=mock_client):
|
||||
await client.update_task_status("task-123", "completed", "Task result here")
|
||||
|
||||
mock_client.patch.assert_called_once_with(
|
||||
"http://test.example:3100/api/tasks/task-123",
|
||||
headers={"Authorization": "Bearer test-api-key"},
|
||||
json={"status": "completed", "result": "Task result here"},
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_task_status_without_result(self):
|
||||
from timmy.paperclip import PaperclipClient
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.paperclip_url = "http://test.example:3100"
|
||||
mock_settings.paperclip_api_key = "test-api-key"
|
||||
mock_settings.paperclip_timeout = 30
|
||||
|
||||
client = PaperclipClient()
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
||||
mock_client.__aexit__ = AsyncMock(return_value=False)
|
||||
mock_client.patch = AsyncMock(return_value=MagicMock())
|
||||
|
||||
with patch("httpx.AsyncClient", return_value=mock_client):
|
||||
await client.update_task_status("task-123", "running")
|
||||
|
||||
mock_client.patch.assert_called_once_with(
|
||||
"http://test.example:3100/api/tasks/task-123",
|
||||
headers={"Authorization": "Bearer test-api-key"},
|
||||
json={"status": "running", "result": None},
|
||||
)
|
||||
|
||||
|
||||
# ── ResearchOrchestrator ───────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestResearchOrchestrator:
|
||||
"""ResearchOrchestrator coordinates research tasks."""
|
||||
|
||||
def test_init_creates_instances(self):
|
||||
from timmy.paperclip import ResearchOrchestrator
|
||||
|
||||
orchestrator = ResearchOrchestrator()
|
||||
assert orchestrator is not None
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_gitea_issue_makes_correct_request(self):
|
||||
from timmy.paperclip import ResearchOrchestrator
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.gitea_repo = "owner/repo"
|
||||
mock_settings.gitea_url = "http://gitea.example:3000"
|
||||
mock_settings.gitea_token = "gitea-token"
|
||||
|
||||
orchestrator = ResearchOrchestrator()
|
||||
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = {"number": 42, "title": "Test Issue"}
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
||||
mock_client.__aexit__ = AsyncMock(return_value=False)
|
||||
mock_client.get = AsyncMock(return_value=mock_response)
|
||||
|
||||
with patch("httpx.AsyncClient", return_value=mock_client):
|
||||
issue = await orchestrator.get_gitea_issue(42)
|
||||
|
||||
mock_client.get.assert_called_once_with(
|
||||
"http://gitea.example:3000/api/v1/repos/owner/repo/issues/42",
|
||||
headers={"Authorization": "token gitea-token"},
|
||||
)
|
||||
mock_response.raise_for_status.assert_called_once()
|
||||
assert issue["number"] == 42
|
||||
assert issue["title"] == "Test Issue"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_gitea_issue_raises_on_http_error(self):
|
||||
from timmy.paperclip import ResearchOrchestrator
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.gitea_repo = "owner/repo"
|
||||
mock_settings.gitea_url = "http://gitea.example:3000"
|
||||
mock_settings.gitea_token = "gitea-token"
|
||||
|
||||
orchestrator = ResearchOrchestrator()
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
||||
mock_client.__aexit__ = AsyncMock(return_value=False)
|
||||
mock_client.get = AsyncMock(side_effect=httpx.HTTPError("Not found"))
|
||||
|
||||
with patch("httpx.AsyncClient", return_value=mock_client):
|
||||
with pytest.raises(httpx.HTTPError):
|
||||
await orchestrator.get_gitea_issue(999)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_post_gitea_comment_makes_correct_request(self):
|
||||
from timmy.paperclip import ResearchOrchestrator
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.gitea_repo = "owner/repo"
|
||||
mock_settings.gitea_url = "http://gitea.example:3000"
|
||||
mock_settings.gitea_token = "gitea-token"
|
||||
|
||||
orchestrator = ResearchOrchestrator()
|
||||
|
||||
mock_client = AsyncMock()
|
||||
mock_client.__aenter__ = AsyncMock(return_value=mock_client)
|
||||
mock_client.__aexit__ = AsyncMock(return_value=False)
|
||||
mock_client.post = AsyncMock(return_value=MagicMock())
|
||||
|
||||
with patch("httpx.AsyncClient", return_value=mock_client):
|
||||
await orchestrator.post_gitea_comment(42, "Test comment body")
|
||||
|
||||
mock_client.post.assert_called_once_with(
|
||||
"http://gitea.example:3000/api/v1/repos/owner/repo/issues/42/comments",
|
||||
headers={"Authorization": "token gitea-token"},
|
||||
json={"body": "Test comment body"},
|
||||
)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_research_pipeline_returns_report(self):
|
||||
from timmy.paperclip import ResearchOrchestrator
|
||||
|
||||
orchestrator = ResearchOrchestrator()
|
||||
|
||||
mock_search_results = "Search result 1\nSearch result 2"
|
||||
mock_llm_response = MagicMock()
|
||||
mock_llm_response.text = "Research report summary"
|
||||
|
||||
mock_llm_client = MagicMock()
|
||||
mock_llm_client.completion = AsyncMock(return_value=mock_llm_response)
|
||||
|
||||
with patch("timmy.paperclip.google_web_search", new=AsyncMock(return_value=mock_search_results)):
|
||||
with patch("timmy.paperclip.get_llm_client", return_value=mock_llm_client):
|
||||
report = await orchestrator.run_research_pipeline("test query")
|
||||
|
||||
assert report == "Research report summary"
|
||||
mock_llm_client.completion.assert_called_once()
|
||||
call_args = mock_llm_client.completion.call_args
|
||||
# The prompt is passed as first positional arg, check it contains expected content
|
||||
prompt = call_args[0][0] if call_args[0] else call_args[1].get("messages", [""])[0]
|
||||
assert "Summarize" in prompt
|
||||
assert "Search result 1" in prompt
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_returns_error_when_missing_issue_number(self):
|
||||
from timmy.paperclip import ResearchOrchestrator
|
||||
|
||||
orchestrator = ResearchOrchestrator()
|
||||
result = await orchestrator.run({})
|
||||
assert result == "Missing issue_number in task context"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_executes_full_pipeline_with_triage_results(self):
|
||||
from timmy.paperclip import ResearchOrchestrator
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.gitea_repo = "owner/repo"
|
||||
mock_settings.gitea_url = "http://gitea.example:3000"
|
||||
mock_settings.gitea_token = "gitea-token"
|
||||
|
||||
orchestrator = ResearchOrchestrator()
|
||||
|
||||
mock_issue = {"number": 42, "title": "Test Research Topic"}
|
||||
mock_report = "Research report content"
|
||||
mock_triage_results = [
|
||||
{
|
||||
"action_item": MagicMock(title="Action 1"),
|
||||
"gitea_issue": {"number": 101},
|
||||
},
|
||||
{
|
||||
"action_item": MagicMock(title="Action 2"),
|
||||
"gitea_issue": {"number": 102},
|
||||
},
|
||||
]
|
||||
|
||||
orchestrator.get_gitea_issue = AsyncMock(return_value=mock_issue)
|
||||
orchestrator.run_research_pipeline = AsyncMock(return_value=mock_report)
|
||||
orchestrator.post_gitea_comment = AsyncMock()
|
||||
|
||||
with patch("timmy.paperclip.triage_research_report", new=AsyncMock(return_value=mock_triage_results)):
|
||||
result = await orchestrator.run({"issue_number": 42})
|
||||
|
||||
assert "Research complete for issue #42" in result
|
||||
orchestrator.get_gitea_issue.assert_called_once_with(42)
|
||||
orchestrator.run_research_pipeline.assert_called_once_with("Test Research Topic")
|
||||
orchestrator.post_gitea_comment.assert_called_once()
|
||||
comment_body = orchestrator.post_gitea_comment.call_args[0][1]
|
||||
assert "Research complete for issue #42" in comment_body
|
||||
assert "#101" in comment_body
|
||||
assert "#102" in comment_body
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_executes_full_pipeline_without_triage_results(self):
|
||||
from timmy.paperclip import ResearchOrchestrator
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.gitea_repo = "owner/repo"
|
||||
mock_settings.gitea_url = "http://gitea.example:3000"
|
||||
mock_settings.gitea_token = "gitea-token"
|
||||
|
||||
orchestrator = ResearchOrchestrator()
|
||||
|
||||
mock_issue = {"number": 42, "title": "Test Research Topic"}
|
||||
mock_report = "Research report content"
|
||||
|
||||
orchestrator.get_gitea_issue = AsyncMock(return_value=mock_issue)
|
||||
orchestrator.run_research_pipeline = AsyncMock(return_value=mock_report)
|
||||
orchestrator.post_gitea_comment = AsyncMock()
|
||||
|
||||
with patch("timmy.paperclip.triage_research_report", new=AsyncMock(return_value=[])):
|
||||
result = await orchestrator.run({"issue_number": 42})
|
||||
|
||||
assert "Research complete for issue #42" in result
|
||||
comment_body = orchestrator.post_gitea_comment.call_args[0][1]
|
||||
assert "No new issues were created" in comment_body
|
||||
|
||||
|
||||
# ── PaperclipPoller ────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestPaperclipPoller:
|
||||
"""PaperclipPoller polls for and executes tasks."""
|
||||
|
||||
def test_init_creates_client_and_orchestrator(self):
|
||||
from timmy.paperclip import PaperclipPoller
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.paperclip_poll_interval = 60
|
||||
|
||||
poller = PaperclipPoller()
|
||||
assert poller.client is not None
|
||||
assert poller.orchestrator is not None
|
||||
assert poller.poll_interval == 60
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_poll_returns_early_when_disabled(self):
|
||||
from timmy.paperclip import PaperclipPoller
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.paperclip_poll_interval = 0
|
||||
|
||||
poller = PaperclipPoller()
|
||||
poller.client.get_tasks = AsyncMock()
|
||||
|
||||
await poller.poll()
|
||||
|
||||
poller.client.get_tasks.assert_not_called()
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_poll_processes_research_tasks(self):
|
||||
from timmy.paperclip import PaperclipPoller, PaperclipTask
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.paperclip_poll_interval = 1
|
||||
|
||||
poller = PaperclipPoller()
|
||||
|
||||
mock_task = PaperclipTask(id="task-1", kind="research", context={"issue_number": 42})
|
||||
poller.client.get_tasks = AsyncMock(return_value=[mock_task])
|
||||
poller.run_research_task = AsyncMock()
|
||||
|
||||
# Stop after first iteration
|
||||
call_count = 0
|
||||
|
||||
async def mock_sleep(duration):
|
||||
nonlocal call_count
|
||||
call_count += 1
|
||||
if call_count >= 1:
|
||||
raise asyncio.CancelledError("Stop the loop")
|
||||
|
||||
import asyncio
|
||||
|
||||
with patch("asyncio.sleep", mock_sleep):
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await poller.poll()
|
||||
|
||||
poller.client.get_tasks.assert_called_once()
|
||||
poller.run_research_task.assert_called_once_with(mock_task)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_poll_logs_http_error_and_continues(self, caplog):
|
||||
import logging
|
||||
|
||||
from timmy.paperclip import PaperclipPoller
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.paperclip_poll_interval = 1
|
||||
|
||||
poller = PaperclipPoller()
|
||||
poller.client.get_tasks = AsyncMock(side_effect=httpx.HTTPError("Connection failed"))
|
||||
|
||||
call_count = 0
|
||||
|
||||
async def mock_sleep(duration):
|
||||
nonlocal call_count
|
||||
call_count += 1
|
||||
if call_count >= 1:
|
||||
raise asyncio.CancelledError("Stop the loop")
|
||||
|
||||
with patch("asyncio.sleep", mock_sleep):
|
||||
with caplog.at_level(logging.WARNING, logger="timmy.paperclip"):
|
||||
with pytest.raises(asyncio.CancelledError):
|
||||
await poller.poll()
|
||||
|
||||
assert any("Error polling Paperclip" in rec.message for rec in caplog.records)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_research_task_success(self):
|
||||
from timmy.paperclip import PaperclipPoller, PaperclipTask
|
||||
|
||||
poller = PaperclipPoller()
|
||||
|
||||
mock_task = PaperclipTask(id="task-1", kind="research", context={"issue_number": 42})
|
||||
|
||||
poller.client.update_task_status = AsyncMock()
|
||||
poller.orchestrator.run = AsyncMock(return_value="Research completed successfully")
|
||||
|
||||
await poller.run_research_task(mock_task)
|
||||
|
||||
assert poller.client.update_task_status.call_count == 2
|
||||
poller.client.update_task_status.assert_any_call("task-1", "running")
|
||||
poller.client.update_task_status.assert_any_call("task-1", "completed", "Research completed successfully")
|
||||
poller.orchestrator.run.assert_called_once_with({"issue_number": 42})
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_research_task_failure(self, caplog):
|
||||
import logging
|
||||
|
||||
from timmy.paperclip import PaperclipPoller, PaperclipTask
|
||||
|
||||
poller = PaperclipPoller()
|
||||
|
||||
mock_task = PaperclipTask(id="task-1", kind="research", context={"issue_number": 42})
|
||||
|
||||
poller.client.update_task_status = AsyncMock()
|
||||
poller.orchestrator.run = AsyncMock(side_effect=Exception("Something went wrong"))
|
||||
|
||||
with caplog.at_level(logging.ERROR, logger="timmy.paperclip"):
|
||||
await poller.run_research_task(mock_task)
|
||||
|
||||
assert poller.client.update_task_status.call_count == 2
|
||||
poller.client.update_task_status.assert_any_call("task-1", "running")
|
||||
poller.client.update_task_status.assert_any_call("task-1", "failed", "Something went wrong")
|
||||
assert any("Error running research task" in rec.message for rec in caplog.records)
|
||||
|
||||
|
||||
# ── start_paperclip_poller ─────────────────────────────────────────────────────
|
||||
|
||||
|
||||
class TestStartPaperclipPoller:
|
||||
"""start_paperclip_poller creates and starts the poller."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_starts_poller_when_enabled(self):
|
||||
from timmy.paperclip import start_paperclip_poller
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.paperclip_enabled = True
|
||||
|
||||
mock_poller = MagicMock()
|
||||
mock_poller.poll = AsyncMock()
|
||||
|
||||
created_tasks = []
|
||||
original_create_task = asyncio.create_task
|
||||
|
||||
def capture_create_task(coro):
|
||||
created_tasks.append(coro)
|
||||
return original_create_task(coro)
|
||||
|
||||
with patch("timmy.paperclip.PaperclipPoller", return_value=mock_poller):
|
||||
with patch("asyncio.create_task", side_effect=capture_create_task):
|
||||
await start_paperclip_poller()
|
||||
|
||||
assert len(created_tasks) == 1
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_does_nothing_when_disabled(self):
|
||||
from timmy.paperclip import start_paperclip_poller
|
||||
|
||||
with patch("timmy.paperclip.settings") as mock_settings:
|
||||
mock_settings.paperclip_enabled = False
|
||||
|
||||
with patch("timmy.paperclip.PaperclipPoller") as mock_poller_class:
|
||||
with patch("asyncio.create_task") as mock_create_task:
|
||||
await start_paperclip_poller()
|
||||
|
||||
mock_poller_class.assert_not_called()
|
||||
mock_create_task.assert_not_called()
|
||||
Reference in New Issue
Block a user