Repository: getmoto/moto (python)

Issue:
BUG: ECR stub not allowing official fields
Per [ECR Docs](https://docs.aws.amazon.com/AmazonECR/latest/userguide/lifecycle_policy_examples.html#lifecycle_policy_syntax) ECR supports some fields which moto is erroring out on.

<img width="938" alt="image" src="https://github.com/user-attachments/assets/6ae7c054-7016-45e5-ba7e-cac1dad89c04" />


<img width="1297" alt="image" src="https://github.com/user-attachments/assets/5b50f22c-1c91-4cc1-afe0-ee68e5634672" />


It appears there is a simple list that needs to be updated but I am unsure of other gotchas with the code base.
https://github.com/getmoto/moto/blob/master/moto/ecr/policy_validation.py#L15C1-L15C27

Pull request description:
ECR: Lifecycle Policies() now support the tagPatternList-parameter
Fixes #8484 

Patch:
diff --git a/moto/dynamodb/models/__init__.py b/moto/dynamodb/models/__init__.py
index 74e76f1fa..d15cd9045 100644
--- a/moto/dynamodb/models/__init__.py
+++ b/moto/dynamodb/models/__init__.py
@@ -627,60 +627,51 @@ class DynamoDBBackend(BaseBackend):
         ] = []  # [(Code, Message, Item), ..]
         for item in transact_items:
             original_item: Optional[Dict[str, Any]] = None
-            # check transact writes are not performing multiple operations
-            # in the same item
+            # Check transact writes are not performing multiple operations on the same item
             if len(list(item.keys())) > 1:
                 raise TransactWriteSingleOpException
 
             try:
-                if "ConditionCheck" in item:
-                    item = item["ConditionCheck"]
-                    key = item["Key"]
-                    table_name = item["TableName"]
-                    check_unicity(table_name, key)
-                    condition_expression = item.get("ConditionExpression", None)
-                    expression_attribute_names = item.get(
-                        "ExpressionAttributeNames", None
-                    )
-                    expression_attribute_values = item.get(
-                        "ExpressionAttributeValues", None
-                    )
-                    current = self.get_item(table_name, key)
+                op_type, op = next(iter(item.items()))
+                if op_type not in {"ConditionCheck", "Put", "Delete", "Update"}:
+                    raise ValueError("Unsupported transaction operation")
+
+                table_name = op["TableName"]
+                condition_expression = op.get("ConditionExpression")
+                expression_attribute_names = op.get("ExpressionAttributeNames")
+                expression_attribute_values = op.get("ExpressionAttributeValues")
+                return_values_on_condition_check_failure = op.get(
+                    "ReturnValuesOnConditionCheckFailure"
+                )
 
-                    condition_op = get_filter_expression(
-                        condition_expression,
-                        expression_attribute_names,
-                        expression_attribute_values,
-                    )
-                    if not condition_op.expr(current):
-                        raise ConditionalCheckFailed()
-                elif "Put" in item:
-                    item = item["Put"]
-                    attrs = item["Item"]
-                    table_name = item["TableName"]
+                if op_type == "Put":
+                    attrs = op["Item"]
                     table = self.get_table(table_name)
                     key = {table.hash_key_attr: attrs[table.hash_key_attr]}
                     if table.range_key_attr is not None:
                         key[table.range_key_attr] = attrs[table.range_key_attr]
-                    check_unicity(table_name, key)
-                    condition_expression = item.get("ConditionExpression", None)
-                    expression_attribute_names = item.get(
-                        "ExpressionAttributeNames", None
-                    )
-                    expression_attribute_values = item.get(
-                        "ExpressionAttributeValues", None
-                    )
+                else:
+                    key = op["Key"]
 
-                    return_values_on_condition_check_failure = item.get(
-                        "ReturnValuesOnConditionCheckFailure", None
-                    )
-                    current = self.get_item(table_name, attrs)
-                    if (
-                        return_values_on_condition_check_failure == "ALL_OLD"
-                        and current
-                    ):
-                        original_item = current.to_json()["Attributes"]
+                check_unicity(table_name, key)
 
