|
| 1 | +"""Automated metadata, manifest, and plugin integrity test suite for CodeBox.""" |
| 2 | + |
| 3 | +import json |
| 4 | +import tomllib |
| 5 | +from pathlib import Path |
| 6 | + |
| 7 | +import pytest |
| 8 | + |
| 9 | +PROJECT_ROOT = Path(__file__).resolve().parents[1] |
| 10 | + |
| 11 | + |
| 12 | +@pytest.fixture(scope="module") |
| 13 | +def pyproject_data() -> dict: |
| 14 | + pyproject_path = PROJECT_ROOT / "pyproject.toml" |
| 15 | + assert pyproject_path.is_file(), "pyproject.toml not found in project root" |
| 16 | + with open(pyproject_path, "rb") as f: |
| 17 | + return tomllib.load(f) |
| 18 | + |
| 19 | + |
| 20 | +def test_version_parity(pyproject_data: dict) -> None: |
| 21 | + """Ensure version parity across pyproject.toml, version.py, and CHANGELOG.md.""" |
| 22 | + toml_version = pyproject_data.get("project", {}).get("version") |
| 23 | + assert toml_version is not None, "pyproject.toml missing project.version" |
| 24 | + |
| 25 | + # Check version.py |
| 26 | + import version |
| 27 | + |
| 28 | + assert version.APP_VERSION == toml_version, ( |
| 29 | + f"version.py APP_VERSION ({version.APP_VERSION}) != pyproject.toml ({toml_version})" |
| 30 | + ) |
| 31 | + assert version.__version__ == toml_version, ( |
| 32 | + f"version.py __version__ ({version.__version__}) != pyproject.toml ({toml_version})" |
| 33 | + ) |
| 34 | + |
| 35 | + # Check CHANGELOG.md contains the version section |
| 36 | + changelog_text = (PROJECT_ROOT / "CHANGELOG.md").read_text(encoding="utf-8") |
| 37 | + assert f"[{toml_version}]" in changelog_text, ( |
| 38 | + f"CHANGELOG.md missing release section for [{toml_version}]" |
| 39 | + ) |
| 40 | + |
| 41 | + |
| 42 | +def test_pyproject_required_fields(pyproject_data: dict) -> None: |
| 43 | + """Verify all expected project metadata fields exist in pyproject.toml.""" |
| 44 | + proj = pyproject_data.get("project", {}) |
| 45 | + assert proj.get("name") == "CodeBox" |
| 46 | + assert proj.get("description") |
| 47 | + assert proj.get("requires-python") |
| 48 | + assert proj.get("license", {}).get("text") == "MIT" |
| 49 | + assert isinstance(proj.get("authors"), list) and len(proj["authors"]) > 0 |
| 50 | + assert isinstance(proj.get("keywords"), list) and len(proj["keywords"]) >= 5 |
| 51 | + assert isinstance(proj.get("classifiers"), list) and len(proj["classifiers"]) >= 5 |
| 52 | + |
| 53 | + urls = proj.get("urls", {}) |
| 54 | + assert "Homepage" in urls |
| 55 | + assert "Repository" in urls |
| 56 | + assert "Issues" in urls |
| 57 | + |
| 58 | + |
| 59 | +def test_core_documentation_files_exist() -> None: |
| 60 | + """Ensure all standard documentation and license files exist and are non-empty.""" |
| 61 | + required_files = [ |
| 62 | + "README.md", |
| 63 | + "README_de.md", |
| 64 | + "llms.txt", |
| 65 | + "CHANGELOG.md", |
| 66 | + "LICENSE", |
| 67 | + "DEVELOPMENT_PLAN.md", |
| 68 | + "API_STATUS.md", |
| 69 | + ] |
| 70 | + for filename in required_files: |
| 71 | + p = PROJECT_ROOT / filename |
| 72 | + assert p.is_file(), f"Missing required file: {filename}" |
| 73 | + assert p.stat().st_size > 0, f"File is empty: {filename}" |
| 74 | + |
| 75 | + |
| 76 | +def test_bundled_plugins_valid_json() -> None: |
| 77 | + """Verify all JSON plugins in plugins/ parse and define required fields.""" |
| 78 | + plugins_dir = PROJECT_ROOT / "plugins" |
| 79 | + assert plugins_dir.is_dir(), "plugins/ directory not found" |
| 80 | + |
| 81 | + plugin_files = list(plugins_dir.glob("*.json")) |
| 82 | + assert len(plugin_files) >= 2, "Expected at least 2 bundled plugins" |
| 83 | + |
| 84 | + required_keys = {"name", "version", "extensions", "keywords", "comment_style", "auto_close_pairs"} |
| 85 | + for p in plugin_files: |
| 86 | + data = json.loads(p.read_text(encoding="utf-8")) |
| 87 | + missing = required_keys - set(data.keys()) |
| 88 | + assert not missing, f"Plugin {p.name} missing keys: {missing}" |
| 89 | + assert isinstance(data["extensions"], list) and len(data["extensions"]) > 0 |
| 90 | + assert isinstance(data["keywords"], list) and len(data["keywords"]) > 0 |
| 91 | + assert isinstance(data["auto_close_pairs"], dict) |
0 commit comments