diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..fabdc2c --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,253 @@ +"""Tests for the CLI interface.""" + +import json +import pytest +from click.testing import CliRunner +from regex_humanizer.cli import main, explain, test, flavors, validate, convert + + +class TestCLIMain: + """Test the main CLI entry point.""" + + def test_main_help(self): + """Test that main help works.""" + runner = CliRunner() + result = runner.invoke(main, ["--help"]) + + assert result.exit_code == 0 + assert "Regex Humanizer" in result.output + assert "explain" in result.output + assert "test" in result.output + assert "interactive" in result.output + + +class TestCLIExplain: + """Test the explain command.""" + + def test_explain_simple_literal(self): + """Test explaining a simple literal.""" + runner = CliRunner() + result = runner.invoke(explain, ["hello"]) + + assert result.exit_code == 0 + assert "Pattern" in result.output or "hello" in result.output + + def test_explain_with_flavor_option(self): + """Test explaining with a flavor option.""" + runner = CliRunner() + result = runner.invoke(explain, ["--flavor", "javascript", "test"]) + + assert result.exit_code == 0 + assert "Flavor" in result.output or "javascript" in result.output + + def test_explain_with_json_output(self): + """Test explaining with JSON output.""" + runner = CliRunner() + result = runner.invoke(explain, ["--output", "json", "\\d+"]) + + assert result.exit_code == 0 + assert "{", "}" in result.output + + def test_explain_with_verbose(self): + """Test explaining with verbose flag.""" + runner = CliRunner() + result = runner.invoke(explain, ["--verbose", "\\d+"]) + + assert result.exit_code == 0 + assert "Features" in result.output or "digit" in result.output.lower() + + def test_explain_complex_pattern(self): + """Test explaining a complex pattern.""" + runner = CliRunner() + pattern = r"^(?:http|https)://[\w.-]+\.(?:com|org|net)$" + result = runner.invoke(explain, [pattern]) + + assert result.exit_code == 0 + assert "Pattern" in result.output + + def test_explain_phone_pattern(self): + """Test explaining a phone pattern.""" + runner = CliRunner() + result = runner.invoke(explain, [r"\d{3}-\d{4}"]) + + assert result.exit_code == 0 + + def test_explain_character_class(self): + """Test explaining a character class.""" + runner = CliRunner() + result = runner.invoke(explain, ["[a-zA-Z]+"]) + + assert result.exit_code == 0 + + +class TestCLITest: + """Test the test command.""" + + def test_test_simple_literal(self): + """Test generating test cases for a simple literal.""" + runner = CliRunner() + result = runner.invoke(test, ["hello"]) + + assert result.exit_code == 0 + assert "Matching" in result.output or "hello" in result.output + assert "Non-matching" in result.output + + def test_test_with_count_option(self): + """Test generating a specific number of test cases.""" + runner = CliRunner() + result = runner.invoke(test, ["--count", "3", "a"]) + + assert result.exit_code == 0 + + def test_test_with_json_output(self): + """Test generating test cases with JSON output.""" + runner = CliRunner() + result = runner.invoke(test, ["--output", "json", "test"]) + + assert result.exit_code == 0 + data = json.loads(result.output) + assert "matching" in data + assert "non_matching" in data + + def test_test_phone_pattern(self): + """Test generating test cases for a phone pattern.""" + runner = CliRunner() + result = runner.invoke(test, [r"\d{3}-\d{4}"]) + + assert result.exit_code == 0 + assert "Matching" in result.output + + def test_test_email_pattern(self): + """Test generating test cases for an email pattern.""" + runner = CliRunner() + result = runner.invoke(test, [r"[a-z]+@[a-z]+\.[a-z]+"]) + + assert result.exit_code == 0 + + def test_test_quantifier_pattern(self): + """Test generating test cases for a quantifier pattern.""" + runner = CliRunner() + result = runner.invoke(test, ["a{2,4}"]) + + assert result.exit_code == 0 + + +class TestCLIFlavors: + """Test the flavors command.""" + + def test_flavors_list(self): + """Test listing available flavors.""" + runner = CliRunner() + result = runner.invoke(flavors) + + assert result.exit_code == 0 + assert "pcre" in result.output.lower() + assert "javascript" in result.output.lower() + assert "python" in result.output.lower() + + +class TestCLIValidate: + """Test the validate command.""" + + def test_validate_valid_pattern(self): + """Test validating a valid pattern.""" + runner = CliRunner() + result = runner.invoke(validate, ["hello"]) + + assert result.exit_code == 0 + assert "PASSED" in result.output or "Validation" in result.output + + def test_validate_with_flavor(self): + """Test validating with a specific flavor.""" + runner = CliRunner() + result = runner.invoke(validate, ["--flavor", "javascript", "test"]) + + assert result.exit_code == 0 + + def test_validate_complex_pattern(self): + """Test validating a complex pattern.""" + runner = CliRunner() + pattern = r"^(?:http|https)://[\w.-]+\.(?:com|org|net)$" + result = runner.invoke(validate, [pattern]) + + assert result.exit_code == 0 + + +class TestCLIConvert: + """Test the convert command.""" + + def test_convert_pcre_to_js(self): + """Test converting a pattern from PCRE to JavaScript.""" + runner = CliRunner() + result = runner.invoke(convert, ["(?Phello)", "--from-flavor", "pcre", "--to-flavor", "javascript"]) + + assert result.exit_code == 0 + assert "Converted" in result.output + + def test_convert_with_defaults(self): + """Test converting with default flavors.""" + runner = CliRunner() + result = runner.invoke(convert, ["test"]) + + assert result.exit_code == 0 + assert "Original" in result.output + assert "Converted" in result.output + + +class TestCLIInteractive: + """Test the interactive command.""" + + def test_interactive_command_exists(self): + """Test that interactive command is available.""" + runner = CliRunner() + result = runner.invoke(main, ["interactive", "--help"]) + + assert result.exit_code == 0 + assert "interactive" in result.output.lower() + + def test_interactive_with_flavor(self): + """Test interactive mode with a specific flavor.""" + runner = CliRunner() + result = runner.invoke(main, ["interactive", "--flavor", "python"]) + + assert result.exit_code == 0 + + +class TestCLIIntegration: + """Integration tests for CLI.""" + + def test_flavor_option_global(self): + """Test that global flavor option works.""" + runner = CliRunner() + result = runner.invoke(main, ["--flavor", "python", "explain", "test"]) + + assert result.exit_code == 0 + + def test_error_handling_invalid_pattern(self): + """Test error handling for invalid patterns.""" + runner = CliRunner() + result = runner.invoke(explain, ["["]) + + assert result.exit_code == 0 + + def test_output_json_structure(self): + """Test JSON output has correct structure.""" + runner = CliRunner() + result = runner.invoke(explain, ["--output", "json", "\\d+"]) + + assert result.exit_code == 0 + data = json.loads(result.output) + assert "pattern" in data + assert "flavor" in data + assert "explanation" in data + + def test_output_test_json_structure(self): + """Test JSON test output has correct structure.""" + runner = CliRunner() + result = runner.invoke(test, ["--output", "json", "\\d"]) + + assert result.exit_code == 0 + data = json.loads(result.output) + assert "pattern" in data + assert "matching" in data + assert "non_matching" in data