Skip to content

Commit

Permalink
Do not assume that Dependency-Track projects and components always ha…
Browse files Browse the repository at this point in the history
…ve a version number.

Fixes #10848.
  • Loading branch information
fniessink committed Feb 27, 2025
1 parent 95192fe commit deb18ec
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class DependencyTrackProject(TypedDict):
lastBomImport: int
name: str
uuid: str
version: str
version: NotRequired[str]
metrics: NotRequired[DependencyTrackMetrics]


Expand Down Expand Up @@ -87,7 +87,8 @@ async def _get_projects_from_response(self, response: Response) -> AsyncIterator
def _project_matches(project: DependencyTrackProject, names: list[str], versions: list[str]) -> bool:
"""Return whether the project name matches the project names and versions."""
project_matches_name = match_string_or_regular_expression(project["name"], names) if names else True
project_matches_version = match_string_or_regular_expression(project["version"], versions) if versions else True
project_version = project.get("version", "unknown")
project_matches_version = match_string_or_regular_expression(project_version, versions) if versions else True
return project_matches_name and project_matches_version

@staticmethod
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Dependency-Track security warnings collector."""

from typing import TypedDict
from typing import NotRequired, TypedDict

from collector_utilities.type import URL
from model import Entities, Entity, SourceResponses
Expand All @@ -14,14 +14,14 @@ class DependencyTrackRepositoryMetaData(TypedDict):
latestVersion: str


class DependencyTrackComponent(TypedDict, total=False):
class DependencyTrackComponent(TypedDict):
"""Component as returned by Dependency-Track."""

name: str
project: DependencyTrackProject
repositoryMeta: DependencyTrackRepositoryMetaData
uuid: str
version: str
repositoryMeta: NotRequired[DependencyTrackRepositoryMetaData]
version: NotRequired[str]


class DependencyTrackDependencies(DependencyTrackLatestVersionStatusBase):
Expand All @@ -45,7 +45,7 @@ async def _parse_entities(self, responses: SourceResponses) -> Entities:
def _create_entity(self, component: DependencyTrackComponent) -> Entity:
"""Create an entity from the component."""
project = component["project"]
current_version = component["version"]
current_version = component.get("version", "unknown")
latest_version = component.get("repositoryMeta", {}).get("latestVersion", "unknown")
landing_url = str(self._parameter("landing_url")).strip("/")
return Entity(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ class DependencyTrackTestCase(SourceCollectorTestCase):

SOURCE_TYPE = "dependency_track"

def projects(self) -> list[DependencyTrackProject]:
def projects(self, version: str = "1.4") -> list[DependencyTrackProject]:
"""Create the Dependency-Track projects fixture."""
return [
DependencyTrackProject(
name="project name",
uuid="project uuid",
version="1.4",
lastBomImport=0,
metrics=DependencyTrackMetrics(),
),
]
project = DependencyTrackProject(
name="project name",
uuid="project uuid",
lastBomImport=0,
metrics=DependencyTrackMetrics(),
)
if version:
project["version"] = version
return [project]
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ def dependencies(self, latest_version: str) -> list[DependencyTrackComponent]:
dependency: DependencyTrackComponent = {
"name": "component name",
"project": self.projects()[0],
"uuid": "component-uuid",
"version": "1.0",
"uuid": "component-uuid",
}
if latest_version:
dependency["repositoryMeta"] = {"latestVersion": latest_version}
Expand Down Expand Up @@ -62,14 +62,26 @@ async def test_unknown_latest_version(self):
response = await self.collect(get_request_json_side_effect=[self.projects(), self.dependencies("")])
self.assert_measurement(response, value="1", entities=self.entities("unknown", "unknown"))

async def test_filter_by_latest_version_status(self):
async def test_filter_by_latest_version_status_with_match(self):
"""Test that components can be filtered by latest version status."""
self.set_source_parameter("latest_version_status", ["up-to-date"])
response = await self.collect(get_request_json_side_effect=[self.projects(), self.dependencies("1.0")])
self.assert_measurement(response, value="1", entities=self.entities("1.0", "up-to-date"))

async def test_filter_by_latest_version_status_without_match(self):
"""Test that components can be filtered by latest version status."""
self.set_source_parameter("latest_version_status", ["update possible"])
response = await self.collect(get_request_json_side_effect=[self.projects(), self.dependencies("1.0")])
self.assert_measurement(response, value="0", entities=[])

async def test_filter_by_project_name(self):
"""Test filtering projects by name."""
async def test_filter_by_project_name_with_match(self):
"""Test filtering projects by name and match."""
self.set_source_parameter("project_names", ["project name", "other project"])
response = await self.collect(get_request_json_side_effect=[self.projects(), self.dependencies("1.0")])
self.assert_measurement(response, value="1", entities=self.entities("1.0", "up-to-date"))

async def test_filter_by_project_name_without_match(self):
"""Test filtering projects by name without match."""
self.set_source_parameter("project_names", ["other project"])
response = await self.collect(get_request_json_return_value=self.projects())
self.assert_measurement(response, value="0", entities=[])
Expand All @@ -80,8 +92,14 @@ async def test_filter_by_project_regular_expression(self):
response = await self.collect(get_request_json_side_effect=[self.projects(), self.dependencies("1.0")])
self.assert_measurement(response, value="1", entities=self.entities("1.0", "up-to-date"))

async def test_filter_by_project_version(self):
"""Test filtering projects by version."""
async def test_filter_by_project_version_with_match(self):
"""Test filtering projects by version with a match."""
self.set_source_parameter("project_versions", ["1.2", "1.3", "1.4"])
response = await self.collect(get_request_json_side_effect=[self.projects(), self.dependencies("1.0")])
self.assert_measurement(response, value="1", entities=self.entities("1.0", "up-to-date"))

async def test_filter_by_project_version_without_match(self):
"""Test filtering projects by version without a match."""
self.set_source_parameter("project_versions", ["1.2", "1.3"])
response = await self.collect(get_request_json_return_value=self.projects())
self.assert_measurement(response, value="0", entities=[])
Expand All @@ -92,3 +110,9 @@ async def test_filter_by_project_name_and_version(self):
self.set_source_parameter("project_versions", ["1.3", "1.4"])
response = await self.collect(get_request_json_side_effect=[self.projects(), self.dependencies("1.0")])
self.assert_measurement(response, value="1", entities=self.entities("1.0", "up-to-date"))

async def test_filter_by_project_version_when_project_has_no_version(self):
"""Test filtering projects by version."""
self.set_source_parameter("project_versions", ["1.2", "1.3"])
response = await self.collect(get_request_json_return_value=self.projects(version=""))
self.assert_measurement(response, value="0", entities=[])
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class DependencyTrackSourceUpToDatenessVersionTest(DependencyTrackTestCase):
METRIC_TYPE = "source_up_to_dateness"
LANDING_URL = "https://dependency_track"

def projects(self) -> list[DependencyTrackProject]:
def projects(self, version: str = "1.4") -> list[DependencyTrackProject]:
"""Create Dependency-Track projects fixture."""
now = datetime.now(tz=tzlocal()).replace(microsecond=0)
self.yesterday = now - timedelta(days=1)
Expand Down
1 change: 1 addition & 0 deletions docs/src/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ If your currently installed *Quality-time* version is not the latest version, pl
- Increase contrast for disabled items in the menu bar. Fixes [#10840](https://github.com/ICTU/quality-time/issues/10840).
- Links to documentation on Read the Docs for subjects, metrics, or sources with hyphens in their name wouldn't scroll to the right location. Fixes [#10843](https://github.com/ICTU/quality-time/issues/10843).
- Metric details were not shown in exports to PDF. Fixes [#10845](https://github.com/ICTU/quality-time/issues/10845).
- Do not assume that Dependency-Track projects and components always have a version number. Fixes [#10848](https://github.com/ICTU/quality-time/issues/10848).
- The software documentation was outdated (among other things, the API-server health check endpoint). Fixes [#10858](https://github.com/ICTU/quality-time/issues/10858).
- Keep the footer at the bottom of the page even if the report is very short. Fixes [#10877](https://github.com/ICTU/quality-time/issues/10877).
- Automatically expand long comments when exporting to PDF. Fixes [#10892](https://github.com/ICTU/quality-time/issues/10892).
Expand Down

0 comments on commit deb18ec

Please sign in to comment.