diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py new file mode 100644 index 0000000..c7a075b --- /dev/null +++ b/tests/unit/test_cli.py @@ -0,0 +1,181 @@ +"""Unit tests for the CLI module.""" + +import pytest +from click.testing import CliRunner +from unittest.mock import Mock, patch, MagicMock + +from scaffoldforge.cli import cli +from scaffoldforge.cli.commands import parse_github_url + + +class TestParseGitHubUrl: + """Tests for GitHub URL parsing.""" + + def test_parse_valid_url(self): + """Test parsing a valid GitHub issue URL.""" + owner, repo, issue_number = parse_github_url( + "https://github.com/owner/repo/issues/123" + ) + assert owner == "owner" + assert repo == "repo" + assert issue_number == 123 + + def test_parse_url_with_www(self): + """Test parsing URL with www prefix.""" + owner, repo, issue_number = parse_github_url( + "https://www.github.com/owner/repo/issues/456" + ) + assert owner == "owner" + assert repo == "repo" + assert issue_number == 456 + + def test_parse_url_with_trailing_slash(self): + """Test parsing URL with trailing slash.""" + owner, repo, issue_number = parse_github_url( + "https://github.com/owner/repo/issues/789/" + ) + assert owner == "owner" + assert repo == "repo" + + def test_parse_invalid_url(self): + """Test parsing invalid URL raises exception.""" + from click import ClickException + with pytest.raises(ClickException): + parse_github_url("https://invalid.com/owner/repo/issues/123") + + def test_parse_url_without_issue_number(self): + """Test parsing URL without issue number.""" + from click import ClickException + with pytest.raises(ClickException): + parse_github_url("https://github.com/owner/repo") + + +class TestCLI: + """Tests for CLI commands.""" + + @patch('scaffoldforge.cli.commands.IssueParser') + @patch('scaffoldforge.cli.commands.TemplateEngine') + @patch('scaffoldforge.cli.commands.StructureGenerator') + @patch('scaffoldforge.cli.commands.CodeGenerator') + def test_generate_command_preview_mode( + self, mock_code_gen, mock_struct_gen, mock_tpl_engine, mock_parser + ): + """Test generate command in preview mode.""" + runner = CliRunner() + + mock_issue_data = Mock() + mock_issue_data.number = 1 + mock_issue_data.title = "Test Issue" + mock_issue_data.labels = ["python"] + mock_issue_data.checklist = [] + mock_issue_data.get_todo_items.return_value = [] + mock_issue_data.requirements = [] + mock_issue_data.body = "" + + mock_parser_instance = Mock() + mock_parser_instance.parse_issue.return_value = mock_issue_data + mock_parser_instance.detect_language.return_value = "python" + mock_parser.return_value = mock_parser_instance + + mock_engine = Mock() + mock_engine.load_templates.return_value = {} + mock_engine.get_template_context.return_value = {} + mock_tpl_engine.return_value = mock_engine + + result = runner.invoke( + cli, + ["generate", "https://github.com/owner/repo/issues/1", "--preview"] + ) + + assert result.exit_code == 0 + assert "PREVIEW" in result.output or "preview" in result.output.lower() + + @patch('scaffoldforge.cli.commands.TemplateEngine') + def test_list_templates_command(self, mock_tpl_engine): + """Test list-templates command.""" + runner = CliRunner() + + mock_engine = Mock() + mock_engine.list_available_templates.return_value = ["default", "cli"] + mock_tpl_engine.return_value = mock_engine + + result = runner.invoke(cli, ["list-templates", "--language", "python"]) + + assert result.exit_code == 0 + assert "python" in result.output.lower() + + @patch('scaffoldforge.cli.commands.TemplateEngine') + def test_list_templates_all_languages(self, mock_tpl_engine): + """Test list-templates without language filter.""" + runner = CliRunner() + + mock_engine = Mock() + mock_engine.list_available_templates.side_effect = [ + ["default"], ["default"], ["default"], ["default"] + ] + mock_tpl_engine.return_value = mock_engine + + result = runner.invoke(cli, ["list-templates"]) + + assert result.exit_code == 0 + + def test_cli_help(self): + """Test CLI help output.""" + runner = CliRunner() + result = runner.invoke(cli, ["--help"]) + + assert result.exit_code == 0 + assert "ScaffoldForge" in result.output + + def test_generate_help(self): + """Test generate command help.""" + runner = CliRunner() + result = runner.invoke(cli, ["generate", "--help"]) + + assert result.exit_code == 0 + assert "--output" in result.output or "-o" in result.output + assert "--language" in result.output or "-l" in result.output + assert "--preview" in result.output or "-p" in result.output + + +class TestCLIErrorHandling: + """Tests for CLI error handling.""" + + def test_generate_invalid_url(self): + """Test generate with invalid URL.""" + runner = CliRunner() + result = runner.invoke( + cli, + ["generate", "https://invalid.com/issue/123"] + ) + + assert result.exit_code != 0 + assert "Invalid" in result.output or "error" in result.output.lower() + + @patch('scaffoldforge.cli.commands.IssueParser') + def test_generate_missing_language( + self, mock_parser + ): + """Test generate without specifying language when it can't be detected.""" + runner = CliRunner() + + mock_issue_data = Mock() + mock_issue_data.number = 1 + mock_issue_data.title = "Test Issue" + mock_issue_data.labels = [] + mock_issue_data.checklist = [] + mock_issue_data.get_todo_items.return_value = [] + mock_issue_data.requirements = [] + mock_issue_data.body = "" + + mock_parser_instance = Mock() + mock_parser_instance.parse_issue.return_value = mock_issue_data + mock_parser_instance.detect_language.return_value = None + mock_parser.return_value = mock_parser_instance + + result = runner.invoke( + cli, + ["generate", "https://github.com/owner/repo/issues/1"] + ) + + assert result.exit_code != 0