All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 37s
CI / test-python-backend-compliance (push) Successful in 32s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 18s
Phase A: PostgreSQL State Store (sdk_states Tabelle, InMemory-Fallback) Phase B: Modules dynamisch vom Backend, Scope DB-Persistenz, Source Policy State Phase C: UCCA Frontend (3 Seiten, Wizard, RiskScoreGauge), Obligations Live-Daten Phase D: Document Import (PDF/LLM/Gap-Analyse), System Screening (SBOM/OSV.dev) Phase E: Company Profile CRUD mit Audit-Logging Phase F: Tests (Python + TypeScript), flow-data.ts DB-Tabellen aktualisiert Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
192 lines
6.1 KiB
Python
192 lines
6.1 KiB
Python
"""Tests for System Screening routes (screening_routes.py)."""
|
|
|
|
import json
|
|
import pytest
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
from compliance.api.screening_routes import (
|
|
parse_package_lock,
|
|
parse_requirements_txt,
|
|
parse_yarn_lock,
|
|
detect_and_parse,
|
|
generate_sbom,
|
|
map_osv_severity,
|
|
extract_fix_version,
|
|
)
|
|
|
|
|
|
class TestParsePackageLock:
|
|
"""Tests for package-lock.json parsing."""
|
|
|
|
def test_v2_format(self):
|
|
data = json.dumps({
|
|
"packages": {
|
|
"": {"name": "my-app", "version": "1.0.0"},
|
|
"node_modules/react": {"version": "18.3.0", "license": "MIT"},
|
|
"node_modules/lodash": {"version": "4.17.21", "license": "MIT"},
|
|
}
|
|
})
|
|
components = parse_package_lock(data)
|
|
assert len(components) == 2
|
|
names = [c["name"] for c in components]
|
|
assert "react" in names
|
|
assert "lodash" in names
|
|
|
|
def test_v1_format(self):
|
|
data = json.dumps({
|
|
"dependencies": {
|
|
"express": {"version": "4.18.2"},
|
|
"cors": {"version": "2.8.5"},
|
|
}
|
|
})
|
|
components = parse_package_lock(data)
|
|
assert len(components) == 2
|
|
|
|
def test_empty_json(self):
|
|
assert parse_package_lock("{}") == []
|
|
|
|
def test_invalid_json(self):
|
|
assert parse_package_lock("not json") == []
|
|
|
|
def test_root_package_skipped(self):
|
|
data = json.dumps({
|
|
"packages": {
|
|
"": {"name": "root", "version": "1.0.0"},
|
|
}
|
|
})
|
|
components = parse_package_lock(data)
|
|
assert len(components) == 0
|
|
|
|
|
|
class TestParseRequirementsTxt:
|
|
"""Tests for requirements.txt parsing."""
|
|
|
|
def test_pinned_versions(self):
|
|
content = "fastapi==0.123.9\nuvicorn==0.38.0\npydantic==2.12.5"
|
|
components = parse_requirements_txt(content)
|
|
assert len(components) == 3
|
|
assert components[0]["name"] == "fastapi"
|
|
assert components[0]["version"] == "0.123.9"
|
|
assert components[0]["ecosystem"] == "PyPI"
|
|
|
|
def test_minimum_versions(self):
|
|
content = "idna>=3.7\ncryptography>=42.0.0"
|
|
components = parse_requirements_txt(content)
|
|
assert len(components) == 2
|
|
assert components[0]["version"] == "3.7"
|
|
|
|
def test_comments_and_blanks_ignored(self):
|
|
content = "# Comment\n\nfastapi==1.0.0\n# Another comment\n-r base.txt"
|
|
components = parse_requirements_txt(content)
|
|
assert len(components) == 1
|
|
|
|
def test_bare_package_name(self):
|
|
content = "requests"
|
|
components = parse_requirements_txt(content)
|
|
assert len(components) == 1
|
|
assert components[0]["version"] == "latest"
|
|
|
|
def test_empty_content(self):
|
|
assert parse_requirements_txt("") == []
|
|
|
|
|
|
class TestParseYarnLock:
|
|
"""Tests for yarn.lock parsing (basic)."""
|
|
|
|
def test_basic_format(self):
|
|
content = '"react@^18.0.0":\n version "18.3.0"\n"lodash@^4.17.0":\n version "4.17.21"'
|
|
components = parse_yarn_lock(content)
|
|
assert len(components) == 2
|
|
|
|
|
|
class TestDetectAndParse:
|
|
"""Tests for file type detection and parsing."""
|
|
|
|
def test_package_lock_detection(self):
|
|
data = json.dumps({"packages": {"node_modules/x": {"version": "1.0"}}})
|
|
components, ecosystem = detect_and_parse("package-lock.json", data)
|
|
assert ecosystem == "npm"
|
|
assert len(components) == 1
|
|
|
|
def test_requirements_detection(self):
|
|
components, ecosystem = detect_and_parse("requirements.txt", "flask==2.0.0")
|
|
assert ecosystem == "PyPI"
|
|
assert len(components) == 1
|
|
|
|
def test_unknown_format(self):
|
|
components, ecosystem = detect_and_parse("readme.md", "Hello World")
|
|
assert len(components) == 0
|
|
|
|
|
|
class TestGenerateSbom:
|
|
"""Tests for CycloneDX SBOM generation."""
|
|
|
|
def test_sbom_structure(self):
|
|
components = [
|
|
{"name": "react", "version": "18.3.0", "type": "library", "ecosystem": "npm", "license": "MIT"},
|
|
]
|
|
sbom = generate_sbom(components, "npm")
|
|
assert sbom["bomFormat"] == "CycloneDX"
|
|
assert sbom["specVersion"] == "1.5"
|
|
assert len(sbom["components"]) == 1
|
|
assert sbom["components"][0]["purl"] == "pkg:npm/react@18.3.0"
|
|
|
|
def test_sbom_empty_components(self):
|
|
sbom = generate_sbom([], "npm")
|
|
assert sbom["components"] == []
|
|
|
|
def test_sbom_unknown_license_excluded(self):
|
|
components = [
|
|
{"name": "x", "version": "1.0", "type": "library", "ecosystem": "npm", "license": "unknown"},
|
|
]
|
|
sbom = generate_sbom(components, "npm")
|
|
assert sbom["components"][0]["licenses"] == []
|
|
|
|
|
|
class TestMapOsvSeverity:
|
|
"""Tests for OSV severity mapping."""
|
|
|
|
def test_critical_severity(self):
|
|
vuln = {"database_specific": {"severity": "CRITICAL"}}
|
|
severity, cvss = map_osv_severity(vuln)
|
|
assert severity == "CRITICAL"
|
|
assert cvss == 9.5
|
|
|
|
def test_medium_default(self):
|
|
vuln = {}
|
|
severity, cvss = map_osv_severity(vuln)
|
|
assert severity == "MEDIUM"
|
|
assert cvss == 5.0
|
|
|
|
def test_low_severity(self):
|
|
vuln = {"database_specific": {"severity": "LOW"}}
|
|
severity, cvss = map_osv_severity(vuln)
|
|
assert severity == "LOW"
|
|
assert cvss == 2.5
|
|
|
|
|
|
class TestExtractFixVersion:
|
|
"""Tests for extracting fix version from OSV data."""
|
|
|
|
def test_fix_version_found(self):
|
|
vuln = {
|
|
"affected": [{
|
|
"package": {"name": "lodash"},
|
|
"ranges": [{"events": [{"introduced": "0"}, {"fixed": "4.17.21"}]}],
|
|
}]
|
|
}
|
|
assert extract_fix_version(vuln, "lodash") == "4.17.21"
|
|
|
|
def test_no_fix_version(self):
|
|
vuln = {"affected": [{"package": {"name": "x"}, "ranges": [{"events": [{"introduced": "0"}]}]}]}
|
|
assert extract_fix_version(vuln, "x") is None
|
|
|
|
def test_wrong_package_name(self):
|
|
vuln = {
|
|
"affected": [{
|
|
"package": {"name": "other"},
|
|
"ranges": [{"events": [{"fixed": "1.0"}]}],
|
|
}]
|
|
}
|
|
assert extract_fix_version(vuln, "lodash") is None
|