SNS: Improve PlatformApplication error handling (#6953)
This commit is contained in:
parent
9c0724e0d0
commit
8fb053fb05
@ -31,7 +31,7 @@ class DuplicateSnsEndpointError(SNSException):
|
||||
code = 400
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__("DuplicateEndpoint", message)
|
||||
super().__init__("InvalidParameter", message)
|
||||
|
||||
|
||||
class SnsEndpointDisabled(SNSException):
|
||||
|
@ -364,7 +364,7 @@ class PlatformEndpoint(BaseModel):
|
||||
|
||||
def publish(self, message: str) -> str:
|
||||
if not self.enabled:
|
||||
raise SnsEndpointDisabled(f"Endpoint {self.id} disabled")
|
||||
raise SnsEndpointDisabled("Endpoint is disabled")
|
||||
|
||||
# This is where we would actually send a message
|
||||
message_id = str(mock_random.uuid4())
|
||||
@ -646,7 +646,7 @@ class SNSBackend(BaseBackend):
|
||||
try:
|
||||
return self.applications[arn]
|
||||
except KeyError:
|
||||
raise SNSNotFoundError(f"Application with arn {arn} not found")
|
||||
raise SNSNotFoundError("PlatformApplication does not exist")
|
||||
|
||||
def set_platform_application_attributes(
|
||||
self, arn: str, attributes: Dict[str, Any]
|
||||
@ -673,13 +673,16 @@ class SNSBackend(BaseBackend):
|
||||
) -> PlatformEndpoint:
|
||||
for endpoint in self.platform_endpoints.values():
|
||||
if token == endpoint.token:
|
||||
if (
|
||||
same_user_data = custom_user_data == endpoint.custom_user_data
|
||||
same_attrs = (
|
||||
attributes.get("Enabled", "").lower()
|
||||
== endpoint.attributes["Enabled"]
|
||||
):
|
||||
)
|
||||
|
||||
if same_user_data and same_attrs:
|
||||
return endpoint
|
||||
raise DuplicateSnsEndpointError(
|
||||
f"Duplicate endpoint token with different attributes: {token}"
|
||||
f"Invalid parameter: Token Reason: Endpoint {endpoint.arn} already exists with the same Token, but different attributes."
|
||||
)
|
||||
platform_endpoint = PlatformEndpoint(
|
||||
self.account_id,
|
||||
|
@ -558,6 +558,7 @@ class SNSResponse(BaseResponse):
|
||||
|
||||
def list_endpoints_by_platform_application(self) -> str:
|
||||
application_arn = self._get_param("PlatformApplicationArn")
|
||||
self.backend.get_application(application_arn)
|
||||
endpoints = self.backend.list_endpoints_by_platform_application(application_arn)
|
||||
|
||||
if self.request_json:
|
||||
|
@ -1 +1,48 @@
|
||||
# This file is intentionally left blank.
|
||||
import boto3
|
||||
import botocore
|
||||
import os
|
||||
from functools import wraps
|
||||
from moto import mock_sns, mock_sts
|
||||
from unittest import SkipTest
|
||||
|
||||
|
||||
def sns_aws_verified(func):
|
||||
"""
|
||||
Function that is verified to work against AWS.
|
||||
Can be run against AWS at any time by setting:
|
||||
MOTO_TEST_ALLOW_AWS_REQUEST=true
|
||||
|
||||
If this environment variable is not set, the function runs in a `mock_ses` context.
|
||||
"""
|
||||
|
||||
@wraps(func)
|
||||
def pagination_wrapper():
|
||||
allow_aws_request = (
|
||||
os.environ.get("MOTO_TEST_ALLOW_AWS_REQUEST", "false").lower() == "true"
|
||||
)
|
||||
|
||||
if allow_aws_request:
|
||||
ssm = boto3.client("ssm", "us-east-1")
|
||||
try:
|
||||
param = ssm.get_parameter(
|
||||
Name="/moto/tests/ses/firebase_api_key", WithDecryption=True
|
||||
)
|
||||
api_key = param["Parameter"]["Value"]
|
||||
resp = func(api_key)
|
||||
except botocore.exceptions.ClientError:
|
||||
# SNS tests try to create a PlatformApplication that connects to GCM
|
||||
# (Google Cloud Messaging, also known as Firebase Messaging)
|
||||
# That requires an account and an API key
|
||||
# If the API key has not been configured in SSM, we'll just skip the test
|
||||
#
|
||||
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sns/client/create_platform_application.html
|
||||
# AWS calls it 'API key', but Firebase calls it Server Key
|
||||
#
|
||||
# https://stackoverflow.com/a/75896532/13245310
|
||||
raise SkipTest("Can't execute SNS tests without Firebase API key")
|
||||
else:
|
||||
with mock_sns(), mock_sts():
|
||||
resp = func("mock_api_key")
|
||||
return resp
|
||||
|
||||
return pagination_wrapper
|
||||
|
@ -1,10 +1,13 @@
|
||||
import boto3
|
||||
from botocore.exceptions import ClientError
|
||||
import pytest
|
||||
from botocore.exceptions import ClientError
|
||||
from uuid import uuid4
|
||||
|
||||
from moto import mock_sns
|
||||
from moto.core import DEFAULT_ACCOUNT_ID as ACCOUNT_ID
|
||||
|
||||
from . import sns_aws_verified
|
||||
|
||||
|
||||
@mock_sns
|
||||
def test_create_platform_application():
|
||||
@ -133,69 +136,124 @@ def test_create_platform_endpoint():
|
||||
)
|
||||
|
||||
|
||||
@mock_sns
|
||||
def test_create_duplicate_platform_endpoint():
|
||||
@pytest.mark.aws_verified
|
||||
@sns_aws_verified
|
||||
def test_create_duplicate_platform_endpoint(api_key=None):
|
||||
identity = boto3.client("sts", region_name="us-east-1").get_caller_identity()
|
||||
account_id = identity["Account"]
|
||||
|
||||
conn = boto3.client("sns", region_name="us-east-1")
|
||||
platform_name = str(uuid4())[0:6]
|
||||
application_arn = None
|
||||
try:
|
||||
platform_application = conn.create_platform_application(
|
||||
Name="my-application", Platform="APNS", Attributes={}
|
||||
Name=platform_name,
|
||||
Platform="GCM",
|
||||
Attributes={"PlatformCredential": api_key},
|
||||
)
|
||||
application_arn = platform_application["PlatformApplicationArn"]
|
||||
assert (
|
||||
application_arn
|
||||
== f"arn:aws:sns:us-east-1:{account_id}:app/GCM/{platform_name}"
|
||||
)
|
||||
|
||||
# WITHOUT custom user data
|
||||
resp = conn.create_platform_endpoint(
|
||||
PlatformApplicationArn=application_arn,
|
||||
Token="token_without_userdata",
|
||||
Attributes={"Enabled": "false"},
|
||||
)
|
||||
endpoint1_arn = resp["EndpointArn"]
|
||||
|
||||
# This operation is idempotent
|
||||
resp = conn.create_platform_endpoint(
|
||||
PlatformApplicationArn=application_arn,
|
||||
Token="token_without_userdata",
|
||||
Attributes={"Enabled": "false"},
|
||||
)
|
||||
assert resp["EndpointArn"] == endpoint1_arn
|
||||
|
||||
# Same token, but with CustomUserData
|
||||
with pytest.raises(ClientError) as exc:
|
||||
conn.create_platform_endpoint(
|
||||
PlatformApplicationArn=application_arn,
|
||||
Token="some_unique_id",
|
||||
Token="token_without_userdata",
|
||||
CustomUserData="some user data",
|
||||
Attributes={"Enabled": "false"},
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"] == "InvalidParameter"
|
||||
assert (
|
||||
err["Message"]
|
||||
== f"Invalid parameter: Token Reason: Endpoint {endpoint1_arn} already exists with the same Token, but different attributes."
|
||||
)
|
||||
|
||||
with pytest.raises(ClientError):
|
||||
# WITH custom user data
|
||||
resp = conn.create_platform_endpoint(
|
||||
PlatformApplicationArn=application_arn,
|
||||
Token="token_with_userdata",
|
||||
CustomUserData="some user data",
|
||||
Attributes={"Enabled": "false"},
|
||||
)
|
||||
endpoint2_arn = resp["EndpointArn"]
|
||||
assert endpoint2_arn.startswith(
|
||||
f"arn:aws:sns:us-east-1:{account_id}:endpoint/GCM/{platform_name}/"
|
||||
)
|
||||
|
||||
# Still idempotent
|
||||
resp = conn.create_platform_endpoint(
|
||||
PlatformApplicationArn=application_arn,
|
||||
Token="token_with_userdata",
|
||||
CustomUserData="some user data",
|
||||
Attributes={"Enabled": "false"},
|
||||
)
|
||||
assert resp["EndpointArn"] == endpoint2_arn
|
||||
|
||||
# Creating a platform endpoint with different attributes fails
|
||||
with pytest.raises(ClientError) as exc:
|
||||
conn.create_platform_endpoint(
|
||||
PlatformApplicationArn=application_arn,
|
||||
Token="some_unique_id",
|
||||
Token="token_with_userdata",
|
||||
CustomUserData="some user data",
|
||||
Attributes={"Enabled": "true"},
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"] == "InvalidParameter"
|
||||
assert (
|
||||
err["Message"]
|
||||
== f"Invalid parameter: Token Reason: Endpoint {endpoint2_arn} already exists with the same Token, but different attributes."
|
||||
)
|
||||
|
||||
with pytest.raises(ClientError) as exc:
|
||||
conn.create_platform_endpoint(
|
||||
PlatformApplicationArn=application_arn + "2",
|
||||
Token="unknown_arn",
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"] == "NotFound"
|
||||
assert err["Message"] == "PlatformApplication does not exist"
|
||||
finally:
|
||||
if application_arn is not None:
|
||||
conn.delete_platform_application(PlatformApplicationArn=application_arn)
|
||||
|
||||
|
||||
@mock_sns
|
||||
def test_create_duplicate_platform_endpoint_with_same_attributes():
|
||||
@pytest.mark.aws_verified
|
||||
@sns_aws_verified
|
||||
def test_get_list_endpoints_by_platform_application(api_key=None):
|
||||
conn = boto3.client("sns", region_name="us-east-1")
|
||||
platform_name = str(uuid4())[0:6]
|
||||
application_arn = None
|
||||
try:
|
||||
platform_application = conn.create_platform_application(
|
||||
Name="my-application", Platform="APNS", Attributes={}
|
||||
)
|
||||
application_arn = platform_application["PlatformApplicationArn"]
|
||||
|
||||
created_endpoint = conn.create_platform_endpoint(
|
||||
PlatformApplicationArn=application_arn,
|
||||
Token="some_unique_id",
|
||||
CustomUserData="some user data",
|
||||
Attributes={"Enabled": "false"},
|
||||
)
|
||||
created_endpoint_arn = created_endpoint["EndpointArn"]
|
||||
|
||||
endpoint = conn.create_platform_endpoint(
|
||||
PlatformApplicationArn=application_arn,
|
||||
Token="some_unique_id",
|
||||
CustomUserData="some user data",
|
||||
Attributes={"Enabled": "false"},
|
||||
)
|
||||
endpoint_arn = endpoint["EndpointArn"]
|
||||
|
||||
assert endpoint_arn == created_endpoint_arn
|
||||
|
||||
|
||||
@mock_sns
|
||||
def test_get_list_endpoints_by_platform_application():
|
||||
conn = boto3.client("sns", region_name="us-east-1")
|
||||
platform_application = conn.create_platform_application(
|
||||
Name="my-application", Platform="APNS", Attributes={}
|
||||
Name=platform_name,
|
||||
Platform="GCM",
|
||||
Attributes={"PlatformCredential": api_key},
|
||||
)
|
||||
application_arn = platform_application["PlatformApplicationArn"]
|
||||
|
||||
endpoint = conn.create_platform_endpoint(
|
||||
PlatformApplicationArn=application_arn,
|
||||
Token="some_unique_id",
|
||||
CustomUserData="some user data",
|
||||
Attributes={"CustomUserData": "some data"},
|
||||
)
|
||||
endpoint_arn = endpoint["EndpointArn"]
|
||||
@ -207,6 +265,17 @@ def test_get_list_endpoints_by_platform_application():
|
||||
assert len(endpoint_list) == 1
|
||||
assert endpoint_list[0]["Attributes"]["CustomUserData"] == "some data"
|
||||
assert endpoint_list[0]["EndpointArn"] == endpoint_arn
|
||||
finally:
|
||||
if application_arn is not None:
|
||||
conn.delete_platform_application(PlatformApplicationArn=application_arn)
|
||||
|
||||
with pytest.raises(ClientError) as exc:
|
||||
conn.list_endpoints_by_platform_application(
|
||||
PlatformApplicationArn=application_arn + "2"
|
||||
)
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"] == "NotFound"
|
||||
assert err["Message"] == "PlatformApplication does not exist"
|
||||
|
||||
|
||||
@mock_sns
|
||||
@ -231,13 +300,16 @@ def test_get_endpoint_attributes():
|
||||
)
|
||||
|
||||
|
||||
@mock_sns
|
||||
def test_get_non_existent_endpoint_attributes():
|
||||
@pytest.mark.aws_verified
|
||||
@sns_aws_verified
|
||||
def test_get_non_existent_endpoint_attributes(
|
||||
api_key=None,
|
||||
): # pylint: disable=unused-argument
|
||||
identity = boto3.client("sts", region_name="us-east-1").get_caller_identity()
|
||||
account_id = identity["Account"]
|
||||
|
||||
conn = boto3.client("sns", region_name="us-east-1")
|
||||
endpoint_arn = (
|
||||
"arn:aws:sns:us-east-1:123456789012:endpoint/APNS/my-application"
|
||||
"/c1f76c42-192a-4e75-b04f-a9268ce2abf3"
|
||||
)
|
||||
endpoint_arn = f"arn:aws:sns:us-east-1:{account_id}:endpoint/APNS/my-application/c1f76c42-192a-4e75-b04f-a9268ce2abf3"
|
||||
with pytest.raises(conn.exceptions.NotFoundException) as excinfo:
|
||||
conn.get_endpoint_attributes(EndpointArn=endpoint_arn)
|
||||
error = excinfo.value.response["Error"]
|
||||
@ -284,33 +356,25 @@ def test_delete_endpoint():
|
||||
platform_application = conn.create_platform_application(
|
||||
Name="my-application", Platform="APNS", Attributes={}
|
||||
)
|
||||
application_arn = platform_application["PlatformApplicationArn"]
|
||||
app_arn = platform_application["PlatformApplicationArn"]
|
||||
endpoint = conn.create_platform_endpoint(
|
||||
PlatformApplicationArn=application_arn,
|
||||
PlatformApplicationArn=app_arn,
|
||||
Token="some_unique_id",
|
||||
CustomUserData="some user data",
|
||||
Attributes={"Enabled": "true"},
|
||||
)
|
||||
|
||||
assert (
|
||||
len(
|
||||
conn.list_endpoints_by_platform_application(
|
||||
PlatformApplicationArn=application_arn
|
||||
endpoints = conn.list_endpoints_by_platform_application(
|
||||
PlatformApplicationArn=app_arn
|
||||
)["Endpoints"]
|
||||
)
|
||||
== 1
|
||||
)
|
||||
assert len(endpoints) == 1
|
||||
|
||||
conn.delete_endpoint(EndpointArn=endpoint["EndpointArn"])
|
||||
|
||||
assert (
|
||||
len(
|
||||
conn.list_endpoints_by_platform_application(
|
||||
PlatformApplicationArn=application_arn
|
||||
endpoints = conn.list_endpoints_by_platform_application(
|
||||
PlatformApplicationArn=app_arn
|
||||
)["Endpoints"]
|
||||
)
|
||||
== 0
|
||||
)
|
||||
assert len(endpoints) == 0
|
||||
|
||||
|
||||
@mock_sns
|
||||
@ -335,27 +399,35 @@ def test_publish_to_platform_endpoint():
|
||||
)
|
||||
|
||||
|
||||
@mock_sns
|
||||
def test_publish_to_disabled_platform_endpoint():
|
||||
@pytest.mark.aws_verified
|
||||
@sns_aws_verified
|
||||
def test_publish_to_disabled_platform_endpoint(api_key=None):
|
||||
conn = boto3.client("sns", region_name="us-east-1")
|
||||
platform_name = str(uuid4())[0:6]
|
||||
application_arn = None
|
||||
try:
|
||||
platform_application = conn.create_platform_application(
|
||||
Name="my-application", Platform="APNS", Attributes={}
|
||||
Name=platform_name,
|
||||
Platform="GCM",
|
||||
Attributes={"PlatformCredential": api_key},
|
||||
)
|
||||
application_arn = platform_application["PlatformApplicationArn"]
|
||||
|
||||
endpoint = conn.create_platform_endpoint(
|
||||
PlatformApplicationArn=application_arn,
|
||||
Token="some_unique_id",
|
||||
CustomUserData="some user data",
|
||||
Attributes={"Enabled": "false"},
|
||||
)
|
||||
|
||||
endpoint_arn = endpoint["EndpointArn"]
|
||||
|
||||
with pytest.raises(ClientError):
|
||||
conn.publish(
|
||||
Message="some message", MessageStructure="json", TargetArn=endpoint_arn
|
||||
)
|
||||
with pytest.raises(ClientError) as exc:
|
||||
conn.publish(Message="msg", MessageStructure="json", TargetArn=endpoint_arn)
|
||||
err = exc.value.response["Error"]
|
||||
assert err["Code"] == "EndpointDisabled"
|
||||
assert err["Message"] == "Endpoint is disabled"
|
||||
finally:
|
||||
if application_arn is not None:
|
||||
conn.delete_platform_application(PlatformApplicationArn=application_arn)
|
||||
|
||||
|
||||
@mock_sns
|
||||
|
Loading…
Reference in New Issue
Block a user