Repository: getmoto/moto
Pull request #8950 (2025-06-07 10:24:56)

S3Tables: Support new URL for botocore 1.38.32
See changeset: https://github.com/boto/botocore/commit/75060cf32da8ba035e74362a2558bdf8edfbb99d#diff-3f3b0b087fa041bc9956863e5c76ee5b7f44d31a5a581182850e2f9bc7fe975b

The URL for the `GetTable`-command changed from `/tables/{tableBucketARN}/{namespace}/{name}` to `/get-table`.


Patch:
diff --git a/moto/cognitoidp/models.py b/moto/cognitoidp/models.py
index 0783bee9c..d1e7cfc05 100644
--- a/moto/cognitoidp/models.py
+++ b/moto/cognitoidp/models.py
@@ -15,7 +15,10 @@ from moto.moto_api._internal import mock_random as random
 from moto.utilities.paginator import paginate
 from moto.utilities.utils import get_partition, load_resource, md5_hash
 
-from ..settings import get_cognito_idp_user_pool_id_strategy
+from ..settings import (
+    get_cognito_idp_user_pool_client_id_strategy,
+    get_cognito_idp_user_pool_id_strategy,
+)
 from .exceptions import (
     AliasExistsException,
     ExpiredCodeException,
@@ -31,7 +34,6 @@ from .exceptions import (
 from .utils import (
     PAGINATION_MODEL,
     check_secret_hash,
-    create_id,
     expand_attrs,
     flatten_attrs,
     generate_id,
@@ -705,6 +707,8 @@ class CognitoIdpUserPoolDomain(BaseModel):
 
 
 class CognitoIdpUserPoolClient(BaseModel):
+    MAX_ID_LENGTH = 26
+
     def __init__(
         self,
         user_pool_id: str,
@@ -712,7 +716,12 @@ class CognitoIdpUserPoolClient(BaseModel):
         extended_config: Optional[Dict[str, Any]],
     ):
         self.user_pool_id = user_pool_id
-        self.id = create_id()
+        self.id = generate_id(
+            get_cognito_idp_user_pool_client_id_strategy(),
+            user_pool_id,
+            generate_secret,
+            extended_config,
+        )[: self.MAX_ID_LENGTH]
         self.secret = str(random.uuid4())
         self.generate_secret = generate_secret or False
         # Some default values - may be overridden by the user
@@ -961,6 +970,7 @@ class CognitoIdpBackend(BaseBackend):
     For example, a single initialization before the start of integration tests.
 
     This behavior can be enabled by passing the environment variable: MOTO_COGNITO_IDP_USER_POOL_ID_STRATEGY=HASH.
+    Passing MOTO_COGNITO_IDP_USER_POOL_CLIENT_ID_STRATEGY=HASH enables the same logic for user pool clients.
     """
 
     def __init__(self, region_name: str, account_id: str):
diff --git a/moto/s3tables/responses.py b/moto/s3tables/responses.py
index ec8066dba..e177187cf 100644
--- a/moto/s3tables/responses.py
+++ b/moto/s3tables/responses.py
@@ -181,8 +181,9 @@ class S3TablesResponse(BaseResponse):
         )
 
     def get_table(self) -> TYPE_RESPONSE:
-        _, table_bucket_arn, namespace, name = self.raw_path.lstrip("/").split("/")
-        table_bucket_arn = unquote(table_bucket_arn)
+        table_bucket_arn = unquote(self._get_param("tableBucketARN"))
+        namespace = self._get_param("namespace")
+        name = self._get_param("name")
         table = self.s3tables_backend.get_table(
             table_bucket_arn=table_bucket_arn,
             namespace=namespace,
diff --git a/moto/s3tables/urls.py b/moto/s3tables/urls.py
index ba6d475ab..59e541d04 100644
--- a/moto/s3tables/urls.py
+++ b/moto/s3tables/urls.py
@@ -10,6 +10,7 @@ url_paths = {
     "{0}/buckets$": S3TablesResponse.dispatch,
     "{0}/buckets/(?P<tableBucketARN>.+)$": S3TablesResponse.dispatch,
     "{0}/buckets/(?P<tableBucketARN_pt_1>[^/]+)/(?P<tableBucketARN_pt_2>[^/]+)$": S3TablesResponse.dispatch,
+    "{0}/get-table$": S3TablesResponse.dispatch,
     "{0}/namespaces/(?P<tableBucketARN>.+)$": S3TablesResponse.dispatch,
     "{0}/tables/(?P<tableBucketARN>[^/]+)/(?P<namespace>[^/]+)$": S3TablesResponse.dispatch,
     "{0}/tables/(?P<tableBucketARN>[^/]+)/(?P<namespace>[^/]+)/(?P<name>[^/]+)$": S3TablesResponse.dispatch,
diff --git a/moto/settings.py b/moto/settings.py
index 410e7a7f6..b42454ab3 100644
--- a/moto/settings.py
+++ b/moto/settings.py
@@ -183,6 +183,10 @@ def get_cognito_idp_user_pool_id_strategy() -> Optional[str]:
     return os.environ.get("MOTO_COGNITO_IDP_USER_POOL_ID_STRATEGY")
 
 
+def get_cognito_idp_user_pool_client_id_strategy() -> Optional[str]:
+    return os.environ.get("MOTO_COGNITO_IDP_USER_POOL_CLIENT_ID_STRATEGY")
+
+
 def enable_iso_regions() -> bool:
     return os.environ.get("MOTO_ENABLE_ISO_REGIONS", "false").lower() == "true"
 


Test patch:
diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py
index b4fb03f8b..b093394af 100644
--- a/tests/test_cognitoidp/test_cognitoidp.py
+++ b/tests/test_cognitoidp/test_cognitoidp.py
@@ -980,7 +980,7 @@ def test_update_user_pool_domain():
 
 
 @mock_aws
-def test_create_user_pool_client():
+def test_create_user_pool_client_default_id_strategy():
     conn = boto3.client("cognito-idp", "us-west-2")
 
     client_name = str(uuid.uuid4())
@@ -998,6 +998,50 @@ def test_create_user_pool_client():
     assert result["UserPoolClient"]["CallbackURLs"][0] == value
 
 
+@mock_aws
+@mock.patch.dict(os.environ, {"MOTO_COGNITO_IDP_USER_POOL_CLIENT_ID_STRATEGY": "HASH"})
+def test_create_user_pool_client_hash_id_strategy_with_equal_args():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Cannot set environemnt variables in ServerMode")
+
+    conn = boto3.client("cognito-idp", "us-west-2")
+
+    client_name = str(uuid.uuid4())
+    value = str(uuid.uuid4())
+    user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
+
+    first = conn.create_user_pool_client(
+        UserPoolId=user_pool_id, ClientName=client_name, CallbackURLs=[value]
+    )
+    second = conn.create_user_pool_client(
+        UserPoolId=user_pool_id, ClientName=client_name, CallbackURLs=[value]
+    )
+
+    assert first["UserPoolClient"]["ClientId"] == second["UserPoolClient"]["ClientId"]
+
+
+@mock_aws
+@mock.patch.dict(os.environ, {"MOTO_COGNITO_IDP_USER_POOL_CLIENT_ID_STRATEGY": "HASH"})
+def test_create_user_pool_client_hash_id_strategy_with_different_args():
+    if settings.TEST_SERVER_MODE:
+        raise SkipTest("Cannot set environemnt variables in ServerMode")
+
+    conn = boto3.client("cognito-idp", "us-west-2")
+
+    first = conn.create_user_pool_client(
+        UserPoolId=conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"],
+        ClientName=str(uuid.uuid4()),
+        CallbackURLs=[str(uuid.uuid4())],
+    )
+    second = conn.create_user_pool_client(
+        UserPoolId=conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"],
+        ClientName=str(uuid.uuid4()),
+        CallbackURLs=[str(uuid.uuid4())],
+    )
+
+    assert first["UserPoolClient"]["ClientId"] != second["UserPoolClient"]["ClientId"]
+
+
 @mock_aws
 def test_create_user_pool_client_returns_secret():
     conn = boto3.client("cognito-idp", "us-west-2")
diff --git a/tests/test_s3tables/test_server.py b/tests/test_s3tables/test_server.py
index 5d18bcd9a..bcff3dbe0 100644
--- a/tests/test_s3tables/test_server.py
+++ b/tests/test_s3tables/test_server.py
@@ -116,13 +116,13 @@ def test_s3tables_get_table(bucket_name: str):
     arn = resp.get_json()["arn"]
 
     quoted_arn = quote(arn, safe="")
-    resp = test_client.put(f"/namespaces/{quoted_arn}", json={"namespace": ["bar"]})
+    test_client.put(f"/namespaces/{quoted_arn}", json={"namespace": ["bar"]})
 
-    resp = test_client.put(
+    test_client.put(
         f"/tables/{quoted_arn}/bar", json={"name": "baz", "format": "ICEBERG"}
     )
 
-    resp = test_client.get(f"/tables/{quoted_arn}/bar/baz")
+    resp = test_client.get(f"/get-table?tableBucketARN={arn}&namespace=bar&name=baz")
     assert resp.status_code == 200