feat(release): generate reproducible CycloneDX SBOM
This commit is contained in:
126
tests/test_release_sbom.py
Normal file
126
tests/test_release_sbom.py
Normal file
@@ -0,0 +1,126 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
from datetime import UTC, datetime
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
||||
META_ROOT = Path(__file__).resolve().parents[1]
|
||||
RELEASE_TOOLS_ROOT = META_ROOT / "tools" / "release"
|
||||
if str(RELEASE_TOOLS_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(RELEASE_TOOLS_ROOT))
|
||||
|
||||
from govoplan_release.sbom import build_release_sbom, render_sbom, sbom_sha256, validate_sbom # noqa: E402
|
||||
|
||||
|
||||
class ReleaseSbomTests(unittest.TestCase):
|
||||
@staticmethod
|
||||
def _build_at(timestamp: datetime) -> dict:
|
||||
return build_release_sbom(
|
||||
product_version="1.2.3",
|
||||
pip_inspect={
|
||||
"version": "1",
|
||||
"installed": [{"metadata": {"name": "FastAPI", "version": "0.100.0"}}],
|
||||
},
|
||||
npm_lock={"packages": {"": {"name": "govoplan", "version": "1.2.3"}}},
|
||||
timestamp=timestamp,
|
||||
)
|
||||
|
||||
def test_builds_valid_deduplicated_python_and_npm_components(self) -> None:
|
||||
integrity = base64.b64encode(b"example digest").decode("ascii")
|
||||
payload = build_release_sbom(
|
||||
product_version="1.2.3",
|
||||
pip_inspect={
|
||||
"version": "1",
|
||||
"installed": [
|
||||
{"metadata": {"name": "FastAPI", "version": "0.100.0"}},
|
||||
{"metadata": {"name": "FastAPI", "version": "0.100.0"}},
|
||||
],
|
||||
},
|
||||
npm_lock={
|
||||
"packages": {
|
||||
"": {"name": "govoplan", "version": "1.2.3"},
|
||||
"node_modules/@scope/example": {
|
||||
"name": "@scope/example",
|
||||
"version": "4.5.6",
|
||||
"integrity": f"sha512-{integrity}",
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
validate_sbom(payload)
|
||||
|
||||
self.assertEqual(2, len(payload["components"]))
|
||||
npm = next(item for item in payload["components"] if item["name"] == "@scope/example")
|
||||
self.assertEqual("SHA-512", npm["hashes"][0]["alg"])
|
||||
self.assertIn("pkg:npm/%40scope/example@4.5.6", npm["purl"])
|
||||
self.assertEqual(
|
||||
hashlib.sha256(render_sbom(payload).encode("utf-8")).hexdigest(),
|
||||
sbom_sha256(payload),
|
||||
)
|
||||
|
||||
def test_rejects_empty_component_inventory(self) -> None:
|
||||
payload = build_release_sbom(
|
||||
product_version="1.2.3",
|
||||
pip_inspect={"version": "1", "installed": []},
|
||||
npm_lock={"packages": {"": {"version": "1.2.3"}}},
|
||||
)
|
||||
|
||||
with self.assertRaisesRegex(ValueError, "no dependency components"):
|
||||
validate_sbom(payload)
|
||||
|
||||
def test_fixed_timestamp_produces_identical_bytes_and_digest(self) -> None:
|
||||
timestamp = datetime(2026, 7, 21, 8, 30, tzinfo=UTC)
|
||||
|
||||
first = self._build_at(timestamp)
|
||||
second = self._build_at(timestamp)
|
||||
|
||||
self.assertEqual(render_sbom(first), render_sbom(second))
|
||||
self.assertEqual(sbom_sha256(first), sbom_sha256(second))
|
||||
|
||||
def test_timestamp_changes_serial_and_digest(self) -> None:
|
||||
first = self._build_at(datetime(2026, 7, 21, 8, 30, tzinfo=UTC))
|
||||
second = self._build_at(datetime(2026, 7, 21, 8, 31, tzinfo=UTC))
|
||||
|
||||
self.assertNotEqual(first["serialNumber"], second["serialNumber"])
|
||||
self.assertNotEqual(sbom_sha256(first), sbom_sha256(second))
|
||||
|
||||
def test_local_and_credential_bearing_distribution_urls_are_omitted(self) -> None:
|
||||
payload = build_release_sbom(
|
||||
product_version="1.2.3",
|
||||
pip_inspect={
|
||||
"version": "1",
|
||||
"installed": [
|
||||
{
|
||||
"metadata": {"name": "LocalPackage", "version": "1.0.0"},
|
||||
"direct_url": {"url": "file:///private/build/govoplan-core"},
|
||||
},
|
||||
{
|
||||
"metadata": {"name": "PrivatePackage", "version": "1.0.0"},
|
||||
"direct_url": {"url": "https://user:secret@example.test/private.whl"},
|
||||
},
|
||||
{
|
||||
"metadata": {"name": "PublicPackage", "version": "1.0.0"},
|
||||
"direct_url": {"url": "https://example.test/public.whl"},
|
||||
},
|
||||
],
|
||||
},
|
||||
npm_lock={"packages": {"": {"name": "govoplan", "version": "1.2.3"}}},
|
||||
timestamp=datetime(2026, 7, 21, 8, 30, tzinfo=UTC),
|
||||
)
|
||||
|
||||
by_name = {component["name"]: component for component in payload["components"]}
|
||||
self.assertNotIn("externalReferences", by_name["LocalPackage"])
|
||||
self.assertNotIn("externalReferences", by_name["PrivatePackage"])
|
||||
self.assertEqual(
|
||||
[{"type": "distribution", "url": "https://example.test/public.whl"}],
|
||||
by_name["PublicPackage"]["externalReferences"],
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user