#!/usr/bin/env python """ Test for AWS Lightsail boto3 parameter fix. Verifies that get_aws_connection_info() works without the deprecated boto3 parameter. Addresses issue #14822. """ import importlib.util import os import sys import unittest from unittest.mock import MagicMock, patch # Add the library directory to the path sys.path.insert(0, os.path.join(os.path.dirname(__file__), "../../library")) class TestLightsailBoto3Fix(unittest.TestCase): """Test that lightsail_region_facts.py works without boto3 parameter.""" def setUp(self): """Set up test fixtures.""" # Mock the ansible module_utils since we're testing outside of Ansible self.mock_modules = { "ansible.module_utils.basic": MagicMock(), "ansible.module_utils.ec2": MagicMock(), "ansible.module_utils.aws.core": MagicMock(), } # Apply mocks self.patches = [] for module_name, mock_module in self.mock_modules.items(): patcher = patch.dict("sys.modules", {module_name: mock_module}) patcher.start() self.patches.append(patcher) def tearDown(self): """Clean up patches.""" for patcher in self.patches: patcher.stop() def test_lightsail_region_facts_imports(self): """Test that lightsail_region_facts can be imported.""" try: # Import the module spec = importlib.util.spec_from_file_location( "lightsail_region_facts", os.path.join(os.path.dirname(__file__), "../../library/lightsail_region_facts.py"), ) module = importlib.util.module_from_spec(spec) # This should not raise an error spec.loader.exec_module(module) # Verify the module loaded self.assertIsNotNone(module) self.assertTrue(hasattr(module, "main")) except Exception as e: self.fail(f"Failed to import lightsail_region_facts: {e}") def test_get_aws_connection_info_called_without_boto3(self): """Test that get_aws_connection_info is called without boto3 parameter.""" # Mock get_aws_connection_info to track calls mock_get_aws_connection_info = MagicMock(return_value=("us-west-2", None, {})) with patch("ansible.module_utils.ec2.get_aws_connection_info", mock_get_aws_connection_info): # Import the module spec = importlib.util.spec_from_file_location( "lightsail_region_facts", os.path.join(os.path.dirname(__file__), "../../library/lightsail_region_facts.py"), ) module = importlib.util.module_from_spec(spec) # Mock AnsibleModule mock_ansible_module = MagicMock() mock_ansible_module.params = {} mock_ansible_module.check_mode = False with patch("ansible.module_utils.basic.AnsibleModule", return_value=mock_ansible_module): # Execute the module try: spec.loader.exec_module(module) module.main() except SystemExit: # Module calls exit_json or fail_json which raises SystemExit pass except Exception: # We expect some exceptions since we're mocking, but we want to check the call pass # Verify get_aws_connection_info was called if mock_get_aws_connection_info.called: # Get the call arguments call_args = mock_get_aws_connection_info.call_args # Ensure boto3=True is NOT in the arguments if call_args: # Check positional arguments if call_args[0]: # args self.assertTrue( len(call_args[0]) <= 1, "get_aws_connection_info should be called with at most 1 positional arg (module)", ) # Check keyword arguments if call_args[1]: # kwargs self.assertNotIn( "boto3", call_args[1], "get_aws_connection_info should not be called with boto3 parameter" ) def test_no_boto3_parameter_in_source(self): """Verify that boto3 parameter is not present in the source code.""" lightsail_path = os.path.join(os.path.dirname(__file__), "../../library/lightsail_region_facts.py") with open(lightsail_path) as f: content = f.read() # Check that boto3=True is not in the file self.assertNotIn( "boto3=True", content, "boto3=True parameter should not be present in lightsail_region_facts.py" ) # Check that boto3 parameter is not used with get_aws_connection_info self.assertNotIn( "get_aws_connection_info(module, boto3", content, "get_aws_connection_info should not be called with boto3 parameter", ) def test_regression_issue_14822(self): """ Regression test for issue #14822. Ensures that the deprecated boto3 parameter is not used. """ # This test documents the specific issue that was fixed # The boto3 parameter was deprecated and removed in amazon.aws collection # that comes with Ansible 11.x lightsail_path = os.path.join(os.path.dirname(__file__), "../../library/lightsail_region_facts.py") with open(lightsail_path) as f: lines = f.readlines() # Find the line that calls get_aws_connection_info for line_num, line in enumerate(lines, 1): if "get_aws_connection_info" in line and "region" in line: # This should be around line 85 # Verify it doesn't have boto3=True self.assertNotIn("boto3", line, f"Line {line_num} should not contain boto3 parameter") # Verify the correct format self.assertIn( "get_aws_connection_info(module)", line, f"Line {line_num} should call get_aws_connection_info(module) without boto3", ) break else: self.fail("Could not find get_aws_connection_info call in lightsail_region_facts.py") if __name__ == "__main__": unittest.main()