From 06ae3b845c7f3dcb6cf5a70942a147df5a0a83a0 Mon Sep 17 00:00:00 2001 From: Mike Reiche Date: Thu, 23 Oct 2025 16:13:12 +0200 Subject: [PATCH 1/6] Fix loading vulnerability --- owasp_dt/models/vulnerability.py | 17 ++-- patch.json | 1 + schema.json | 1 - test/__init__.py | 33 ++++++- test/files/test.sbom.xml | 2 + test/test_upload.py | 157 +++++++++++++++++++++++++++++++ 6 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 test/files/test.sbom.xml create mode 100644 test/test_upload.py diff --git a/owasp_dt/models/vulnerability.py b/owasp_dt/models/vulnerability.py index 0589ce0..65fb7d5 100644 --- a/owasp_dt/models/vulnerability.py +++ b/owasp_dt/models/vulnerability.py @@ -33,8 +33,8 @@ class Vulnerability: Attributes: vuln_id (str): source (str): - friendly_vuln_id (str): uuid (UUID): + friendly_vuln_id (Union[Unset, str]): title (Union[Unset, str]): sub_title (Union[Unset, str]): description (Union[Unset, str]): @@ -76,8 +76,8 @@ class Vulnerability: vuln_id: str source: str - friendly_vuln_id: str uuid: UUID + friendly_vuln_id: Union[Unset, str] = UNSET title: Union[Unset, str] = UNSET sub_title: Union[Unset, str] = UNSET description: Union[Unset, str] = UNSET @@ -122,10 +122,10 @@ def to_dict(self) -> dict[str, Any]: source = self.source - friendly_vuln_id = self.friendly_vuln_id - uuid = str(self.uuid) + friendly_vuln_id = self.friendly_vuln_id + title = self.title sub_title = self.sub_title @@ -243,10 +243,11 @@ def to_dict(self) -> dict[str, Any]: { "vulnId": vuln_id, "source": source, - "friendlyVulnId": friendly_vuln_id, "uuid": uuid, } ) + if friendly_vuln_id is not UNSET: + field_dict["friendlyVulnId"] = friendly_vuln_id if title is not UNSET: field_dict["title"] = title if sub_title is not UNSET: @@ -342,10 +343,10 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: source = d.pop("source") - friendly_vuln_id = d.pop("friendlyVulnId") - uuid = UUID(d.pop("uuid")) + friendly_vuln_id = d.pop("friendlyVulnId", UNSET) + title = d.pop("title", UNSET) sub_title = d.pop("subTitle", UNSET) @@ -482,8 +483,8 @@ def from_dict(cls: type[T], src_dict: Mapping[str, Any]) -> T: vulnerability = cls( vuln_id=vuln_id, source=source, - friendly_vuln_id=friendly_vuln_id, uuid=uuid, + friendly_vuln_id=friendly_vuln_id, title=title, sub_title=sub_title, description=description, diff --git a/patch.json b/patch.json index 8f5137a..80d9c0c 100644 --- a/patch.json +++ b/patch.json @@ -133,6 +133,7 @@ } }, "Vulnerability": { + "required" : [ "source", "uuid", "vulnId" ], "properties": { "findingAttribution": { "$ref": "#/components/schemas/FindingAttrib" diff --git a/schema.json b/schema.json index 62dc231..1224d9d 100644 --- a/schema.json +++ b/schema.json @@ -15231,7 +15231,6 @@ }, "Vulnerability": { "required": [ - "friendlyVulnId", "source", "uuid", "vulnId" diff --git a/test/__init__.py b/test/__init__.py index 8f297e5..c797b7c 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,13 +1,19 @@ +import math +import time from pathlib import Path +from typing import Callable from dotenv import load_dotenv -from owasp_dt import Client + import owasp_dt +from owasp_dt import Client from test import config + __base_dir = Path(__file__).parent test_project_name = "test-api" + def create_client_from_env() -> owasp_dt.Client: base_url = config.reqenv("OWASP_DTRACK_URL") return Client( @@ -19,12 +25,35 @@ def create_client_from_env() -> owasp_dt.Client: raise_on_unexpected_status=False, httpx_args={ "proxy": config.getenv("HTTPS_PROXY", lambda: config.getenv("HTTP_PROXY", None)), - #"no_proxy": getenv("NO_PROXY", "") + # "no_proxy": getenv("NO_PROXY", "") } ) + +def retry(callable: Callable, seconds: float, wait_time: float = 3): + retries = math.ceil(seconds / wait_time) + #start_date = datetime.now() + exception = None + ret = None + for i in range(retries): + try: + exception = None + ret = callable() + break + except Exception as e: + exception = e + time.sleep(wait_time) + + if exception: + raise exception + #raise Exception(f"{exception} after {datetime.now()-start_date}") + + return ret + + def setup_module(): assert load_dotenv(__base_dir / "test.env") + def teardown_module(): pass diff --git a/test/files/test.sbom.xml b/test/files/test.sbom.xml new file mode 100644 index 0000000..cc73fbc --- /dev/null +++ b/test/files/test.sbom.xml @@ -0,0 +1,2 @@ + +2025-05-12T08:09:17.852859+00:00CycloneDXcyclonedx-py6.0.0CycloneDXcyclonedx-python-lib10.0.0arrow1.3.0Better dates & times for PythonLicense :: OSI Approved :: Apache Software Licensepkg:pypi/arrow@1.3.0https://arrow.readthedocs.iofrom packaging metadata Project-URL: Documentationhttps://github.com/arrow-py/arrow/issuesfrom packaging metadata Project-URL: Issueshttps://github.com/arrow-py/arrowfrom packaging metadata Project-URL: Sourceattrs25.3.0Classes Without Boilerplatepkg:pypi/attrs@25.3.0https://www.attrs.org/from packaging metadata Project-URL: Documentationhttps://github.com/sponsors/hynekfrom packaging metadata Project-URL: Fundinghttps://tidelift.com/subscription/pkg/pypi-attrs?utm_source=pypi-attrs&utm_medium=pypifrom packaging metadata Project-URL: Tidelifthttps://www.attrs.org/en/stable/changelog.htmlfrom packaging metadata Project-URL: Changeloghttps://github.com/python-attrs/attrsfrom packaging metadata Project-URL: GitHubboolean.py5.0Define boolean algebras, create and parse boolean expressions and create custom boolean DSL.BSD-2-Clausepkg:pypi/boolean.py@5.0https://github.com/bastikr/boolean.pyfrom packaging metadata: Home-pagecertifi2025.4.26Python package for providing Mozilla's CA Bundle.MPL-2.0License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)pkg:pypi/certifi@2025.4.26https://github.com/certifi/python-certififrom packaging metadata Project-URL: Sourcehttps://github.com/certifi/python-certififrom packaging metadata: Home-pagechardet5.2.0Universal encoding detector for Python 3License :: OSI Approved :: GNU Lesser General Public License v2 or later (LGPLv2+)declared license of 'chardet'LGPLpkg:pypi/chardet@5.2.0https://chardet.readthedocs.io/from packaging metadata Project-URL: Documentationhttps://github.com/chardet/chardet/issuesfrom packaging metadata Project-URL: Issue Trackerhttps://github.com/chardet/chardetfrom packaging metadata Project-URL: GitHub Projecthttps://github.com/chardet/chardetfrom packaging metadata: Home-pagecharset-normalizer3.4.2The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet.MITpkg:pypi/charset-normalizer@3.4.2https://charset-normalizer.readthedocs.io/from packaging metadata Project-URL: Documentationhttps://github.com/jawah/charset_normalizer/issuesfrom packaging metadata Project-URL: Issue trackerhttps://github.com/jawah/charset_normalizerfrom packaging metadata Project-URL: Codehttps://github.com/jawah/charset_normalizer/blob/master/CHANGELOG.mdfrom packaging metadata Project-URL: Changelogcyclonedx-bom6.0.0CycloneDX Software Bill of Materials (SBOM) generator for Python projects and environmentsApache-2.0License :: OSI Approved :: Apache Software Licensepkg:pypi/cyclonedx-bom@6.0.0https://cyclonedx-bom-tool.readthedocs.io/from packaging metadata Project-URL: Documentationhttps://github.com/CycloneDX/cyclonedx-python/issuesfrom packaging metadata Project-URL: Bug Trackerhttps://owasp.org/donate/?reponame=www-project-cyclonedx&title=OWASP+CycloneDXfrom packaging metadata Project-URL: Fundinghttps://github.com/CycloneDX/cyclonedx-python/from packaging metadata Project-URL: Repositoryhttps://github.com/CycloneDX/cyclonedx-python/#readmefrom packaging metadata Project-URL: Homepagecyclonedx-python-lib10.0.0Python library for CycloneDXApache-2.0License :: OSI Approved :: Apache Software Licensepkg:pypi/cyclonedx-python-lib@10.0.0https://cyclonedx-python-library.readthedocs.io/from packaging metadata Project-URL: Documentationhttps://github.com/CycloneDX/cyclonedx-python-lib/issuesfrom packaging metadata Project-URL: Bug Trackerhttps://owasp.org/donate/?reponame=www-project-cyclonedx&title=OWASP+CycloneDXfrom packaging metadata Project-URL: Fundinghttps://github.com/CycloneDX/cyclonedx-python-libfrom packaging metadata Project-URL: Repositoryhttps://github.com/CycloneDX/cyclonedx-python-lib/#readmefrom packaging metadata Project-URL: Homepagedefusedxml0.7.1XML bomb protection for Python stdlib modulesPython-2.0declared license of 'defusedxml'PSFLpkg:pypi/defusedxml@0.7.1https://pypi.python.org/pypi/defusedxmlfrom packaging metadata: Download-URLhttps://github.com/tiran/defusedxmlfrom packaging metadata: Home-pagefqdn1.5.1Validates fully-qualified domain names against RFC 1123, so that they are acceptable to modern bowsersLicense :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)declared license of 'fqdn'MPL 2.0pkg:pypi/fqdn@1.5.1https://github.com/ypcrts/fqdnfrom packaging metadata: Home-pageidna3.10Internationalized Domain Names in Applications (IDNA)License :: OSI Approved :: BSD Licensepkg:pypi/idna@3.10https://github.com/kjd/idna/issuesfrom packaging metadata Project-URL: Issue trackerhttps://github.com/kjd/idnafrom packaging metadata Project-URL: Sourcehttps://github.com/kjd/idna/blob/master/HISTORY.rstfrom packaging metadata Project-URL: Changeloginiconfig2.1.0brain-dead simple config-ini parsingMITpkg:pypi/iniconfig@2.1.0https://github.com/pytest-dev/iniconfigfrom packaging metadata Project-URL: Homepageis-empty1.0.1Python package to check whether a variable is empty or not.MITpkg:pypi/is-empty@1.0.1https://github.com/wskoly/is_empty/from packaging metadata: Home-pageisoduration20.11.0Operations with ISO 8601 durationsISCdeclared license of 'isoduration'UNKNOWNpkg:pypi/isoduration@20.11.0https://github.com/bolsote/isoduration/issuesfrom packaging metadata Project-URL: Bug Reportshttps://github.com/bolsote/isoduration/blob/master/CHANGELOGfrom packaging metadata Project-URL: Changeloghttps://github.com/bolsote/isodurationfrom packaging metadata Project-URL: Repositoryhttps://github.com/bolsote/isodurationfrom packaging metadata: Home-pagejsonpointer3.0.0Identify specific nodes in a JSON document (RFC 6901) License :: OSI Approved :: BSD Licensedeclared license of 'jsonpointer'Modified BSD Licensepkg:pypi/jsonpointer@3.0.0https://github.com/stefankoegl/python-json-pointerfrom packaging metadata: Home-pagejsonschema4.23.0An implementation of JSON Schema validation for PythonMITpkg:pypi/jsonschema@4.23.0https://python-jsonschema.readthedocs.io/from packaging metadata Project-URL: Documentationhttps://github.com/python-jsonschema/jsonschema/issues/from packaging metadata Project-URL: Issueshttps://github.com/python-jsonschema/jsonschemafrom packaging metadata Project-URL: Sourcehttps://github.com/sponsors/Julianfrom packaging metadata Project-URL: Fundinghttps://tidelift.com/subscription/pkg/pypi-jsonschema?utm_source=pypi-jsonschema&utm_medium=referral&utm_campaign=pypi-linkfrom packaging metadata Project-URL: Tidelifthttps://github.com/python-jsonschema/jsonschema/blob/main/CHANGELOG.rstfrom packaging metadata Project-URL: Changeloghttps://github.com/python-jsonschema/jsonschemafrom packaging metadata Project-URL: Homepagejsonschema-specifications2025.4.1The JSON Schema meta-schemas and vocabularies, exposed as a Registrypkg:pypi/jsonschema-specifications@2025.4.1https://jsonschema-specifications.readthedocs.io/from packaging metadata Project-URL: Documentationhttps://github.com/python-jsonschema/jsonschema-specifications/issues/from packaging metadata Project-URL: Issueshttps://github.com/python-jsonschema/jsonschema-specificationsfrom packaging metadata Project-URL: Sourcehttps://github.com/sponsors/Julianfrom packaging metadata Project-URL: Fundinghttps://tidelift.com/subscription/pkg/pypi-jsonschema-specifications?utm_source=pypi-jsonschema-specifications&utm_medium=referral&utm_campaign=pypi-linkfrom packaging metadata Project-URL: Tidelifthttps://github.com/python-jsonschema/jsonschema-specificationsfrom packaging metadata Project-URL: Homepagelicense-expression30.4.1license-expression is a comprehensive utility library to parse, compare, simplify and normalize license expressions (such as SPDX license expressions) using boolean logic.Apache-2.0pkg:pypi/license-expression@30.4.1https://github.com/aboutcode-org/license-expressionfrom packaging metadata: Home-pagelxml5.4.0Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API.BSD-3-ClauseLicense :: OSI Approved :: BSD Licensepkg:pypi/lxml@5.4.0https://github.com/lxml/lxmlfrom packaging metadata Project-URL: Sourcehttps://lxml.de/from packaging metadata: Home-pagepackageurl-python0.16.0A purl aka. Package URL parser and builderMITpkg:pypi/packageurl-python@0.16.0https://github.com/package-url/packageurl-pythonfrom packaging metadata: Home-pagepackaging25.0Core utilities for Python packagesLicense :: OSI Approved :: Apache Software LicenseLicense :: OSI Approved :: BSD Licensepkg:pypi/packaging@25.0https://packaging.pypa.io/from packaging metadata Project-URL: Documentationhttps://github.com/pypa/packagingfrom packaging metadata Project-URL: Sourcepip25.0.1The PyPA recommended tool for installing Python packages.MITpkg:pypi/pip@25.0.1https://pip.pypa.iofrom packaging metadata Project-URL: Documentationhttps://github.com/pypa/pipfrom packaging metadata Project-URL: Sourcehttps://pip.pypa.io/en/stable/news/from packaging metadata Project-URL: Changeloghttps://pip.pypa.io/from packaging metadata Project-URL: Homepagepip-requirements-parser32.0.1pip requirements parser - a mostly correct pip requirements parsing library because it uses pip's own code.MITpkg:pypi/pip-requirements-parser@32.0.1https://github.com/nexB/pip-requirements-parserfrom packaging metadata: Home-pagepluggy1.5.0plugin and hook calling mechanisms for pythonMITpkg:pypi/pluggy@1.5.0https://github.com/pytest-dev/pluggyfrom packaging metadata: Home-pagepy-serializable2.0.0Library for serializing and deserializing Python Objects to and from JSON and XML.Apache-2.0License :: OSI Approved :: Apache Software Licensepkg:pypi/py-serializable@2.0.0https://py-serializable.readthedocs.io/from packaging metadata Project-URL: Documentationhttps://github.com/madpah/serializable/issuesfrom packaging metadata Project-URL: Bug Trackerhttps://github.com/madpah/serializablefrom packaging metadata Project-URL: Repositoryhttps://github.com/madpah/serializable#readmefrom packaging metadata Project-URL: Homepagepyparsing3.2.3pyparsing module - Classes and methods to define and execute parsing grammarsMITpkg:pypi/pyparsing@3.2.3https://github.com/pyparsing/pyparsing/from packaging metadata Project-URL: Homepagepytest8.3.5pytest: simple powerful testing with PythonMITpkg:pypi/pytest@8.3.5https://github.com/pytest-dev/pytest/issuesfrom packaging metadata Project-URL: Trackerhttps://docs.pytest.org/en/stable/contact.htmlfrom packaging metadata Project-URL: Contacthttps://docs.pytest.org/en/stable/sponsor.htmlfrom packaging metadata Project-URL: Fundinghttps://github.com/pytest-dev/pytestfrom packaging metadata Project-URL: Sourcehttps://docs.pytest.org/en/stable/changelog.htmlfrom packaging metadata Project-URL: Changeloghttps://docs.pytest.org/en/latest/from packaging metadata Project-URL: Homepagepython-dateutil2.9.0.post0Extensions to the standard Python datetime moduleLicense :: OSI Approved :: Apache Software LicenseLicense :: OSI Approved :: BSD Licensedeclared license of 'python-dateutil'Dual Licensepkg:pypi/python-dateutil@2.9.0.post0https://dateutil.readthedocs.io/en/stable/from packaging metadata Project-URL: Documentationhttps://github.com/dateutil/dateutilfrom packaging metadata Project-URL: Sourcehttps://github.com/dateutil/dateutilfrom packaging metadata: Home-pagereferencing0.36.2JSON Referencing + Pythonpkg:pypi/referencing@0.36.2https://referencing.readthedocs.io/from packaging metadata Project-URL: Documentationhttps://github.com/python-jsonschema/referencing/issues/from packaging metadata Project-URL: Issueshttps://github.com/python-jsonschema/referencingfrom packaging metadata Project-URL: Sourcehttps://github.com/sponsors/Julianfrom packaging metadata Project-URL: Fundinghttps://tidelift.com/subscription/pkg/pypi-referencing?utm_source=pypi-referencing&utm_medium=referral&utm_campaign=pypi-linkfrom packaging metadata Project-URL: Tidelifthttps://referencing.readthedocs.io/en/stable/changes/from packaging metadata Project-URL: Changeloghttps://github.com/python-jsonschema/referencingfrom packaging metadata Project-URL: Homepagerequests2.32.3Python HTTP for Humans.Apache-2.0License :: OSI Approved :: Apache Software Licensepkg:pypi/requests@2.32.3https://requests.readthedocs.iofrom packaging metadata Project-URL: Documentationhttps://github.com/psf/requestsfrom packaging metadata Project-URL: Sourcehttps://requests.readthedocs.iofrom packaging metadata: Home-pagerfc3339-validator0.1.4A pure python RFC3339 validatorMITdeclared license of 'rfc3339-validator'MIT licensepkg:pypi/rfc3339-validator@0.1.4https://github.com/naimetti/rfc3339-validatorfrom packaging metadata: Home-pagerfc39871.3.8Parsing and validation of URIs (RFC 3986) and IRIs (RFC 3987)GPL-3.0-or-laterdeclared license of 'rfc3987'GNU GPLv3+pkg:pypi/rfc3987@1.3.8https://github.com/dgerber/rfc3987from packaging metadata: Download-URLhttp://pypi.python.org/pypi/rfc3987from packaging metadata: Home-pagerpds-py0.24.0Python bindings to Rust's persistent data structures (rpds)MITpkg:pypi/rpds-py@0.24.0https://rpds.readthedocs.io/from packaging metadata Project-URL: Documentationhttps://github.com/crate-py/rpds/issues/from packaging metadata Project-URL: Issueshttps://github.com/crate-py/rpdsfrom packaging metadata Project-URL: Sourcehttps://github.com/orium/rpdsfrom packaging metadata Project-URL: Upstreamhttps://github.com/sponsors/Julianfrom packaging metadata Project-URL: Fundinghttps://tidelift.com/subscription/pkg/pypi-rpds-py?utm_source=pypi-rpds-py&utm_medium=referral&utm_campaign=pypi-linkfrom packaging metadata Project-URL: Tidelifthttps://github.com/crate-py/rpdsfrom packaging metadata Project-URL: Homepagesix1.17.0Python 2 and 3 compatibility utilitiesMITpkg:pypi/six@1.17.0https://github.com/benjaminp/sixfrom packaging metadata: Home-pagesortedcontainers2.4.0Sorted Containers -- Sorted List, Sorted Dict, Sorted SetLicense :: OSI Approved :: Apache Software Licensedeclared license of 'sortedcontainers'Apache 2.0pkg:pypi/sortedcontainers@2.4.0http://www.grantjenks.com/docs/sortedcontainers/from packaging metadata: Home-pagetypes-python-dateutil2.9.0.20241206Typing stubs for python-dateutilApache-2.0License :: OSI Approved :: Apache Software Licensepkg:pypi/types-python-dateutil@2.9.0.20241206https://gitter.im/python/typingfrom packaging metadata Project-URL: Chathttps://github.com/python/typeshed/issuesfrom packaging metadata Project-URL: Issue trackerhttps://github.com/typeshed-internal/stub_uploader/blob/main/data/changelogs/python-dateutil.mdfrom packaging metadata Project-URL: Changeshttps://github.com/python/typeshedfrom packaging metadata Project-URL: GitHubhttps://github.com/python/typeshedfrom packaging metadata: Home-pageuri-template1.3.0RFC 6570 URI Template ProcessorMITdeclared license of 'uri-template'MIT Licensepkg:pypi/uri-template@1.3.0https://gitlab.linss.com/open-source/python/uri-templatefrom packaging metadata Project-URL: homepageurllib32.4.0HTTP library with thread-safe connection pooling, file post, and more.pkg:pypi/urllib3@2.4.0https://urllib3.readthedocs.iofrom packaging metadata Project-URL: Documentationhttps://github.com/urllib3/urllib3/issuesfrom packaging metadata Project-URL: Issue trackerhttps://github.com/urllib3/urllib3from packaging metadata Project-URL: Codehttps://github.com/urllib3/urllib3/blob/main/CHANGES.rstfrom packaging metadata Project-URL: Changelogwebcolors24.11.1A library for working with the color formats defined by HTML and CSS.BSD-3-ClauseLicense :: OSI Approved :: BSD Licensepkg:pypi/webcolors@24.11.1https://webcolors.readthedocs.iofrom packaging metadata Project-URL: Documentationhttps://github.com/ubernostrum/webcolorsfrom packaging metadata Project-URL: Source Code \ No newline at end of file diff --git a/test/test_upload.py b/test/test_upload.py new file mode 100644 index 0000000..a22c02a --- /dev/null +++ b/test/test_upload.py @@ -0,0 +1,157 @@ +from pathlib import Path +from time import sleep + +import pytest +from is_empty import empty + +import owasp_dt +from owasp_dt.api.bom import upload_bom +from owasp_dt.api.config_property import update_config_property +from owasp_dt.api.event import is_token_being_processed_1 +from owasp_dt.api.license_ import get_license +from owasp_dt.api.metrics import get_project_current_metrics +from owasp_dt.api.metrics import get_vulnerability_metrics +from owasp_dt.api.policy import create_policy +from owasp_dt.api.policy_condition import create_policy_condition +from owasp_dt.api.project import get_projects +from owasp_dt.api.violation import get_violations_by_project, get_violations +from owasp_dt.api.vulnerability import get_all_vulnerabilities +from owasp_dt.models import UploadBomBody, IsTokenBeingProcessedResponse, ConfigProperty, ConfigPropertyPropertyType, Policy, PolicyViolationState, PolicyCondition, PolicyConditionSubject, PolicyConditionOperator, License, PolicyOperator +from owasp_dt.types import UNSET +from test import test_project_name, retry + +__base_dir = Path(__file__).parent +__upload_token: str | None = None +__project_uuid: str | None = None +__mit_license_uuid: str | None = None + + +def test_upload_sbom(client: owasp_dt.Client): + global __upload_token + with open(__base_dir / "files/test.sbom.xml") as sbom_file: + resp = upload_bom.sync_detailed(client=client, body=UploadBomBody( + project_name=test_project_name, + auto_create=True, + bom=sbom_file.read() + )) + upload = resp.parsed + assert upload is not None, "API call failed. Check client permissions." + assert upload.token is not None + __upload_token = upload.token + + +@pytest.mark.depends(on=['test_upload_sbom']) +def test_get_scan_status(client: owasp_dt.Client): + max_tries = 10 + i = 0 + for i in range(max_tries): + resp = is_token_being_processed_1.sync_detailed(client=client, uuid=__upload_token) + status = resp.parsed + assert isinstance(status, IsTokenBeingProcessedResponse) + if not status.processing: + break + sleep(1) + + assert i < max_tries, f"Scan not finished within {max_tries} seconds" + +@pytest.mark.depends(on=['test_upload_sbom']) +def test_search_project_by_name(client: owasp_dt.Client): + global __project_uuid + resp = get_projects.sync_detailed(client=client, name=test_project_name) + projects = resp.parsed + assert len(projects) > 0 + assert projects[0].uuid is not None + __project_uuid = projects[0].uuid + +@pytest.mark.depends(on=['test_search_project_by_name', 'test_get_scan_status']) +def test_get_project_metrics(client: owasp_dt.Client): + resp = get_project_current_metrics.sync_detailed(client=client, uuid=__project_uuid) + metrics = resp.parsed + + +@pytest.mark.depends(on=['test_search_project_by_name', 'test_get_scan_status']) +def test_get_project_violations(client: owasp_dt.Client): + resp = get_violations_by_project.sync_detailed(client=client, uuid=__project_uuid) + violations = resp.parsed + +@pytest.mark.depends(on=['test_trigger_vulnerabilities_update']) +def test_get_vulnerabilities(client: owasp_dt.Client): + def _get_vulnerabilities(): + resp = get_all_vulnerabilities.sync_detailed(client=client, page_size=1) + vulnerabilities = resp.parsed + assert len(vulnerabilities) > 0 + + _get_vulnerabilities() + #retry(_get_vulnerabilities, 600) + + +@pytest.mark.depends(on=["test_get_vulnerabilities", 'test_upload_sbom']) +@pytest.mark.xfail(reason="https://github.com/DependencyTrack/dependency-track/issues/5401") +def test_get_vulnerability_metrics(client: owasp_dt.Client): + def _get_vulnerability_metrics(): + resp = get_vulnerability_metrics.sync_detailed(client=client) + vulnerabilities = resp.parsed + assert len(vulnerabilities) > 0 + + retry(_get_vulnerability_metrics, 10) + + +def test_trigger_vulnerabilities_update(client: owasp_dt.Client): + config = ConfigProperty( + group_name="task-scheduler", + property_name="nist.mirror.cadence", + property_value="1", + property_type=ConfigPropertyPropertyType.NUMBER, + ) + resp = update_config_property.sync_detailed(client=client, body=config) + assert resp.status_code == 200 + + +def assert_mit_license_uuid(client: owasp_dt.Client): + global __mit_license_uuid + if empty(__mit_license_uuid): + resp = get_license.sync_detailed(client=client, license_id="MIT") + assert resp.status_code == 200 + license = resp.parsed + assert isinstance(license, License) + __mit_license_uuid = str(license.uuid) + return __mit_license_uuid + + +def test_create_test_policy(client: owasp_dt.Client): + policy = Policy( + uuid="", + name="Forbid MIT license", + violation_state=PolicyViolationState.FAIL, + operator=PolicyOperator.ANY, + ) + resp = create_policy.sync_detailed(client=client, body=policy) + if resp.status_code == 409: + return + assert resp.status_code == 201 + policy = resp.parsed + assert isinstance(policy, Policy) + + license_uuid = assert_mit_license_uuid(client) + + assert not empty(license_uuid), "MIT license not found" + + condition = PolicyCondition( + uuid="", + policy=UNSET, + subject=PolicyConditionSubject.LICENSE, + operator=PolicyConditionOperator.IS, + value=license_uuid, + ) + resp = create_policy_condition.sync_detailed(client=client, uuid=policy.uuid, body=condition) + assert resp.status_code == 201 + + +@pytest.mark.depends(on=['test_create_test_policy', 'test_upload_sbom']) +def test_get_violations(client: owasp_dt.Client): + def _get_violations(): + resp = get_violations.sync_detailed(client=client, page_size=1) + violations = resp.parsed + assert len(violations) > 0 + + retry(_get_violations, 600) From 43675005a332a80c5eb9664a2d8cf118b67ce4ff Mon Sep 17 00:00:00 2001 From: Mike Reiche Date: Thu, 23 Oct 2025 16:31:18 +0200 Subject: [PATCH 2/6] Add more tests --- pyproject.toml | 1 + test/__init__.py | 22 ++++---------------- test/api.py | 47 +++++++++++++++++++++++++++++++++++++++++++ test/conftest.py | 4 ++-- test/test_config.py | 14 +++++++++++++ test/test_property.py | 36 +++++++++++++++++++++++++++++++++ test/test_upload.py | 47 +++++++++++++------------------------------ 7 files changed, 118 insertions(+), 53 deletions(-) create mode 100644 test/api.py create mode 100644 test/test_config.py create mode 100644 test/test_property.py diff --git a/pyproject.toml b/pyproject.toml index 39f945f..f9f1446 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,4 +26,5 @@ test = [ "pytest-cov", "dotenv", "openapi-python-client", + "tinystream==0.1.18", ] diff --git a/test/__init__.py b/test/__init__.py index c797b7c..b3f50eb 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -11,24 +11,10 @@ __base_dir = Path(__file__).parent -test_project_name = "test-api" - - -def create_client_from_env() -> owasp_dt.Client: - base_url = config.reqenv("OWASP_DTRACK_URL") - return Client( - base_url=f"{base_url}/api", - headers={ - "X-Api-Key": config.reqenv("OWASP_DTRACK_API_KEY") - }, - verify_ssl=config.getenv("OWASP_DTRACK_VERIFY_SSL", "1", config.parse_true), - raise_on_unexpected_status=False, - httpx_args={ - "proxy": config.getenv("HTTPS_PROXY", lambda: config.getenv("HTTP_PROXY", None)), - # "no_proxy": getenv("NO_PROXY", "") - } - ) - +project_name = "test-api" +upload_token: str | None = None +project_uuid: str | None = None +mit_license_uuid: str | None = None def retry(callable: Callable, seconds: float, wait_time: float = 3): retries = math.ceil(seconds / wait_time) diff --git a/test/api.py b/test/api.py new file mode 100644 index 0000000..cd7897d --- /dev/null +++ b/test/api.py @@ -0,0 +1,47 @@ +from typing import Generator, Callable, TypeVar + +from owasp_dt import Client +from owasp_dt.api.project import get_projects +from owasp_dt.api.project_property import create_property_1, update_property +from owasp_dt.models import Project, ProjectProperty +from owasp_dt.types import Response +from tinystream import Stream + +from test import config + + +def create_client_from_env() -> Client: + base_url = config.reqenv("OWASP_DTRACK_URL") + return Client( + base_url=f"{base_url}/api", + headers={ + "X-Api-Key": config.reqenv("OWASP_DTRACK_API_KEY") + }, + verify_ssl=config.getenv("OWASP_DTRACK_VERIFY_SSL", "1", config.parse_true), + raise_on_unexpected_status=False, + httpx_args={ + "proxy": config.getenv("HTTPS_PROXY", lambda: config.getenv("HTTP_PROXY", None)), + #"no_proxy": getenv("NO_PROXY", "") + } + ) + +def upsert_project_property(client: Client, uuid: str, property: ProjectProperty): + resp = create_property_1.sync_detailed(client=client, uuid=uuid, body=property) + if resp.status_code == 409: + resp = update_property.sync_detailed(client=client, uuid=uuid, body=property) + + assert resp.status_code in [200, 201] + +T = TypeVar('T') + +def page_result(cb: Callable[[int], Response[list[T]]]) -> Generator[list[T]]: + page_number = 0 + while True: + page_number += 1 + resp = cb(page_number) + assert resp.status_code == 200 + items = resp.parsed + if len(items) == 0: + break + else: + yield items diff --git a/test/conftest.py b/test/conftest.py index b5cdd36..1a751c2 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -1,8 +1,8 @@ import pytest -from test import create_client_from_env +from test import api @pytest.fixture def client(): - yield create_client_from_env() + yield api.create_client_from_env() diff --git a/test/test_config.py b/test/test_config.py new file mode 100644 index 0000000..131b294 --- /dev/null +++ b/test/test_config.py @@ -0,0 +1,14 @@ +import owasp_dt +from owasp_dt.api.config_property import update_config_property +from owasp_dt.models import ConfigProperty, ConfigPropertyPropertyType + + +def test_trigger_vulnerabilities_update(client: owasp_dt.Client): + config = ConfigProperty( + group_name="task-scheduler", + property_name="nist.mirror.cadence", + property_value="1", + property_type=ConfigPropertyPropertyType.NUMBER, + ) + resp = update_config_property.sync_detailed(client=client, body=config) + assert resp.status_code == 200 diff --git a/test/test_property.py b/test/test_property.py new file mode 100644 index 0000000..797503b --- /dev/null +++ b/test/test_property.py @@ -0,0 +1,36 @@ +import pytest +from tinystream import Opt + +import owasp_dt +from owasp_dt.api.project import get_project +from owasp_dt.models import ProjectPropertyPropertyType, ProjectProperty +from test import api +import test + +@pytest.mark.depends(on=['test/test_upload.py::test_search_project_by_name']) +def test_upsert_project_property(client: owasp_dt.Client): + property = ProjectProperty( + group_name="owasp-dtrack-python-client", + property_name="test", + property_type=ProjectPropertyPropertyType.STRING, + property_value="set", + description="Custom property test" + ) + api.upsert_project_property(client=client, uuid=test.project_uuid, property=property) + + def _filter_property(property:ProjectProperty): + return property.group_name == "owasp-dtrack-python-client" and property.property_name == "test" + + resp = get_project.sync_detailed(client=client, uuid=test.project_uuid) + project = resp.parsed + opt_property = Opt(project).map_key("properties").stream().filter(_filter_property).next() + assert opt_property.present + assert opt_property.get().property_value == "set" + + property.property_value = "new_value" + api.upsert_project_property(client=client, uuid=test.project_uuid, property=property) + resp = get_project.sync_detailed(client=client, uuid=test.project_uuid) + project = resp.parsed + opt_property = Opt(project).map_key("properties").stream().filter(_filter_property).next() + assert opt_property.present + assert opt_property.get().property_value == "new_value" diff --git a/test/test_upload.py b/test/test_upload.py index a22c02a..0d5e70c 100644 --- a/test/test_upload.py +++ b/test/test_upload.py @@ -18,26 +18,21 @@ from owasp_dt.api.vulnerability import get_all_vulnerabilities from owasp_dt.models import UploadBomBody, IsTokenBeingProcessedResponse, ConfigProperty, ConfigPropertyPropertyType, Policy, PolicyViolationState, PolicyCondition, PolicyConditionSubject, PolicyConditionOperator, License, PolicyOperator from owasp_dt.types import UNSET -from test import test_project_name, retry +import test __base_dir = Path(__file__).parent -__upload_token: str | None = None -__project_uuid: str | None = None -__mit_license_uuid: str | None = None - def test_upload_sbom(client: owasp_dt.Client): - global __upload_token with open(__base_dir / "files/test.sbom.xml") as sbom_file: resp = upload_bom.sync_detailed(client=client, body=UploadBomBody( - project_name=test_project_name, + project_name=test.project_name, auto_create=True, bom=sbom_file.read() )) upload = resp.parsed assert upload is not None, "API call failed. Check client permissions." assert upload.token is not None - __upload_token = upload.token + test.upload_token = upload.token @pytest.mark.depends(on=['test_upload_sbom']) @@ -45,7 +40,7 @@ def test_get_scan_status(client: owasp_dt.Client): max_tries = 10 i = 0 for i in range(max_tries): - resp = is_token_being_processed_1.sync_detailed(client=client, uuid=__upload_token) + resp = is_token_being_processed_1.sync_detailed(client=client, uuid=test.upload_token) status = resp.parsed assert isinstance(status, IsTokenBeingProcessedResponse) if not status.processing: @@ -56,33 +51,31 @@ def test_get_scan_status(client: owasp_dt.Client): @pytest.mark.depends(on=['test_upload_sbom']) def test_search_project_by_name(client: owasp_dt.Client): - global __project_uuid - resp = get_projects.sync_detailed(client=client, name=test_project_name) + resp = get_projects.sync_detailed(client=client, name=test.project_name) projects = resp.parsed assert len(projects) > 0 assert projects[0].uuid is not None - __project_uuid = projects[0].uuid + test.project_uuid = projects[0].uuid @pytest.mark.depends(on=['test_search_project_by_name', 'test_get_scan_status']) def test_get_project_metrics(client: owasp_dt.Client): - resp = get_project_current_metrics.sync_detailed(client=client, uuid=__project_uuid) + resp = get_project_current_metrics.sync_detailed(client=client, uuid=test.project_uuid) metrics = resp.parsed @pytest.mark.depends(on=['test_search_project_by_name', 'test_get_scan_status']) def test_get_project_violations(client: owasp_dt.Client): - resp = get_violations_by_project.sync_detailed(client=client, uuid=__project_uuid) + resp = get_violations_by_project.sync_detailed(client=client, uuid=test.project_uuid) violations = resp.parsed -@pytest.mark.depends(on=['test_trigger_vulnerabilities_update']) +@pytest.mark.depends(on=['test/test_config.py::test_trigger_vulnerabilities_update']) def test_get_vulnerabilities(client: owasp_dt.Client): def _get_vulnerabilities(): resp = get_all_vulnerabilities.sync_detailed(client=client, page_size=1) vulnerabilities = resp.parsed assert len(vulnerabilities) > 0 - _get_vulnerabilities() - #retry(_get_vulnerabilities, 600) + test.retry(_get_vulnerabilities, 10) @pytest.mark.depends(on=["test_get_vulnerabilities", 'test_upload_sbom']) @@ -93,29 +86,17 @@ def _get_vulnerability_metrics(): vulnerabilities = resp.parsed assert len(vulnerabilities) > 0 - retry(_get_vulnerability_metrics, 10) - - -def test_trigger_vulnerabilities_update(client: owasp_dt.Client): - config = ConfigProperty( - group_name="task-scheduler", - property_name="nist.mirror.cadence", - property_value="1", - property_type=ConfigPropertyPropertyType.NUMBER, - ) - resp = update_config_property.sync_detailed(client=client, body=config) - assert resp.status_code == 200 + test.retry(_get_vulnerability_metrics, 10) def assert_mit_license_uuid(client: owasp_dt.Client): - global __mit_license_uuid - if empty(__mit_license_uuid): + if empty(test.mit_license_uuid): resp = get_license.sync_detailed(client=client, license_id="MIT") assert resp.status_code == 200 license = resp.parsed assert isinstance(license, License) __mit_license_uuid = str(license.uuid) - return __mit_license_uuid + return test.mit_license_uuid def test_create_test_policy(client: owasp_dt.Client): @@ -154,4 +135,4 @@ def _get_violations(): violations = resp.parsed assert len(violations) > 0 - retry(_get_violations, 600) + test.retry(_get_violations, 600) From 00dc344de66b43192427c2112c1d3644a30d9e6e Mon Sep 17 00:00:00 2001 From: Mike Reiche Date: Thu, 23 Oct 2025 16:33:51 +0200 Subject: [PATCH 3/6] Add is_empty --- pyproject.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f9f1446..8b74d2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,8 @@ test = [ "pytest>=7", "pytest-depends", "pytest-cov", - "dotenv", + "dotenv== 0.9.9", "openapi-python-client", "tinystream==0.1.18", + "is_empty==1.0.1", ] From b9a9dd8605c730f362d2291828f33231cce643c6 Mon Sep 17 00:00:00 2001 From: Mike Reiche Date: Thu, 23 Oct 2025 16:42:06 +0200 Subject: [PATCH 4/6] Split test files --- test/__init__.py | 6 +- test/api.py | 5 +- test/test_projects.py | 20 +++++ test/test_property.py | 5 +- test/{test_team.py => test_teams.py} | 3 +- test/test_upload.py | 107 +-------------------------- test/test_violations.py | 66 +++++++++++++++++ test/test_vulnerabilities.py | 27 +++++++ 8 files changed, 124 insertions(+), 115 deletions(-) create mode 100644 test/test_projects.py rename test/{test_team.py => test_teams.py} (99%) create mode 100644 test/test_violations.py create mode 100644 test/test_vulnerabilities.py diff --git a/test/__init__.py b/test/__init__.py index b3f50eb..e47d9cf 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -5,11 +5,9 @@ from dotenv import load_dotenv -import owasp_dt -from owasp_dt import Client from test import config -__base_dir = Path(__file__).parent +base_dir = Path(__file__).parent project_name = "test-api" upload_token: str | None = None @@ -38,7 +36,7 @@ def retry(callable: Callable, seconds: float, wait_time: float = 3): def setup_module(): - assert load_dotenv(__base_dir / "test.env") + assert load_dotenv(base_dir / "test.env") def teardown_module(): diff --git a/test/api.py b/test/api.py index cd7897d..d356298 100644 --- a/test/api.py +++ b/test/api.py @@ -1,12 +1,9 @@ from typing import Generator, Callable, TypeVar from owasp_dt import Client -from owasp_dt.api.project import get_projects from owasp_dt.api.project_property import create_property_1, update_property -from owasp_dt.models import Project, ProjectProperty +from owasp_dt.models import ProjectProperty from owasp_dt.types import Response -from tinystream import Stream - from test import config diff --git a/test/test_projects.py b/test/test_projects.py new file mode 100644 index 0000000..cdaff66 --- /dev/null +++ b/test/test_projects.py @@ -0,0 +1,20 @@ +import pytest + +import owasp_dt +import test +from owasp_dt.api.metrics import get_project_current_metrics +from owasp_dt.api.project import get_projects + + +@pytest.mark.depends(on=['test/test_upload.py::test_upload_sbom']) +def test_search_project_by_name(client: owasp_dt.Client): + resp = get_projects.sync_detailed(client=client, name=test.project_name) + projects = resp.parsed + assert len(projects) > 0 + assert projects[0].uuid is not None + test.project_uuid = projects[0].uuid + +@pytest.mark.depends(on=['test/test_upload.py::test_get_scan_status', 'test_search_project_by_name']) +def test_get_project_metrics(client: owasp_dt.Client): + resp = get_project_current_metrics.sync_detailed(client=client, uuid=test.project_uuid) + metrics = resp.parsed diff --git a/test/test_property.py b/test/test_property.py index 797503b..17032bf 100644 --- a/test/test_property.py +++ b/test/test_property.py @@ -2,12 +2,13 @@ from tinystream import Opt import owasp_dt +import test from owasp_dt.api.project import get_project from owasp_dt.models import ProjectPropertyPropertyType, ProjectProperty from test import api -import test -@pytest.mark.depends(on=['test/test_upload.py::test_search_project_by_name']) + +@pytest.mark.depends(on=['test/test_projects.py::test_search_project_by_name']) def test_upsert_project_property(client: owasp_dt.Client): property = ProjectProperty( group_name="owasp-dtrack-python-client", diff --git a/test/test_team.py b/test/test_teams.py similarity index 99% rename from test/test_team.py rename to test/test_teams.py index 957afec..7a9a6ee 100644 --- a/test/test_team.py +++ b/test/test_teams.py @@ -1,5 +1,6 @@ -from owasp_dt.api.team import get_teams import owasp_dt +from owasp_dt.api.team import get_teams + def test_team_epoch(client: owasp_dt.Client): teams = get_teams.sync(client=client) diff --git a/test/test_upload.py b/test/test_upload.py index 0d5e70c..461b826 100644 --- a/test/test_upload.py +++ b/test/test_upload.py @@ -2,28 +2,15 @@ from time import sleep import pytest -from is_empty import empty import owasp_dt +import test from owasp_dt.api.bom import upload_bom -from owasp_dt.api.config_property import update_config_property from owasp_dt.api.event import is_token_being_processed_1 -from owasp_dt.api.license_ import get_license -from owasp_dt.api.metrics import get_project_current_metrics -from owasp_dt.api.metrics import get_vulnerability_metrics -from owasp_dt.api.policy import create_policy -from owasp_dt.api.policy_condition import create_policy_condition -from owasp_dt.api.project import get_projects -from owasp_dt.api.violation import get_violations_by_project, get_violations -from owasp_dt.api.vulnerability import get_all_vulnerabilities -from owasp_dt.models import UploadBomBody, IsTokenBeingProcessedResponse, ConfigProperty, ConfigPropertyPropertyType, Policy, PolicyViolationState, PolicyCondition, PolicyConditionSubject, PolicyConditionOperator, License, PolicyOperator -from owasp_dt.types import UNSET -import test - -__base_dir = Path(__file__).parent +from owasp_dt.models import UploadBomBody, IsTokenBeingProcessedResponse def test_upload_sbom(client: owasp_dt.Client): - with open(__base_dir / "files/test.sbom.xml") as sbom_file: + with open(test.base_dir / "files/test.sbom.xml") as sbom_file: resp = upload_bom.sync_detailed(client=client, body=UploadBomBody( project_name=test.project_name, auto_create=True, @@ -48,91 +35,3 @@ def test_get_scan_status(client: owasp_dt.Client): sleep(1) assert i < max_tries, f"Scan not finished within {max_tries} seconds" - -@pytest.mark.depends(on=['test_upload_sbom']) -def test_search_project_by_name(client: owasp_dt.Client): - resp = get_projects.sync_detailed(client=client, name=test.project_name) - projects = resp.parsed - assert len(projects) > 0 - assert projects[0].uuid is not None - test.project_uuid = projects[0].uuid - -@pytest.mark.depends(on=['test_search_project_by_name', 'test_get_scan_status']) -def test_get_project_metrics(client: owasp_dt.Client): - resp = get_project_current_metrics.sync_detailed(client=client, uuid=test.project_uuid) - metrics = resp.parsed - - -@pytest.mark.depends(on=['test_search_project_by_name', 'test_get_scan_status']) -def test_get_project_violations(client: owasp_dt.Client): - resp = get_violations_by_project.sync_detailed(client=client, uuid=test.project_uuid) - violations = resp.parsed - -@pytest.mark.depends(on=['test/test_config.py::test_trigger_vulnerabilities_update']) -def test_get_vulnerabilities(client: owasp_dt.Client): - def _get_vulnerabilities(): - resp = get_all_vulnerabilities.sync_detailed(client=client, page_size=1) - vulnerabilities = resp.parsed - assert len(vulnerabilities) > 0 - - test.retry(_get_vulnerabilities, 10) - - -@pytest.mark.depends(on=["test_get_vulnerabilities", 'test_upload_sbom']) -@pytest.mark.xfail(reason="https://github.com/DependencyTrack/dependency-track/issues/5401") -def test_get_vulnerability_metrics(client: owasp_dt.Client): - def _get_vulnerability_metrics(): - resp = get_vulnerability_metrics.sync_detailed(client=client) - vulnerabilities = resp.parsed - assert len(vulnerabilities) > 0 - - test.retry(_get_vulnerability_metrics, 10) - - -def assert_mit_license_uuid(client: owasp_dt.Client): - if empty(test.mit_license_uuid): - resp = get_license.sync_detailed(client=client, license_id="MIT") - assert resp.status_code == 200 - license = resp.parsed - assert isinstance(license, License) - __mit_license_uuid = str(license.uuid) - return test.mit_license_uuid - - -def test_create_test_policy(client: owasp_dt.Client): - policy = Policy( - uuid="", - name="Forbid MIT license", - violation_state=PolicyViolationState.FAIL, - operator=PolicyOperator.ANY, - ) - resp = create_policy.sync_detailed(client=client, body=policy) - if resp.status_code == 409: - return - assert resp.status_code == 201 - policy = resp.parsed - assert isinstance(policy, Policy) - - license_uuid = assert_mit_license_uuid(client) - - assert not empty(license_uuid), "MIT license not found" - - condition = PolicyCondition( - uuid="", - policy=UNSET, - subject=PolicyConditionSubject.LICENSE, - operator=PolicyConditionOperator.IS, - value=license_uuid, - ) - resp = create_policy_condition.sync_detailed(client=client, uuid=policy.uuid, body=condition) - assert resp.status_code == 201 - - -@pytest.mark.depends(on=['test_create_test_policy', 'test_upload_sbom']) -def test_get_violations(client: owasp_dt.Client): - def _get_violations(): - resp = get_violations.sync_detailed(client=client, page_size=1) - violations = resp.parsed - assert len(violations) > 0 - - test.retry(_get_violations, 600) diff --git a/test/test_violations.py b/test/test_violations.py new file mode 100644 index 0000000..4a12381 --- /dev/null +++ b/test/test_violations.py @@ -0,0 +1,66 @@ +import pytest +from is_empty import empty + +import owasp_dt +import test +from owasp_dt.api.license_ import get_license +from owasp_dt.api.policy import create_policy +from owasp_dt.api.policy_condition import create_policy_condition +from owasp_dt.api.violation import get_violations_by_project, get_violations +from owasp_dt.models import Policy, PolicyViolationState, PolicyCondition, PolicyConditionSubject, PolicyConditionOperator, License, PolicyOperator +from owasp_dt.types import UNSET + + +def assert_mit_license_uuid(client: owasp_dt.Client): + if empty(test.mit_license_uuid): + resp = get_license.sync_detailed(client=client, license_id="MIT") + assert resp.status_code == 200 + license = resp.parsed + assert isinstance(license, License) + __mit_license_uuid = str(license.uuid) + return test.mit_license_uuid + + +def test_create_test_policy(client: owasp_dt.Client): + policy = Policy( + uuid="", + name="Forbid MIT license", + violation_state=PolicyViolationState.FAIL, + operator=PolicyOperator.ANY, + ) + resp = create_policy.sync_detailed(client=client, body=policy) + if resp.status_code == 409: + return + assert resp.status_code == 201 + policy = resp.parsed + assert isinstance(policy, Policy) + + license_uuid = assert_mit_license_uuid(client) + + assert not empty(license_uuid), "MIT license not found" + + condition = PolicyCondition( + uuid="", + policy=UNSET, + subject=PolicyConditionSubject.LICENSE, + operator=PolicyConditionOperator.IS, + value=license_uuid, + ) + resp = create_policy_condition.sync_detailed(client=client, uuid=policy.uuid, body=condition) + assert resp.status_code == 201 + + +@pytest.mark.depends(on=['test_create_test_policy', 'test/test_upload.py::test_upload_sbom']) +def test_get_violations(client: owasp_dt.Client): + def _get_violations(): + resp = get_violations.sync_detailed(client=client, page_size=1) + violations = resp.parsed + assert len(violations) > 0 + + test.retry(_get_violations, 600) + + +@pytest.mark.depends(on=['test/test_projects.py::test_search_project_by_name', 'test/test_upload.py::test_get_scan_status']) +def test_get_project_violations(client: owasp_dt.Client): + resp = get_violations_by_project.sync_detailed(client=client, uuid=test.project_uuid) + violations = resp.parsed diff --git a/test/test_vulnerabilities.py b/test/test_vulnerabilities.py new file mode 100644 index 0000000..0878f8f --- /dev/null +++ b/test/test_vulnerabilities.py @@ -0,0 +1,27 @@ +import pytest + +import owasp_dt +import test +from owasp_dt.api.metrics import get_vulnerability_metrics +from owasp_dt.api.vulnerability import get_all_vulnerabilities + + +@pytest.mark.depends(on=['test/test_config.py::test_trigger_vulnerabilities_update']) +def test_get_vulnerabilities(client: owasp_dt.Client): + def _get_vulnerabilities(): + resp = get_all_vulnerabilities.sync_detailed(client=client, page_size=1) + vulnerabilities = resp.parsed + assert len(vulnerabilities) > 0 + + test.retry(_get_vulnerabilities, 10) + + +@pytest.mark.depends(on=["test_get_vulnerabilities", 'test/test_upload.py::test_upload_sbom']) +@pytest.mark.xfail(reason="https://github.com/DependencyTrack/dependency-track/issues/5401") +def test_get_vulnerability_metrics(client: owasp_dt.Client): + def _get_vulnerability_metrics(): + resp = get_vulnerability_metrics.sync_detailed(client=client) + vulnerabilities = resp.parsed + assert len(vulnerabilities) > 0 + + test.retry(_get_vulnerability_metrics, 10) From fe4c8d47e9e4a2b5e29e51274c1485f65fbfbd5b Mon Sep 17 00:00:00 2001 From: Mike Reiche Date: Thu, 23 Oct 2025 16:50:34 +0200 Subject: [PATCH 5/6] Improve tests --- test/test_config.py | 14 -------------- test/test_violations.py | 15 ++++++--------- test/test_vulnerabilities.py | 17 +++++++++++++++-- 3 files changed, 21 insertions(+), 25 deletions(-) delete mode 100644 test/test_config.py diff --git a/test/test_config.py b/test/test_config.py deleted file mode 100644 index 131b294..0000000 --- a/test/test_config.py +++ /dev/null @@ -1,14 +0,0 @@ -import owasp_dt -from owasp_dt.api.config_property import update_config_property -from owasp_dt.models import ConfigProperty, ConfigPropertyPropertyType - - -def test_trigger_vulnerabilities_update(client: owasp_dt.Client): - config = ConfigProperty( - group_name="task-scheduler", - property_name="nist.mirror.cadence", - property_value="1", - property_type=ConfigPropertyPropertyType.NUMBER, - ) - resp = update_config_property.sync_detailed(client=client, body=config) - assert resp.status_code == 200 diff --git a/test/test_violations.py b/test/test_violations.py index 4a12381..8065350 100644 --- a/test/test_violations.py +++ b/test/test_violations.py @@ -11,16 +11,17 @@ from owasp_dt.types import UNSET -def assert_mit_license_uuid(client: owasp_dt.Client): - if empty(test.mit_license_uuid): +def test_mit_license(client: owasp_dt.Client): + def _test_mit_license(): resp = get_license.sync_detailed(client=client, license_id="MIT") assert resp.status_code == 200 license = resp.parsed assert isinstance(license, License) - __mit_license_uuid = str(license.uuid) - return test.mit_license_uuid + test.mit_license_uuid = str(license.uuid) + test.retry(_test_mit_license, 600) +@pytest.mark.depends(on=['test_mit_license']) def test_create_test_policy(client: owasp_dt.Client): policy = Policy( uuid="", @@ -35,16 +36,12 @@ def test_create_test_policy(client: owasp_dt.Client): policy = resp.parsed assert isinstance(policy, Policy) - license_uuid = assert_mit_license_uuid(client) - - assert not empty(license_uuid), "MIT license not found" - condition = PolicyCondition( uuid="", policy=UNSET, subject=PolicyConditionSubject.LICENSE, operator=PolicyConditionOperator.IS, - value=license_uuid, + value=test.mit_license_uuid, ) resp = create_policy_condition.sync_detailed(client=client, uuid=policy.uuid, body=condition) assert resp.status_code == 201 diff --git a/test/test_vulnerabilities.py b/test/test_vulnerabilities.py index 0878f8f..4985a5d 100644 --- a/test/test_vulnerabilities.py +++ b/test/test_vulnerabilities.py @@ -2,18 +2,31 @@ import owasp_dt import test +from owasp_dt.api.config_property import update_config_property from owasp_dt.api.metrics import get_vulnerability_metrics from owasp_dt.api.vulnerability import get_all_vulnerabilities +from owasp_dt.models import ConfigProperty, ConfigPropertyPropertyType -@pytest.mark.depends(on=['test/test_config.py::test_trigger_vulnerabilities_update']) +def test_trigger_vulnerabilities_update(client: owasp_dt.Client): + config = ConfigProperty( + group_name="task-scheduler", + property_name="nist.mirror.cadence", + property_value="1", + property_type=ConfigPropertyPropertyType.NUMBER, + ) + resp = update_config_property.sync_detailed(client=client, body=config) + assert resp.status_code == 200 + + +@pytest.mark.depends(on=['test_trigger_vulnerabilities_update']) def test_get_vulnerabilities(client: owasp_dt.Client): def _get_vulnerabilities(): resp = get_all_vulnerabilities.sync_detailed(client=client, page_size=1) vulnerabilities = resp.parsed assert len(vulnerabilities) > 0 - test.retry(_get_vulnerabilities, 10) + test.retry(_get_vulnerabilities, 600) @pytest.mark.depends(on=["test_get_vulnerabilities", 'test/test_upload.py::test_upload_sbom']) From b362d4194aa831356a10507bb8c3012b466f3cd3 Mon Sep 17 00:00:00 2001 From: Mike Reiche Date: Fri, 24 Oct 2025 09:20:03 +0200 Subject: [PATCH 6/6] Add team tests --- test/test_teams.py | 33 +++++++++++++++++++++++++++++---- test/test_vulnerabilities.py | 21 ++++++++++++++++++--- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/test/test_teams.py b/test/test_teams.py index 7a9a6ee..7c8acab 100644 --- a/test/test_teams.py +++ b/test/test_teams.py @@ -1,8 +1,33 @@ +import pytest +from tinystream import Stream + import owasp_dt -from owasp_dt.api.team import get_teams +from owasp_dt.api.team import get_teams, create_team, delete_team +from owasp_dt.models import Team + +test_team = Team( + uuid="", + name="test-team", +) + +def test_create_team(client: owasp_dt.Client): + global test_team + resp = create_team.sync_detailed(client=client, body=test_team) + assert resp.status_code == 201 + test_team = resp.parsed + +@pytest.mark.depends(on=["test_create_team"]) +def test_created_team(client: owasp_dt.Client): + teams = get_teams.sync(client=client) + assert Stream(teams).filter(lambda team: team.name == "test-team").count() == 1 + +@pytest.mark.depends(on=["test_created_team"]) +def test_delete_team(client: owasp_dt.Client): + resp = delete_team.sync_detailed(client=client, body=test_team) + assert resp.status_code == 204 -def test_team_epoch(client: owasp_dt.Client): +@pytest.mark.depends(on=["test_delete_team"]) +def test_deleted_team(client: owasp_dt.Client): teams = get_teams.sync(client=client) - for team in teams: - pass + assert Stream(teams).filter(lambda team: team.name == "test-team").count() == 0 diff --git a/test/test_vulnerabilities.py b/test/test_vulnerabilities.py index 4985a5d..edc44c6 100644 --- a/test/test_vulnerabilities.py +++ b/test/test_vulnerabilities.py @@ -9,17 +9,32 @@ def test_trigger_vulnerabilities_update(client: owasp_dt.Client): - config = ConfigProperty( + config_property = ConfigProperty( group_name="task-scheduler", property_name="nist.mirror.cadence", property_value="1", property_type=ConfigPropertyPropertyType.NUMBER, ) - resp = update_config_property.sync_detailed(client=client, body=config) + resp = update_config_property.sync_detailed(client=client, body=config_property) assert resp.status_code == 200 -@pytest.mark.depends(on=['test_trigger_vulnerabilities_update']) +def test_enable_nvd(client: owasp_dt.Client): + config_property = ConfigProperty( + group_name="vuln-source", + property_name="nvd.enabled", + property_value="false", + property_type=ConfigPropertyPropertyType.BOOLEAN, + ) + resp = update_config_property.sync_detailed(client=client, body=config_property) + assert resp.status_code == 200 + + config_property.property_value = "true" + resp = update_config_property.sync_detailed(client=client, body=config_property) + assert resp.status_code == 200 + + +@pytest.mark.depends(on=['test_trigger_vulnerabilities_update', "test_enable_nvd"]) def test_get_vulnerabilities(client: owasp_dt.Client): def _get_vulnerabilities(): resp = get_all_vulnerabilities.sync_detailed(client=client, page_size=1)