Repository: getmoto/moto

Issue:
cognito-idp – admin_delete_user_attributes is unimplemented
Request feature for supporting `admin_delete_user_attriburtes` of `cognito-idp` client.

Will open PR after resolving #4467 and #4434 


Discussion:

Resolving patch:
diff --git a/moto/cognitoidp/models.py b/moto/cognitoidp/models.py
--- a/moto/cognitoidp/models.py
+++ b/moto/cognitoidp/models.py
@@ -355,6 +355,28 @@ def update_attributes(self, new_attributes):
         self.attribute_lookup = flat_attributes
         self.attributes = expand_attrs(flat_attributes)
 
+    def delete_attributes(self, attrs_to_delete):
+        flat_attributes = flatten_attrs(self.attributes)
+        wrong_attrs = []
+        for attr in attrs_to_delete:
+            try:
+                flat_attributes.pop(attr)
+            except KeyError:
+                wrong_attrs.append(attr)
+        if wrong_attrs:
+            raise InvalidParameterException(
+                "Invalid user attributes: "
+                + "\n".join(
+                    [
+                        f"user.{w}: Attribute does not exist in the schema."
+                        for w in wrong_attrs
+                    ]
+                )
+                + "\\n"
+            )
+        self.attribute_lookup = flat_attributes
+        self.attributes = expand_attrs(flat_attributes)
+
 
 class CognitoResourceServer(BaseModel):
     def __init__(self, user_pool_id, identifier, name, scopes):
@@ -975,6 +997,9 @@ def admin_update_user_attributes(self, user_pool_id, username, attributes):
 
         user.update_attributes(attributes)
 
+    def admin_delete_user_attributes(self, user_pool_id, username, attributes):
+        self.admin_get_user(user_pool_id, username).delete_attributes(attributes)
+
     def admin_user_global_sign_out(self, user_pool_id, username):
         user_pool = self.describe_user_pool(user_pool_id)
         self.admin_get_user(user_pool_id, username)
diff --git a/moto/cognitoidp/responses.py b/moto/cognitoidp/responses.py
--- a/moto/cognitoidp/responses.py
+++ b/moto/cognitoidp/responses.py
@@ -478,6 +478,15 @@ def admin_update_user_attributes(self):
         )
         return ""
 
+    def admin_delete_user_attributes(self):
+        user_pool_id = self._get_param("UserPoolId")
+        username = self._get_param("Username")
+        attributes = self._get_param("UserAttributeNames")
+        cognitoidp_backends[self.region].admin_delete_user_attributes(
+            user_pool_id, username, attributes
+        )
+        return ""
+
     def admin_user_global_sign_out(self):
         user_pool_id = self._get_param("UserPoolId")
         username = self._get_param("Username")


Test patch:
diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py
--- a/tests/test_cognitoidp/test_cognitoidp.py
+++ b/tests/test_cognitoidp/test_cognitoidp.py
@@ -2443,6 +2443,114 @@ def test_admin_update_user_attributes():
             val.should.equal("Jane")
 
 
+@mock_cognitoidp
+def test_admin_delete_user_attributes():
+    conn = boto3.client("cognito-idp", "us-east-1")
+
+    username = str(uuid.uuid4())
+    user_pool_id = conn.create_user_pool(
+        PoolName=str(uuid.uuid4()),
+        Schema=[
+            {
+                "Name": "foo",
+                "AttributeDataType": "String",
+                "Mutable": True,
+                "Required": False,
+            }
+        ],
+    )["UserPool"]["Id"]
+
+    conn.admin_create_user(
+        UserPoolId=user_pool_id,
+        Username=username,
+        UserAttributes=[
+            {"Name": "family_name", "Value": "Doe"},
+            {"Name": "given_name", "Value": "John"},
+            {"Name": "nickname", "Value": "Joe"},
+            {"Name": "custom:foo", "Value": "bar"},
+        ],
+    )
+
+    conn.admin_delete_user_attributes(
+        UserPoolId=user_pool_id,
+        Username=username,
+        UserAttributeNames=["nickname", "custom:foo"],
+    )
+
+    user = conn.admin_get_user(UserPoolId=user_pool_id, Username=username)
+
+    user["UserAttributes"].should.have.length_of(3)  # family_name, given_name and sub
+    user["UserAttributes"].should.contain({"Name": "family_name", "Value": "Doe"})
+    user["UserAttributes"].should.contain({"Name": "given_name", "Value": "John"})
+    user["UserAttributes"].should_not.contain({"Name": "nickname", "Value": "Joe"})
+    user["UserAttributes"].should_not.contain({"Name": "custom:foo", "Value": "bar"})
+
+
+@mock_cognitoidp
+def test_admin_delete_user_attributes_non_existing_attribute():
+    conn = boto3.client("cognito-idp", "us-east-1")
+
+    username = str(uuid.uuid4())
+    user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
+
+    conn.admin_create_user(
+        UserPoolId=user_pool_id,
+        Username=username,
+        UserAttributes=[
+            {"Name": "family_name", "Value": "Doe"},
+            {"Name": "given_name", "Value": "John"},
+            {"Name": "nickname", "Value": "Joe"},
+        ],
+    )
+
+    with pytest.raises(ClientError) as exc:
+        conn.admin_delete_user_attributes(
+            UserPoolId=user_pool_id,
+            Username=username,
+            UserAttributeNames=["nickname", "custom:foo"],
+        )
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("InvalidParameterException")
+    err["Message"].should.equal(
+        "Invalid user attributes: user.custom:foo: Attribute does not exist in the schema.\n"
+    )
+
+
+@mock_cognitoidp
+def test_admin_delete_user_attributes_non_existing_user():
+    conn = boto3.client("cognito-idp", "us-east-1")
+
+    username = str(uuid.uuid4())
+    user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
+
+    with pytest.raises(ClientError) as exc:
+        conn.admin_delete_user_attributes(
+            UserPoolId=user_pool_id,
+            Username=username,
+            UserAttributeNames=["nickname", "custom:foo"],
+        )
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("UserNotFoundException")
+    err["Message"].should.equal("User does not exist.")
+
+
+@mock_cognitoidp
+def test_admin_delete_user_attributes_non_existing_pool():
+    conn = boto3.client("cognito-idp", "us-east-1")
+
+    user_pool_id = "us-east-1_aaaaaaaa"
+    with pytest.raises(ClientError) as exc:
+        conn.admin_delete_user_attributes(
+            UserPoolId=user_pool_id,
+            Username=str(uuid.uuid4()),
+            UserAttributeNames=["nickname"],
+        )
+
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("ResourceNotFoundException")
+    err["Message"].should.equal(f"User pool {user_pool_id} does not exist.")
+
+
 @mock_cognitoidp
 def test_resource_server():