more changes

This commit is contained in:
David Duran Rosich committed 2026-10-28 00:38:34 +01:00
1 parent 0fe06f2ba5
commit be5d4dd97f
3 files changed
+152 -55

No files matched your search

+96 -6
View File
@@ -559,9 +559,28 @@ class ReportTests(unittest.TestCase):
self.assertIn("openssl 3.0.1 -> 3.0.2", body)
self.assertIn("Overall severity: C 1/1 H 1/2", body)
self.assertNotIn("Overall severity: C 1/1 H 1/2 M", body)
self.assertIn("Overall: 2/3 fixable/total in 1 container(s)", body)
self.assertIn("Overall: 2/3 fixable/total in 1 image(s)", body)
self.assertNotIn("Total:", body)
def test_build_final_summary_body_merges_important_containers_per_image(self) -> None:
result = scan.summarise(REPORT)
target = scan.ScanTarget(image="nginx:1.27", containers=["web", "worker"])
aggregated = scan.AggregatedReport()
aggregated.results["nginx:1.27"] = result
all_rows = scan.build_container_rows(
[target], aggregated, scan.severity_rank("HIGH"), include_empty=True
)
body = scan.build_final_summary_body(
[("worker", target, result), ("web", target, result)], all_rows, "HIGH"
)
# Both important containers share one image, so one table row and one
# package line, listing both names.
self.assertRegex(body, table_row_pattern(["nginx:1.27", "web, worker", "1/1", "1/2"]))
self.assertIn("* web, worker: ", body)
self.assertNotIn("* worker: ", body)
self.assertNotIn("* web: ", body)
self.assertIn("Overall: 2/3 fixable/total in 1 image(s)", body)
def test_summary_table_pads_every_column(self) -> None:
table = scan.summary_table(
[
@@ -587,6 +606,54 @@ class ReportTests(unittest.TestCase):
),
)
def test_summary_table_sorts_by_container_then_image(self) -> None:
counts = {"CRITICAL": 0, "HIGH": 0, "MEDIUM": 0, "LOW": 0}
table = scan.summary_table(
[
("nginx:1.27", "web", counts, counts),
("redis:7", "cache", counts, counts),
("nginx:1.25", "web", counts, counts),
],
["CRITICAL", "HIGH"],
)
self.assertEqual(
table,
"\n".join(
[
"```",
"| Image | Container | C | H |",
"| ---------- | --------- | --- | --- |",
"| redis:7 | cache | 0/0 | 0/0 |",
"| nginx:1.25 | web | 0/0 | 0/0 |",
"| nginx:1.27 | web | 0/0 | 0/0 |",
"```",
]
),
)
def test_build_container_rows_merges_containers_per_image(self) -> None:
aggregated = scan.AggregatedReport()
for image in ("redis:7", "nginx:1.27", "alpine:3.20"):
aggregated.results[image] = scan.summarise(REPORT)
targets = [
scan.ScanTarget(image="redis:7", containers=["cache"]),
scan.ScanTarget(image="nginx:1.27", containers=["web", "cache", "worker"]),
scan.ScanTarget(image="alpine:3.20"),
]
rows = scan.build_container_rows(
targets, aggregated, scan.severity_rank("HIGH"), skip={"worker"}
)
# One row per image, containers joined and sorted; images without
# containers keep an empty container cell.
self.assertEqual(
[(row[0], row[1]) for row in rows],
[
("alpine:3.20", ""),
("redis:7", "cache"),
("nginx:1.27", "cache, web"),
],
)
def test_build_missing_body_separates_missing_and_unscanned(self) -> None:
body = scan.build_missing_body(["gone"], ["web"])
self.assertIn("not running", body)
@@ -710,7 +777,7 @@ printf '%s\\n' "$*" >> "$APPRISE_LOG"
self.assertTrue(os.path.exists(os.path.join(self.env["REPORT_DIR"], "testphp-php.json")))
self.assertIn("testphp-php", Path(self.apprise_log).read_text())
def test_important_container_always_notified_when_clean(self) -> None:
def test_clean_important_container_only_in_final_summary(self) -> None:
containers = [container("a" * 64, "alpine:3.20", name="web")]
with FakeDocker(containers) as docker:
code = self.run_main(
@@ -719,12 +786,14 @@ printf '%s\\n' "$*" >> "$APPRISE_LOG"
self.assertEqual(code, 0)
notification = Path(self.apprise_log).read_text()
self.assertIn("Vulnerability scan: web (alpine:3.20)", notification)
# No individual notification for a clean important container.
self.assertNotIn("Vulnerability scan: web (alpine:3.20)", notification)
self.assertNotIn("No CVEs at or above", notification)
self.assertNotIn("Total:", notification)
self.assertIn("No CVEs at or above", notification)
# The final summary still lists it with 0/0 totals.
self.assertIn("Vulnerability scan: summary", notification)
self.assertRegex(notification, table_row_pattern(["alpine:3.20", "web", "0/0", "0/0"]))
self.assertIn("Overall: 0/0 fixable/total in 1 container(s)", notification)
self.assertIn("Overall: 0/0 fixable/total in 1 image(s)", notification)
def test_notification_includes_container(self) -> None:
containers = [container("a" * 64, "testphp-php", name="testphp")]
@@ -755,10 +824,31 @@ printf '%s\\n' "$*" >> "$APPRISE_LOG"
self.assertRegex(notification, table_row_pattern(["redis:7", "cache", "1/1", "0/0"]))
self.assertNotIn("Total:", notification)
self.assertIn("Vulnerability scan: summary", notification)
self.assertIn("Overall: 2/2 fixable/total in 2 container(s)", notification)
self.assertIn("Overall: 2/2 fixable/total in 2 image(s)", notification)
# The important container must not also appear in the summary.
self.assertNotIn("testphp-php [web]", notification)
def test_containers_sharing_an_image_share_one_table_row(self) -> None:
containers = [
container("a" * 64, "testphp-php", name="web"),
container("b" * 64, "testphp-php", name="web2"),
container("c" * 64, "redis:7", name="cache"),
]
with FakeDocker(containers) as docker:
code = self.run_main([], docker.socket_path, IMPORTANT_CONTAINERS="cache")
self.assertEqual(code, 1)
notification = Path(self.apprise_log).read_text()
self.assertIn("Vulnerability scan: cache (redis:7)", notification)
self.assertIn("Vulnerability scan: other containers", notification)
# Both containers on the same image are merged into a single row.
self.assertRegex(
notification, table_row_pattern(["testphp-php", "web, web2", "1/1", "0/0"])
)
self.assertIn("Vulnerability scan: summary", notification)
self.assertRegex(notification, table_row_pattern(["redis:7", "cache", "1/1", "0/0"]))
self.assertIn("Overall: 2/2 fixable/total in 2 image(s)", notification)
def test_missing_important_container_is_reported(self) -> None:
containers = [container("a" * 64, "testphp-php", name="web")]
with FakeDocker(containers) as docker: