#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Unit tests for the Report Generator Service """ import unittest from unittest.mock import patch, MagicMock, mock_open import os import sys import json from pathlib import Path # Add the project root directory to the Python path project_root = Path(__file__).resolve().parent.parent sys.path.insert(0, str(project_root)) from src.services.report_generator import ReportGenerator class TestReportGenerator(unittest.TestCase): """Test cases for the ReportGenerator class""" def setUp(self): """Set up test fixtures""" # Create a temporary output directory for testing self.test_output_dir = "test_reports" self.generator = ReportGenerator(output_dir=self.test_output_dir) # Sample test data self.repo_name = "test-repo" self.test_results = { "repository_info": { "branch": "main", "commit": "abc123", "remote_url": "https://github.com/test/test-repo", "size": 1024, "file_count": 10 }, "language_breakdown": { "Python": {"files": 5, "lines": 500, "percentage": 70}, "JavaScript": {"files": 3, "lines": 200, "percentage": 30} }, "code_analysis": { "Python": { "issue_count": 3, "issues": [ {"severity": "high", "issue": "Unused variable", "file": "test.py", "line": 10, "description": "Variable 'x' is not used"}, {"severity": "medium", "issue": "Missing docstring", "file": "test.py", "line": 5, "description": "Function missing docstring"} ] }, "JavaScript": { "issue_count": 2, "issues": [ {"severity": "medium", "issue": "Unused variable", "file": "test.js", "line": 15, "description": "Variable 'y' is not used"} ] } }, "security_scan": { "Python": { "vulnerability_count": 1, "vulnerabilities": [ {"severity": "critical", "issue": "SQL Injection", "file": "db.py", "line": 25, "description": "Unsanitized SQL query"} ] }, "JavaScript": { "vulnerability_count": 0, "vulnerabilities": [] } }, "performance_analysis": { "language_results": { "Python": { "issue_count": 2, "issues": [ {"issue": "Inefficient loop", "file": "test.py", "line": 20, "description": "Use list comprehension instead"} ] } }, "hotspots": [ {"file": "test.py", "language": "Python", "issue_count": 2} ] }, "ai_review": { "reviews": { "test.py": { "status": "success", "review_text": "Code review for test.py", "suggestions": [ {"section": "Code Quality", "line": 10, "description": "Variable 'x' is not used", "details": "Remove unused variable"} ] } }, "summary": "Overall, the code quality is good but there are some issues to address." } } def tearDown(self): """Tear down test fixtures""" # Clean up the test output directory if os.path.exists(self.test_output_dir): for file in os.listdir(self.test_output_dir): os.remove(os.path.join(self.test_output_dir, file)) os.rmdir(self.test_output_dir) def test_init(self): """Test initialization of the generator""" self.assertIsNotNone(self.generator) self.assertEqual(self.generator.output_dir, self.test_output_dir) self.assertTrue(os.path.exists(self.test_output_dir)) @patch('builtins.open', new_callable=mock_open) @patch('json.dump') def test_generate_json_report(self, mock_json_dump, mock_file_open): """Test _generate_json_report method""" # Call the method report_content = {"test": "content"} report_path = self.generator._generate_json_report("test_report", report_content) # Verify the result expected_path = os.path.join(self.test_output_dir, "test_report.json") self.assertEqual(report_path, expected_path) mock_file_open.assert_called_once_with(expected_path, "w", encoding="utf-8") mock_json_dump.assert_called_once() @patch('builtins.open', new_callable=mock_open) @patch('markdown.markdown') def test_generate_html_report(self, mock_markdown, mock_file_open): """Test _generate_html_report method""" # Mock markdown conversion mock_markdown.return_value = "

Test

