# #region Test.GitLLMExtension [C:3] [TYPE Module] [SEMANTICS test, git, llm, commit, message, generation] # @BRIEF Tests for git/llm_extension.py — GitLLMExtension.suggest_commit_message. # @RELATION BINDS_TO -> [GitLLMExtensionModule] # @TEST_CONTRACT: suggest_commit_message -> str | LLM-based commit message # @TEST_EDGE: empty_response -> fallback message # @TEST_EDGE: missing_choices -> fallback message import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src")) import os os.environ.setdefault("SECRET_KEY", "test-secret-key-for-testing") os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:") os.environ.setdefault("AUTH_DATABASE_URL", "sqlite:///:memory:") from unittest.mock import AsyncMock, MagicMock, patch import pytest from src.plugins.git.llm_extension import GitLLMExtension class TestGitLLMExtension: """GitLLMExtension — commit message generation via LLM.""" def setup_method(self): self.mock_client = MagicMock() self.mock_client.default_model = "gpt-4o-mini" self.extension = GitLLMExtension(self.mock_client) def test_init_stores_client(self): """Client stored on init.""" assert self.extension.client is self.mock_client @pytest.mark.asyncio async def test_suggest_commit_message_success(self): """Successful LLM response returns stripped content.""" self.mock_client.client.chat.completions.create = AsyncMock() mock_choice = MagicMock() mock_choice.message.content = " feat: add new feature " mock_response = MagicMock() mock_response.choices = [mock_choice] self.mock_client.client.chat.completions.create.return_value = mock_response result = await self.extension.suggest_commit_message( diff="+ new feature", history=["feat: old feature"], ) assert result == "feat: add new feature" @pytest.mark.asyncio async def test_suggest_commit_message_empty_choices(self): """Response with no choices returns fallback message.""" self.mock_client.client.chat.completions.create = AsyncMock() mock_response = MagicMock() mock_response.choices = [] mock_response.error = None self.mock_client.client.chat.completions.create.return_value = mock_response result = await self.extension.suggest_commit_message( diff="+ new feature", history=[], ) assert result == "Update dashboard configurations (LLM generation failed)" @pytest.mark.asyncio async def test_suggest_commit_message_no_response(self): """None response returns fallback message.""" self.mock_client.client.chat.completions.create = AsyncMock() self.mock_client.client.chat.completions.create.return_value = None result = await self.extension.suggest_commit_message( diff="+ new feature", history=[], ) assert result == "Update dashboard configurations (LLM generation failed)" @pytest.mark.asyncio async def test_suggest_commit_message_empty_history(self): """Empty history list is joined to empty string.""" self.mock_client.client.chat.completions.create = AsyncMock() mock_choice = MagicMock() mock_choice.message.content = "Initial commit" mock_response = MagicMock() mock_response.choices = [mock_choice] self.mock_client.client.chat.completions.create.return_value = mock_response result = await self.extension.suggest_commit_message( diff="+ initial", history=[], ) assert result == "Initial commit" @pytest.mark.asyncio async def test_suggest_commit_message_with_history(self): """Multiple history entries joined by newline.""" self.mock_client.client.chat.completions.create = AsyncMock() mock_choice = MagicMock() mock_choice.message.content = "fix: bug" mock_response = MagicMock() mock_response.choices = [mock_choice] self.mock_client.client.chat.completions.create.return_value = mock_response result = await self.extension.suggest_commit_message( diff="- bug line", history=["feat: a", "fix: b"], ) assert result == "fix: bug" @pytest.mark.asyncio async def test_suggest_commit_message_custom_prompt(self): """Custom prompt template is rendered and passed to LLM.""" self.mock_client.client.chat.completions.create = AsyncMock() mock_choice = MagicMock() mock_choice.message.content = "custom message" mock_response = MagicMock() mock_response.choices = [mock_choice] self.mock_client.client.chat.completions.create.return_value = mock_response with patch("src.plugins.git.llm_extension.render_prompt") as mock_render: mock_render.return_value = "Custom template output" await self.extension.suggest_commit_message( diff="+ change", history=["prev"], prompt_template="Custom template with {history} and {diff}", ) mock_render.assert_called_once() call_args = mock_render.call_args[0] assert "Custom template" in call_args[0] @pytest.mark.asyncio async def test_suggest_commit_message_model_and_temp(self): """Correct model and temperature passed to LLM.""" self.mock_client.client.chat.completions.create = AsyncMock() mock_choice = MagicMock() mock_choice.message.content = "msg" mock_response = MagicMock() mock_response.choices = [mock_choice] self.mock_client.client.chat.completions.create.return_value = mock_response await self.extension.suggest_commit_message( diff="+ change", history=["prev"], ) call_kwargs = self.mock_client.client.chat.completions.create.call_args[1] assert call_kwargs["model"] == "gpt-4o-mini" assert call_kwargs["temperature"] == 0.7 assert len(call_kwargs["messages"]) == 1 assert call_kwargs["messages"][0]["role"] == "user" # #endregion Test.GitLLMExtension