feat: scoped npm support (@angular/core style paths) in extract_npm_info
This commit is contained in:
@@ -63,16 +63,27 @@ def extract_go_info(asset_path: str) -> tuple[str, str] | None:
|
|||||||
def extract_npm_info(asset_path: str) -> tuple[str, str] | None:
|
def extract_npm_info(asset_path: str) -> tuple[str, str] | None:
|
||||||
"""Extract package name and version from an npm proxy asset path.
|
"""Extract package name and version from an npm proxy asset path.
|
||||||
|
|
||||||
Path format: packages/react/-/react-18.2.0.tgz
|
Path format:
|
||||||
|
packages/react/-/react-18.2.0.tgz
|
||||||
|
packages/@angular/core/-/core-18.0.0.tgz (scoped)
|
||||||
"""
|
"""
|
||||||
parts = asset_path.strip("/").split("/")
|
parts = asset_path.strip("/").split("/")
|
||||||
if len(parts) < 4 or parts[0] != PKG_PATH_PREFIX:
|
if len(parts) < 4 or parts[0] != PKG_PATH_PREFIX:
|
||||||
return None
|
return None
|
||||||
name = parts[1]
|
|
||||||
# Last segment: <name>-<version>.tgz
|
# Scoped package: @scope/name
|
||||||
|
if parts[1].startswith("@"):
|
||||||
|
if len(parts) < 5:
|
||||||
|
return None
|
||||||
|
name = f"{parts[1]}/{parts[2]}"
|
||||||
|
short_name = parts[2]
|
||||||
|
else:
|
||||||
|
name = parts[1]
|
||||||
|
short_name = name
|
||||||
|
|
||||||
last = parts[-1]
|
last = parts[-1]
|
||||||
if last.startswith(name + "-"):
|
if last.startswith(short_name + "-"):
|
||||||
raw = last[len(name) + 1 :]
|
raw = last[len(short_name) + 1 :]
|
||||||
for ext in (".tgz", ".tar.gz"):
|
for ext in (".tgz", ".tar.gz"):
|
||||||
if raw.endswith(ext):
|
if raw.endswith(ext):
|
||||||
return name, raw[: -len(ext)]
|
return name, raw[: -len(ext)]
|
||||||
|
|||||||
@@ -103,6 +103,44 @@ class TestWebhookToScanFlow:
|
|||||||
data = resp.json()
|
data = resp.json()
|
||||||
assert data["status"] == "accepted"
|
assert data["status"] == "accepted"
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_e2e_webhook_accepts_scoped_npm_asset(self, e2e_client, e2e_db_session):
|
||||||
|
"""Verify that scoped npm (@scope/name) assets are accepted."""
|
||||||
|
payload = {
|
||||||
|
"action": "UPDATED",
|
||||||
|
"repositoryName": "npm-proxy",
|
||||||
|
"initiator": "e2e-test",
|
||||||
|
"asset": {
|
||||||
|
"format": "npm",
|
||||||
|
"name": "/packages/@angular/core/-/core-18.0.0.tgz",
|
||||||
|
"downloadUrl": "http://nexus:8081/repository/npm-proxy/@angular/core/-/core-18.0.0.tgz",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
async def mock_harvest(*args, **kwargs):
|
||||||
|
from guarddog_nexus.db.models import Scan, ScanStatus
|
||||||
|
|
||||||
|
scan = Scan(
|
||||||
|
package_name="@angular/core",
|
||||||
|
package_version="18.0.0",
|
||||||
|
ecosystem="npm",
|
||||||
|
repository="npm-proxy",
|
||||||
|
nexus_asset_url=args[0],
|
||||||
|
status=ScanStatus.COMPLETED.value,
|
||||||
|
total_findings=0,
|
||||||
|
flagged=False,
|
||||||
|
)
|
||||||
|
e2e_db_session.add(scan)
|
||||||
|
await e2e_db_session.commit()
|
||||||
|
await e2e_db_session.refresh(scan)
|
||||||
|
return scan
|
||||||
|
|
||||||
|
with patch("guarddog_nexus.routes.webhooks._scan_in_background", mock_harvest):
|
||||||
|
resp = await e2e_client.post("/webhooks/nexus", json=payload)
|
||||||
|
|
||||||
|
assert resp.status_code == 200
|
||||||
|
assert resp.json()["status"] == "accepted"
|
||||||
|
|
||||||
|
|
||||||
class TestWebhookSignatureValidation:
|
class TestWebhookSignatureValidation:
|
||||||
"""E2E tests for webhook signature validation."""
|
"""E2E tests for webhook signature validation."""
|
||||||
|
|||||||
@@ -56,8 +56,16 @@ class TestNpmExtractor:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_scoped_package(self):
|
def test_scoped_package(self):
|
||||||
# Note: scoped packages have a different path in Nexus
|
assert extract_npm_info("packages/@scope/name/-/name-1.0.0.tgz") == (
|
||||||
assert extract_npm_info("packages/@scope/name/-/name-1.0.0.tgz") is None
|
"@scope/name",
|
||||||
|
"1.0.0",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_scoped_angular_core(self):
|
||||||
|
assert extract_npm_info("packages/@angular/core/-/core-18.0.0.tgz") == (
|
||||||
|
"@angular/core",
|
||||||
|
"18.0.0",
|
||||||
|
)
|
||||||
|
|
||||||
def test_not_packages(self):
|
def test_not_packages(self):
|
||||||
assert extract_npm_info("/other/lodash/-/lodash-4.17.21.tgz") is None
|
assert extract_npm_info("/other/lodash/-/lodash-4.17.21.tgz") is None
|
||||||
|
|||||||
Reference in New Issue
Block a user