"""Tests for CLI commands."""
import json
from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
from click.testing import CliRunner
from context_manager.cli import main
from models import ContextEntry, TodoListSnapshot
@pytest.fixture
def cli_runner() -> CliRunner:
"""Create a CLI runner for testing."""
return CliRunner()
@pytest.fixture
def mock_storage(temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> MagicMock:
"""Mock storage for CLI tests."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
return MagicMock()
class TestContextCommands:
"""Test context CLI commands."""
def test_context_save_with_content(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test saving context with inline content."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(
main,
[
"context",
"save",
"--type",
"code",
"--title",
"Test Context",
"--content",
"def hello(): pass",
"--tags",
"python,test",
],
)
assert result.exit_code == 0
assert "Context saved" in result.output
assert "ID:" in result.output
def test_context_save_with_file(
self, cli_runner: CliRunner, temp_db_path: str, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test saving context from file."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
# Create temporary file
test_file = tmp_path / "test.py"
test_file.write_text("def test(): pass")
result = cli_runner.invoke(
main,
["context", "save", "--type", "code", "--title", "From File", "--file", str(test_file)],
)
assert result.exit_code == 0
assert "Context saved" in result.output
def test_context_save_missing_content(self, cli_runner: CliRunner) -> None:
"""Test saving context without content or file."""
result = cli_runner.invoke(
main,
["context", "save", "--type", "code", "--title", "No Content"],
)
assert result.exit_code == 1
assert "Either --content or --file must be provided" in result.output
def test_context_list(
self, cli_runner: CliRunner, temp_db_path: str, sample_context: ContextEntry, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test listing contexts."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
# First save a context
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_context(sample_context)
result = cli_runner.invoke(main, ["context", "list"])
assert result.exit_code == 0
assert "Test Context" in result.output
def test_context_list_with_type_filter(
self, cli_runner: CliRunner, temp_db_path: str, sample_context: ContextEntry, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test listing contexts with type filter."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_context(sample_context)
result = cli_runner.invoke(main, ["context", "list", "--type", "code"])
assert result.exit_code == 0
def test_context_search(
self, cli_runner: CliRunner, temp_db_path: str, sample_context: ContextEntry, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test searching contexts."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_context(sample_context)
result = cli_runner.invoke(main, ["context", "search", "test"])
assert result.exit_code == 0
def test_context_show(
self, cli_runner: CliRunner, temp_db_path: str, sample_context: ContextEntry, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test showing a specific context."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_context(sample_context)
result = cli_runner.invoke(main, ["context", "show", sample_context.id])
assert result.exit_code == 0
assert "Test Context" in result.output
def test_context_show_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test showing a non-existent context."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(main, ["context", "show", "nonexistent-id"])
assert result.exit_code == 1
assert "not found" in result.output
def test_context_show_output(
self, cli_runner: CliRunner, temp_db_path: str, sample_context: ContextEntry, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test showing context output format."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_context(sample_context)
result = cli_runner.invoke(main, ["context", "show", sample_context.id])
assert result.exit_code == 0
assert "Test Context" in result.output
assert "Type:" in result.output
def test_context_delete(
self, cli_runner: CliRunner, temp_db_path: str, sample_context: ContextEntry, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test deleting a context."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_context(sample_context)
result = cli_runner.invoke(main, ["context", "delete", sample_context.id], input="y\n")
assert result.exit_code == 0
assert "deleted" in result.output
def test_context_delete_cancelled(
self, cli_runner: CliRunner, temp_db_path: str, sample_context: ContextEntry, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test cancelling context deletion."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_context(sample_context)
result = cli_runner.invoke(main, ["context", "delete", sample_context.id], input="n\n")
assert result.exit_code == 0
assert "Cancelled" in result.output
@patch("context_manager.cli.ChatGPTClient")
def test_context_save_and_query(
self,
mock_chatgpt_class: MagicMock,
cli_runner: CliRunner,
temp_db_path: str,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test save-and-query command."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
# Mock ChatGPT client
mock_client = MagicMock()
mock_client.get_second_opinion = MagicMock(return_value="Mocked response")
mock_chatgpt_class.return_value = mock_client
result = cli_runner.invoke(
main,
[
"context",
"save-and-query",
"--type",
"code",
"--title",
"Test",
"--content",
"test content",
],
)
assert result.exit_code == 0
assert "Context saved" in result.output
@patch("context_manager.cli.ChatGPTClient")
def test_context_ask_chatgpt(
self,
mock_chatgpt_class: MagicMock,
cli_runner: CliRunner,
temp_db_path: str,
sample_context: ContextEntry,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test ask-chatgpt command."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
# Save context first
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_context(sample_context)
# Mock ChatGPT client
mock_client = MagicMock()
mock_client.query_context = MagicMock(return_value="Mocked response")
mock_chatgpt_class.return_value = mock_client
result = cli_runner.invoke(
main,
["context", "ask-chatgpt", sample_context.id, "--question", "What is this?"],
)
assert result.exit_code == 0
@patch("context_manager.anthropic_client.ClaudeClient")
def test_context_ask_claude(
self,
mock_claude_class: MagicMock,
cli_runner: CliRunner,
temp_db_path: str,
sample_context: ContextEntry,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test ask-claude command."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
# Save context first
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_context(sample_context)
# Mock Claude client
mock_client = MagicMock()
mock_client.get_second_opinion = MagicMock(return_value="Mocked response")
mock_claude_class.return_value = mock_client
result = cli_runner.invoke(
main,
["context", "ask-claude", sample_context.id, "--question", "What is this?"],
)
assert result.exit_code == 0
@patch("context_manager.gemini_client.genai.configure")
@patch("context_manager.gemini_client.genai.GenerativeModel")
def test_context_ask_gemini(
self,
mock_model: MagicMock,
mock_configure: MagicMock,
cli_runner: CliRunner,
temp_db_path: str,
sample_context: ContextEntry,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test ask-gemini command."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
monkeypatch.setenv("GOOGLE_API_KEY", "test-key")
# Save context first
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_context(sample_context)
# Mock Gemini client
mock_instance = MagicMock()
mock_response = MagicMock()
mock_response.text = "Mocked Gemini response"
mock_instance.generate_content.return_value = mock_response
mock_model.return_value = mock_instance
result = cli_runner.invoke(
main,
["context", "ask-gemini", sample_context.id, "--question", "What is this?"],
)
assert result.exit_code == 0
@patch("context_manager.deepseek_client.OpenAI")
def test_context_ask_deepseek(
self,
mock_openai: MagicMock,
cli_runner: CliRunner,
temp_db_path: str,
sample_context: ContextEntry,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test ask-deepseek command."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
monkeypatch.setenv("DEEPSEEK_API_KEY", "test-key")
# Save context first
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_context(sample_context)
# Mock DeepSeek client
mock_client = MagicMock()
mock_response = MagicMock()
mock_response.choices = [MagicMock()]
mock_response.choices[0].message.content = "Mocked DeepSeek response"
mock_client.chat.completions.create.return_value = mock_response
mock_openai.return_value = mock_client
result = cli_runner.invoke(
main,
["context", "ask-deepseek", sample_context.id, "--question", "What is this?"],
)
assert result.exit_code == 0
class TestTodoCommands:
"""Test todo CLI commands."""
def test_todo_save(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test saving todos."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
# Create todos JSON
todos_data = [
{"content": "Task 1", "status": "pending", "activeForm": "Doing task 1"},
{"content": "Task 2", "status": "in_progress", "activeForm": "Doing task 2"},
]
result = cli_runner.invoke(
main,
["todo", "save", "--todos", json.dumps(todos_data), "--context", "Test todos"],
)
assert result.exit_code == 0
assert "saved" in result.output
def test_todo_save_missing_file(self, cli_runner: CliRunner) -> None:
"""Test saving todos without file."""
result = cli_runner.invoke(main, ["todo", "save"])
assert result.exit_code != 0
def test_todo_list(
self,
cli_runner: CliRunner,
temp_db_path: str,
sample_todo_snapshot: TodoListSnapshot,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test listing todo snapshots."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_todo_snapshot(sample_todo_snapshot)
result = cli_runner.invoke(main, ["todo", "list"])
assert result.exit_code == 0
assert "snapshots" in result.output or "Task" in result.output
def test_todo_show(
self,
cli_runner: CliRunner,
temp_db_path: str,
sample_todo_snapshot: TodoListSnapshot,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test showing a todo snapshot."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_todo_snapshot(sample_todo_snapshot)
result = cli_runner.invoke(main, ["todo", "show", sample_todo_snapshot.id])
assert result.exit_code == 0
assert "Task 1" in result.output
def test_todo_show_output(
self,
cli_runner: CliRunner,
temp_db_path: str,
sample_todo_snapshot: TodoListSnapshot,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test showing todo snapshot output format."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_todo_snapshot(sample_todo_snapshot)
result = cli_runner.invoke(main, ["todo", "show", sample_todo_snapshot.id])
assert result.exit_code == 0
assert "Task 1" in result.output
assert "Snapshot ID" in result.output
def test_todo_restore(
self,
cli_runner: CliRunner,
temp_db_path: str,
sample_todo_snapshot: TodoListSnapshot,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test restoring a todo snapshot."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_todo_snapshot(sample_todo_snapshot)
result = cli_runner.invoke(main, ["todo", "restore", sample_todo_snapshot.id])
assert result.exit_code == 0
assert "Task 1" in result.output
def test_todo_search(
self,
cli_runner: CliRunner,
temp_db_path: str,
sample_todo_snapshot: TodoListSnapshot,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test searching todo snapshots."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_todo_snapshot(sample_todo_snapshot)
result = cli_runner.invoke(main, ["todo", "search", "Task"])
assert result.exit_code == 0
def test_todo_delete(
self,
cli_runner: CliRunner,
temp_db_path: str,
sample_todo_snapshot: TodoListSnapshot,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test deleting a todo snapshot."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_todo_snapshot(sample_todo_snapshot)
result = cli_runner.invoke(main, ["todo", "delete", sample_todo_snapshot.id], input="y\n")
assert result.exit_code == 0
assert "deleted" in result.output
def test_todo_delete_cancelled(
self,
cli_runner: CliRunner,
temp_db_path: str,
sample_todo_snapshot: TodoListSnapshot,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test cancelling todo deletion."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
from context_manager.storage import ContextStorage
storage = ContextStorage(temp_db_path)
storage.save_todo_snapshot(sample_todo_snapshot)
result = cli_runner.invoke(main, ["todo", "delete", sample_todo_snapshot.id], input="n\n")
assert result.exit_code == 0
assert "Cancelled" in result.output
def test_todo_save_invalid_json(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test saving todos with invalid JSON."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(main, ["todo", "save", "--todos", "not-valid-json"])
assert result.exit_code == 1
assert "Invalid todos JSON" in result.output
def test_todo_show_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test showing non-existent todo snapshot."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(main, ["todo", "show", "nonexistent-id"])
assert result.exit_code == 1
assert "not found" in result.output
def test_todo_delete_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test deleting non-existent todo snapshot."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(main, ["todo", "delete", "nonexistent-id"])
assert result.exit_code == 1
def test_context_list_empty(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test listing contexts when empty."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(main, ["context", "list"])
assert result.exit_code == 0
def test_context_search_no_results(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test searching contexts with no results."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(main, ["context", "search", "nonexistent"])
assert result.exit_code == 0
assert "No contexts found" in result.output
def test_todo_search_no_results(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test searching todos with no results."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(main, ["todo", "search", "nonexistent"])
assert result.exit_code == 0
assert "No todo snapshots found" in result.output
def test_todo_restore_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test restoring non-existent todo snapshot."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(main, ["todo", "restore", "nonexistent-id"])
assert result.exit_code == 1
@patch("context_manager.cli.ChatGPTClient")
def test_context_save_and_query_with_file(
self, mock_client: MagicMock, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch, tmp_path: Path
) -> None:
"""Test save-and-query with file path."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
monkeypatch.setenv("OPENAI_API_KEY", "test-key")
# Create temp file
content_file = tmp_path / "content.txt"
content_file.write_text("def test(): pass")
# Mock ChatGPT response
mock_instance = MagicMock()
mock_instance.get_second_opinion.return_value = "Looks good"
mock_client.return_value = mock_instance
result = cli_runner.invoke(
main,
[
"context",
"save-and-query",
"--type",
"code",
"--title",
"Test",
"--file",
str(content_file),
],
)
assert result.exit_code == 0
assert "Looks good" in result.output
def test_context_save_and_query_missing_content(
self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test save-and-query without content or file."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(
main,
["context", "save-and-query", "--type", "code", "--title", "Test"],
)
assert result.exit_code == 1
assert "Either --content or --file must be provided" in result.output
@patch("context_manager.cli.ChatGPTClient")
def test_context_save_and_query_chatgpt_error(
self, mock_client: MagicMock, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test save-and-query with ChatGPT error."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
monkeypatch.setenv("OPENAI_API_KEY", "test-key")
# Mock ChatGPT error
mock_instance = MagicMock()
mock_instance.get_second_opinion.side_effect = Exception("API error")
mock_client.return_value = mock_instance
result = cli_runner.invoke(
main,
[
"context",
"save-and-query",
"--type",
"code",
"--title",
"Test",
"--content",
"test code",
],
)
assert result.exit_code == 1
assert "Error querying ChatGPT" in result.output
@patch("context_manager.anthropic_client.ClaudeClient")
def test_context_ask_claude_error(
self, mock_client: MagicMock, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test ask-claude with Claude error."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
monkeypatch.setenv("ANTHROPIC_API_KEY", "test-key")
# First save a context
result = cli_runner.invoke(
main,
["context", "save", "--type", "code", "--title", "Test", "--content", "test"],
)
# Extract context ID (remove trailing parenthesis)
context_id = result.output.split("ID: ")[1].split(")")[0].strip()
# Mock Claude error
mock_instance = MagicMock()
mock_instance.get_second_opinion.side_effect = Exception("API error")
mock_client.return_value = mock_instance
result = cli_runner.invoke(main, ["context", "ask-claude", context_id])
assert result.exit_code == 1
assert "Error querying Claude" in result.output
class TestErrorHandling:
"""Test error handling in CLI commands."""
def test_ask_chatgpt_context_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test ask-chatgpt with non-existent context."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(main, ["context", "ask-chatgpt", "nonexistent-id"])
assert result.exit_code == 1
assert "Context nonexistent-id not found" in result.output
def test_ask_chatgpt_with_question(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test ask-chatgpt with custom question."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
# First save a context and extract ID from save output
save_result = cli_runner.invoke(main, ["context", "save", "--type", "code", "--title", "Test", "--content", "test code"])
# Extract ID from save output - format: "Context saved (ID: <uuid>)"
import re
match = re.search(r"ID: ([a-f0-9-]+)", save_result.output)
assert match, f"Could not find context ID in: {save_result.output}"
context_id = match.group(1)
with patch("context_manager.cli.ChatGPTClient") as mock_client:
mock_instance = MagicMock()
mock_instance.get_second_opinion.return_value = "Custom answer"
mock_client.return_value = mock_instance
result = cli_runner.invoke(main, ["context", "ask-chatgpt", context_id, "--question", "What is this?"])
assert result.exit_code == 0
assert "Custom answer" in result.output
assert "Response saved" not in result.output # Should not save with custom question
def test_ask_gemini_context_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test ask-gemini with non-existent context."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(main, ["context", "ask-gemini", "nonexistent-id"])
assert result.exit_code == 1
assert "Context nonexistent-id not found" in result.output
def test_ask_deepseek_context_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test ask-deepseek with non-existent context."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(main, ["context", "ask-deepseek", "nonexistent-id"])
assert result.exit_code == 1
assert "Context nonexistent-id not found" in result.output
def test_context_delete_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test deleting non-existent context."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(main, ["context", "delete", "nonexistent-id"])
assert result.exit_code == 1
assert "Context nonexistent-id not found" in result.output
def test_context_show_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test showing non-existent context."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(main, ["context", "show", "nonexistent-id"])
assert result.exit_code == 1
assert "Context nonexistent-id not found" in result.output
def test_context_search_no_results(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test search with no matching results."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(main, ["context", "search", "nonexistentquery"])
assert result.exit_code == 0
assert "No contexts found" in result.output
def test_todo_restore_no_snapshot(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test restoring todo when no snapshot exists."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(main, ["todo", "restore"])
assert result.exit_code == 1
assert "No active todo snapshot found" in result.output
@patch("context_manager.cli.ChatGPTClient")
def test_ask_chatgpt_without_question(
self, mock_client: MagicMock, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test ask-chatgpt without custom question (second opinion)."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
# First save a context
save_result = cli_runner.invoke(main, ["context", "save", "--type", "code", "--title", "Test", "--content", "test code"])
import re
match = re.search(r"ID: ([a-f0-9-]+)", save_result.output)
assert match
context_id = match.group(1)
# Mock ChatGPT response
mock_instance = MagicMock()
mock_instance.get_second_opinion.return_value = "This looks good"
mock_client.return_value = mock_instance
result = cli_runner.invoke(main, ["context", "ask-chatgpt", context_id])
assert result.exit_code == 0
assert "This looks good" in result.output
assert "Response saved" in result.output # Should save when no custom question
@patch("context_manager.anthropic_client.ClaudeClient")
def test_ask_claude_without_question(
self, mock_client: MagicMock, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test ask-claude without custom question (second opinion)."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
# First save a context
save_result = cli_runner.invoke(main, ["context", "save", "--type", "code", "--title", "Test", "--content", "test code"])
import re
match = re.search(r"ID: ([a-f0-9-]+)", save_result.output)
assert match
context_id = match.group(1)
# Mock Claude response
mock_instance = MagicMock()
mock_instance.get_second_opinion.return_value = "Claude thinks this is fine"
mock_client.return_value = mock_instance
result = cli_runner.invoke(main, ["context", "ask-claude", context_id])
assert result.exit_code == 0
assert "Claude thinks this is fine" in result.output
assert "Response saved" in result.output
def test_ask_claude_context_not_found(self, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Test ask-claude with non-existent context."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
result = cli_runner.invoke(main, ["context", "ask-claude", "nonexistent-id"])
assert result.exit_code == 1
assert "Context nonexistent-id not found" in result.output
@patch("context_manager.gemini_client.genai.configure")
@patch("context_manager.gemini_client.genai.GenerativeModel")
def test_ask_gemini_without_question(
self,
mock_model: MagicMock,
mock_configure: MagicMock,
cli_runner: CliRunner,
temp_db_path: str,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test ask-gemini without custom question (second opinion)."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
monkeypatch.setenv("GOOGLE_API_KEY", "test-key")
# First save a context
save_result = cli_runner.invoke(main, ["context", "save", "--type", "code", "--title", "Test", "--content", "test code"])
import re
match = re.search(r"ID: ([a-f0-9-]+)", save_result.output)
assert match
context_id = match.group(1)
# Mock Gemini response
mock_instance = MagicMock()
mock_response = MagicMock()
mock_response.text = "Gemini analysis looks good"
mock_instance.generate_content.return_value = mock_response
mock_model.return_value = mock_instance
result = cli_runner.invoke(main, ["context", "ask-gemini", context_id])
assert result.exit_code == 0
assert "Gemini analysis looks good" in result.output
assert "Response saved" in result.output
@patch("context_manager.gemini_client.genai.configure")
@patch("context_manager.gemini_client.genai.GenerativeModel")
def test_ask_gemini_error(
self,
mock_model: MagicMock,
mock_configure: MagicMock,
cli_runner: CliRunner,
temp_db_path: str,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test ask-gemini with API error."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
monkeypatch.setenv("GOOGLE_API_KEY", "test-key")
# First save a context
save_result = cli_runner.invoke(main, ["context", "save", "--type", "code", "--title", "Test", "--content", "test code"])
import re
match = re.search(r"ID: ([a-f0-9-]+)", save_result.output)
assert match
context_id = match.group(1)
# Mock Gemini error
mock_instance = MagicMock()
mock_instance.generate_content.side_effect = Exception("API error")
mock_model.return_value = mock_instance
result = cli_runner.invoke(main, ["context", "ask-gemini", context_id])
assert result.exit_code == 1
assert "Error querying Gemini" in result.output
@patch("context_manager.deepseek_client.OpenAI")
def test_ask_deepseek_without_question(
self, mock_openai: MagicMock, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test ask-deepseek without custom question (second opinion)."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
monkeypatch.setenv("DEEPSEEK_API_KEY", "test-key")
# First save a context
save_result = cli_runner.invoke(main, ["context", "save", "--type", "code", "--title", "Test", "--content", "test code"])
import re
match = re.search(r"ID: ([a-f0-9-]+)", save_result.output)
assert match
context_id = match.group(1)
# Mock DeepSeek response
mock_client = MagicMock()
mock_response = MagicMock()
mock_response.choices = [MagicMock()]
mock_response.choices[0].message.content = "DeepSeek analysis complete"
mock_client.chat.completions.create.return_value = mock_response
mock_openai.return_value = mock_client
result = cli_runner.invoke(main, ["context", "ask-deepseek", context_id])
assert result.exit_code == 0
assert "DeepSeek analysis complete" in result.output
assert "Response saved" in result.output
@patch("context_manager.deepseek_client.OpenAI")
def test_ask_deepseek_error(
self, mock_openai: MagicMock, cli_runner: CliRunner, temp_db_path: str, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test ask-deepseek with API error."""
monkeypatch.setenv("MCP_TOOLZ_DB_PATH", temp_db_path)
monkeypatch.setenv("DEEPSEEK_API_KEY", "test-key")
# First save a context
save_result = cli_runner.invoke(main, ["context", "save", "--type", "code", "--title", "Test", "--content", "test code"])
import re
match = re.search(r"ID: ([a-f0-9-]+)", save_result.output)
assert match
context_id = match.group(1)
# Mock DeepSeek error
mock_client = MagicMock()
mock_client.chat.completions.create.side_effect = Exception("API error")
mock_openai.return_value = mock_client
result = cli_runner.invoke(main, ["context", "ask-deepseek", context_id])
assert result.exit_code == 1
assert "Error querying DeepSeek" in result.output