+                current = self.get_item(table_name, key)
+                if condition_expression is not None:
+                    condition_op = get_filter_expression(
+                        condition_expression,
+                        expression_attribute_names,
+                        expression_attribute_values,
+                    )
+                    if not condition_op.expr(current):
+                        if (
+                            return_values_on_condition_check_failure == "ALL_OLD"
+                            and current
+                        ):
+                            original_item = current.to_json()["Attributes"]
+                        raise ConditionalCheckFailed(item=original_item)
+
+                if op_type == "Put":
+                    attrs = op["Item"]
                     self.put_item(
                         table_name,
                         attrs,
@@ -688,18 +679,7 @@ class DynamoDBBackend(BaseBackend):
                         expression_attribute_names=expression_attribute_names,
                         expression_attribute_values=expression_attribute_values,
                     )
-                elif "Delete" in item:
-                    item = item["Delete"]
-                    key = item["Key"]
-                    table_name = item["TableName"]
-                    check_unicity(table_name, key)
-                    condition_expression = item.get("ConditionExpression", None)
-                    expression_attribute_names = item.get(
-                        "ExpressionAttributeNames", None
-                    )
-                    expression_attribute_values = item.get(
-                        "ExpressionAttributeValues", None
-                    )
+                elif op_type == "Delete":
                     self.delete_item(
                         table_name,
                         key,
@@ -707,19 +687,8 @@ class DynamoDBBackend(BaseBackend):
                         expression_attribute_names=expression_attribute_names,
                         expression_attribute_values=expression_attribute_values,
                     )
-                elif "Update" in item:
-                    item = item["Update"]
-                    key = item["Key"]
-                    table_name = item["TableName"]
-                    check_unicity(table_name, key)
-                    update_expression = item["UpdateExpression"]
-                    condition_expression = item.get("ConditionExpression", None)
-                    expression_attribute_names = item.get(
-                        "ExpressionAttributeNames", None
-                    )
-                    expression_attribute_values = item.get(
-                        "ExpressionAttributeValues", None
-                    )
+                elif op_type == "Update":
+                    update_expression = op["UpdateExpression"]
                     self.update_item(
                         table_name,
                         key,
@@ -728,15 +697,17 @@ class DynamoDBBackend(BaseBackend):
                         expression_attribute_names=expression_attribute_names,
                         expression_attribute_values=expression_attribute_values,
                     )
-                else:
-                    raise ValueError
                 errors.append((None, None, None))
             except MultipleTransactionsException:
                 # Rollback to the original state, and reraise the error
                 self.tables = original_table_state
                 raise MultipleTransactionsException()
+            except ConditionalCheckFailed as e:
+                errors.append(("ConditionalCheckFailed", str(e.message), original_item))  # type: ignore
             except Exception as e:  # noqa: E722 Do not use bare except
-                errors.append((type(e).__name__, e.message, original_item))  # type: ignore
+                # For other exceptions, capture their details
+                errors.append((type(e).__name__, str(e), original_item))  # type: ignore
+
         if any([code is not None for code, _, _ in errors]):
             # Rollback to the original state, and reraise the errors
             self.tables = original_table_state
diff --git a/moto/ecr/policy_validation.py b/moto/ecr/policy_validation.py
index 286599de6..f15b814cb 100644
--- a/moto/ecr/policy_validation.py
+++ b/moto/ecr/policy_validation.py
@@ -13,6 +13,7 @@ VALID_ACTION_TYPE_VALUES = {"expire"}
 
 REQUIRED_SELECTION_PROPERTIES = {"tagStatus", "countType", "countNumber"}
 VALID_SELECTION_PROPERTIES = {
+    "tagPatternList",
     "tagPrefixList",
     "countUnit",
     *REQUIRED_SELECTION_PROPERTIES,
diff --git a/moto/ssm/resources/regions.json b/moto/ssm/resources/regions.json
index 37baec0fa..ed4e03673 100644
--- a/moto/ssm/resources/regions.json
+++ b/moto/ssm/resources/regions.json
@@ -47919,6 +47919,11 @@
                   "Value": "HTTPS"
                 }
               }
