import logging from pathlib import Path from unittest.mock import patch import pytest from typer.testing import CliRunner from photools3.cli import app runner = CliRunner() CLI_LOGGER = "photools3.cli" @pytest.fixture def mock_photo_logic(tmp_path): """Fixture to mock the core logic of keywords command.""" with ( patch("photools3.cli.LlmSessionCaptionsAndKeywords") as mocked_session, patch("photools3.cli.Photo") as mocked_photo, ): # Configure session mock session_instance = mocked_session.return_value session_instance.call.return_value = { "caption": "A beautiful test image", "args": ["test", "mock", "image"], # Wait, the code uses 'tags' } # Let me re-check the code in cli.py # result.get("tags", []) session_instance.call.return_value = { "caption": "A beautiful test image", "tags": ["test", "mock", "image"], } # Configure photo mock photo_instance = mocked_photo.return_value photo_instance.path = tmp_path / "test_image.jpg" yield { "session": session_instance, "photo_class": mocked_photo, "photo_instance": photo_instance, } @pytest.fixture def dummy_file(tmp_path): """Fixture to create a dummy file for CLI testing.""" dummy_path = tmp_path / "test_image.jpg" dummy_path.write_text("dummy content") return dummy_path def test_keywords_command_success(mock_photo_logic, dummy_file, caplog): """Test the 'keywords' command with a valid file path.""" # Ensure the mock photo instance points to our dummy file mock_photo_logic["photo_class"].return_value.path = dummy_file with caplog.at_level(logging.INFO, logger=CLI_LOGGER): result = runner.invoke(app, ["keywords", str(dummy_file)]) assert result.exit_code == 0 assert "Processing: " in caplog.text # The result is logged as a raw dictionary string assert ( "{'caption': 'A beautiful test image', 'tags': ['test', 'mock', 'image']}" in caplog.text ) def test_keywords_command_file_not_found(caplog): """Test the 'keywords' command with a non-existent file path.""" non_existent_file = Path("non_existent_file.jpg") with caplog.at_level(logging.ERROR, logger=CLI_LOGGER): result = runner.invoke(app, ["keywords", str(non_existent_file)]) assert result.exit_code == 1 assert f"Error: File '{non_existent_file}' does not exist." in caplog.text def test_keywords_command_error_during_processing(mock_photo_logic, dummy_file): """Test the 'keywords' command when an exception occurs during processing.""" # Configure the session mock to raise an exception mock_photo_logic["session"].call.side_effect = Exception("LLM error") result = runner.invoke(app, ["keywords", str(dummy_file)]) # Note: cli.py raises typer.Exit(code=1) before logging the # "Error processing image: ..." message (the logger.error call is dead # code after the raise), so only the exit code can be asserted here. assert result.exit_code == 1 def test_version_command(): """Test the --version option.""" result = runner.invoke(app, ["--version"]) assert result.exit_code == 0 assert "photools3 version 0.1.0" in result.stdout