Skip to content

Commit 18ead91

Browse files
committed
[ESM] Fix validation of StartingPosition streams parameter
1 parent 288a34a commit 18ead91

File tree

4 files changed

+141
-11
lines changed

4 files changed

+141
-11
lines changed

localstack-core/localstack/services/lambda_/provider.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@
139139
)
140140
from localstack.aws.api.lambda_ import FunctionVersion as FunctionVersionApi
141141
from localstack.aws.api.lambda_ import ServiceException as LambdaServiceException
142+
from localstack.aws.api.pipes import (
143+
KinesisStreamStartPosition,
144+
)
142145
from localstack.aws.connect import connect_to
143146
from localstack.aws.spec import load_service
144147
from localstack.services.edge import ROUTER
@@ -1923,11 +1926,23 @@ def validate_event_source_mapping(self, context, request):
19231926
service = extract_service_from_arn(request["EventSourceArn"])
19241927

19251928
batch_size = api_utils.validate_and_set_batch_size(service, request.get("BatchSize"))
1926-
if service in ["dynamodb", "kinesis"] and "StartingPosition" not in request:
1927-
raise InvalidParameterValueException(
1928-
"1 validation error detected: Value null at 'startingPosition' failed to satisfy constraint: Member must not be null.",
1929-
Type="User",
1930-
)
1929+
if service in ["dynamodb", "kinesis"]:
1930+
if "StartingPosition" not in request:
1931+
raise InvalidParameterValueException(
1932+
"1 validation error detected: Value null at 'startingPosition' failed to satisfy constraint: Member must not be null.",
1933+
Type="User",
1934+
)
1935+
1936+
if request["StartingPosition"] not in KinesisStreamStartPosition.__members__:
1937+
raise ValidationException(
1938+
"1 validation error detected: Value 'invalid' at 'startingPosition' failed to satisfy constraint: Member must satisfy enum value set: [LATEST, AT_TIMESTAMP, TRIM_HORIZON]"
1939+
)
1940+
elif service == "dynamodb":
1941+
raise InvalidParameterValueException(
1942+
f"Unsupported starting position for arn type: {request['EventSourceArn']}",
1943+
Type="User",
1944+
)
1945+
19311946
if service in ["sqs", "sqs-fifo"]:
19321947
if batch_size > 10 and request.get("MaximumBatchingWindowInSeconds", 0) == 0:
19331948
raise InvalidParameterValueException(

tests/aws/services/lambda_/test_lambda_api.py

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5689,7 +5689,7 @@ def _assert_esm_deleted():
56895689
def test_create_event_source_validation(
56905690
self, create_lambda_function, lambda_su_role, dynamodb_create_table, snapshot, aws_client
56915691
):
5692-
"""missing required field for DynamoDb stream event source mapping"""
5692+
"""missing & invalid required field for DynamoDb stream event source mapping"""
56935693
function_name = f"function-{short_uid()}"
56945694
create_lambda_function(
56955695
handler_file=TEST_LAMBDA_PYTHON_ECHO,
@@ -5699,21 +5699,84 @@ def test_create_event_source_validation(
56995699
)
57005700

57015701
table_name = f"table-{short_uid()}"
5702+
snapshot.add_transformer(snapshot.transform.regex(table_name, "<table-name>"))
5703+
57025704
dynamodb_create_table(table_name=table_name, partition_key="id")
57035705
_await_dynamodb_table_active(aws_client.dynamodb, table_name)
57045706
update_table_response = aws_client.dynamodb.update_table(
57055707
TableName=table_name,
57065708
StreamSpecification={"StreamEnabled": True, "StreamViewType": "NEW_AND_OLD_IMAGES"},
57075709
)
57085710
stream_arn = update_table_response["TableDescription"]["LatestStreamArn"]
5711+
snapshot.add_transformer(
5712+
snapshot.transform.regex(
5713+
update_table_response["TableDescription"]["LatestStreamLabel"], "<stream-name>"
5714+
)
5715+
)
57095716

57105717
with pytest.raises(ClientError) as e:
57115718
aws_client.lambda_.create_event_source_mapping(
57125719
FunctionName=function_name, EventSourceArn=stream_arn
57135720
)
5721+
snapshot.match("no_starting_position", e.value.response)
5722+
5723+
with pytest.raises(ClientError) as e:
5724+
aws_client.lambda_.create_event_source_mapping(
5725+
FunctionName=function_name, EventSourceArn=stream_arn, StartingPosition="invalid"
5726+
)
5727+
snapshot.match("invalid_starting_position", e.value.response)
5728+
5729+
# AT_TIMESTAMP is not supported for DynamoDBStreams
5730+
with pytest.raises(ClientError) as e:
5731+
aws_client.lambda_.create_event_source_mapping(
5732+
FunctionName=function_name,
5733+
EventSourceArn=stream_arn,
5734+
StartingPosition="AT_TIMESTAMP",
5735+
StartingPositionTimestamp="1741010802",
5736+
)
5737+
snapshot.match("incompatible_starting_position", e.value.response)
57145738

5715-
response = e.value.response
5716-
snapshot.match("error", response)
5739+
@markers.aws.validated
5740+
def test_create_event_source_validation_kinesis(
5741+
self,
5742+
create_lambda_function,
5743+
lambda_su_role,
5744+
kinesis_create_stream,
5745+
wait_for_stream_ready,
5746+
snapshot,
5747+
aws_client,
5748+
):
5749+
"""missing & invalid required field for Kinesis stream event source mapping"""
5750+
5751+
snapshot.add_transformer(snapshot.transform.kinesis_api())
5752+
5753+
function_name = f"function-{short_uid()}"
5754+
create_lambda_function(
5755+
handler_file=TEST_LAMBDA_PYTHON_ECHO,
5756+
func_name=function_name,
5757+
runtime=Runtime.python3_12,
5758+
role=lambda_su_role,
5759+
)
5760+
5761+
stream_name = f"stream-{short_uid()}"
5762+
kinesis_create_stream(StreamName=stream_name, ShardCount=1)
5763+
wait_for_stream_ready(stream_name)
5764+
5765+
stream_arn = aws_client.kinesis.describe_stream(StreamName=stream_name)[
5766+
"StreamDescription"
5767+
]["StreamARN"]
5768+
5769+
with pytest.raises(ClientError) as e:
5770+
aws_client.lambda_.create_event_source_mapping(
5771+
FunctionName=function_name, EventSourceArn=stream_arn
5772+
)
5773+
snapshot.match("no_starting_position", e.value.response)
5774+
5775+
with pytest.raises(ClientError) as e:
5776+
aws_client.lambda_.create_event_source_mapping(
5777+
FunctionName=function_name, EventSourceArn=stream_arn, StartingPosition="invalid"
5778+
)
5779+
snapshot.match("invalid_starting_position", e.value.response)
57175780

57185781
@markers.aws.validated
57195782
def test_create_event_filter_criteria_validation(

tests/aws/services/lambda_/test_lambda_api.snapshot.json

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12368,9 +12368,9 @@
1236812368
}
1236912369
},
1237012370
"tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_validation": {
12371-
"recorded-date": "10-04-2024, 09:22:02",
12371+
"recorded-date": "03-03-2025, 17:07:45",
1237212372
"recorded-content": {
12373-
"error": {
12373+
"no_starting_position": {
1237412374
"Error": {
1237512375
"Code": "InvalidParameterValueException",
1237612376
"Message": "1 validation error detected: Value null at 'startingPosition' failed to satisfy constraint: Member must not be null."
@@ -12381,6 +12381,28 @@
1238112381
"HTTPHeaders": {},
1238212382
"HTTPStatusCode": 400
1238312383
}
12384+
},
12385+
"invalid_starting_position": {
12386+
"Error": {
12387+
"Code": "ValidationException",
12388+
"Message": "1 validation error detected: Value 'invalid' at 'startingPosition' failed to satisfy constraint: Member must satisfy enum value set: [LATEST, AT_TIMESTAMP, TRIM_HORIZON]"
12389+
},
12390+
"ResponseMetadata": {
12391+
"HTTPHeaders": {},
12392+
"HTTPStatusCode": 400
12393+
}
12394+
},
12395+
"incompatible_starting_position": {
12396+
"Error": {
12397+
"Code": "InvalidParameterValueException",
12398+
"Message": "Unsupported starting position for arn type: arn:<partition>:dynamodb:<region>:111111111111:table/<table-name>/stream/<stream-name>"
12399+
},
12400+
"Type": "User",
12401+
"message": "Unsupported starting position for arn type: arn:<partition>:dynamodb:<region>:111111111111:table/<table-name>/stream/<stream-name>",
12402+
"ResponseMetadata": {
12403+
"HTTPHeaders": {},
12404+
"HTTPStatusCode": 400
12405+
}
1238412406
}
1238512407
}
1238612408
},
@@ -22312,5 +22334,32 @@
2231222334
}
2231322335
}
2231422336
}
22337+
},
22338+
"tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_validation_kinesis": {
22339+
"recorded-date": "03-03-2025, 16:49:40",
22340+
"recorded-content": {
22341+
"no_starting_position": {
22342+
"Error": {
22343+
"Code": "InvalidParameterValueException",
22344+
"Message": "1 validation error detected: Value null at 'startingPosition' failed to satisfy constraint: Member must not be null."
22345+
},
22346+
"Type": "User",
22347+
"message": "1 validation error detected: Value null at 'startingPosition' failed to satisfy constraint: Member must not be null.",
22348+
"ResponseMetadata": {
22349+
"HTTPHeaders": {},
22350+
"HTTPStatusCode": 400
22351+
}
22352+
},
22353+
"invalid_starting_position": {
22354+
"Error": {
22355+
"Code": "ValidationException",
22356+
"Message": "1 validation error detected: Value 'invalid' at 'startingPosition' failed to satisfy constraint: Member must satisfy enum value set: [LATEST, AT_TIMESTAMP, TRIM_HORIZON]"
22357+
},
22358+
"ResponseMetadata": {
22359+
"HTTPHeaders": {},
22360+
"HTTPStatusCode": 400
22361+
}
22362+
}
22363+
}
2231522364
}
2231622365
}

tests/aws/services/lambda_/test_lambda_api.validation.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@
4242
"last_validated_date": "2024-09-03T20:58:27+00:00"
4343
},
4444
"tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_validation": {
45-
"last_validated_date": "2024-04-10T09:21:59+00:00"
45+
"last_validated_date": "2025-03-03T17:07:41+00:00"
46+
},
47+
"tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_create_event_source_validation_kinesis": {
48+
"last_validated_date": "2025-03-03T16:49:39+00:00"
4649
},
4750
"tests/aws/services/lambda_/test_lambda_api.py::TestLambdaEventSourceMappings::test_event_source_mapping_exceptions": {
4851
"last_validated_date": "2024-12-05T10:52:30+00:00"

0 commit comments

Comments
 (0)