Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "socketdev"
version = "3.3.0"
version = "3.4.0"
requires-python = ">= 3.9"
dependencies = [
'requests',
Expand Down
38 changes: 34 additions & 4 deletions socketdev/diffscans/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,41 @@ def list(self, org_slug: str, params: Optional[Dict[str, Any]] = None) -> dict:
log.error(f"Error listing diff scans: {response.status_code}, message: {response.text}")
return {}

def get(self, org_slug: str, diff_scan_id: str) -> dict:
"""Fetch a diff scan by ID."""
def get(self, org_slug: str, diff_scan_id: str, params: Optional[Dict[str, Any]] = None) -> dict:
"""Fetch a diff scan by ID.

Args:
org_slug: Organization slug
diff_scan_id: The ID of the diff scan to fetch
params: Optional query parameters. Supports:
cached: When "true", return pre-computed results immediately
(200) or a processing status (202) instead of holding
the connection open while the diff is computed.
omit_unchanged: When "true", omit unchanged artifacts.
omit_license_details: When "true", omit license details.

Returns:
dict: On 200, the API response containing the diff_scan object.
On 202 (results still processing when cached=true), a dict of
{"status": "processing", "id": diff_scan_id} so callers can
poll until the diff scan is ready. Empty dict on error.
"""
import urllib.parse
path = f"orgs/{org_slug}/diff-scans/{diff_scan_id}"
if params:
path += "?" + urllib.parse.urlencode(params, doseq=True)
response = self.api.do_request(path=path, method="GET")
if response.status_code == 200:
return response.json()
if response.status_code == 202:
result = {"status": "processing", "id": diff_scan_id}
try:
body = response.json()
if isinstance(body, dict):
result.update(body)
except ValueError:
pass
return result
log.error(f"Error fetching diff scan: {response.status_code}, message: {response.text}")
return {}

Expand Down Expand Up @@ -61,7 +90,8 @@ def create_from_repo(self, org_slug: str, repo_slug: str, files: list, params: O
import urllib.parse
path = f"orgs/{org_slug}/diff-scans/from-repo/{repo_slug}"
if params:
path += "?" + urllib.parse.urlencode(params)
# doseq=True so list values (e.g. committers) become repeated params
path += "?" + urllib.parse.urlencode(params, doseq=True)

# Use lazy loading if requested
if use_lazy_loading:
Expand All @@ -80,7 +110,7 @@ def create_from_ids(self, org_slug: str, params: Dict[str, Any]) -> dict:
import urllib.parse
path = f"orgs/{org_slug}/diff-scans/from-ids"
if params:
path += "?" + urllib.parse.urlencode(params)
path += "?" + urllib.parse.urlencode(params, doseq=True)
response = self.api.do_request(path=path, method="POST")
if response.status_code in (200, 201):
return response.json()
Expand Down
2 changes: 1 addition & 1 deletion socketdev/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "3.3.0"
__version__ = "3.4.0"
62 changes: 62 additions & 0 deletions tests/unit/test_all_endpoints_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,45 @@ def test_diffscans_get_unit(self):
self.assertEqual(call_args[0][0], "GET")
self.assertIn("/orgs/test-org/diff-scans/diff-123", call_args[0][1])

def test_diffscans_get_cached_unit(self):
"""Test diffscans get passes cached/omit params through as a query string."""
expected_data = {
"diff_scan": {
"id": "diff-123",
"artifacts": {"added": [], "removed": [], "unchanged": [], "replaced": [], "updated": []},
}
}
self._mock_response(expected_data)

result = self.sdk.diffscans.get("test-org", "diff-123", params={"cached": "true"})

self.assertEqual(result, expected_data)
call_args = self.mock_requests.request.call_args
self.assertEqual(call_args[0][0], "GET")
self.assertIn("/orgs/test-org/diff-scans/diff-123?cached=true", call_args[0][1])

def test_diffscans_get_processing_unit(self):
"""Test diffscans get surfaces 202 processing status instead of an error."""
self._mock_response({"status": "processing", "id": "diff-123"}, 202)

result = self.sdk.diffscans.get("test-org", "diff-123", params={"cached": "true"})

self.assertEqual(result.get("status"), "processing")
self.assertEqual(result.get("id"), "diff-123")

def test_diffscans_get_processing_empty_body_unit(self):
"""Test diffscans get synthesizes the processing status when the 202 body is empty."""
mock_response = Mock()
mock_response.status_code = 202
mock_response.headers = {}
mock_response.json.side_effect = ValueError("no body")
mock_response.text = ""
self.mock_requests.request.return_value = mock_response

result = self.sdk.diffscans.get("test-org", "diff-123", params={"cached": "true"})

self.assertEqual(result, {"status": "processing", "id": "diff-123"})

def test_diffscans_create_from_ids_unit(self):
"""Test diffscans creation from scan IDs."""
expected_data = {"id": "new-diff-scan", "status": "queued"}
Expand Down Expand Up @@ -153,6 +192,29 @@ def test_diffscans_create_from_repo_unit(self):
finally:
os.unlink(f.name)

def test_diffscans_create_from_repo_committers_list_unit(self):
"""Test list-valued params (committers) encode as repeated query params."""
self._mock_response({"id": "repo-diff-scan"}, 201)

with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
json.dump({"name": "test", "version": "1.0.0"}, f)
f.flush()

try:
with open(f.name, "rb") as file_obj:
files = [("file", ("package.json", file_obj))]
params = {"committers": ["alice", "bob"], "branch": "main"}
self.sdk.diffscans.create_from_repo("test-org", "test-repo", files, params)

call_args = self.mock_requests.request.call_args
url = call_args[0][1]
self.assertIn("committers=alice", url)
self.assertIn("committers=bob", url)
self.assertIn("branch=main", url)

finally:
os.unlink(f.name)

def test_diffscans_gfm_unit(self):
"""Test diffscans GitHub Flavored Markdown export."""
expected_data = {"markdown": "# Diff Report\n\n## Summary\n- Added: 0\n- Removed: 0"}
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading