Repository-wide mechanical cleanup so `ruff check src tests` has a chance of passing; no behavioural changes. - Add `from __future__ import annotations` where PEP 604 unions are used. This has to come first: pyproject declares requires-python >= 3.9, where `X | None` is not evaluable at runtime unless annotations are stringified. - Replace typing.List/Dict/Tuple/Set with the builtin generics and Optional[X] with X | None, then drop the imports that became unused. The four actions/*/__init__.py files needed this by hand because ruff leaves unused imports in __init__.py alone (possible re-exports). - Strip shebangs from 72 importable modules. None of them are executable or invoked directly; the entry points are console_scripts and runpy. - Flatten nested `with` blocks, collapse needless-bool returns, and apply the remaining mechanical ruff fixes (PIE810, FLY002, PERF102, FURB192, RUF059, I001). - Pass check=False explicitly to the four subprocess.run() calls that inspect returncode themselves. That is the existing default. Two rewrites are visible to mocks, so their tests move with them: subprocess.run(stdout=PIPE, stderr=PIPE) became capture_output=True, and open(path, "r", ...) lost the redundant mode. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
99 lines
3.3 KiB
Python
99 lines
3.3 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import unittest
|
|
from types import SimpleNamespace
|
|
from unittest import mock
|
|
|
|
from pkgmgr.actions.code_scanning import (
|
|
CodeScanningError,
|
|
download_code_scanning,
|
|
)
|
|
|
|
_ALERTS = [
|
|
{
|
|
"number": 1,
|
|
"state": "open",
|
|
"html_url": "https://example/1",
|
|
"rule": {
|
|
"id": "py/sql-injection",
|
|
"security_severity_level": "high",
|
|
"severity": "error",
|
|
"description": "SQL injection",
|
|
},
|
|
"most_recent_instance": {
|
|
"location": {"path": "app.py", "start_line": 10},
|
|
"message": {"text": "Possible SQL injection"},
|
|
},
|
|
"tool": {"name": "CodeQL"},
|
|
}
|
|
]
|
|
|
|
|
|
def _cp(stdout: str = "", returncode: int = 0, stderr: str = "") -> SimpleNamespace:
|
|
return SimpleNamespace(stdout=stdout, returncode=returncode, stderr=stderr)
|
|
|
|
|
|
def _fake_gh(args):
|
|
if args[:2] == ["repo", "view"]:
|
|
return _cp(stdout="owner/pkgmgr-cs-test\n")
|
|
endpoint = args[1] if len(args) > 1 else ""
|
|
if endpoint.endswith("code-scanning/alerts"):
|
|
return _cp(stdout=json.dumps(_ALERTS))
|
|
if endpoint.endswith("code-scanning/analyses"):
|
|
return _cp(stdout=json.dumps([{"id": 42, "created_at": "2026-01-01"}]))
|
|
return _cp(returncode=1, stderr=f"unexpected: {args}")
|
|
|
|
|
|
class TestDownloadCodeScanning(unittest.TestCase):
|
|
def test_writes_alerts_summary_and_analyses(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
with (
|
|
mock.patch(
|
|
"pkgmgr.actions.code_scanning.shutil.which",
|
|
return_value="/usr/bin/gh",
|
|
),
|
|
mock.patch("pkgmgr.actions.code_scanning._gh", side_effect=_fake_gh),
|
|
):
|
|
result = download_code_scanning(output_dir=tmp)
|
|
|
|
self.assertEqual(result.repo, "owner/pkgmgr-cs-test")
|
|
self.assertEqual(result.alert_count, 1)
|
|
|
|
with open(os.path.join(tmp, "alerts.json"), encoding="utf-8") as f:
|
|
self.assertEqual(json.load(f), _ALERTS)
|
|
with open(os.path.join(tmp, "summary.md"), encoding="utf-8") as f:
|
|
summary = f.read()
|
|
self.assertIn("py/sql-injection", summary)
|
|
self.assertIn("app.py:10", summary)
|
|
self.assertIn("high", summary)
|
|
self.assertTrue(os.path.exists(os.path.join(tmp, "analyses.json")))
|
|
|
|
def test_default_output_dir_uses_repo_name(self) -> None:
|
|
with (
|
|
mock.patch(
|
|
"pkgmgr.actions.code_scanning.shutil.which", return_value="/usr/bin/gh"
|
|
),
|
|
mock.patch("pkgmgr.actions.code_scanning._gh", side_effect=_fake_gh),
|
|
):
|
|
result = download_code_scanning()
|
|
self.addCleanup(shutil.rmtree, "/tmp/pkgmgr-cs-test", ignore_errors=True)
|
|
self.assertTrue(
|
|
result.output_dir.startswith("/tmp/pkgmgr-cs-test/code-scanner/"),
|
|
msg=result.output_dir,
|
|
)
|
|
|
|
def test_errors_when_gh_missing(self) -> None:
|
|
with (
|
|
mock.patch("pkgmgr.actions.code_scanning.shutil.which", return_value=None),
|
|
self.assertRaises(CodeScanningError),
|
|
):
|
|
download_code_scanning(output_dir="/tmp/should-not-be-created")
|
|
|
|
|
|
if __name__ == "__main__": # pragma: no cover
|
|
unittest.main()
|