From 59be4fc95964b421abc6994bf38ec6cce840a520 Mon Sep 17 00:00:00 2001 From: Claude Sonnet Date: Fri, 29 May 2026 00:18:22 +0530 Subject: [PATCH 1/2] test(ci): add pytest discovery and lint validation workflow Add minimal GitHub Actions workflow to catch test suite breakage before merge: - Python 3.11 and 3.12 matrix - pytest --collect-only for test discovery sanity check - ruff check for linting (non-blocking) Fixes #123 Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/ci.yml | 44 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..0153599 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: Test Suite Validation + +on: + pull_request: + branches: + - main + paths: + - 'tests/**' + - 'requirements.txt' + - 'pyproject.toml' + - '.github/workflows/ci.yml' + push: + branches: + - main + paths: + - 'tests/**' + - 'requirements.txt' + - 'pyproject.toml' + +jobs: + test-validation: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.11', '3.12'] + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + + - name: Pytest discovery check + run: python -m pytest --collect-only tests/ + + - name: Lint check with ruff + run: python -m ruff check tests/ + continue-on-error: true From 09efa7e95beb0eb4334a6ebebcfe1d21aca06457 Mon Sep 17 00:00:00 2001 From: James Reilly Date: Sat, 30 May 2026 17:07:28 +0100 Subject: [PATCH 2/2] fix(ci): explicitly install pytest/ruff and stub dogtail for headless collection --- .github/workflows/ci.yml | 3 ++- tests/conftest.py | 23 +++++++++++++++++++++++ tests/service_catalog/shared/kube.py | 3 +++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 tests/conftest.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0153599..b80d2e6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,8 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + python -m pip install pytest ruff + - name: Pytest discovery check run: python -m pytest --collect-only tests/ diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..32b1c80 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,23 @@ +import os +import sys +from unittest.mock import MagicMock + +# Inject any missing environment variables required at module import time +os.environ.setdefault("TEST_NAMESPACE", "dummy-namespace") + +# Ensure repository root is in sys.path so 'tests.service_catalog...' imports succeed during collection +repo_root = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) +if repo_root not in sys.path: + sys.path.insert(0, repo_root) + +# Stub out native GUI/desktop libraries so pytest collection succeeds headlessly +class DummyModule(MagicMock): + def __getattr__(self, name): + return MagicMock() + +# Inject dogtail stubs into sys.modules +sys.modules["dogtail"] = DummyModule() +sys.modules["dogtail.rawinput"] = DummyModule() +sys.modules["dogtail.tree"] = DummyModule() +sys.modules["dogtail.utils"] = DummyModule() + diff --git a/tests/service_catalog/shared/kube.py b/tests/service_catalog/shared/kube.py index 34dffe3..ff956ac 100644 --- a/tests/service_catalog/shared/kube.py +++ b/tests/service_catalog/shared/kube.py @@ -10,11 +10,14 @@ NAMESPACE = os.environ["TEST_NAMESPACE"] APP_LABEL = os.environ.get("TEST_APP_LABEL", "app=service-catalog-workload") SERVICE_NAME = os.environ.get("TEST_SERVICE_NAME", "service-catalog") +TEST_NAMESPACE = NAMESPACE +TEST_SERVICE_NAME = SERVICE_NAME TEST_LANE = os.environ.get("TEST_LANE", "") RESULTS_DIR = Path(os.environ.get("TEST_RESULTS_DIR", "/tmp/results")) RESULTS_DIR.mkdir(parents=True, exist_ok=True) + def run_kubectl(*args: str) -> subprocess.CompletedProcess[str]: return subprocess.run( ["kubectl", *args],