diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..06cdc9f --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,275 @@ +"""Tests for CLI commands.""" +import json +import tempfile +from pathlib import Path + +from click.testing import CliRunner + +from i18n_key_sync.cli import main + + +class TestCLIExtract: + """Tests for extract command.""" + + def test_extract_from_python_file(self, sample_python_code): + """Test extracting keys from Python file.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write(sample_python_code) + f.flush() + f_path = f.name + + runner = CliRunner() + result = runner.invoke(main, ["extract", f_path]) + + assert result.exit_code == 0 + assert "hello" in result.output or "hello" in result.output.lower() + assert "goodbye" in result.output or "goodbye" in result.output.lower() + + Path(f_path).unlink() + + def test_extract_json_output(self, sample_python_code): + """Test extract with JSON output format.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write('_("test_key")') + f.flush() + f_path = f.name + + runner = CliRunner() + result = runner.invoke(main, ["--output-format", "json", "extract", f_path]) + + assert result.exit_code == 0 + data = json.loads(result.output) + assert "test_key" in data + + Path(f_path).unlink() + + def test_extract_with_patterns(self, sample_python_code): + """Test extract with custom patterns.""" + with tempfile.NamedTemporaryFile(mode="w", suffix=".py", delete=False) as f: + f.write('t("custom_key")') + f.flush() + f_path = f.name + + runner = CliRunner() + result = runner.invoke(main, ["extract", f_path, "--patterns", "t"]) + + assert result.exit_code == 0 + assert "custom_key" in result.output + + Path(f_path).unlink() + + +class TestCLIValidate: + """Tests for validate command.""" + + def test_validate_valid_keys(self, temp_project_dir, temp_locale_dir): + """Test validation with all keys present.""" + runner = CliRunner() + result = runner.invoke(main, [ + "--locale-dir", str(temp_locale_dir), + "validate", + str(temp_project_dir / "src"), + ]) + + assert result.exit_code == 0 + assert "valid" in result.output.lower() or "matching" in result.output.lower() + + def test_validate_missing_keys(self, tmp_path, temp_locale_dir): + """Test validation with missing keys.""" + src_dir = tmp_path / "src" + src_dir.mkdir() + + code_file = src_dir / "main.py" + code_file.write_text('_("nonexistent_key")') + + runner = CliRunner() + result = runner.invoke(main, [ + "--locale-dir", str(temp_locale_dir), + "validate", + str(src_dir), + ]) + + assert result.exit_code in [0, 1] + assert "missing" in result.output.lower() or "nonexistent_key" in result.output + + def test_validate_strict_mode(self, tmp_path, temp_locale_dir): + """Test validation in strict mode.""" + src_dir = tmp_path / "src" + src_dir.mkdir() + + code_file = src_dir / "main.py" + code_file.write_text('_("missing_key")\n_("unused_in_locale")') + + locale_file = temp_locale_dir / "en.json" + locale_data = json.loads(locale_file.read_text()) + locale_data["unused_in_locale"] = "Unused" + locale_file.write_text(json.dumps(locale_data, indent=2)) + + runner = CliRunner() + result = runner.invoke(main, [ + "--locale-dir", str(temp_locale_dir), + "validate", + str(src_dir), + "--strict", + ]) + + assert result.exit_code == 3 + + def test_validate_json_output(self, temp_project_dir, temp_locale_dir): + """Test validate with JSON output.""" + runner = CliRunner() + result = runner.invoke(main, [ + "--output-format", "json", + "--locale-dir", str(temp_locale_dir), + "validate", + str(temp_project_dir / "src"), + ]) + + assert result.exit_code == 0 + data = json.loads(result.output) + assert "missing_keys" in data + assert "unused_keys" in data + assert "matching_keys" in data + + +class TestCLISync: + """Tests for sync command.""" + + def test_sync_dry_run(self, tmp_path, temp_locale_dir): + """Test sync in dry run mode.""" + src_dir = tmp_path / "src" + src_dir.mkdir() + + code_file = src_dir / "main.py" + code_file.write_text('_("new_key")') + + runner = CliRunner() + result = runner.invoke(main, [ + "--locale-dir", str(temp_locale_dir), + "sync", + str(src_dir), + "--dry-run", + ]) + + assert result.exit_code == 0 + assert "DRY RUN" in result.output or "dry" in result.output.lower() + + locale_file = temp_locale_dir / "en.json" + locale_data = json.loads(locale_file.read_text()) + assert "new_key" not in locale_data + + def test_sync_actual(self, tmp_path, temp_locale_dir): + """Test actual sync modifies files.""" + src_dir = tmp_path / "src" + src_dir.mkdir() + + code_file = src_dir / "main.py" + code_file.write_text('_("new_sync_key")') + + runner = CliRunner() + result = runner.invoke(main, [ + "--locale-dir", str(temp_locale_dir), + "sync", + str(src_dir), + ]) + + assert result.exit_code == 0 + + locale_file = temp_locale_dir / "en.json" + locale_data = json.loads(locale_file.read_text()) + assert "new_sync_key" in locale_data + + def test_sync_custom_placeholder(self, tmp_path, temp_locale_dir): + """Test sync with custom placeholder value.""" + src_dir = tmp_path / "src" + src_dir.mkdir() + + code_file = src_dir / "main.py" + code_file.write_text('_("placeholder_key")') + + runner = CliRunner() + result = runner.invoke(main, [ + "--locale-dir", str(temp_locale_dir), + "sync", + str(src_dir), + "--value", "TRANSLATE_ME", + ]) + + assert result.exit_code == 0 + + locale_file = temp_locale_dir / "en.json" + locale_data = json.loads(locale_file.read_text()) + assert locale_data["placeholder_key"] == "TRANSLATE_ME" + + +class TestCLIReport: + """Tests for report command.""" + + def test_report_generates_output(self, temp_project_dir, temp_locale_dir): + """Test report command generates output.""" + runner = CliRunner() + result = runner.invoke(main, [ + "--locale-dir", str(temp_locale_dir), + "report", + str(temp_project_dir / "src"), + ]) + + assert result.exit_code == 0 + assert len(result.output) > 0 + + def test_report_json_format(self, temp_project_dir, temp_locale_dir): + """Test report with JSON format.""" + runner = CliRunner() + result = runner.invoke(main, [ + "--locale-dir", str(temp_locale_dir), + "report", + str(temp_project_dir / "src"), + "--format", "json", + ]) + + assert result.exit_code == 0 + data = json.loads(result.output) + assert "summary" in data + + def test_report_markdown_format(self, temp_project_dir, temp_locale_dir): + """Test report with markdown format.""" + runner = CliRunner() + result = runner.invoke(main, [ + "--locale-dir", str(temp_locale_dir), + "report", + str(temp_project_dir / "src"), + "--format", "markdown", + ]) + + assert result.exit_code == 0 + assert "# i18n Key Report" in result.output + + def test_report_to_file(self, temp_project_dir, temp_locale_dir, tmp_path): + """Test report output to file.""" + output_file = tmp_path / "report.md" + + runner = CliRunner() + result = runner.invoke(main, [ + "--locale-dir", str(temp_locale_dir), + "report", + str(temp_project_dir / "src"), + "--output", str(output_file), + "--format", "markdown", + ]) + + assert result.exit_code == 0 + assert output_file.exists() + content = output_file.read_text() + assert "# i18n Key Report" in content + + +class TestCLIVersion: + """Tests for version flag.""" + + def test_version_flag(self): + """Test --version flag displays version.""" + runner = CliRunner() + result = runner.invoke(main, ["--version"]) + + assert result.exit_code == 0 + assert "i18n-key-sync" in result.output