Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions codebeaver.yml
Original file line number Diff line number Diff line change
@@ -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/
180 changes: 180 additions & 0 deletions tests/test_test_awslambda.py
Original file line number Diff line number Diff line change
@@ -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()
113 changes: 113 additions & 0 deletions tests/test_test_basic.py
Original file line number Diff line number Diff line change
@@ -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
46 changes: 46 additions & 0 deletions tests/test_test_mappings.py
Original file line number Diff line number Diff line change
@@ -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