Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not assume that Dependency-Track projects and components always ha… #10909

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading