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:
|
||||
"""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("/")
|
||||
if len(parts) < 4 or parts[0] != PKG_PATH_PREFIX:
|
||||
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]
|
||||
if last.startswith(name + "-"):
|
||||
raw = last[len(name) + 1 :]
|
||||
if last.startswith(short_name + "-"):
|
||||
raw = last[len(short_name) + 1 :]
|
||||
for ext in (".tgz", ".tar.gz"):
|
||||
if raw.endswith(ext):
|
||||
return name, raw[: -len(ext)]
|
||||
|
||||
@@ -103,6 +103,44 @@ class TestWebhookToScanFlow:
|
||||
data = resp.json()
|
||||
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:
|
||||
"""E2E tests for webhook signature validation."""
|
||||
|
||||
@@ -56,8 +56,16 @@ class TestNpmExtractor:
|
||||
)
|
||||
|
||||
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") is None
|
||||
assert extract_npm_info("packages/@scope/name/-/name-1.0.0.tgz") == (
|
||||
"@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):
|
||||
assert extract_npm_info("/other/lodash/-/lodash-4.17.21.tgz") is None
|
||||
|
||||
Reference in New Issue
Block a user