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/pyproject.toml b/pyproject.toml index 39f945f..8b74d2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +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", ] 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..e47d9cf 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -1,30 +1,43 @@ +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 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( - 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", "") - } - ) + +base_dir = Path(__file__).parent + +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) + #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") + assert load_dotenv(base_dir / "test.env") + def teardown_module(): pass diff --git a/test/api.py b/test/api.py new file mode 100644 index 0000000..d356298 --- /dev/null +++ b/test/api.py @@ -0,0 +1,44 @@ +from typing import Generator, Callable, TypeVar + +from owasp_dt import Client +from owasp_dt.api.project_property import create_property_1, update_property +from owasp_dt.models import ProjectProperty +from owasp_dt.types import Response +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/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_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 new file mode 100644 index 0000000..17032bf --- /dev/null +++ b/test/test_property.py @@ -0,0 +1,37 @@ +import pytest +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 + + +@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", + 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_team.py b/test/test_team.py deleted file mode 100644 index 957afec..0000000 --- a/test/test_team.py +++ /dev/null @@ -1,7 +0,0 @@ -from owasp_dt.api.team import get_teams -import owasp_dt - -def test_team_epoch(client: owasp_dt.Client): - teams = get_teams.sync(client=client) - for team in teams: - pass diff --git a/test/test_teams.py b/test/test_teams.py new file mode 100644 index 0000000..7c8acab --- /dev/null +++ b/test/test_teams.py @@ -0,0 +1,33 @@ +import pytest +from tinystream import Stream + +import owasp_dt +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 + +@pytest.mark.depends(on=["test_delete_team"]) +def test_deleted_team(client: owasp_dt.Client): + teams = get_teams.sync(client=client) + assert Stream(teams).filter(lambda team: team.name == "test-team").count() == 0 diff --git a/test/test_upload.py b/test/test_upload.py new file mode 100644 index 0000000..461b826 --- /dev/null +++ b/test/test_upload.py @@ -0,0 +1,37 @@ +from pathlib import Path +from time import sleep + +import pytest + +import owasp_dt +import test +from owasp_dt.api.bom import upload_bom +from owasp_dt.api.event import is_token_being_processed_1 +from owasp_dt.models import UploadBomBody, IsTokenBeingProcessedResponse + +def test_upload_sbom(client: owasp_dt.Client): + 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, + bom=sbom_file.read() + )) + upload = resp.parsed + assert upload is not None, "API call failed. Check client permissions." + assert upload.token is not None + test.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=test.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" diff --git a/test/test_violations.py b/test/test_violations.py new file mode 100644 index 0000000..8065350 --- /dev/null +++ b/test/test_violations.py @@ -0,0 +1,63 @@ +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 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) + 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="", + 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) + + condition = PolicyCondition( + uuid="", + policy=UNSET, + subject=PolicyConditionSubject.LICENSE, + operator=PolicyConditionOperator.IS, + value=test.mit_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..edc44c6 --- /dev/null +++ b/test/test_vulnerabilities.py @@ -0,0 +1,55 @@ +import pytest + +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 + + +def test_trigger_vulnerabilities_update(client: owasp_dt.Client): + 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_property) + assert resp.status_code == 200 + + +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) + vulnerabilities = resp.parsed + assert len(vulnerabilities) > 0 + + test.retry(_get_vulnerabilities, 600) + + +@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)