diff --git a/scripts/_entrypoint.py b/scripts/_entrypoint.py new file mode 100644 index 0000000..36accf1 --- /dev/null +++ b/scripts/_entrypoint.py @@ -0,0 +1,13 @@ +"""Bootstrap direct script execution from the repository checkout.""" + +from __future__ import annotations + +import sys +from pathlib import Path + + +def add_repository_root_to_path() -> None: + """Make project packages importable when a script is run by file path.""" + repository_root = str(Path(__file__).resolve().parent.parent) + if repository_root not in sys.path: + sys.path.insert(0, repository_root) diff --git a/scripts/check_environment.py b/scripts/check_environment.py index fc33c8a..1fcd222 100644 --- a/scripts/check_environment.py +++ b/scripts/check_environment.py @@ -3,7 +3,12 @@ import sys from importlib.metadata import PackageNotFoundError, version -from ingestion.common.environment import ( +if __package__ in {None, ""}: + from _entrypoint import add_repository_root_to_path + + add_repository_root_to_path() + +from ingestion.common.environment import ( # noqa: E402 MINIMUM_PYTHON_VERSION, is_supported_python, ) diff --git a/scripts/check_github_connection.py b/scripts/check_github_connection.py index 98b6bd7..0996e77 100644 --- a/scripts/check_github_connection.py +++ b/scripts/check_github_connection.py @@ -2,10 +2,15 @@ from typing import Any -from ingestion.common.config import ConfigurationError, Settings -from ingestion.common.logging import configure_logging -from ingestion.github.client import GitHubClient -from ingestion.github.exceptions import GitHubAPIError +if __package__ in {None, ""}: + from _entrypoint import add_repository_root_to_path + + add_repository_root_to_path() + +from ingestion.common.config import ConfigurationError, Settings # noqa: E402 +from ingestion.common.logging import configure_logging # noqa: E402 +from ingestion.github.client import GitHubClient # noqa: E402 +from ingestion.github.exceptions import GitHubAPIError # noqa: E402 def require_mapping( diff --git a/tests/test_diagnostic_entrypoints.py b/tests/test_diagnostic_entrypoints.py new file mode 100644 index 0000000..cf65c73 --- /dev/null +++ b/tests/test_diagnostic_entrypoints.py @@ -0,0 +1,70 @@ +"""Regression tests for supported diagnostic invocation forms.""" + +from __future__ import annotations + +import os +import subprocess +import sys +from pathlib import Path + +import pytest + +REPOSITORY_ROOT = Path(__file__).resolve().parent.parent + + +def run_diagnostic( + command: list[str], *, without_token: bool = False +) -> subprocess.CompletedProcess[str]: + """Run a diagnostic from the checkout without inheriting import helpers.""" + environment = os.environ.copy() + environment.pop("PYTHONPATH", None) + if without_token: + environment["GITHUB_TOKEN"] = "" + + return subprocess.run( + [sys.executable, *command], + cwd=REPOSITORY_ROOT, + env=environment, + capture_output=True, + text=True, + timeout=30, + check=False, + ) + + +@pytest.mark.parametrize( + "command", + [ + ["scripts/check_environment.py"], + ["-m", "scripts.check_environment"], + ], + ids=["direct", "module"], +) +def test_environment_diagnostic_supports_both_invocation_forms( + command: list[str], +) -> None: + """The environment diagnostic must run both directly and as a module.""" + result = run_diagnostic(command) + + assert result.returncode == 0, result.stderr + assert "[SUCCESS] The development environment is ready." in result.stdout + + +@pytest.mark.parametrize( + "command", + [ + ["scripts/check_github_connection.py"], + ["-m", "scripts.check_github_connection"], + ], + ids=["direct", "module"], +) +def test_github_diagnostic_imports_in_both_invocation_forms_without_network( + command: list[str], +) -> None: + """Both entrypoints must reach configuration before requiring a token.""" + result = run_diagnostic(command, without_token=True) + combined_output = result.stdout + result.stderr + + assert result.returncode == 1 + assert "[ERROR] GITHUB_TOKEN is missing." in result.stdout + assert "ModuleNotFoundError" not in combined_output