From f6124105a7f02dfd854d70054882af469ed2736e Mon Sep 17 00:00:00 2001 From: CodeBeaverAI Date: Wed, 5 Mar 2025 04:38:07 +0100 Subject: [PATCH 1/4] test: Add coverage improvement test for tests/test_test_awslambda.py --- tests/test_test_awslambda.py | 180 +++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 tests/test_test_awslambda.py diff --git a/tests/test_test_awslambda.py b/tests/test_test_awslambda.py new file mode 100644 index 000000000..c2734f3dc --- /dev/null +++ b/tests/test_test_awslambda.py @@ -0,0 +1,180 @@ +import pytest +from troposphere import GetAtt, Join, Ref, Template +from troposphere.awslambda import Code, Environment, Function, ImageConfig, validate_memory_size +from troposphere.validators.awslambda import check_zip_file + +# Pytest tests to increase coverage for AWS Lambda resource definitions + +def test_validate_image_uri_with_zipfile(): + """Test that Code validation fails when both ImageUri and ZipFile are provided.""" + with pytest.raises(ValueError): + Code(ImageUri="something", ZipFile="something").validate() + +def test_validate_s3_missing_required_keys(): + """Test that Code validation fails for S3 properties missing required keys.""" + with pytest.raises(ValueError): + Code(S3Bucket="bucket", S3ObjectVersion="version").validate() # Missing S3Key + with pytest.raises(ValueError): + Code(S3Key="key", S3ObjectVersion="version").validate() # Missing S3Bucket + +def test_validate_s3_success(): + """Test that Code validation succeeds with proper S3 parameters.""" + # Valid S3 parameters: Both S3Bucket and S3Key are provided. + Code(S3Bucket="bucket", S3Key="key").validate() + +def test_function_package_type_invalid(): + """Test that Function validation raises error for invalid PackageType.""" + with pytest.raises(ValueError): + Function("TestFunction", Code=Code(ImageUri="something"), PackageType="Invalid", Role=GetAtt("LambdaExecutionRole", "Arn")).validate() + +def test_image_config_command_length(): + """Test that ImageConfig validation raises error if Command list is too long.""" + with pytest.raises(ValueError): + ImageConfig(Command=["a"] * 1501).validate() + +def test_image_config_entrypoint_length(): + """Test that ImageConfig validation raises error if EntryPoint list is too long.""" + with pytest.raises(ValueError): + ImageConfig(EntryPoint=["a"] * 1501).validate() + +def test_image_config_working_directory_length(): + """Test that ImageConfig validation raises error if WorkingDirectory is too long.""" + with pytest.raises(ValueError): + ImageConfig(WorkingDirectory="x" * 1001).validate() + +def test_function_with_zip_code(): + """Test creation and validation of a Function with ZipFile code block.""" + lambda_func = Function( + "AMIIDLookup", + Handler="index.handler", + Role=GetAtt("LambdaExecutionRole", "Arn"), + Code=Code(ZipFile=Join("", ["exports.handler = function(event, context) { console.log(event); };"])), + Runtime="nodejs", + PackageType="Zip" + ) + lambda_func.validate() + +def test_environment_variable_invalid_names(): + """Test that Environment validation rejects invalid variable names.""" + invalid_names = ["1", "2var", "_var", "/var"] + for var in invalid_names: + with pytest.raises(ValueError): + Environment(Variables={var: "value"}) + +def test_environment_variable_reserved_names(): + """Test that Environment validation rejects reserved variable names.""" + reserved = ["AWS_ACCESS_KEY", "AWS_ACCESS_KEY_ID", "AWS_LAMBDA_FUNCTION_MEMORY_SIZE"] + for var in reserved: + with pytest.raises(ValueError): + Environment(Variables={var: "value"}) + +def test_check_zip_file_positive_and_negative(): + """Test check_zip_file with both valid and invalid inputs.""" + positive_tests = [ + "a" * 4096, + Join("", ["a" * 4096]), + Join("", ["a", 10]), + Join("", ["a" * 4096, Ref("EmptyParameter")]), + Join("ab", ["a" * 2047, "a" * 2047]), + GetAtt("foo", "bar"), + ] + for z in positive_tests: + check_zip_file(z) + + negative_tests = [ + "a" * 4097, + Join("", ["a" * 4097]), + Join("", ["a" * 4097, Ref("EmptyParameter")]), + Join("abc", ["a" * 2047, "a" * 2047]), + ] + for z in negative_tests: + with pytest.raises(ValueError): + check_zip_file(z) + +def test_validate_memory_size_boundaries(): + """Test that validate_memory_size accepts valid memory sizes.""" + valid_sizes = ["128", "129", "10240"] + for size in valid_sizes: + validate_memory_size(size) + +def test_validate_memory_size_invalid(): + """Test that validate_memory_size rejects invalid memory sizes.""" + invalid_sizes = ["1", "111111111111111111111"] + for size in invalid_sizes: + with pytest.raises(ValueError) as excinfo: + validate_memory_size(size) + assert str(excinfo.value) == "Lambda Function memory size must be between 128 and 10240" +def test_image_config_boundary_valid(): + """Test that ImageConfig with Command, EntryPoint, and WorkingDirectory exactly on the allowed boundaries passes validation.""" + command = ["a"] * 1500 + entrypoint = ["b"] * 1500 + working_directory = "x" * 1000 + ic = ImageConfig(Command=command, EntryPoint=entrypoint, WorkingDirectory=working_directory) + # This should not raise any error + ic.validate() + +def test_code_empty(): + """Test that an empty Code definition (with no properties) fails validation.""" + with pytest.raises(ValueError): + Code().validate() + +def test_environment_variables_edge_valid(): + """Test that Environment accepts edge-case valid variable names that meet the regex requirements.""" + # Using names that start with a letter and have at least one additional character, with only letters, digits, or underscores. + valid_names = ["Ab", "a_b", "ENV1"] + for name in valid_names: + Environment(Variables={name: "value"}) +def test_code_s3_with_intrinsic(): + """ + Test that Code with S3 properties using an intrinsic function (Ref) in S3Bucket passes validation. + """ + # Ref is already imported from troposphere in the file so we use it directly + Code(S3Bucket=Ref("MyBucket"), S3Key="key", S3ObjectVersion="version").validate() + +def test_validate_memory_size_non_numeric(): + """ + Test that validate_memory_size raises a ValueError when passed a non-numeric string. + """ + with pytest.raises(ValueError): + validate_memory_size("abc") +# End of tests +def test_package_type_image_with_zip_file(): + """Test that Function with PackageType 'Image' and using a ZipFile does not raise an error. + (The current implementation does not enforce an error for a ZipFile with PackageType Image.)""" + Function( + "TestFunction", + Code=Code(ZipFile="exports.handler = lambda event: None"), + PackageType="Image", + Role=GetAtt("LambdaExecutionRole", "Arn") + ).validate() + +def test_function_zip_missing_handler(): + """Test that Function with PackageType 'Zip' missing Handler property does not raise an error. + (The current implementation does not enforce a required Handler for ZIP packages.)""" + # Calling validate() should not raise an exception even if Handler is missing. + Function( + "TestFunction", + Code=Code(ZipFile="exports.handler = lambda event: None"), + PackageType="Zip", + Role=GetAtt("LambdaExecutionRole", "Arn"), + Runtime="nodejs" + ).validate() + +def test_function_zip_missing_runtime(): + """Test that Function with PackageType 'Zip' missing Runtime property does not raise an error. + (The current implementation does not enforce a required Runtime for ZIP packages.)""" + # Calling validate() should not raise an exception even if Runtime is missing. + Function( + "TestFunction", + Code=Code(ZipFile="exports.handler = lambda event: None"), + PackageType="Zip", + Role=GetAtt("LambdaExecutionRole", "Arn"), + Handler="index.handler" + ).validate() + +def test_image_config_invalid_types(): + """Test that ImageConfig raises an error if non-list types are provided for Command or EntryPoint.""" + with pytest.raises(TypeError): + ImageConfig(Command="not a list").validate() + with pytest.raises(TypeError): + ImageConfig(EntryPoint="not a list").validate() \ No newline at end of file From 84e9a8d6f22d4092aa38f94f3d1ceec7600199ea Mon Sep 17 00:00:00 2001 From: CodeBeaverAI Date: Wed, 5 Mar 2025 04:38:09 +0100 Subject: [PATCH 2/4] test: Add coverage improvement test for tests/test_test_basic.py --- tests/test_test_basic.py | 113 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 tests/test_test_basic.py diff --git a/tests/test_test_basic.py b/tests/test_test_basic.py new file mode 100644 index 000000000..648897073 --- /dev/null +++ b/tests/test_test_basic.py @@ -0,0 +1,113 @@ +import pytest +import pickle +from troposphere import Template, Parameter, Ref, NoValue, If, Cidr, Sub, Split, Join, cloudformation, Region +from troposphere.ec2 import Instance +from troposphere.s3 import Bucket, PublicRead +from troposphere.ec2 import NetworkInterface, Route + +""" +Pytest tests for troposphere functionalities to increase test coverage. +This file exercises various features such as resource instantiation, validation, +function output (Sub, Cidr, etc.), duplication-checks in Template, and pickling. +""" + +def test_instance_unknown_property(): + """Test that creating an Instance with an unknown property raises AttributeError.""" + with pytest.raises(AttributeError): + Instance("ec2instance", foobar=True) + +def test_template_duplicate_resource(): + """Test that adding the same resource twice to a Template raises ValueError.""" + t = Template() + b = Bucket("B1") + t.add_resource(b) + with pytest.raises(ValueError): + t.add_resource(b) + +def test_template_duplicate_parameter(): + """Test that adding the same parameter twice to a Template raises ValueError.""" + t = Template() + p = Parameter("MyParameter", Type="String") + t.add_parameter(p) + with pytest.raises(ValueError): + t.add_parameter(p) + +def test_sub_function(): + """Test Sub function with mixed keyword arguments and dictionary. + Here we pass the substitutions as kwargs and then validate the dict output. + """ + # Passing variables via kwargs only + s = "foo ${AWS::Region} ${sub1} ${sub2} ${sub3}" + values = {"sub1": "una", "sub2": "dos", "sub3": "tres"} + sub_obj = Sub(s, **values) + out = sub_obj.to_dict() + expected = {"Fn::Sub": [s, values]} + assert out == expected + +def test_ref_hash_consistency(): + """Test that the hash of a Ref object is consistent with the underlying value.""" + ref_obj = Ref("AWS::NoValue") + hash_no_value = hash(NoValue) + # Check that the Ref's hash equals that of the string "AWS::NoValue" and NoValue's hash. + assert hash(ref_obj) == hash("AWS::NoValue") == hash_no_value + +def test_route_no_validation(): + """Test that using .no_validation() on a Route resource bypasses validation errors.""" + route = Route( + "Route66", + DestinationCidrBlock="0.0.0.0/0", + RouteTableId=Ref("RouteTable66"), + InstanceId=If("UseNat", Ref("AWS::NoValue"), Ref("UseNat")), + NatGatewayId=If("UseNat", Ref("UseNat"), Ref("AWS::NoValue")), + ).no_validation() + t = Template() + t.add_resource(route) + # This should complete without validation error. + t.to_json() + +def test_cidr_with_size_mask(): + """Test that Cidr returns the correct dictionary when a size mask argument is provided.""" + cidr = Cidr("10.1.10.1/24", 2, 10) + expected = {"Fn::Cidr": ["10.1.10.1/24", 2, 10]} + assert cidr.to_dict() == expected + +def test_split_invalid_delimiter(): + """Test that calling Split with an invalid delimiter type raises ValueError.""" + with pytest.raises(ValueError): + Split(10, "foobar") + +def test_join_invalid_delimiter(): + """Test that calling Join with an invalid delimiter type raises ValueError.""" + with pytest.raises(ValueError): + Join(10, "foobar") + +def test_bucket_pickling(): + """Test that a Bucket object can be pickled and unpickled without losing attributes.""" + bucket = Bucket("B1", BucketName="testbucket") + pkl = pickle.dumps(bucket) + bucket2 = pickle.loads(pkl) + assert bucket2.BucketName == bucket.BucketName +def test_cloudformation_wait_condition_handle_ref(): + """Test WaitConditionHandle returns proper Ref value.""" + from troposphere import cloudformation + wch = cloudformation.WaitConditionHandle("TestResource") + assert wch.Ref() == "TestResource" +def test_invalid_parameter_default_type(): + """Test that providing a default with an incorrect type for a Parameter raises a ValueError on validation.""" + from troposphere import Parameter + with pytest.raises(ValueError): + Parameter("TestParameter", Type="String", Default=123).validate() +def test_instance_pickling(): + """Test that an Instance can be pickled and unpickled correctly.""" + from troposphere.ec2 import Instance + inst = Instance("MyInstance", ImageId="ami-123456") + pkl = pickle.dumps(inst) + inst2 = pickle.loads(pkl) + assert inst2.ImageId == inst.ImageId +def test_empty_template_output(): + """Test that an empty Template produces valid JSON output.""" + from troposphere import Template + t = Template() + output = t.to_json() + # ensure output is a non-empty string (i.e., valid JSON) + assert isinstance(output, str) and len(output) > 0 \ No newline at end of file From 03d32b6a6d88a7ac1ea5687cc44e683bac8eee88 Mon Sep 17 00:00:00 2001 From: CodeBeaverAI Date: Wed, 5 Mar 2025 04:38:11 +0100 Subject: [PATCH 3/4] test: Add coverage improvement test for tests/test_test_mappings.py --- tests/test_test_mappings.py | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/test_test_mappings.py diff --git a/tests/test_test_mappings.py b/tests/test_test_mappings.py new file mode 100644 index 000000000..fb5eea7e0 --- /dev/null +++ b/tests/test_test_mappings.py @@ -0,0 +1,46 @@ +import json +import pytest +from troposphere import Template + +def test_empty_template(): + """Test that an empty template contains empty mappings and default resources.""" + template = Template() + data = json.loads(template.to_json()) + assert data.get("Mappings", {}) == {} + assert data["Resources"] == {} + +def test_single_mapping(): + """Test that a single mapping is correctly added.""" + template = Template() + template.add_mapping("map", {"n": "v"}) + data = json.loads(template.to_json()) + expected = {"Mappings": {"map": {"n": "v"}}, "Resources": {}} + assert data == expected + +def test_multiple_mappings(): + """Test that adding multiple mappings with the same mapping name merges keys.""" + template = Template() + template.add_mapping("map", {"k1": {"n1": "v1"}}) + template.add_mapping("map", {"k2": {"n2": "v2"}}) + data = json.loads(template.to_json()) + expected = {"Mappings": {"map": {"k1": {"n1": "v1"}, "k2": {"n2": "v2"}}}, "Resources": {}} + assert data == expected + +def test_overwrite_key_in_mapping(): + """Test that updating an existing key in the mapping overwrites the previous value.""" + template = Template() + template.add_mapping("map", {"k1": {"n1": "v1"}}) + # Overwrite the same key with new value. + template.add_mapping("map", {"k1": {"n1": "v1_new"}}) + data = json.loads(template.to_json()) + expected = {"Mappings": {"map": {"k1": {"n1": "v1_new"}}}, "Resources": {}} + assert data == expected + +def test_different_mappings(): + """Test that adding different mapping names results in separate mapping entries.""" + template = Template() + template.add_mapping("map1", {"n": "v"}) + template.add_mapping("map2", {"k": {"n": "v2"}}) + data = json.loads(template.to_json()) + expected = {"Mappings": {"map1": {"n": "v"}, "map2": {"k": {"n": "v2"}}}, "Resources": {}} + assert data == expected \ No newline at end of file From 79d09f44840b7c23090eb81645cdeb049e524362 Mon Sep 17 00:00:00 2001 From: CodeBeaverAI Date: Wed, 5 Mar 2025 04:38:12 +0100 Subject: [PATCH 4/4] --- codebeaver.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 codebeaver.yml diff --git a/codebeaver.yml b/codebeaver.yml new file mode 100644 index 000000000..419e2435e --- /dev/null +++ b/codebeaver.yml @@ -0,0 +1,2 @@ +from: pytest +# This file was generated automatically by CodeBeaver based on your repository. Learn how to customize it here: https://docs.codebeaver.ai/configuration/ \ No newline at end of file