"""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