" # Call the method report_content = {"metadata": {"repository_name": "test-repo"}} report_path = self.generator._generate_html_report("test_report", report_content) # Verify the result expected_path = os.path.join(self.test_output_dir, "test_report.html") self.assertEqual(report_path, expected_path) mock_file_open.assert_called_once_with(expected_path, "w", encoding="utf-8") mock_markdown.assert_called_once() @patch('pdfkit.from_file') @patch('os.remove') def test_generate_pdf_report(self, mock_remove, mock_pdfkit): """Test _generate_pdf_report method""" # Mock the HTML report generation with patch.object(self.generator, '_generate_html_report') as mock_html_report: mock_html_report.return_value = os.path.join(self.test_output_dir, "test_report_temp.html") # Call the method report_content = {"test": "content"} report_path = self.generator._generate_pdf_report("test_report", report_content) # Verify the result expected_path = os.path.join(self.test_output_dir, "test_report.pdf") self.assertEqual(report_path, expected_path) mock_html_report.assert_called_once_with("test_report_temp", report_content) mock_pdfkit.assert_called_once_with( os.path.join(self.test_output_dir, "test_report_temp.html"), expected_path ) mock_remove.assert_called_once_with(os.path.join(self.test_output_dir, "test_report_temp.html")) @patch('builtins.open', new_callable=mock_open) @patch('csv.DictWriter') def test_generate_csv_report(self, mock_csv_writer, mock_file_open): """Test _generate_csv_report method""" # Mock CSV writer mock_writer = MagicMock() mock_csv_writer.return_value = mock_writer # Call the method report_content = { "code_quality": {"issues_by_language": {}}, "security": {"vulnerabilities_by_language": {}}, "performance": {"issues_by_language": {}}, "ai_review": {"file_reviews": {}} } report_path = self.generator._generate_csv_report("test_report", report_content) # Verify the result expected_path = os.path.join(self.test_output_dir, "test_report.csv") self.assertEqual(report_path, expected_path) mock_file_open.assert_called_once_with(expected_path, "w", newline="", encoding="utf-8") mock_writer.writeheader.assert_called_once() mock_writer.writerows.assert_called_once() def test_calculate_summary_metrics(self): """Test _calculate_summary_metrics method""" # Call the method metrics = self.generator._calculate_summary_metrics(self.test_results) # Verify the result self.assertEqual(metrics["total_files"], 10) self.assertEqual(metrics["repository_size"], 1024) self.assertEqual(metrics["total_code_issues"], 5) # 3 Python + 2 JavaScript self.assertEqual(metrics["critical_code_issues"], 1) # 1 high severity issue self.assertEqual(metrics["total_vulnerabilities"], 1) # 1 Python vulnerability self.assertEqual(metrics["critical_vulnerabilities"], 1) # 1 critical vulnerability self.assertEqual(metrics["total_performance_issues"], 2) # 2 Python performance issues self.assertEqual(metrics["performance_hotspots"], 1) # 1 hotspot self.assertIn("overall_score", metrics) self.assertIn("quality_rating", metrics) def test_extract_top_issues(self): """Test _extract_top_issues method""" # Call the method top_issues = self.generator._extract_top_issues(self.test_results["code_analysis"]) # Verify the result self.assertEqual(len(top_issues), 3) # Total issues in the test data self.assertEqual(top_issues[0]["severity"], "high") # First issue should be high severity def test_extract_critical_vulnerabilities(self): """Test _extract_critical_vulnerabilities method""" # Call the method critical_vulns = self.generator._extract_critical_vulnerabilities(self.test_results["security_scan"]) # Verify the result self.assertEqual(len(critical_vulns), 1) # Only one vulnerability in the test data self.assertEqual(critical_vulns[0]["severity"], "critical") def test_generate_recommendations(self): """Test _generate_recommendations method""" # Call the method recommendations = self.generator._generate_recommendations(self.test_results) # Verify the result self.assertIn("high_priority", recommendations) self.assertIn("medium_priority", recommendations) self.assertIn("low_priority", recommendations) self.assertEqual(len(recommendations["high_priority"]), 1) # One critical security vulnerability self.assertGreaterEqual(len(recommendations["medium_priority"]), 1) # At least one high code issue @patch('os.path.exists') @patch('os.listdir') def test_generate_report(self, mock_listdir, mock_exists): """Test generate_report method""" # Mock the report generation methods with patch.object(self.generator, '_create_report_content') as mock_create_content, \ patch.object(self.generator, '_generate_json_report') as mock_json_report, \ patch.object(self.generator, '_generate_html_report') as mock_html_report, \ patch.object(self.generator, '_generate_pdf_report') as mock_pdf_report, \ patch.object(self.generator, '_generate_csv_report') as mock_csv_report: # Set up the mocks mock_create_content.return_value = {"test": "content"} mock_json_report.return_value = "json_path" mock_html_report.return_value = "html_path" mock_pdf_report.return_value = "pdf_path" mock_csv_report.return_value = "csv_path" # Call the method with all formats report_paths = self.generator.generate_report(self.repo_name, self.test_results, "all") # Verify the result self.assertEqual(report_paths["json"], "json_path") self.assertEqual(report_paths["html"], "html_path") self.assertEqual(report_paths["pdf"], "pdf_path") self.assertEqual(report_paths["csv"], "csv_path") mock_create_content.assert_called_once_with(self.repo_name, self.test_results) # Call the method with specific format report_paths = self.generator.generate_report(self.repo_name, self.test_results, "json") # Verify the result self.assertEqual(len(report_paths), 1) self.assertEqual(report_paths["json"], "json_path") if __name__ == "__main__": unittest.main()