Skip to content

Commit f6f19a7

Browse files
committed
unit tests for scorecard model functions and minor fixes nexB#1283
Signed-off-by: 404-geek <[email protected]>
1 parent 89d2636 commit f6f19a7

File tree

5 files changed

+238
-116
lines changed

5 files changed

+238
-116
lines changed

scanpipe/models.py

Lines changed: 119 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -4038,103 +4038,6 @@ def as_spdx(self):
40384038
)
40394039

40404040

4041-
class DiscoveredPackageScore(UUIDPKModel, PackageScoreMixin):
4042-
def __str__(self):
4043-
return self.score or str(self.uuid)
4044-
4045-
discovered_package = models.ForeignKey(
4046-
DiscoveredPackage,
4047-
related_name="discovered_packages_score",
4048-
help_text=_("The package for which the score is given"),
4049-
on_delete=models.CASCADE,
4050-
editable=False,
4051-
blank=True,
4052-
null=True,
4053-
)
4054-
4055-
def parse_score_date(date_str, formats=None):
4056-
"""
4057-
Parse a date string into a timezone-aware datetime object,
4058-
or return None if parsing fails.
4059-
"""
4060-
if not formats:
4061-
formats = ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%SZ"]
4062-
4063-
if date_str:
4064-
for fmt in formats:
4065-
try:
4066-
naive_datetime = datetime.strptime(date_str, fmt)
4067-
return timezone.make_aware(
4068-
naive_datetime, timezone.get_current_timezone()
4069-
)
4070-
except ValueError:
4071-
continue
4072-
4073-
# Return None if date_str is None or parsing fails
4074-
return None
4075-
4076-
@classmethod
4077-
@transaction.atomic()
4078-
def create_from_scorecard_data(
4079-
cls, discovered_package, scorecard_data, scoring_tool=None
4080-
):
4081-
"""Create ScoreCard object from scorecard data and discovered package"""
4082-
final_data = {
4083-
"score": scorecard_data.score,
4084-
"scoring_tool_version": scorecard_data.scoring_tool_version,
4085-
"scoring_tool_documentation_url": (
4086-
scorecard_data.scoring_tool_documentation_url
4087-
),
4088-
"score_date": cls.parse_score_date(scorecard_data.score_date),
4089-
}
4090-
4091-
scorecard_object = cls.objects.create(
4092-
**final_data,
4093-
discovered_package=discovered_package,
4094-
scoring_tool=scoring_tool,
4095-
)
4096-
4097-
for check in scorecard_data.checks:
4098-
ScorecardCheck.create_from_data(package_score=scorecard_object, check=check)
4099-
4100-
return scorecard_object
4101-
4102-
@classmethod
4103-
def create_from_package_and_scorecard(cls, scorecard_data, package):
4104-
score_object = cls.create_from_scorecard_data(
4105-
discovered_package=package,
4106-
scorecard_data=scorecard_data,
4107-
scoring_tool="ossf-scorecard",
4108-
)
4109-
return score_object
4110-
4111-
4112-
class ScorecardCheck(UUIDPKModel, ScorecardChecksMixin):
4113-
def __str__(self):
4114-
return self.check_score or str(self.uuid)
4115-
4116-
for_package_score = models.ForeignKey(
4117-
DiscoveredPackageScore,
4118-
related_name="discovered_packages_score_checks",
4119-
help_text=_("The checks for which the score is given"),
4120-
on_delete=models.CASCADE,
4121-
editable=False,
4122-
blank=True,
4123-
null=True,
4124-
)
4125-
4126-
@classmethod
4127-
def create_from_data(cls, package_score, check):
4128-
"""Create a ScorecardCheck instance from provided data."""
4129-
return cls.objects.create(
4130-
check_name=check.check_name,
4131-
check_score=check.check_score,
4132-
reason=check.reason or "",
4133-
details=check.details or [],
4134-
for_package_score=package_score,
4135-
)
4136-
4137-
41384041
def normalize_package_url_data(purl_mapping, ignore_nulls=False):
41394042
"""
41404043
Normalize a mapping of purl data so database queries with
@@ -4376,3 +4279,122 @@ def create_auth_token(sender, instance=None, created=False, **kwargs):
43764279
"""Create an API key token on user creation, using the signal system."""
43774280
if created:
43784281
Token.objects.create(user_id=instance.pk)
4282+
4283+
4284+
class DiscoveredPackageScore(UUIDPKModel, PackageScoreMixin):
4285+
"""Represents a security or quality score for a DiscoveredPackage."""
4286+
4287+
discovered_package = models.ForeignKey(
4288+
DiscoveredPackage,
4289+
related_name="discovered_packages_score",
4290+
help_text=_("The package for which the score is given"),
4291+
on_delete=models.CASCADE,
4292+
editable=False,
4293+
)
4294+
4295+
class Meta:
4296+
verbose_name = "discovered package score"
4297+
verbose_name_plural = "discovered package scores"
4298+
ordering = ["-score"]
4299+
indexes = [
4300+
models.Index(fields=["score"]),
4301+
models.Index(fields=["scoring_tool_version"]),
4302+
]
4303+
4304+
def __str__(self):
4305+
return self.score or str(self.uuid)
4306+
4307+
@classmethod
4308+
def parse_score_date(cls, date_str, formats=None):
4309+
"""
4310+
Parse a date string into a timezone-aware datetime object,
4311+
or return None if parsing fails.
4312+
"""
4313+
if not formats:
4314+
formats = ["%Y-%m-%d", "%Y-%m-%dT%H:%M:%SZ"]
4315+
4316+
if date_str:
4317+
for fmt in formats:
4318+
try:
4319+
naive_datetime = datetime.strptime(date_str, fmt)
4320+
return timezone.make_aware(
4321+
naive_datetime, timezone.get_current_timezone()
4322+
)
4323+
except ValueError:
4324+
continue
4325+
4326+
# Return None if date_str is None or parsing fails
4327+
return None
4328+
4329+
@classmethod
4330+
@transaction.atomic()
4331+
def create_from_scorecard_data(
4332+
cls, discovered_package, scorecard_data, scoring_tool=None
4333+
):
4334+
"""Create ScoreCard object from scorecard data and discovered package"""
4335+
final_data = {
4336+
"score": scorecard_data.score,
4337+
"scoring_tool_version": scorecard_data.scoring_tool_version,
4338+
"scoring_tool_documentation_url": (
4339+
scorecard_data.scoring_tool_documentation_url
4340+
),
4341+
"score_date": cls.parse_score_date(scorecard_data.score_date),
4342+
}
4343+
4344+
scorecard_object = cls.objects.create(
4345+
**final_data,
4346+
discovered_package=discovered_package,
4347+
scoring_tool=scoring_tool,
4348+
)
4349+
4350+
for check in scorecard_data.checks:
4351+
ScorecardCheck.create_from_data(package_score=scorecard_object, check=check)
4352+
4353+
return scorecard_object
4354+
4355+
@classmethod
4356+
def create_from_package_and_scorecard(cls, scorecard_data, package):
4357+
score_object = cls.create_from_scorecard_data(
4358+
discovered_package=package,
4359+
scorecard_data=scorecard_data,
4360+
scoring_tool="ossf-scorecard",
4361+
)
4362+
return score_object
4363+
4364+
4365+
class ScorecardCheck(UUIDPKModel, ScorecardChecksMixin):
4366+
"""
4367+
Represents an individual check within a Scorecard evaluation for a
4368+
DiscoveredPackageScore.
4369+
"""
4370+
4371+
for_package_score = models.ForeignKey(
4372+
DiscoveredPackageScore,
4373+
related_name="discovered_packages_score_checks",
4374+
help_text=_("The checks for which the score is given"),
4375+
on_delete=models.CASCADE,
4376+
editable=False,
4377+
)
4378+
4379+
class Meta:
4380+
verbose_name = "scorecard check"
4381+
verbose_name_plural = "scorecard checks"
4382+
ordering = ["-check_score"]
4383+
indexes = [
4384+
models.Index(fields=["check_score"]),
4385+
models.Index(fields=["check_name"]),
4386+
]
4387+
4388+
def __str__(self):
4389+
return self.check_score or str(self.uuid)
4390+
4391+
@classmethod
4392+
def create_from_data(cls, package_score, check):
4393+
"""Create a ScorecardCheck instance from provided data."""
4394+
return cls.objects.create(
4395+
check_name=check.check_name,
4396+
check_score=check.check_score,
4397+
reason=check.reason or "",
4398+
details=check.details or [],
4399+
for_package_score=package_score,
4400+
)

scanpipe/pipelines/fetch_scorecode_info.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,17 @@ class FetchScoreCodeInfo(Pipeline):
4141
@classmethod
4242
def steps(cls):
4343
return (
44-
cls.check_scorecode_service_availability,
45-
cls.fetch_packages_scorecode_info,
44+
cls.check_ScoreCode_service_availability,
45+
cls.fetch_packages_ScoreCode_info,
4646
)
4747

48-
def check_scorecode_service_availability(self):
49-
"""Check if the scorecode service is configured and available."""
48+
def check_ScoreCode_service_availability(self):
49+
"""Check if the ScoreCode service is configured and available."""
5050
if not ossf_scorecard.is_available():
51-
raise Exception("scorecode service is not available.")
51+
raise Exception("ScoreCode service is not available.")
5252

53-
def fetch_packages_scorecode_info(self):
54-
"""Fetch scorecode information for each of the project's discovered packages."""
53+
def fetch_packages_ScoreCode_info(self):
54+
"""Fetch ScoreCode information for each of the project's discovered packages."""
5555
for package in self.project.discoveredpackages.all():
5656
scorecard_data = ossf_scorecard.fetch_scorecard_info(
5757
package=package, logger=None

scanpipe/tests/__init__.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,9 @@
2020
# ScanCode.io is a free software code scanning tool from nexB Inc. and others.
2121
# Visit https://github.com/nexB/scancode.io for support and download.
2222

23-
import json
2423
import os
2524
import uuid
2625
from datetime import datetime
27-
from pathlib import Path
2826
from unittest import mock
2927

3028
from django.apps import apps
@@ -300,10 +298,3 @@ def make_message(project, **data):
300298
"license_key": "mpl-2.0",
301299
},
302300
}
303-
304-
scorecard_data = None
305-
306-
data = Path(__file__).parent / "data"
307-
308-
with open(f"{data}/scorecode/scorecard_response.json") as file:
309-
scorecard_data = json.load(file)

0 commit comments

Comments
 (0)