Repository: getmoto/moto
Pull request #7059 (2023-11-23 05:35:48)

CognitoIdentity: get_credentials_for_identity() should have Expiration as number.
Hi.
The type of Credentials.Expiration is a timestamp number type.

ref: https://docs.aws.amazon.com/cognitoidentity/latest/APIReference/API_GetCredentialsForIdentity.html#API_GetCredentialsForIdentity_ResponseSyntax

It was discovered by a type conversion error in Golang's sdk v2.
ref: https://github.com/aws/aws-sdk-go-v2/blob/6afe5286d243f06aa040d3954b765b38366b88c2/service/cognitoidentity/deserializers.go#L3374-L3383

Thank you very much.


Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 2e0096eb1..b5360e452 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -78,6 +78,10 @@ jobs:
     needs: lint
     uses: ./.github/workflows/tests_sdk_dotnet.yml
 
+  rubytest:
+    needs: lint
+    uses: ./.github/workflows/tests_sdk_ruby.yml
+
   test:
     needs: [lint]
     if: "!contains(github.event.pull_request.labels.*.name, 'java')"
diff --git a/.gitignore b/.gitignore
index b7cc0fbe3..690cc0cff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,3 +36,4 @@ moto/moto_proxy/certs/*.conf
 other_langs/tests_java/target
 other_langs/tests_dotnet/ExampleTestProject/bin
 other_langs/tests_dotnet/ExampleTestProject/obj
+other_langs/tests_ruby/Gemfile.lock
diff --git a/moto/cognitoidentity/models.py b/moto/cognitoidentity/models.py
index d326b7a4a..2be58aa7f 100644
--- a/moto/cognitoidentity/models.py
+++ b/moto/cognitoidentity/models.py
@@ -5,7 +5,7 @@ import re
 from collections import OrderedDict
 from typing import Any, Dict, List, Optional
 from moto.core import BaseBackend, BackendDict, BaseModel
-from moto.core.utils import iso_8601_datetime_with_milliseconds, utcnow
+from moto.core.utils import utcnow
 from .exceptions import InvalidNameException, ResourceNotFoundError
 from .utils import get_random_identity_id
 
@@ -139,12 +139,11 @@ class CognitoIdentityBackend(BaseBackend):
         duration = 90
         now = utcnow()
         expiration = now + datetime.timedelta(seconds=duration)
-        expiration_str = str(iso_8601_datetime_with_milliseconds(expiration))
         return json.dumps(
             {
                 "Credentials": {
                     "AccessKeyId": "TESTACCESSKEY12345",
-                    "Expiration": expiration_str,
+                    "Expiration": expiration.timestamp(),
                     "SecretKey": "ABCSECRETKEY",
                     "SessionToken": "ABC12345",
                 },
@@ -174,5 +173,17 @@ class CognitoIdentityBackend(BaseBackend):
         """
         return json.dumps(self.pools_identities[identity_pool_id])
 
+    def list_identity_pools(self) -> str:
+        """
+        The MaxResults-parameter has not yet been implemented
+        """
+        return json.dumps(
+            {
+                "IdentityPools": [
+                    json.loads(pool.to_json()) for pool in self.identity_pools.values()
+                ]
+            }
+        )
+
 
 cognitoidentity_backends = BackendDict(CognitoIdentityBackend, "cognito-identity")
diff --git a/moto/cognitoidentity/responses.py b/moto/cognitoidentity/responses.py
index 34499ed02..ba1d157bc 100644
--- a/moto/cognitoidentity/responses.py
+++ b/moto/cognitoidentity/responses.py
@@ -80,3 +80,6 @@ class CognitoIdentityResponse(BaseResponse):
         return self.backend.list_identities(
             self._get_param("IdentityPoolId") or get_random_identity_id(self.region)
         )
+
+    def list_identity_pools(self) -> str:
+        return self.backend.list_identity_pools()
diff --git a/moto/sqs/responses.py b/moto/sqs/responses.py
index 9c5207a12..2dc827f3a 100644
--- a/moto/sqs/responses.py
+++ b/moto/sqs/responses.py
@@ -289,7 +289,13 @@ class SQSResponse(BaseResponse):
         queue_name = self._get_queue_name()
         self.sqs_backend.set_queue_attributes(queue_name, attribute)
 
-        return SET_QUEUE_ATTRIBUTE_RESPONSE
+        return self._empty_response(SET_QUEUE_ATTRIBUTE_RESPONSE)
+
+    def _empty_response(self, xml_response: str) -> str:
+        # 'Empty' XML response always needs some fields
+        if self.is_json():
+            return "{}"
+        return xml_response
 
     @jsonify_error
     def delete_queue(self) -> str:
@@ -298,8 +304,7 @@ class SQSResponse(BaseResponse):
 
         self.sqs_backend.delete_queue(queue_name)
 
-        template = self.response_template(DELETE_QUEUE_RESPONSE)
-        return template.render()
+        return self._empty_response(DELETE_QUEUE_RESPONSE)
 
     @jsonify_error
     def send_message(self) -> Union[str, TYPE_RESPONSE]:
@@ -506,8 +511,7 @@ class SQSResponse(BaseResponse):
     def purge_queue(self) -> str:
         queue_name = self._get_queue_name()
         self.sqs_backend.purge_queue(queue_name)
-        template = self.response_template(PURGE_QUEUE_RESPONSE)
-        return template.render()
+        return self._empty_response(PURGE_QUEUE_RESPONSE)
 
     @jsonify_error
     def receive_message(self) -> Union[str, TYPE_RESPONSE]:
@@ -684,8 +688,7 @@ class SQSResponse(BaseResponse):
 
         self.sqs_backend.add_permission(queue_name, actions, account_ids, label)
 
-        template = self.response_template(ADD_PERMISSION_RESPONSE)
-        return template.render()
+        return self._empty_response(ADD_PERMISSION_RESPONSE)
 
     @jsonify_error
     def remove_permission(self) -> str:
@@ -694,8 +697,7 @@ class SQSResponse(BaseResponse):
 
         self.sqs_backend.remove_permission(queue_name, label)
 
-        template = self.response_template(REMOVE_PERMISSION_RESPONSE)
-        return template.render()
+        return self._empty_response(REMOVE_PERMISSION_RESPONSE)
 
     @jsonify_error
     def tag_queue(self) -> str:


Test patch:
diff --git a/.github/workflows/tests_sdk_dotnet.yml b/.github/workflows/tests_sdk_dotnet.yml
index 4bf92e76f..cf84b9f7e 100644
--- a/.github/workflows/tests_sdk_dotnet.yml
+++ b/.github/workflows/tests_sdk_dotnet.yml
@@ -17,7 +17,7 @@ jobs:
       run: |
         pip install build
         python -m build
-        docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:3.7-buster /moto/scripts/ci_moto_server.sh &
+        docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:3.10-slim /moto/scripts/ci_moto_server.sh &
         python scripts/ci_wait_for_server.py
     - uses: actions/setup-dotnet@v3
     - uses: actions/cache@v3
diff --git a/.github/workflows/tests_sdk_java.yml b/.github/workflows/tests_sdk_java.yml
index 298c40fd6..6e8199648 100644
--- a/.github/workflows/tests_sdk_java.yml
+++ b/.github/workflows/tests_sdk_java.yml
@@ -17,7 +17,7 @@ jobs:
       run: |
         pip install build
         python -m build
-        docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:3.7-buster /moto/scripts/ci_moto_server.sh &
+        docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:3.10-slim /moto/scripts/ci_moto_server.sh &
         python scripts/ci_wait_for_server.py
     - uses: actions/setup-java@v3
       with:
diff --git a/.github/workflows/tests_sdk_ruby.yml b/.github/workflows/tests_sdk_ruby.yml
new file mode 100644
index 000000000..14e4cbbca
--- /dev/null
+++ b/.github/workflows/tests_sdk_ruby.yml
@@ -0,0 +1,36 @@
+name: Ruby SDK test
+on: [workflow_call]
+
+jobs:
+  test:
+
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        ruby-version: ['3.2']
+
+    steps:
+      - uses: actions/checkout@v4
+      - name: Set up Ruby ${{ matrix.ruby-version }}
+        uses: ruby/setup-ruby@ec02537da5712d66d4d50a0f33b7eb52773b5ed1
+        with:
+          ruby-version: ${{ matrix.ruby-version }}
+      - name: Set up Python 3.8
+        uses: actions/setup-python@v4
+        with:
+          python-version: "3.8"
+      - name: Start MotoServer
+        run: |
+          pip install build
+          python -m build
+          docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:3.10-slim /moto/scripts/ci_moto_server.sh &
+          python scripts/ci_wait_for_server.py
+      - name: Install dependencies
+        run: cd other_langs/tests_ruby && bundle install
+      - name: Run tests
+        env:
+          AWS_ENDPOINT_URL: "http://localhost:5000"
+        run: |
+          mkdir ~/.aws && touch ~/.aws/credentials && echo -e "[default]\naws_access_key_id = test\naws_secret_access_key = test" > ~/.aws/credentials
+          cd other_langs/tests_ruby && ruby test/*
diff --git a/other_langs/tests_ruby/Gemfile b/other_langs/tests_ruby/Gemfile
new file mode 100644
index 000000000..e26f68af1
--- /dev/null
+++ b/other_langs/tests_ruby/Gemfile
@@ -0,0 +1,4 @@
+source "https://rubygems.org"
+
+gem 'aws-sdk-sqs', '1.65.0'
+gem "minitest"
\ No newline at end of file
diff --git a/other_langs/tests_ruby/test/aws_sqs_test.rb b/other_langs/tests_ruby/test/aws_sqs_test.rb
new file mode 100644
index 000000000..7bae8b26f
--- /dev/null
+++ b/other_langs/tests_ruby/test/aws_sqs_test.rb
@@ -0,0 +1,40 @@
+require 'aws-sdk-sqs'
+require 'minitest/autorun'
+
+class AwsSqsTest < Minitest::Test
+  def test_all_sqs_actions
+    region = 'us-east-1'
+
+    sqs_client = Aws::SQS::Client.new(region: region)
+
+    sqs_client.list_queues
+
+    sqs_client.create_queue(queue_name: "q1")
+
+    sqs_client.list_queues
+
+    queue_url = sqs_client.get_queue_url(queue_name: "q1").queue_url
+
+    sqs_client.add_permission({queue_url: queue_url, label: "String", aws_account_ids: ["String"], actions: ["GetQueueUrl"]})
+
+    sqs_client.send_message(
+      queue_url: queue_url,
+      message_body: "message_body"
+    )
+
+    sqs_client.remove_permission({queue_url: queue_url, label: "String"})
+
+    sqs_client.set_queue_attributes({queue_url: queue_url, attributes: {"All" => "DelaySeconds"}})
+
+    sqs_client.receive_message(
+      queue_url: queue_url,
+      max_number_of_messages: 10,
+      attribute_names: ['All'],
+      message_attribute_names: ['All']
+    ).messages
+
+    sqs_client.purge_queue({queue_url: queue_url})
+
+    sqs_client.delete_queue(queue_url: queue_url)
+  end
+end
\ No newline at end of file
diff --git a/tests/test_cognitoidentity/test_cognitoidentity.py b/tests/test_cognitoidentity/test_cognitoidentity.py
index 197cc4647..742c27a81 100644
--- a/tests/test_cognitoidentity/test_cognitoidentity.py
+++ b/tests/test_cognitoidentity/test_cognitoidentity.py
@@ -240,3 +240,15 @@ def test_list_identities():
     identities = conn.list_identities(IdentityPoolId=identity_pool_id, MaxResults=123)
     assert "IdentityPoolId" in identities and "Identities" in identities
     assert identity_id in [x["IdentityId"] for x in identities["Identities"]]
+
+
+@mock_cognitoidentity
+def test_list_identity_pools():
+    conn = boto3.client("cognito-identity", "us-west-2")
+    identity_pool_data = conn.create_identity_pool(
+        IdentityPoolName="test_identity_pool", AllowUnauthenticatedIdentities=True
+    )
+    identity_pool_id = identity_pool_data["IdentityPoolId"]
+    identity_data = conn.list_identity_pools(MaxResults=10)
+    assert identity_pool_id == identity_data["IdentityPools"][0]["IdentityPoolId"]
+    assert "test_identity_pool" == identity_data["IdentityPools"][0]["IdentityPoolName"]