""" End-to-end tests for CodiMD CLI. Tests use real files and simulate full CLI workflows. """ import pytest import json import subprocess import tempfile from pathlib import Path from unittest.mock import Mock, patch import sys # Add parent directory to path sys.path.insert(0, str(Path(__file__).parent.parent.parent)) from cli_anything.codimd.core import Config, Session, CodiMDClient from cli_anything.codimd.codimd_cli import cli from click.testing import CliRunner class TestCLISubprocess: """Tests for installed CLI via subprocess.""" @staticmethod def _resolve_cli(cli_name: str) -> str: """Resolve CLI command path. If CLI_ANYTHING_FORCE_INSTALLED is set, use the command name directly. Otherwise, use the Python module path. """ if "CLI_ANYTHING_FORCE_INSTALLED" in str(__import__("os").environ): return cli_name # For testing, use python -m to run the module return f"python -m cli_anything.codimd.codimd_cli" def test_cli_help(self): """Test CLI help command.""" result = subprocess.run( ["python", "-m", "cli_anything.codimd.codimd_cli", "--help"], capture_output=True, text=True ) assert result.returncode == 0 assert "CodiMD CLI" in result.stdout def test_cli_version(self): """Test CLI version command.""" result = subprocess.run( ["python", "-m", "cli_anything.codimd.codimd_cli", "--version"], capture_output=True, text=True ) assert result.returncode == 0 assert "0.1.0" in result.stdout class TestE2EClickRunner: """End-to-end tests using Click CliRunner.""" def setup_method(self): """Set up test environment.""" self.runner = CliRunner() self.temp_dir = tempfile.mkdtemp() def teardown_method(self): """Clean up test environment.""" import shutil shutil.rmtree(self.temp_dir, ignore_errors=True) @patch("cli_anything.codimd.codimd_cli.Config") def test_config_get_command(self, mock_config_class): """Test config get command.""" mock_config = Mock() mock_config.server = "http://localhost:3000" mock_config.timeout = 30 mock_config.verify_ssl = True mock_config_class.return_value = mock_config result = self.runner.invoke(cli, ["config", "get"]) assert result.exit_code == 0 assert "Server:" in result.output or "localhost:3000" in result.output @patch("cli_anything.codimd.codimd_cli.Config") def test_config_set_command(self, mock_config_class): """Test config set command.""" mock_config = Mock() mock_config.server = "http://localhost:3000" mock_config_class.return_value = mock_config result = self.runner.invoke(cli, ["config", "set", "timeout", "60"]) assert result.exit_code == 0 def test_help_command(self): """Test help command.""" result = self.runner.invoke(cli, ["--help"]) assert result.exit_code == 0 assert "CodiMD CLI" in result.output assert "note" in result.output assert "user" in result.output assert "export" in result.output def test_version_command(self): """Test version command.""" result = self.runner.invoke(cli, ["--version"]) assert result.exit_code == 0 assert "0.1.0" in result.output def test_note_help(self): """Test note group help.""" result = self.runner.invoke(cli, ["note", "--help"]) assert result.exit_code == 0 assert "list" in result.output assert "get" in result.output assert "create" in result.output def test_user_help(self): """Test user group help.""" result = self.runner.invoke(cli, ["user", "--help"]) assert result.exit_code == 0 assert "me" in result.output assert "login" in result.output assert "logout" in result.output def test_export_help(self): """Test export group help.""" result = self.runner.invoke(cli, ["export", "--help"]) assert result.exit_code == 0 assert "markdown" in result.output assert "pdf" in result.output assert "html" in result.output @patch("cli_anything.codimd.codimd_cli.Config") @patch("cli_anything.codimd.codimd_cli.Session") def test_json_output_mode(self, mock_session_class, mock_config_class): """Test JSON output mode.""" mock_config = Mock() mock_config.config_dir = Path(self.temp_dir) mock_config_class.return_value = mock_config mock_session = Mock() mock_session.is_authenticated.return_value = False mock_session_class.return_value = mock_session result = self.runner.invoke(cli, ["--json", "config", "get"]) # Should output JSON assert '"status"' in result.output or '{"server"' in result.output or '"timeout"' in result.output class TestE2ENoteCommands: """End-to-end tests for note commands.""" def setup_method(self): """Set up test environment.""" self.runner = CliRunner() self.temp_dir = tempfile.mkdtemp() def teardown_method(self): """Clean up test environment.""" import shutil shutil.rmtree(self.temp_dir, ignore_errors=True) @patch("cli_anything.codimd.codimd_cli.get_client") @patch("cli_anything.codimd.codimd_cli.Config") @patch("cli_anything.codimd.codimd_cli.Session") def test_note_list_authenticated(self, mock_session_class, mock_config_class, mock_get_client): """Test listing notes when authenticated.""" mock_config = Mock() mock_config.config_dir = Path(self.temp_dir) mock_config_class.return_value = mock_config mock_session = Mock() mock_session.is_authenticated.return_value = True mock_session_class.return_value = mock_session mock_client = Mock() mock_note_manager = Mock() mock_note_manager.list_my_notes.return_value = [ {"id": "abc123", "text": "Test Note"} ] mock_get_client.return_value = (mock_client, mock_session) with patch("cli_anything.codimd.codimd_cli.NoteManager", return_value=mock_note_manager): result = self.runner.invoke(cli, ["note", "list"]) assert result.exit_code == 0 @patch("cli_anything.codimd.codimd_cli.get_client") @patch("cli_anything.codimd.codimd_cli.Config") @patch("cli_anything.codimd.codimd_cli.Session") def test_note_create(self, mock_session_class, mock_config_class, mock_get_client): """Test creating a note.""" mock_config = Mock() mock_config.config_dir = Path(self.temp_dir) mock_config_class.return_value = mock_config mock_session = Mock() mock_session_class.return_value = mock_session mock_client = Mock() mock_note_manager = Mock() mock_note_manager.create_note.return_value = { "id": "new-id", "url": "http://example.com/new-id", "status": "created" } mock_get_client.return_value = (mock_client, mock_session) with patch("cli_anything.codimd.codimd_cli.NoteManager", return_value=mock_note_manager): result = self.runner.invoke(cli, ["note", "create", "--content", "Test content"]) assert result.exit_code == 0 assert "created" in result.output class TestE2EExportCommands: """End-to-end tests for export commands.""" def setup_method(self): """Set up test environment.""" self.runner = CliRunner() self.temp_dir = tempfile.mkdtemp() def teardown_method(self): """Clean up test environment.""" import shutil shutil.rmtree(self.temp_dir, ignore_errors=True) @patch("cli_anything.codimd.codimd_cli.get_client") @patch("cli_anything.codimd.codimd_cli.Config") @patch("cli_anything.codimd.codimd_cli.Session") def test_export_markdown_to_file(self, mock_session_class, mock_config_class, mock_get_client): """Test exporting markdown to file.""" mock_config = Mock() mock_config.config_dir = Path(self.temp_dir) mock_config_class.return_value = mock_config mock_session = Mock() mock_session_class.return_value = mock_session mock_client = Mock() mock_export_manager = Mock() mock_export_manager.export_markdown.return_value = "# Test Note" mock_get_client.return_value = (mock_client, mock_session) output_file = Path(self.temp_dir) / "output.md" with patch("cli_anything.codimd.codimd_cli.ExportManager", return_value=mock_export_manager): result = self.runner.invoke( cli, ["export", "markdown", "test-id", "--output", str(output_file)] ) assert result.exit_code == 0 class TestE2EUserCommands: """End-to-end tests for user commands.""" def setup_method(self): """Set up test environment.""" self.runner = CliRunner() self.temp_dir = tempfile.mkdtemp() def teardown_method(self): """Clean up test environment.""" import shutil shutil.rmtree(self.temp_dir, ignore_errors=True) @patch("cli_anything.codimd.codimd_cli.get_client") @patch("cli_anything.codimd.codimd_cli.Config") @patch("cli_anything.codimd.codimd_cli.Session") def test_user_logout(self, mock_session_class, mock_config_class, mock_get_client): """Test user logout.""" mock_config = Mock() mock_config.config_dir = Path(self.temp_dir) mock_config_class.return_value = mock_config mock_session = Mock() mock_session_class.return_value = mock_session mock_get_client.return_value = (Mock(), mock_session) result = self.runner.invoke(cli, ["user", "logout"]) assert result.exit_code == 0 assert "logged out" in result.output.lower() @patch("cli_anything.codimd.codimd_cli.get_client") @patch("cli_anything.codimd.codimd_cli.Config") @patch("cli_anything.codimd.codimd_cli.Session") def test_user_me_unauthenticated(self, mock_session_class, mock_config_class, mock_get_client): """Test getting user info when not authenticated.""" mock_config = Mock() mock_config.config_dir = Path(self.temp_dir) mock_config_class.return_value = mock_config mock_session = Mock() mock_session.is_authenticated.return_value = False mock_session_class.return_value = mock_session mock_get_client.return_value = (Mock(), mock_session) result = self.runner.invoke(cli, ["user", "me"]) assert result.exit_code == 1 assert "authenticated" in result.output.lower() if __name__ == "__main__": pytest.main([__file__, "-v"])