#!/usr/bin/env python3 """ Tests for the Let's Encrypt functionality of the SSL Manager. This module contains tests for generating certificates using Let's Encrypt. """ import os import sys import tempfile import unittest import subprocess from unittest.mock import patch, MagicMock # Add the src directory to the Python path sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../src'))) from ssl_manager import SSLManager class TestLetsEncrypt(unittest.TestCase): """Test cases for Let's Encrypt certificate generation.""" def setUp(self): """Set up test fixtures.""" # Create a temporary directory for test files self.temp_dir = tempfile.TemporaryDirectory() # Sample config for testing self.test_config = { "cert_dir": self.temp_dir.name, "default_port": 8443, "connection_timeout": 5.0, "default_validity_days": 730, "key_size": 4096, "letsencrypt": { "email": "test@example.com", "validation_method": "standalone", "use_staging": True, "agree_tos": True } } # Create a temporary config file self.config_path = os.path.join(self.temp_dir.name, "test_config.json") with open(self.config_path, 'w') as f: import json json.dump(self.test_config, f) # Create an SSLManager with the test config self.ssl_manager = SSLManager(config_path=self.config_path) # Create directories for certbot self.config_dir = os.path.join(self.temp_dir.name, '.config') self.work_dir = os.path.join(self.temp_dir.name, '.work') self.logs_dir = os.path.join(self.temp_dir.name, '.logs') self.live_dir = os.path.join(self.config_dir, 'live', 'test.example.com') # Create directories os.makedirs(self.live_dir, exist_ok=True) # Create dummy certificate and key files self.fullchain_path = os.path.join(self.live_dir, 'fullchain.pem') self.privkey_path = os.path.join(self.live_dir, 'privkey.pem') with open(self.fullchain_path, 'w') as f: f.write("-----BEGIN CERTIFICATE-----\nDummy Certificate\n-----END CERTIFICATE-----") with open(self.privkey_path, 'w') as f: f.write("-----BEGIN PRIVATE KEY-----\nDummy Private Key\n-----END PRIVATE KEY-----") def tearDown(self): """Tear down test fixtures.""" # Clean up the temporary directory self.temp_dir.cleanup() @patch('subprocess.run') def test_generate_letsencrypt_cert(self, mock_run): """Test generating a Let's Encrypt certificate.""" # Mock the subprocess.run call mock_run.return_value = MagicMock(returncode=0, stdout="Certificate issued successfully") # Call the method cert_path, key_path = self.ssl_manager.generate_letsencrypt_cert( common_name="test.example.com", email="test@example.com", validation_method="standalone", use_staging=True, agree_tos=True ) # Verify that subprocess.run was called with the correct arguments mock_run.assert_called_once() args, kwargs = mock_run.call_args # Verify the command includes the expected arguments cmd = args[0] self.assertEqual(cmd[0], 'certbot') self.assertEqual(cmd[1], 'certonly') self.assertIn('--standalone', cmd) self.assertIn('-d', cmd) self.assertIn('test.example.com', cmd) self.assertIn('-m', cmd) self.assertIn('test@example.com', cmd) self.assertIn('--test-cert', cmd) self.assertIn('--agree-tos', cmd) self.assertIn('-n', cmd) # Verify the paths expected_cert_path = os.path.join(self.temp_dir.name, 'test.example.com.crt') expected_key_path = os.path.join(self.temp_dir.name, 'test.example.com.key') self.assertEqual(cert_path, expected_cert_path) self.assertEqual(key_path, expected_key_path) # Verify the certificate and key files were created self.assertTrue(os.path.isfile(cert_path)) self.assertTrue(os.path.isfile(key_path)) # Verify the content of the certificate and key files with open(cert_path, 'r') as f: cert_content = f.read() with open(key_path, 'r') as f: key_content = f.read() self.assertIn("Dummy Certificate", cert_content) self.assertIn("Dummy Private Key", key_content) @patch('subprocess.run') def test_generate_letsencrypt_cert_with_defaults(self, mock_run): """Test generating a Let's Encrypt certificate with default values from config.""" # Mock the subprocess.run call mock_run.return_value = MagicMock(returncode=0, stdout="Certificate issued successfully") # Call the method with minimal arguments cert_path, key_path = self.ssl_manager.generate_letsencrypt_cert( common_name="test.example.com" ) # Verify that subprocess.run was called with the correct arguments mock_run.assert_called_once() args, kwargs = mock_run.call_args # Verify the command includes the expected arguments cmd = args[0] self.assertEqual(cmd[0], 'certbot') self.assertEqual(cmd[1], 'certonly') self.assertIn('--standalone', cmd) # Default from config self.assertIn('-d', cmd) self.assertIn('test.example.com', cmd) self.assertIn('-m', cmd) self.assertIn('test@example.com', cmd) # Default from config self.assertIn('--test-cert', cmd) # Default from config self.assertIn('--agree-tos', cmd) # Default from config self.assertIn('-n', cmd) # Verify the paths expected_cert_path = os.path.join(self.temp_dir.name, 'test.example.com.crt') expected_key_path = os.path.join(self.temp_dir.name, 'test.example.com.key') self.assertEqual(cert_path, expected_cert_path) self.assertEqual(key_path, expected_key_path) @patch('subprocess.run') def test_generate_letsencrypt_cert_error(self, mock_run): """Test error handling when generating a Let's Encrypt certificate.""" # Mock the subprocess.run call to raise an exception mock_run.side_effect = subprocess.CalledProcessError( returncode=1, cmd=['certbot', 'certonly'], output="An error occurred", stderr="Certificate issuance failed" ) # Call the method and expect an exception with self.assertRaises(subprocess.CalledProcessError): self.ssl_manager.generate_letsencrypt_cert( common_name="test.example.com", email="test@example.com" ) if __name__ == '__main__': unittest.main()