+
+
+
+
+ Alternative Technologies ({{ alternative_technologies | length }})
+
+ {% if alternative_technologies %}
+
+ {% endif %}
+
+
-
-
- {% if alternative_technologies %}
- {% for alt_tech in alternative_technologies %}
-
-
-
-
-
-
{{ alt_tech.product_name }}
-
{{ alt_tech.product_description }}
-
-
-
- {% if alt_tech.open_source %}
-
- {% else %}
-
- {% endif %}
- Open Source
-
-
-
- {% if alt_tech.support_plan %}
-
- {% else %}
-
- {% endif %}
- Enterprise Support
-
-
-
- {{ alt_tech.product_url }}
-
-
-
-
- {% endfor %}
- {% else %}
-
-
-
No Alternative Technologies available for the selected Resource type.
-
- {% endif %}
+
+
+
+ {% if alternative_technologies %} {% for alt_tech in
+ alternative_technologies %} {% set alt_resource = namespace(name="Resource Type " ~ alt_tech.resource_type_id) %}
+ {% for resource in resource_inventory %}
+ {% if resource.resource_type|string == alt_tech.resource_type_id|string %}
+ {% set alt_resource.name = resource.name %}
+ {% endif %}
+ {% endfor %}
+
+
+
+
+
Category: {{ alt_resource.name }}
+ {{ alt_tech.product_name }}
+
+
+
+
{{ alt_tech.product_description }}
+
+
+
+ {% if alt_tech.open_source %}
+
+
+
+ {% else %}
+
+
+
+ {% endif %}
+
Open Source
+
+
+ {% if alt_tech.support_plan %}
+
+
+
+ {% else %}
+
+
+
+ {% endif %}
+
Enterprise Support
+
+
+
+
+ {% if alt_tech.product_url %}
+
+
+ Visit {{ alt_tech.product_name }}
+
+ {% endif %}
+
+
+ {% endfor %} {% else %}
+
+
+
+
+
+ No alternative technologies are available for this
+ assessment.
+
+
+
+
+ {% endif %}
+
-
+
-
+
+
-
-
- {% if total_resources > 0 %}
-
- {% endif %}
- {% if total_cost > 0 %}
-
- {% endif %}
- {% if scoring_data %}
-
+
+ {% if total_risks > 0 %}
+
+ {% endif %}
+
+ {% if total_cost > 0 %}
+
+ {% endif %}
+
+ {% if scoring_data %}
+
- {% endif %}
-
-
+ if (gaugeContainer) {
+ const exitScoreChart = new VChart.default(gaugeSpec, {
+ dom: gaugeContainer,
+ });
+ exitScoreChart.renderSync();
+ }
+
+ {% endif %}
+
+
diff --git a/core/utils.py b/core/utils.py
index 3ba82d0..399882f 100644
--- a/core/utils.py
+++ b/core/utils.py
@@ -7,7 +7,7 @@
def copy_assets(report_path: str) -> None:
- assets_folders = ["css", "img", "icons"]
+ assets_folders = ["css", "icons", "img"]
assets_path = os.path.join(report_path, "assets")
# Create the 'assets' directory if it doesn't exist
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1 @@
+
diff --git a/tests/report_fixtures.py b/tests/report_fixtures.py
new file mode 100644
index 0000000..63071fc
--- /dev/null
+++ b/tests/report_fixtures.py
@@ -0,0 +1,113 @@
+import shutil
+from pathlib import Path
+
+
+def build_report_fixture():
+ metadata = {
+ "name": "Smoke Test Assessment",
+ "cloud_service_provider": 2,
+ "exit_strategy": 1,
+ "assessment_type": 1,
+ "timestamp": "2026-05-07 12:00:00 UTC",
+ }
+ provider_details = {
+ "accessKey": "AKIA_TEST",
+ "secretKey": "SECRET_TEST",
+ "region": "eu-central-1",
+ }
+ resource_type_mapping = {
+ "101": {
+ "id": 101,
+ "code": "AWS.EC2.DescribeInstances.Reservations",
+ "name": "EC2 Instance",
+ "icon": "/icons/misc/no_image.png",
+ }
+ }
+ resource_inventory = [
+ {"resource_type": 101, "location": "eu-central-1", "count": 2},
+ ]
+ cost_data = [
+ {"month": "2025-11-01", "cost": 10.5, "currency": "USD"},
+ {"month": "2025-12-01", "cost": 12.0, "currency": "USD"},
+ {"month": "2026-01-01", "cost": 14.75, "currency": "USD"},
+ {"month": "2026-02-01", "cost": 11.25, "currency": "USD"},
+ {"month": "2026-03-01", "cost": 9.0, "currency": "USD"},
+ {"month": "2026-04-01", "cost": 13.4, "currency": "USD"},
+ ]
+ risk_definitions = [
+ {
+ "id": "1",
+ "name": "Limited Alternatives",
+ "description": "There are only a few alternatives available.",
+ "severity": "high",
+ }
+ ]
+ risk_data = [
+ {"resource_type": "101", "risk": "1"},
+ ]
+ alternatives = [
+ {"resource_type": "101", "strategy_type": "1", "alternative_technology": 1},
+ ]
+ alternative_technologies = [
+ {
+ "id": 1,
+ "product_name": "OpenStack",
+ "product_description": "Open source cloud platform.",
+ "product_url": "https://www.openstack.org/",
+ "open_source": "t",
+ "support_plan": "t",
+ "status": "t",
+ }
+ ]
+ return {
+ "metadata": metadata,
+ "provider_details": provider_details,
+ "resource_type_mapping": resource_type_mapping,
+ "resource_inventory": resource_inventory,
+ "cost_data": cost_data,
+ "risk_definitions": risk_definitions,
+ "risk_data": risk_data,
+ "alternatives": alternatives,
+ "alternative_technologies": alternative_technologies,
+ "exit_strategy": 1,
+ }
+
+
+def build_empty_report_fixture():
+ metadata = {
+ "name": "Empty State Assessment",
+ "cloud_service_provider": 2,
+ "exit_strategy": 1,
+ "assessment_type": 2,
+ "timestamp": "2026-05-08 10:00:00 UTC",
+ }
+ provider_details = {
+ "accessKey": "AKIA_EMPTY",
+ "secretKey": "SECRET_EMPTY",
+ "region": "eu-central-1",
+ }
+ return {
+ "metadata": metadata,
+ "provider_details": provider_details,
+ "resource_type_mapping": {},
+ "resource_inventory": [],
+ "cost_data": [],
+ "risk_definitions": [],
+ "risk_data": [],
+ "alternatives": [],
+ "alternative_technologies": [],
+ "exit_strategy": 1,
+ }
+
+
+def stage_report_assets(report_path: str) -> None:
+ report_assets = Path(report_path) / "assets"
+ report_assets.mkdir(parents=True, exist_ok=True)
+
+ source_assets = Path("assets")
+ for folder in ("css", "img", "icons"):
+ shutil.copytree(
+ source_assets / folder,
+ report_assets / folder,
+ dirs_exist_ok=True,
+ )
diff --git a/tests/test_report_pipeline.py b/tests/test_report_pipeline.py
index 7ebe513..edafb4e 100644
--- a/tests/test_report_pipeline.py
+++ b/tests/test_report_pipeline.py
@@ -1,5 +1,4 @@
import json
-import shutil
import tempfile
import unittest
from pathlib import Path
@@ -10,90 +9,11 @@
generate_pdf_report,
)
from core.utils_report_json import transform_cost_inventory_for_json
-
-
-def build_report_fixture():
- metadata = {
- "name": "Smoke Test Assessment",
- "cloud_service_provider": 2,
- "exit_strategy": 1,
- "assessment_type": 1,
- "timestamp": "2026-05-07 12:00:00 UTC",
- }
- provider_details = {
- "accessKey": "AKIA_TEST",
- "secretKey": "SECRET_TEST",
- "region": "eu-central-1",
- }
- resource_type_mapping = {
- "101": {
- "id": 101,
- "code": "AWS.EC2.DescribeInstances.Reservations",
- "name": "EC2 Instance",
- "icon": "/icons/misc/no_image.png",
- }
- }
- resource_inventory = [
- {"resource_type": 101, "location": "eu-central-1", "count": 2},
- ]
- cost_data = [
- {"month": "2025-11-01", "cost": 10.5, "currency": "USD"},
- {"month": "2025-12-01", "cost": 12.0, "currency": "USD"},
- {"month": "2026-01-01", "cost": 14.75, "currency": "USD"},
- {"month": "2026-02-01", "cost": 11.25, "currency": "USD"},
- {"month": "2026-03-01", "cost": 9.0, "currency": "USD"},
- {"month": "2026-04-01", "cost": 13.4, "currency": "USD"},
- ]
- risk_definitions = [
- {
- "id": "1",
- "name": "Limited Alternatives",
- "description": "There are only a few alternatives available.",
- "severity": "high",
- }
- ]
- risk_data = [
- {"resource_type": "101", "risk": "1"},
- ]
- alternatives = [
- {"resource_type": "101", "strategy_type": "1", "alternative_technology": 1},
- ]
- alternative_technologies = [
- {
- "id": 1,
- "product_name": "OpenStack",
- "product_description": "Open source cloud platform.",
- "product_url": "https://www.openstack.org/",
- "open_source": "t",
- "support_plan": "t",
- "status": "t",
- }
- ]
- return {
- "metadata": metadata,
- "provider_details": provider_details,
- "resource_type_mapping": resource_type_mapping,
- "resource_inventory": resource_inventory,
- "cost_data": cost_data,
- "risk_definitions": risk_definitions,
- "risk_data": risk_data,
- "alternatives": alternatives,
- "alternative_technologies": alternative_technologies,
- "exit_strategy": 1,
- }
-
-
-def stage_report_assets(report_path: str) -> None:
- report_assets = Path(report_path) / "assets"
- report_assets.mkdir(parents=True, exist_ok=True)
-
- source_assets = Path("assets")
- for folder in ("css", "img", "icons"):
- shutil.copytree(
- source_assets / folder,
- report_assets / folder,
- dirs_exist_ok=True,
- )
+from tests.report_fixtures import (
+ build_empty_report_fixture,
+ build_report_fixture,
+ stage_report_assets,
+)
class ReportPipelineSmokeTests(unittest.TestCase):
@@ -123,6 +43,39 @@ def test_generate_html_report_creates_expected_output(self):
self.assertIn("OpenStack", html)
self.assertIn("EC2 Instance", html)
+ def test_generate_html_report_renders_empty_state_output(self):
+ fixture = build_empty_report_fixture()
+
+ with tempfile.TemporaryDirectory() as report_dir:
+ html_path = generate_html_report(
+ report_dir,
+ fixture["metadata"],
+ fixture["resource_type_mapping"],
+ fixture["resource_inventory"],
+ fixture["cost_data"],
+ None,
+ fixture["risk_data"],
+ fixture["risk_definitions"],
+ fixture["alternatives"],
+ fixture["alternative_technologies"],
+ fixture["exit_strategy"],
+ )
+
+ self.assertTrue(Path(html_path).exists())
+ html = Path(html_path).read_text(encoding="utf-8")
+
+ self.assertIn("Empty State Assessment", html)
+ self.assertIn("No risk data available.", html)
+ self.assertIn("No cost data available.", html)
+ self.assertIn("No exit score data available.", html)
+ self.assertIn("No vendor lock-in score data available.", html)
+ self.assertIn("No resources were discovered during the assessment.", html)
+ self.assertIn("No alternative technologies are available", html)
+ self.assertNotIn('id="risksChart"', html)
+ self.assertNotIn('id="costsChart"', html)
+ self.assertNotIn('id="exitScoreChart"', html)
+ self.assertNotIn('id="vendorLockInScoreChart"', html)
+
def test_generate_json_report_creates_expected_structure(self):
fixture = build_report_fixture()