diff --git a/.github/workflows/pr-preview.yml b/.github/workflows/pr-preview.yml index a8eaa9e..6701058 100644 --- a/.github/workflows/pr-preview.yml +++ b/.github/workflows/pr-preview.yml @@ -11,9 +11,10 @@ jobs: contents: read pull-requests: write steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 + persist-credentials: false - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 with: python-version: '3.13' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d1a1eab..acc8981 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,9 +10,10 @@ jobs: id-token: write contents: read steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 + persist-credentials: false - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 with: python-version: '3.13' @@ -27,11 +28,13 @@ jobs: - name: Get Version id: version + env: + REF_NAME: ${{ github.ref_name }} run: | RAW_VERSION=$(hatch version) echo "VERSION=$RAW_VERSION" >> $GITHUB_ENV - if [ "v$RAW_VERSION" != "${{ github.ref_name }}" ]; then - echo "Error: Git tag (${{ github.ref_name }}) does not match hatch version (v$RAW_VERSION)" + if [ "v$RAW_VERSION" != "$REF_NAME" ]; then + echo "Error: Git tag ($REF_NAME) does not match hatch version (v$RAW_VERSION)" exit 1 fi diff --git a/.github/workflows/version-check.yml b/.github/workflows/version-check.yml index 8fa9203..40b9536 100644 --- a/.github/workflows/version-check.yml +++ b/.github/workflows/version-check.yml @@ -7,13 +7,19 @@ on: - 'setup.py' - 'pyproject.toml' +permissions: + contents: read + pull-requests: write + issues: write + jobs: check_version: runs-on: ubuntu-latest steps: - - uses: actions/checkout@eef61447b9ff4aafe5dcd4e0bbf5d482be7e7871 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 # Fetch all history for all branches + persist-credentials: false - name: Check version increment id: version_check diff --git a/context7.json b/context7.json new file mode 100644 index 0000000..39e12bc --- /dev/null +++ b/context7.json @@ -0,0 +1,4 @@ +{ + "url": "https://context7.com/socketdev/socket-sdk-python", + "public_key": "pk_9HRbh6e3q2AL9xuPAiJYT" +} diff --git a/pyproject.toml b/pyproject.toml index 262ca14..bbcccd8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "socketdev" -version = "3.0.32" +version = "3.0.33" requires-python = ">= 3.9" dependencies = [ 'requests', diff --git a/socketdev/fullscans/__init__.py b/socketdev/fullscans/__init__.py index a2eb4f2..f295237 100644 --- a/socketdev/fullscans/__init__.py +++ b/socketdev/fullscans/__init__.py @@ -443,11 +443,20 @@ def to_dict(self): @classmethod def from_dict(cls, data: dict) -> "SocketAlert": + try: + category = SocketCategory(data["category"]) + except ValueError: + log.warning( + "Unknown SocketCategory %r; falling back to MISCELLANEOUS. " + "Upgrade socketdev to pick up newer categories.", + data["category"], + ) + category = SocketCategory.MISCELLANEOUS return cls( key=data["key"], type=data["type"], severity=SocketIssueSeverity(data["severity"]), - category=SocketCategory(data["category"]), + category=category, file=data.get("file"), start=data.get("start"), end=data.get("end"), diff --git a/socketdev/version.py b/socketdev/version.py index b70a418..46ae1b5 100644 --- a/socketdev/version.py +++ b/socketdev/version.py @@ -1 +1 @@ -__version__ = "3.0.32" +__version__ = "3.0.33" diff --git a/tests/unit/test_socket_alert_category.py b/tests/unit/test_socket_alert_category.py new file mode 100644 index 0000000..02ce1c1 --- /dev/null +++ b/tests/unit/test_socket_alert_category.py @@ -0,0 +1,63 @@ +""" +Unit tests for lenient SocketCategory parsing in SocketAlert.from_dict. + +Regression coverage for +https://github.com/SocketDev/socket-sdk-python/issues/78: the Socket API can +emit category values the SDK does not yet know about (e.g. ``"other"``). Strict +enum parsing turned that into a hard failure that took down every consumer +(notably socketsecurity CI runs) whenever a diff included one of those alerts. + +These tests pin the fallback behavior so the SDK stays forward-compatible with +new server-side categories. +""" + +import logging +import unittest + +from socketdev.fullscans import SocketAlert, SocketCategory, SocketIssueSeverity + + +class TestSocketAlertCategoryParsing(unittest.TestCase): + """SocketAlert.from_dict should tolerate unknown category values.""" + + def _base_payload(self, category: str) -> dict: + return { + "key": "alert-key", + "type": "someAlertType", + "severity": "low", + "category": category, + } + + def test_known_category_is_preserved(self): + alert = SocketAlert.from_dict(self._base_payload("supplyChainRisk")) + self.assertEqual(alert.category, SocketCategory.SUPPLY_CHAIN_RISK) + self.assertEqual(alert.severity, SocketIssueSeverity.LOW) + + def test_unknown_category_falls_back_to_miscellaneous(self): + alert = SocketAlert.from_dict(self._base_payload("other")) + self.assertEqual(alert.category, SocketCategory.MISCELLANEOUS) + + def test_unknown_category_does_not_raise(self): + # Explicit regression assertion: no ValueError for brand-new categories. + try: + SocketAlert.from_dict(self._base_payload("somethingCompletelyNew")) + except ValueError as exc: + self.fail(f"SocketAlert.from_dict raised ValueError for unknown category: {exc}") + + def test_unknown_category_emits_warning(self): + with self.assertLogs("socketdev", level=logging.WARNING) as captured: + SocketAlert.from_dict(self._base_payload("other")) + self.assertTrue( + any("Unknown SocketCategory" in message for message in captured.output), + f"expected a warning about the unknown category, got: {captured.output}", + ) + + def test_every_known_category_round_trips(self): + for category in SocketCategory: + with self.subTest(category=category): + alert = SocketAlert.from_dict(self._base_payload(category.value)) + self.assertEqual(alert.category, category) + + +if __name__ == "__main__": + unittest.main()