Skip to content

Commit

Permalink
tests: Add integration tests (#157)
Browse files Browse the repository at this point in the history
  • Loading branch information
mmarseu authored Aug 8, 2024
1 parent cf7a671 commit 668b9f2
Show file tree
Hide file tree
Showing 77 changed files with 24,665 additions and 575 deletions.
8 changes: 5 additions & 3 deletions .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
max_line_length = 99

ignore =
# Bare except
E722
# Whitespace before : (conflicting with black, see https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html)
E203
# Multiple statements on one line (def) : (conflicting with black, see https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html)
E704
# Bare except
E722
# Line break occurred before a binary operator (conflicting with black)
W503
# line break after binary operator (conflicting with black)
W504
W504
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ jobs:
- name: Run black
run: poetry run black cdxev tests --check
- name: Run isort
run: poetry run isort cdxev/ tests/ --check-only --profile black
run: poetry run isort cdxev/ tests/ --check-only
- name: Run flake8
run: poetry run flake8 cdxev tests
- name: Run mypy
run: poetry run mypy --install-types --non-interactive --config-file=pyproject.toml
- name: Run bandit
run: poetry run bandit -r cdxev tests
run: poetry run bandit -c pyproject.toml -r cdxev

pytest:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ ENV/

# CycloneDX json files except for those in the tests directory
*.cdx.json
!tests/auxiliary/test_*/*.cdx.json
!tests/**/*.cdx.json
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,5 @@ repos:
rev: "1.7.7"
hooks:
- id: bandit
args: ["-c", "pyproject.toml"]
additional_dependencies: ["bandit[toml]"]
24 changes: 13 additions & 11 deletions cdxev/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def read_sbom(sbom_file: Path, file_type: Optional[str] = None) -> Tuple[dict, s
:raise FileTypeError: If *file_type* isn't specified and can't be guessed.
"""
if not sbom_file.is_file():
raise InputFileError("File not found.")
raise InputFileError(f"File not found: {sbom_file}")

if file_type is None:
file_type = sbom_file.suffix[1:]
Expand Down Expand Up @@ -664,7 +664,7 @@ def invoke_amend(args: argparse.Namespace) -> int:
print(long_desc)
print()

sys.exit()
return Status.OK

if not args.input:
usage_error("<input> argument missing.", args.parser)
Expand Down Expand Up @@ -815,7 +815,7 @@ def has_target() -> bool:
isinstance(target.key, cdxev.set.CoordinatesWithVersionRange)
and target.key.version_range is not None
):
updates[0]["id"]["version_range"] = target.key.version_range
updates[0]["id"]["version-range"] = target.key.version_range

else:
if has_target() or args.key is not None or args.value is not None:
Expand All @@ -825,15 +825,17 @@ def has_target() -> bool:
)

updates = []
with open(args.from_file) as from_file:
try:
try:
with open(args.from_file) as from_file:
updates = json.load(from_file)
except json.JSONDecodeError as ex:
raise InputFileError(
"Invalid JSON passed to --from-file",
None,
ex.lineno,
) from ex
except json.JSONDecodeError as ex:
raise InputFileError(
"Invalid JSON passed to --from-file",
None,
ex.lineno,
) from ex
except FileNotFoundError as ex:
raise InputFileError(f"File not found: {args.from_file}", None) from ex

sbom, _ = read_sbom(args.input)
cfg = cdxev.set.SetConfig(
Expand Down
2 changes: 1 addition & 1 deletion cdxev/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def __init__(
)

def __str__(self) -> str:
return f"{self.details.message}: {self.details.description}"
return str(self.details)


class InputFileError(AppError):
Expand Down
3 changes: 3 additions & 0 deletions cdxev/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ class LogMessage:
line_start: t.Optional[int] = None
"""The line where the error occurred in :py:attr:`file_name`."""

def __str__(self) -> str:
return f"{self.message} at [{self.module_name}]: {self.description}"


class LogMessageFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str: # noqa: N802
Expand Down
40 changes: 19 additions & 21 deletions cdxev/set.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,20 +192,19 @@ def _should_overwrite(
"Use the --force option to overwrite."
),
)
else: # pragma: no cover
if _prompt_for_overwrite(property, component_id):
return True

if _prompt_for_overwrite(property, component_id):
logger.debug(
f'Overwriting "{property}" on component "{component_id}" due to user choice.'
f'Not overwriting "{property}" on component "{component_id}" due to user choice.'
)
return True

logger.debug(
f'Not overwriting "{property}" on component "{component_id}" due to user choice.'
)
return False
return False


def _prompt_for_overwrite(property: str, component_id: ComponentIdentity) -> bool:
def _prompt_for_overwrite(
property: str, component_id: ComponentIdentity
) -> bool: # pragma: no cover
print(
f'The property "{property}" is already present on the component with id "{component_id}".'
)
Expand All @@ -232,8 +231,7 @@ def _update_id(
for key in old:
instance_list = map.pop(key)

if instance_list is None:
return
instance_list = t.cast(list[dict], instance_list)

for key in new:
map[key] = instance_list
Expand All @@ -247,6 +245,12 @@ def _do_update(component: dict, update: dict, ctx: Context) -> None:
remap = False

for prop in update_set:
if _should_update_id(prop):
original_id = original_id or ComponentIdentity.create(component, True)

if _should_remap(prop):
remap = True

if _should_delete(prop, component, update_set):
logger.debug(f'Deleting "{prop}" on component "{component_id}".')
del component[prop]
Expand All @@ -257,12 +261,6 @@ def _do_update(component: dict, update: dict, ctx: Context) -> None:
component[prop].append(update_set[prop])
continue

if _should_update_id(prop):
original_id = original_id or ComponentIdentity.create(component, True)

if _should_remap(prop):
remap = True

if prop not in component or _should_overwrite(
prop, component_id, ctx.config.force
):
Expand Down Expand Up @@ -349,12 +347,12 @@ def run(sbom: dict, updates: t.Sequence[dict[str, t.Any]], cfg: SetConfig) -> No

try:
_validate_update_list(updates, ctx)
except AppError:
msg = LogMessage(
except AppError as e:
raise AppError(
"Set not performed",
f'Exception was raised while setting from file "{cfg.from_file}',
f"Invalid update record: {e.details.description}",
log_msg=e.details,
)
raise AppError(log_msg=msg)

ctx.component_map = _map_out_components(sbom)

Expand Down
9 changes: 5 additions & 4 deletions cdxev/validator/customreports.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ def __init__(
self,
file_path: pathlib.Path,
target: t.Union[t.TextIO, pathlib.Path],
buffer: dict[str, list] = {"issues": []},
buffer: t.Optional[dict[str, list]] = None,
):
"""
Creates a new handler with the given target.
:param target: The target can be either a path to a file or a text stream object.
"""
super().__init__(logging.ERROR)
self.buffer = buffer
self.buffer = buffer if buffer is not None else {"issues": []}
self.target = target
self.file_path = file_path

Expand Down Expand Up @@ -110,15 +110,16 @@ def __init__(
self,
file_path: pathlib.Path,
target: t.Union[t.TextIO, pathlib.Path],
buffer: list = [],
buffer: t.Optional[list] = None,
):
"""
Creates a new handler with the given target.
:param target: The target can be either a path to a file or a text stream object.
"""
super().__init__(logging.ERROR)
self.buffer = buffer

self.buffer = buffer if buffer is not None else []
self.target = target
self.file_path = file_path

Expand Down
21 changes: 14 additions & 7 deletions cdxev/validator/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ def validate_sbom(
) -> int:
errors: list[str] = []
if (schema_path is not None) == bool(schema_type):
raise AssertionError(
raise AssertionError( # pragma: no cover
"Exactly one of schema_path or schema_type must be non-None"
)

if input_format == "json":
try:
spec_version: str = sbom["specVersion"]
except KeyError:
except (KeyError, TypeError):
raise AppError(
"Invalid SBOM",
"Failed to validate against built-in schema because 'specVersion' is missing. "
Expand All @@ -48,13 +48,16 @@ def validate_sbom(
sbom_schema = open_schema(spec_version, schema_type, schema_path)

if filename_regex is not None:
# Filename should be validated
filename_error = validate_filename(
file.name, filename_regex, sbom, schema_type
)
if filename_error:
if filename_regex == "" and schema_type == "default":
if filename_regex == "" and schema_type != "custom":
# Implicit validation against CycloneDX recommendations is only a warning
logger.warning(filename_error)
else:
# Explicit filename pattern or custom schema produces validation errors
errors.append("SBOM has the mistake: " + filename_error)

schema_spdx = Resource.from_contents(
Expand Down Expand Up @@ -175,15 +178,17 @@ def validate_sbom(
else:
errors.append(error_path + error.message)
sorted_errors = set(sorted(errors))

report_handler: t.Optional[logging.Handler] = None
if report_format == "warnings-ng":
# The following cast is safe because the caller of this function made sure that
# report_path is not None when report_format is not None.
warnings_ng_handler = WarningsNgReporter(file, t.cast(Path, report_path))
logger.addHandler(warnings_ng_handler)
report_handler = WarningsNgReporter(file, t.cast(Path, report_path))
logger.addHandler(report_handler)
elif report_format == "gitlab-code-quality":
# See comment above
gitlab_cq_handler = GitLabCQReporter(file, t.cast(Path, report_path))
logger.addHandler(gitlab_cq_handler)
report_handler = GitLabCQReporter(file, t.cast(Path, report_path))
logger.addHandler(report_handler)
if len(sorted_errors) == 0:
logger.info("SBOM is compliant to the provided specification schema")
return 0
Expand All @@ -198,4 +203,6 @@ def validate_sbom(
module_name=error_msg[0 : error_msg.find("has the mistake") - 1],
)
)
if report_handler is not None:
report_handler.close()
return 1
5 changes: 3 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 15 additions & 11 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
[tool.poetry]
name = "cyclonedx-editor-validator"
version = "0"
version = "0.0.0"
description = "Tool for creating, modifying and validating CycloneDX SBOMs."
authors = [
"Aleg Vilinski <[email protected]>",
"Christian Beck <[email protected]>",
"Moritz Marseu <[email protected]>"
"Moritz Marseu <[email protected]>",
]
license = "GPL-3.0-or-later"
readme = "README.md"
packages = [{include = "cdxev"}]
packages = [{ include = "cdxev" }]

[tool.poetry.urls]
Documentation = 'https://festo-se.github.io/cyclonedx-editor-validator/'
Expand All @@ -23,13 +23,13 @@ cdx-ev = "cdxev.__main__:main"
[tool.poetry.dependencies]
python = "^3.9.0"
python-dateutil = "2.9.0.post0"
jsonschema = {version = "4.23.0", extras = ["format"]}
jsonschema = { version = "4.23.0", extras = ["format"] }
docstring-parser = "^0.16"
charset-normalizer = "^3.3.2"
pyicu = [
{version = "^2.13.1", platform = "darwin"},
{version = "^2.13.1", platform = "linux"}
]
{ version = "^2.13.1", platform = "darwin" },
{ version = "^2.13.1", platform = "linux" },
]
natsort = "^8.4.0"
univers = "30.12.0"

Expand All @@ -44,7 +44,7 @@ pytest = "8.3.2"
coverage = "7.6.1"
toml = "0.10.2"
typing-extensions = "4.12.2"
bandit = "1.7.9"
bandit = { version = "1.7.9", extras = ["toml"] }
isort = "5.13.2"
pre-commit = "3.8.0"

Expand All @@ -57,9 +57,7 @@ requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[tool.semantic_release]
version_variable = [
"pyproject.toml:version"
]
version_variable = ["pyproject.toml:version"]
branch = "master"
upload_to_repository = false
upload_to_release = false
Expand All @@ -77,3 +75,9 @@ source = ["cdxev"]
omit = ["*__init__.py*"]

[tool.black]

[tool.bandit]
exclude_dirs = ["tests"]

[tool.isort]
profile = "black"
Loading

0 comments on commit 668b9f2

Please sign in to comment.