From 4d1dc2b19adad411957ebf9103f2ded539efa92b Mon Sep 17 00:00:00 2001 From: armichaud Date: Fri, 31 Jan 2025 09:52:19 -0500 Subject: [PATCH 01/11] Require Valid Elasticache Engine Type --- moto/elasticache/responses.py | 14 ++++- moto/elasticache/utils.py | 8 +++ tests/test_elasticache/test_elasticache.py | 67 +++++++++++++--------- 3 files changed, 60 insertions(+), 29 deletions(-) diff --git a/moto/elasticache/responses.py b/moto/elasticache/responses.py index 82d6ecc0b12c..f177fb9fcfa4 100644 --- a/moto/elasticache/responses.py +++ b/moto/elasticache/responses.py @@ -6,7 +6,7 @@ PasswordTooShort, ) from .models import ElastiCacheBackend, elasticache_backends -from .utils import AuthenticationTypes +from .utils import AuthenticationTypes, EngineTypes, ValidAuthModeKeys class ElastiCacheResponse(BaseResponse): @@ -39,12 +39,20 @@ def create_user(self) -> str: if passwords: authentication_type = AuthenticationTypes.PASSWORD.value + + if engine: + engine_types = [e.value for e in EngineTypes] + if engine not in engine_types: + raise InvalidParameterValueException( + f'Unknown parameter for Engine: "{engine}", must be one of: {', '.join(engine_types)}' + ) if authentication_mode: for key in authentication_mode.keys(): - if key not in ["Type", "Passwords"]: + valid_keys = [e.value for e in ValidAuthModeKeys] + if key not in valid_keys: raise InvalidParameterValueException( - f'Unknown parameter in AuthenticationMode: "{key}", must be one of: Type, Passwords' + f'Unknown parameter in AuthenticationMode: "{key}", must be one of: {', '.join(valid_keys)}' ) authentication_type = authentication_mode.get("Type") diff --git a/moto/elasticache/utils.py b/moto/elasticache/utils.py index 9377e997d2b1..011f26f7bdea 100644 --- a/moto/elasticache/utils.py +++ b/moto/elasticache/utils.py @@ -14,3 +14,11 @@ class AuthenticationTypes(str, Enum): NOPASSWORD = "no-password-required" PASSWORD = "password" IAM = "iam" + +class EngineTypes(str, Enum): + REDIS = "redis" + VALKEY = "valkey" + +class ValidAuthModeKeys(str, Enum): + TYPE = "Type" + PASSWORD = "Passwords" diff --git a/tests/test_elasticache/test_elasticache.py b/tests/test_elasticache/test_elasticache.py index 183aff040d3e..23fff8a5227b 100644 --- a/tests/test_elasticache/test_elasticache.py +++ b/tests/test_elasticache/test_elasticache.py @@ -16,7 +16,7 @@ def test_create_user_no_password_required(): resp = client.create_user( UserId=user_id, UserName="User1", - Engine="Redis", + Engine="redis", AccessString="on ~* +@all", NoPasswordRequired=True, ) @@ -24,7 +24,7 @@ def test_create_user_no_password_required(): assert resp["UserId"] == user_id assert resp["UserName"] == "User1" assert resp["Status"] == "active" - assert resp["Engine"] == "Redis" + assert resp["Engine"] == "redis" assert resp["MinimumEngineVersion"] == "6.0" assert resp["AccessString"] == "on ~* +@all" assert resp["UserGroupIds"] == [] @@ -43,7 +43,7 @@ def test_create_user_with_password_too_short(): client.create_user( UserId=user_id, UserName="User1", - Engine="Redis", + Engine="redis", AccessString="on ~* +@all", Passwords=["mysecretpass"], ) @@ -51,6 +51,21 @@ def test_create_user_with_password_too_short(): assert err["Code"] == "InvalidParameterValue" assert err["Message"] == "Passwords length must be between 16-128 characters." +@mock_aws +def test_create_user_with_wrong_engine_type(): + client = boto3.client("elasticache", region_name="ap-southeast-1") + user_id = "user1" + with pytest.raises(ClientError) as exc: + client.create_user( + UserId=user_id, + UserName="User1", + Engine="invalidengine", + AccessString="on ~* +@all", + Passwords=["mysecretpassthatsverylong"], + ) + err = exc.value.response["Error"] + assert err["Code"] == "InvalidParameterValue" + assert err["Message"] == "Unknown parameter for Engine: invalidengine, must be one of: redis, valkey" @mock_aws def test_create_user_with_password(): @@ -59,7 +74,7 @@ def test_create_user_with_password(): resp = client.create_user( UserId=user_id, UserName="User1", - Engine="Redis", + Engine="redis", AccessString="on ~* +@all", Passwords=["mysecretpassthatsverylong"], ) @@ -67,7 +82,7 @@ def test_create_user_with_password(): assert resp["UserId"] == user_id assert resp["UserName"] == "User1" assert resp["Status"] == "active" - assert resp["Engine"] == "Redis" + assert resp["Engine"] == "redis" assert resp["MinimumEngineVersion"] == "6.0" assert resp["AccessString"] == "on ~* +@all" assert resp["UserGroupIds"] == [] @@ -83,7 +98,7 @@ def test_create_user_without_password(): client = boto3.client("elasticache", region_name="ap-southeast-1") with pytest.raises(ClientError) as exc: client.create_user( - UserId="user1", UserName="User1", Engine="Redis", AccessString="?" + UserId="user1", UserName="User1", Engine="redis", AccessString="?" ) err = exc.value.response["Error"] assert err["Code"] == "InvalidParameterValue" @@ -100,13 +115,13 @@ def test_create_user_with_iam(): resp = client.create_user( UserId=user_id, UserName="User1", - Engine="Redis", + Engine="redis", AccessString="on ~* +@all", AuthenticationMode={"Type": "iam"}, ) assert resp["Status"] == "active" - assert resp["Engine"] == "Redis" + assert resp["Engine"] == "redis" assert resp["AccessString"] == "on ~* +@all" assert resp["UserGroupIds"] == [] assert resp["Authentication"]["Type"] == "iam" @@ -119,7 +134,7 @@ def test_create_user_invalid_authentication_type(): client.create_user( UserId="user1", UserName="User1", - Engine="Redis", + Engine="redis", AccessString="?", AuthenticationMode={"Type": "invalidtype"}, ) @@ -139,7 +154,7 @@ def test_create_user_with_iam_with_passwords(): client.create_user( UserId="user1", UserName="user1", - Engine="Redis", + Engine="redis", AccessString="?", AuthenticationMode={"Type": "iam"}, Passwords=["mysecretpassthatsverylong"], @@ -158,7 +173,7 @@ def test_create_user_authmode_password_with_multiple_password_fields(): client.create_user( UserId="user1", UserName="user1", - Engine="Redis", + Engine="redis", AccessString="on ~* +@all", AuthenticationMode={"Type": "password", "Passwords": ["authmodepassword"]}, Passwords=["requestpassword"], @@ -179,7 +194,7 @@ def test_create_user_with_authmode_password_without_passwords(): client.create_user( UserId="user1", UserName="user1", - Engine="Redis", + Engine="redis", AccessString="?", AuthenticationMode={"Type": "password"}, ) @@ -199,13 +214,13 @@ def test_create_user_with_authmode_no_password(): resp = client.create_user( UserId=user_id, UserName="User1", - Engine="Redis", + Engine="redis", AccessString="on ~* +@all", AuthenticationMode={"Type": "no-password-required"}, ) assert resp["Status"] == "active" - assert resp["Engine"] == "Redis" + assert resp["Engine"] == "redis" assert resp["AccessString"] == "on ~* +@all" assert resp["UserGroupIds"] == [] assert resp["Authentication"]["Type"] == "no-password-required" @@ -221,14 +236,14 @@ def test_create_user_with_no_password_required_and_authmode_nopassword(): resp = client.create_user( UserId=user_id, UserName="User1", - Engine="Redis", + Engine="redis", AccessString="on ~* +@all", NoPasswordRequired=True, AuthenticationMode={"Type": "no-password-required"}, ) assert resp["Status"] == "active" - assert resp["Engine"] == "Redis" + assert resp["Engine"] == "redis" assert resp["AccessString"] == "on ~* +@all" assert resp["UserGroupIds"] == [] assert resp["Authentication"]["Type"] == "no-password" @@ -245,7 +260,7 @@ def test_create_user_with_no_password_required_and_authmode_different(): client.create_user( UserId="user1", UserName="user1", - Engine="Redis", + Engine="redis", AccessString="on ~* +@all", NoPasswordRequired=True, AuthenticationMode={"Type": auth_mode}, @@ -266,7 +281,7 @@ def test_create_user_with_authmode_password(): resp = client.create_user( UserId=user_id, UserName="User1", - Engine="Redis", + Engine="redis", AccessString="on ~* +@all", AuthenticationMode={ "Type": "password", @@ -275,7 +290,7 @@ def test_create_user_with_authmode_password(): ) assert resp["Status"] == "active" - assert resp["Engine"] == "Redis" + assert resp["Engine"] == "redis" assert resp["AccessString"] == "on ~* +@all" assert resp["UserGroupIds"] == [] assert resp["Authentication"]["Type"] == "password" @@ -290,7 +305,7 @@ def test_create_user_with_authmode_password_multiple(): resp = client.create_user( UserId=user_id, UserName="User1", - Engine="Redis", + Engine="redis", AccessString="on ~* +@all", AuthenticationMode={ "Type": "password", @@ -299,7 +314,7 @@ def test_create_user_with_authmode_password_multiple(): ) assert resp["Status"] == "active" - assert resp["Engine"] == "Redis" + assert resp["Engine"] == "redis" assert resp["AccessString"] == "on ~* +@all" assert resp["UserGroupIds"] == [] assert resp["Authentication"]["Type"] == "password" @@ -313,7 +328,7 @@ def test_create_user_twice(): client.create_user( UserId=user_id, UserName="User1", - Engine="Redis", + Engine="redis", AccessString="on ~* +@all", Passwords=["mysecretpassthatsverylong"], ) @@ -322,7 +337,7 @@ def test_create_user_twice(): client.create_user( UserId=user_id, UserName="User1", - Engine="Redis", + Engine="redis", AccessString="on ~* +@all", Passwords=["mysecretpassthatsverylong"], ) @@ -348,7 +363,7 @@ def test_delete_user(): client.create_user( UserId="user1", UserName="User1", - Engine="Redis", + Engine="redis", AccessString="on ~* +@all", Passwords=["mysecretpassthatsverylong"], ) @@ -391,7 +406,7 @@ def test_describe_users(): client.create_user( UserId="user1", UserName="User1", - Engine="Redis", + Engine="redis", AccessString="on ~* +@all", Passwords=["mysecretpassthatsverylong"], ) @@ -403,7 +418,7 @@ def test_describe_users(): "UserId": "user1", "UserName": "User1", "Status": "active", - "Engine": "Redis", + "Engine": "redis", "MinimumEngineVersion": "6.0", "AccessString": "on ~* +@all", "UserGroupIds": [], From 4cf4176755eadc46f3cd9eb5f4e6c0f67b2e86bb Mon Sep 17 00:00:00 2001 From: armichaud Date: Fri, 31 Jan 2025 10:30:59 -0500 Subject: [PATCH 02/11] Reformat --- moto/elasticache/responses.py | 2 +- moto/elasticache/utils.py | 2 ++ tests/test_elasticache/test_elasticache.py | 7 ++++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/moto/elasticache/responses.py b/moto/elasticache/responses.py index f177fb9fcfa4..62868cc1bac6 100644 --- a/moto/elasticache/responses.py +++ b/moto/elasticache/responses.py @@ -39,7 +39,7 @@ def create_user(self) -> str: if passwords: authentication_type = AuthenticationTypes.PASSWORD.value - + if engine: engine_types = [e.value for e in EngineTypes] if engine not in engine_types: diff --git a/moto/elasticache/utils.py b/moto/elasticache/utils.py index 011f26f7bdea..fe1a0dc8fe52 100644 --- a/moto/elasticache/utils.py +++ b/moto/elasticache/utils.py @@ -15,10 +15,12 @@ class AuthenticationTypes(str, Enum): PASSWORD = "password" IAM = "iam" + class EngineTypes(str, Enum): REDIS = "redis" VALKEY = "valkey" + class ValidAuthModeKeys(str, Enum): TYPE = "Type" PASSWORD = "Passwords" diff --git a/tests/test_elasticache/test_elasticache.py b/tests/test_elasticache/test_elasticache.py index 23fff8a5227b..e646c24fde07 100644 --- a/tests/test_elasticache/test_elasticache.py +++ b/tests/test_elasticache/test_elasticache.py @@ -51,6 +51,7 @@ def test_create_user_with_password_too_short(): assert err["Code"] == "InvalidParameterValue" assert err["Message"] == "Passwords length must be between 16-128 characters." + @mock_aws def test_create_user_with_wrong_engine_type(): client = boto3.client("elasticache", region_name="ap-southeast-1") @@ -65,7 +66,11 @@ def test_create_user_with_wrong_engine_type(): ) err = exc.value.response["Error"] assert err["Code"] == "InvalidParameterValue" - assert err["Message"] == "Unknown parameter for Engine: invalidengine, must be one of: redis, valkey" + assert ( + err["Message"] + == "Unknown parameter for Engine: invalidengine, must be one of: redis, valkey" + ) + @mock_aws def test_create_user_with_password(): From f08c559afcbce6ca6a9cfd5c4148469f435ce77f Mon Sep 17 00:00:00 2001 From: armichaud Date: Fri, 31 Jan 2025 10:39:23 -0500 Subject: [PATCH 03/11] Syntax --- moto/elasticache/responses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moto/elasticache/responses.py b/moto/elasticache/responses.py index 62868cc1bac6..7fd259c00902 100644 --- a/moto/elasticache/responses.py +++ b/moto/elasticache/responses.py @@ -44,7 +44,7 @@ def create_user(self) -> str: engine_types = [e.value for e in EngineTypes] if engine not in engine_types: raise InvalidParameterValueException( - f'Unknown parameter for Engine: "{engine}", must be one of: {', '.join(engine_types)}' + f'Unknown parameter for Engine: "{engine}", must be one of: {", ".join(engine_types)}' ) if authentication_mode: From 09ce68190b952ce3010c6f519ef567c3557e8c3f Mon Sep 17 00:00:00 2001 From: armichaud Date: Fri, 31 Jan 2025 10:46:23 -0500 Subject: [PATCH 04/11] double quotes --- moto/elasticache/responses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moto/elasticache/responses.py b/moto/elasticache/responses.py index 7fd259c00902..d5079e51f3da 100644 --- a/moto/elasticache/responses.py +++ b/moto/elasticache/responses.py @@ -52,7 +52,7 @@ def create_user(self) -> str: valid_keys = [e.value for e in ValidAuthModeKeys] if key not in valid_keys: raise InvalidParameterValueException( - f'Unknown parameter in AuthenticationMode: "{key}", must be one of: {', '.join(valid_keys)}' + f'Unknown parameter in AuthenticationMode: "{key}", must be one of: {", ".join(valid_keys)}' ) authentication_type = authentication_mode.get("Type") From 0ee28c3b5addd80b0f75132af195e2e4327ff5dd Mon Sep 17 00:00:00 2001 From: armichaud Date: Fri, 31 Jan 2025 11:06:04 -0500 Subject: [PATCH 05/11] fix test --- tests/test_elasticache/test_elasticache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_elasticache/test_elasticache.py b/tests/test_elasticache/test_elasticache.py index e646c24fde07..05e23d5706ec 100644 --- a/tests/test_elasticache/test_elasticache.py +++ b/tests/test_elasticache/test_elasticache.py @@ -68,7 +68,7 @@ def test_create_user_with_wrong_engine_type(): assert err["Code"] == "InvalidParameterValue" assert ( err["Message"] - == "Unknown parameter for Engine: invalidengine, must be one of: redis, valkey" + == 'Unknown parameter for Engine: "invalidengine", must be one of: redis, valkey' ) From 53b44cd88eb7503342258da35f0802306ade99ad Mon Sep 17 00:00:00 2001 From: armichaud Date: Sat, 1 Feb 2025 09:48:16 -0500 Subject: [PATCH 06/11] feedback from bpandola --- moto/elasticache/responses.py | 19 +++--- moto/elasticache/utils.py | 10 +-- tests/test_elasticache/test_elasticache.py | 72 ++++++++++------------ 3 files changed, 45 insertions(+), 56 deletions(-) diff --git a/moto/elasticache/responses.py b/moto/elasticache/responses.py index d5079e51f3da..07cef098ef56 100644 --- a/moto/elasticache/responses.py +++ b/moto/elasticache/responses.py @@ -6,7 +6,7 @@ PasswordTooShort, ) from .models import ElastiCacheBackend, elasticache_backends -from .utils import AuthenticationTypes, EngineTypes, ValidAuthModeKeys +from .utils import AuthenticationTypes, VALID_AUTH_MODE_KEYS, VALID_ENGINE_TYPES class ElastiCacheResponse(BaseResponse): @@ -40,19 +40,18 @@ def create_user(self) -> str: if passwords: authentication_type = AuthenticationTypes.PASSWORD.value - if engine: - engine_types = [e.value for e in EngineTypes] - if engine not in engine_types: - raise InvalidParameterValueException( - f'Unknown parameter for Engine: "{engine}", must be one of: {", ".join(engine_types)}' - ) + if engine and engine not in VALID_ENGINE_TYPES: + raise InvalidParameterValueException( + f'Unknown parameter for Engine: "{engine}", must be one of: {", ".join(VALID_ENGINE_TYPES)}' + ) + if engine == "Redis": + engine = "redis" if authentication_mode: for key in authentication_mode.keys(): - valid_keys = [e.value for e in ValidAuthModeKeys] - if key not in valid_keys: + if key not in VALID_AUTH_MODE_KEYS: raise InvalidParameterValueException( - f'Unknown parameter in AuthenticationMode: "{key}", must be one of: {", ".join(valid_keys)}' + f'Unknown parameter in AuthenticationMode: "{key}", must be one of: {", ".join(VALID_AUTH_MODE_KEYS)}' ) authentication_type = authentication_mode.get("Type") diff --git a/moto/elasticache/utils.py b/moto/elasticache/utils.py index fe1a0dc8fe52..229f8cd6757a 100644 --- a/moto/elasticache/utils.py +++ b/moto/elasticache/utils.py @@ -16,11 +16,5 @@ class AuthenticationTypes(str, Enum): IAM = "iam" -class EngineTypes(str, Enum): - REDIS = "redis" - VALKEY = "valkey" - - -class ValidAuthModeKeys(str, Enum): - TYPE = "Type" - PASSWORD = "Passwords" +VALID_ENGINE_TYPES = ["Redis", "redis", "valkey"] +VALID_AUTH_MODE_KEYS = ["Type", "Passwords"] diff --git a/tests/test_elasticache/test_elasticache.py b/tests/test_elasticache/test_elasticache.py index 05e23d5706ec..7b350f45c1cb 100644 --- a/tests/test_elasticache/test_elasticache.py +++ b/tests/test_elasticache/test_elasticache.py @@ -16,7 +16,7 @@ def test_create_user_no_password_required(): resp = client.create_user( UserId=user_id, UserName="User1", - Engine="redis", + Engine="Redis", AccessString="on ~* +@all", NoPasswordRequired=True, ) @@ -43,7 +43,7 @@ def test_create_user_with_password_too_short(): client.create_user( UserId=user_id, UserName="User1", - Engine="redis", + Engine="Redis", AccessString="on ~* +@all", Passwords=["mysecretpass"], ) @@ -52,26 +52,6 @@ def test_create_user_with_password_too_short(): assert err["Message"] == "Passwords length must be between 16-128 characters." -@mock_aws -def test_create_user_with_wrong_engine_type(): - client = boto3.client("elasticache", region_name="ap-southeast-1") - user_id = "user1" - with pytest.raises(ClientError) as exc: - client.create_user( - UserId=user_id, - UserName="User1", - Engine="invalidengine", - AccessString="on ~* +@all", - Passwords=["mysecretpassthatsverylong"], - ) - err = exc.value.response["Error"] - assert err["Code"] == "InvalidParameterValue" - assert ( - err["Message"] - == 'Unknown parameter for Engine: "invalidengine", must be one of: redis, valkey' - ) - - @mock_aws def test_create_user_with_password(): client = boto3.client("elasticache", region_name="ap-southeast-1") @@ -79,7 +59,7 @@ def test_create_user_with_password(): resp = client.create_user( UserId=user_id, UserName="User1", - Engine="redis", + Engine="Redis", AccessString="on ~* +@all", Passwords=["mysecretpassthatsverylong"], ) @@ -103,7 +83,7 @@ def test_create_user_without_password(): client = boto3.client("elasticache", region_name="ap-southeast-1") with pytest.raises(ClientError) as exc: client.create_user( - UserId="user1", UserName="User1", Engine="redis", AccessString="?" + UserId="user1", UserName="User1", Engine="Redis", AccessString="?" ) err = exc.value.response["Error"] assert err["Code"] == "InvalidParameterValue" @@ -120,7 +100,7 @@ def test_create_user_with_iam(): resp = client.create_user( UserId=user_id, UserName="User1", - Engine="redis", + Engine="Redis", AccessString="on ~* +@all", AuthenticationMode={"Type": "iam"}, ) @@ -139,7 +119,7 @@ def test_create_user_invalid_authentication_type(): client.create_user( UserId="user1", UserName="User1", - Engine="redis", + Engine="Redis", AccessString="?", AuthenticationMode={"Type": "invalidtype"}, ) @@ -159,7 +139,7 @@ def test_create_user_with_iam_with_passwords(): client.create_user( UserId="user1", UserName="user1", - Engine="redis", + Engine="Redis", AccessString="?", AuthenticationMode={"Type": "iam"}, Passwords=["mysecretpassthatsverylong"], @@ -178,7 +158,7 @@ def test_create_user_authmode_password_with_multiple_password_fields(): client.create_user( UserId="user1", UserName="user1", - Engine="redis", + Engine="Redis", AccessString="on ~* +@all", AuthenticationMode={"Type": "password", "Passwords": ["authmodepassword"]}, Passwords=["requestpassword"], @@ -199,7 +179,7 @@ def test_create_user_with_authmode_password_without_passwords(): client.create_user( UserId="user1", UserName="user1", - Engine="redis", + Engine="Redis", AccessString="?", AuthenticationMode={"Type": "password"}, ) @@ -219,7 +199,7 @@ def test_create_user_with_authmode_no_password(): resp = client.create_user( UserId=user_id, UserName="User1", - Engine="redis", + Engine="Redis", AccessString="on ~* +@all", AuthenticationMode={"Type": "no-password-required"}, ) @@ -241,7 +221,7 @@ def test_create_user_with_no_password_required_and_authmode_nopassword(): resp = client.create_user( UserId=user_id, UserName="User1", - Engine="redis", + Engine="Redis", AccessString="on ~* +@all", NoPasswordRequired=True, AuthenticationMode={"Type": "no-password-required"}, @@ -265,7 +245,7 @@ def test_create_user_with_no_password_required_and_authmode_different(): client.create_user( UserId="user1", UserName="user1", - Engine="redis", + Engine="Redis", AccessString="on ~* +@all", NoPasswordRequired=True, AuthenticationMode={"Type": auth_mode}, @@ -286,7 +266,7 @@ def test_create_user_with_authmode_password(): resp = client.create_user( UserId=user_id, UserName="User1", - Engine="redis", + Engine="Redis", AccessString="on ~* +@all", AuthenticationMode={ "Type": "password", @@ -310,7 +290,7 @@ def test_create_user_with_authmode_password_multiple(): resp = client.create_user( UserId=user_id, UserName="User1", - Engine="redis", + Engine="Redis", AccessString="on ~* +@all", AuthenticationMode={ "Type": "password", @@ -333,7 +313,7 @@ def test_create_user_twice(): client.create_user( UserId=user_id, UserName="User1", - Engine="redis", + Engine="Redis", AccessString="on ~* +@all", Passwords=["mysecretpassthatsverylong"], ) @@ -342,7 +322,7 @@ def test_create_user_twice(): client.create_user( UserId=user_id, UserName="User1", - Engine="redis", + Engine="Redis", AccessString="on ~* +@all", Passwords=["mysecretpassthatsverylong"], ) @@ -368,7 +348,7 @@ def test_delete_user(): client.create_user( UserId="user1", UserName="User1", - Engine="redis", + Engine="Redis", AccessString="on ~* +@all", Passwords=["mysecretpassthatsverylong"], ) @@ -411,7 +391,7 @@ def test_describe_users(): client.create_user( UserId="user1", UserName="User1", - Engine="redis", + Engine="Redis", AccessString="on ~* +@all", Passwords=["mysecretpassthatsverylong"], ) @@ -432,6 +412,22 @@ def test_describe_users(): } in resp["Users"] +@mock_aws +def test_create_user_with_wrong_engine_type(): + client = boto3.client("elasticache", region_name="ap-southeast-1") + user_id = "user1" + with pytest.raises(ClientError) as exc: + client.create_user( + UserId=user_id, + UserName="User1", + Engine="invalidengine", + AccessString="on ~* +@all", + Passwords=["mysecretpassthatsverylong"], + ) + err = exc.value.response["Error"] + assert err["Code"] == "InvalidParameterValue" + + @mock_aws def test_describe_users_unknown_userid(): client = boto3.client("elasticache", region_name="ap-southeast-1") From 797327ca1120c352d59f9957770ea9dc881e314a Mon Sep 17 00:00:00 2001 From: armichaud Date: Sat, 1 Feb 2025 10:08:41 -0500 Subject: [PATCH 07/11] lint --- moto/elasticache/responses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moto/elasticache/responses.py b/moto/elasticache/responses.py index 07cef098ef56..d718da22478d 100644 --- a/moto/elasticache/responses.py +++ b/moto/elasticache/responses.py @@ -6,7 +6,7 @@ PasswordTooShort, ) from .models import ElastiCacheBackend, elasticache_backends -from .utils import AuthenticationTypes, VALID_AUTH_MODE_KEYS, VALID_ENGINE_TYPES +from .utils import VALID_AUTH_MODE_KEYS, VALID_ENGINE_TYPES, AuthenticationTypes class ElastiCacheResponse(BaseResponse): From 2ddf78a7ca4d4fb1d3cdb173eda92824af4d45d1 Mon Sep 17 00:00:00 2001 From: Brian Pandola Date: Sat, 1 Feb 2025 13:33:38 -0800 Subject: [PATCH 08/11] A bit of clean up to get this PR over the finish line --- moto/elasticache/responses.py | 6 +-- moto/elasticache/utils.py | 6 +-- tests/test_elasticache/test_elasticache.py | 47 ++++++++++++++-------- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/moto/elasticache/responses.py b/moto/elasticache/responses.py index d718da22478d..d1cc71f48d2a 100644 --- a/moto/elasticache/responses.py +++ b/moto/elasticache/responses.py @@ -24,7 +24,7 @@ def create_user(self) -> str: params = self._get_params() user_id = params.get("UserId") user_name = params.get("UserName") - engine = params.get("Engine") + engine = params.get("Engine", "").lower() passwords = params.get("Passwords", []) no_password_required = self._get_bool_param("NoPasswordRequired") authentication_mode = params.get("AuthenticationMode") @@ -40,12 +40,10 @@ def create_user(self) -> str: if passwords: authentication_type = AuthenticationTypes.PASSWORD.value - if engine and engine not in VALID_ENGINE_TYPES: + if engine not in VALID_ENGINE_TYPES: raise InvalidParameterValueException( f'Unknown parameter for Engine: "{engine}", must be one of: {", ".join(VALID_ENGINE_TYPES)}' ) - if engine == "Redis": - engine = "redis" if authentication_mode: for key in authentication_mode.keys(): diff --git a/moto/elasticache/utils.py b/moto/elasticache/utils.py index 229f8cd6757a..e006361cd773 100644 --- a/moto/elasticache/utils.py +++ b/moto/elasticache/utils.py @@ -8,13 +8,11 @@ "unique_attribute": "cache_cluster_id", }, } +VALID_AUTH_MODE_KEYS = ["Type", "Passwords"] +VALID_ENGINE_TYPES = ["redis", "valkey"] class AuthenticationTypes(str, Enum): NOPASSWORD = "no-password-required" PASSWORD = "password" IAM = "iam" - - -VALID_ENGINE_TYPES = ["Redis", "redis", "valkey"] -VALID_AUTH_MODE_KEYS = ["Type", "Passwords"] diff --git a/tests/test_elasticache/test_elasticache.py b/tests/test_elasticache/test_elasticache.py index 7b350f45c1cb..e2a065478550 100644 --- a/tests/test_elasticache/test_elasticache.py +++ b/tests/test_elasticache/test_elasticache.py @@ -331,6 +331,37 @@ def test_create_user_twice(): assert err["Message"] == "User user1 already exists." +@mock_aws +@pytest.mark.parametrize("engine", ["redis", "Redis", "reDis"]) +def test_create_user_engine_parameter_is_case_insensitive(engine): + client = boto3.client("elasticache", region_name="us-east-1") + user_id = "user1" + resp = client.create_user( + UserId=user_id, + UserName="User1", + Engine=engine, + AccessString="on ~* +@all", + AuthenticationMode={"Type": "iam"}, + ) + assert resp["Engine"] == engine.lower() + + +@mock_aws +def test_create_user_with_invalid_engine_type(): + client = boto3.client("elasticache", region_name="ap-southeast-1") + user_id = "user1" + with pytest.raises(ClientError) as exc: + client.create_user( + UserId=user_id, + UserName="User1", + Engine="invalidengine", + AccessString="on ~* +@all", + Passwords=["mysecretpassthatsverylong"], + ) + err = exc.value.response["Error"] + assert err["Code"] == "InvalidParameterValue" + + @mock_aws def test_delete_user_unknown(): client = boto3.client("elasticache", region_name="ap-southeast-1") @@ -412,22 +443,6 @@ def test_describe_users(): } in resp["Users"] -@mock_aws -def test_create_user_with_wrong_engine_type(): - client = boto3.client("elasticache", region_name="ap-southeast-1") - user_id = "user1" - with pytest.raises(ClientError) as exc: - client.create_user( - UserId=user_id, - UserName="User1", - Engine="invalidengine", - AccessString="on ~* +@all", - Passwords=["mysecretpassthatsverylong"], - ) - err = exc.value.response["Error"] - assert err["Code"] == "InvalidParameterValue" - - @mock_aws def test_describe_users_unknown_userid(): client = boto3.client("elasticache", region_name="ap-southeast-1") From cd9ad87b21e3323a17ad8509949de73fe90b4551 Mon Sep 17 00:00:00 2001 From: Brian Pandola Date: Sat, 1 Feb 2025 13:36:36 -0800 Subject: [PATCH 09/11] Revert last Redis/redis --- tests/test_elasticache/test_elasticache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_elasticache/test_elasticache.py b/tests/test_elasticache/test_elasticache.py index e2a065478550..f5fad996a225 100644 --- a/tests/test_elasticache/test_elasticache.py +++ b/tests/test_elasticache/test_elasticache.py @@ -406,7 +406,7 @@ def test_describe_users_initial(): "UserId": "default", "UserName": "default", "Status": "active", - "Engine": "redis", + "Engine": "Redis", "MinimumEngineVersion": "6.0", "AccessString": "on ~* +@all", "UserGroupIds": [], From bfa5a33c51a02d1a89dccc054a309c270df18701 Mon Sep 17 00:00:00 2001 From: Brian Pandola Date: Sat, 1 Feb 2025 13:42:34 -0800 Subject: [PATCH 10/11] Revert unnecessary Redis/redis changes --- tests/test_elasticache/test_elasticache.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_elasticache/test_elasticache.py b/tests/test_elasticache/test_elasticache.py index f5fad996a225..279804f72565 100644 --- a/tests/test_elasticache/test_elasticache.py +++ b/tests/test_elasticache/test_elasticache.py @@ -406,7 +406,7 @@ def test_describe_users_initial(): "UserId": "default", "UserName": "default", "Status": "active", - "Engine": "Redis", + "Engine": "redis", "MinimumEngineVersion": "6.0", "AccessString": "on ~* +@all", "UserGroupIds": [], @@ -434,7 +434,7 @@ def test_describe_users(): "UserId": "user1", "UserName": "User1", "Status": "active", - "Engine": "redis", + "Engine": "Redis", "MinimumEngineVersion": "6.0", "AccessString": "on ~* +@all", "UserGroupIds": [], From 758767f31a85967fc5debefdd13ff7d9bfd52660 Mon Sep 17 00:00:00 2001 From: Brian Pandola Date: Sat, 1 Feb 2025 14:53:33 -0800 Subject: [PATCH 11/11] Fix case --- tests/test_elasticache/test_elasticache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_elasticache/test_elasticache.py b/tests/test_elasticache/test_elasticache.py index 279804f72565..e2a065478550 100644 --- a/tests/test_elasticache/test_elasticache.py +++ b/tests/test_elasticache/test_elasticache.py @@ -434,7 +434,7 @@ def test_describe_users(): "UserId": "user1", "UserName": "User1", "Status": "active", - "Engine": "Redis", + "Engine": "redis", "MinimumEngineVersion": "6.0", "AccessString": "on ~* +@all", "UserGroupIds": [],