+            },
+            "wavelength-zones": {
+              "euw3-cmn1-wlz1": {
+                "Value": "euw3-cmn1-wlz1"
+              }
             }
           },
           "il-central-1": {


Test patch:
diff --git a/tests/test_dynamodb/test_dynamodb_transact.py b/tests/test_dynamodb/test_dynamodb_transact.py
index 13e635302..fec16b170 100644
--- a/tests/test_dynamodb/test_dynamodb_transact.py
+++ b/tests/test_dynamodb/test_dynamodb_transact.py
@@ -307,34 +307,38 @@ def test_transact_write_items_put_conditional_expressions(table_name=None):
 
 @pytest.mark.aws_verified
 @dynamodb_aws_verified()
-def test_transact_write_items_failure__return_item(table_name=None):
+@pytest.mark.parametrize("operation", ["ConditionCheck", "Put", "Delete", "Update"])
+def test_transact_write_items_failure__return_item(operation: str, table_name=None):
     dynamodb = boto3.client("dynamodb", region_name="us-east-1")
     dynamodb.put_item(TableName=table_name, Item={"pk": {"S": "foo2"}})
-    # Put multiple items
+
+    transact_items = []
+    for i in range(0, 5):
+        item = {
+            operation: {
+                "Key": {"pk": {"S": f"foo{i}"}},
+                "TableName": table_name,
+                "ConditionExpression": "#i <> :i",
+                "ExpressionAttributeNames": {"#i": "pk"},
+                # This man right here - should return item as part of error message
+                "ReturnValuesOnConditionCheckFailure": "ALL_OLD",
+                "ExpressionAttributeValues": {
+                    ":i": {"S": "foo2"},
+                },  # This item already exist, so the ConditionExpression should fail
+            }
+        }
+        if operation == "Update":
+            item[operation]["UpdateExpression"] = "SET foo = :i"
+        elif operation == "Put":
+            del item[operation]["Key"]
+            item[operation]["Item"] = {
+                "pk": {"S": f"foo{i}"},
+                "foo": {"S": "bar"},
+            }
+        transact_items.append(item)
+
     with pytest.raises(ClientError) as ex:
-        dynamodb.transact_write_items(
-            TransactItems=[
-                {
-                    "Put": {
-                        "Item": {
-                            "pk": {"S": f"foo{i}"},
-                            "foo": {"S": "bar"},
-                        },
-                        "TableName": table_name,
-                        "ConditionExpression": "#i <> :i",
-                        "ExpressionAttributeNames": {"#i": "pk"},
-                        # This man right here - should return item as part of error message
-                        "ReturnValuesOnConditionCheckFailure": "ALL_OLD",
-                        "ExpressionAttributeValues": {
-                            ":i": {
-                                "S": "foo2"
-                            }  # This item already exist, so the ConditionExpression should fail
-                        },
-                    }
-                }
-                for i in range(0, 5)
-            ]
-        )
+        dynamodb.transact_write_items(TransactItems=transact_items)
     # Assert the exception is correct
     assert ex.value.response["Error"]["Code"] == "TransactionCanceledException"
     reasons = ex.value.response["CancellationReasons"]
diff --git a/tests/test_ecr/test_ecr_boto3.py b/tests/test_ecr/test_ecr_boto3.py
index 8086df85e..373d069ac 100644
--- a/tests/test_ecr/test_ecr_boto3.py
+++ b/tests/test_ecr/test_ecr_boto3.py
@@ -2016,7 +2016,10 @@ def test_put_lifecycle_policy():
                 "description": "test policy",
                 "selection": {
                     "tagStatus": "untagged",
+                    "tagPatternList": ["tagx"],
+                    "tagPrefixList": ["tag-"],
                     "countType": "imageCountMoreThan",
+                    "countUnit": "days",
                     "countNumber": 30,
                 },
                 "action": {"type": "expire"},
diff --git a/tests/test_ecr/test_ecr_policy_validation.py b/tests/test_ecr/test_ecr_policy_validation.py
index d749c3f78..3355f9bd3 100644
--- a/tests/test_ecr/test_ecr_policy_validation.py
+++ b/tests/test_ecr/test_ecr_policy_validation.py
@@ -25,6 +25,7 @@ def test_validate():
                 "description": "test policy",
                 "selection": {
                     "tagStatus": "tagged",
+                    "tagPatternList": ["tagx"],
                     "tagPrefixList": ["3.9"],
                     "countType": "sinceImagePushed",
                     "countUnit": "days",