diff --git a/.github/workflows/snyk.yml b/.github/workflows/snyk.yml index b8fee54f..78789639 100644 --- a/.github/workflows/snyk.yml +++ b/.github/workflows/snyk.yml @@ -35,11 +35,14 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt - pip install -e . - name: Setup Snyk if: ${{ env.SNYK_TOKEN != '' }} uses: snyk/actions/setup@master - - name: Run Snyk (dependencies) + - name: Run Snyk (requirements.txt) if: ${{ env.SNYK_TOKEN != '' }} - run: snyk test --severity-threshold=high + run: snyk test --file=requirements.txt --package-manager=pip --severity-threshold=high + + - name: Run Snyk (pyproject.toml) + if: ${{ env.SNYK_TOKEN != '' }} + run: snyk test --file=pyproject.toml --package-manager=pip --severity-threshold=high diff --git a/requirements.txt b/requirements.txt index 9aad3373..289085f0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,8 +12,9 @@ pyyaml>=6.0.3 ttkbootstrap>=1.20.3 pillow>=12.2.0 openai>=2.37.0 +httpx<0.29 python-dotenv>=1.2.2 -Jinja2>=3.1.6 +jinja2>=3.1.6 tiktoken>=0.13.0 cachetools>=7.1.3 sqlalchemy>=2.0.49 diff --git a/tests/test_snyk_workflow.py b/tests/test_snyk_workflow.py new file mode 100644 index 00000000..34671548 --- /dev/null +++ b/tests/test_snyk_workflow.py @@ -0,0 +1,68 @@ +from pathlib import Path + +try: + import tomllib +except ModuleNotFoundError: # pragma: no cover - Python <3.11 fallback + import tomli as tomllib + +import yaml + + +def _repo_root() -> Path: + return Path(__file__).resolve().parents[1] + + +def _load_pyproject() -> dict: + pyproject_path = _repo_root() / "pyproject.toml" + return tomllib.loads(pyproject_path.read_text(encoding="utf-8")) + + +def _load_requirements() -> list[str]: + requirements_path = _repo_root() / "requirements.txt" + return [ + line.strip() + for line in requirements_path.read_text(encoding="utf-8").splitlines() + if line.strip() and not line.lstrip().startswith("#") + ] + + +def _load_snyk_workflow() -> dict: + workflow_path = _repo_root() / ".github" / "workflows" / "snyk.yml" + return yaml.safe_load(workflow_path.read_text(encoding="utf-8")) + + +def test_runtime_manifests_match_exactly(): + pyproject = _load_pyproject() + runtime_dependencies = sorted(pyproject["project"]["dependencies"]) + requirements = sorted(_load_requirements()) + + assert runtime_dependencies == requirements + + +def test_snyk_workflow_scans_python_manifests_explicitly(): + workflow = _load_snyk_workflow() + steps = workflow["jobs"]["snyk"]["steps"] + + install_step = next( + (step for step in steps if step.get("name") == "Install dependencies"), None + ) + assert install_step is not None, "Snyk install step is missing" + assert "pip install -e ." not in install_step.get("run", "") + + requirements_scan = next( + (step for step in steps if step.get("name") == "Run Snyk (requirements.txt)"), None + ) + assert requirements_scan is not None, "requirements.txt Snyk scan step is missing" + assert ( + requirements_scan.get("run") + == "snyk test --file=requirements.txt --package-manager=pip --severity-threshold=high" + ) + + pyproject_scan = next( + (step for step in steps if step.get("name") == "Run Snyk (pyproject.toml)"), None + ) + assert pyproject_scan is not None, "pyproject.toml Snyk scan step is missing" + assert ( + pyproject_scan.get("run") + == "snyk test --file=pyproject.toml --package-manager=pip --severity-threshold=high" + )