From f6033cddeb121e97eafed28a4f6fe4dfd261d99b Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Mon, 7 Mar 2022 20:37:50 -0100 Subject: [PATCH] Pinpoint - initial implementation (#4911) --- IMPLEMENTATION_COVERAGE.md | 126 +++++++++++++- docs/docs/services/pinpoint.rst | 153 +++++++++++++++++ moto/__init__.py | 1 + moto/awslambda/policy.py | 3 + moto/backend_index.py | 1 + moto/moto_server/werkzeug_app.py | 1 + moto/pinpoint/__init__.py | 5 + moto/pinpoint/exceptions.py | 20 +++ moto/pinpoint/models.py | 155 +++++++++++++++++ moto/pinpoint/responses.py | 161 ++++++++++++++++++ moto/pinpoint/urls.py | 18 ++ tests/terraform-tests.success.txt | 2 + tests/test_awslambda/test_policy.py | 4 +- tests/test_pinpoint/__init__.py | 0 tests/test_pinpoint/test_pinpoint.py | 129 ++++++++++++++ .../test_pinpoint_application_tags.py | 73 ++++++++ .../test_pinpoint_event_stream.py | 74 ++++++++ 17 files changed, 923 insertions(+), 3 deletions(-) create mode 100644 docs/docs/services/pinpoint.rst create mode 100644 moto/pinpoint/__init__.py create mode 100644 moto/pinpoint/exceptions.py create mode 100644 moto/pinpoint/models.py create mode 100644 moto/pinpoint/responses.py create mode 100644 moto/pinpoint/urls.py create mode 100644 tests/test_pinpoint/__init__.py create mode 100644 tests/test_pinpoint/test_pinpoint.py create mode 100644 tests/test_pinpoint/test_pinpoint_application_tags.py create mode 100644 tests/test_pinpoint/test_pinpoint_event_stream.py diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index 8d22d7f4a..e3c71fc0c 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -3839,6 +3839,131 @@ - [X] update_policy +## pinpoint +
+10% implemented + +- [X] create_app +- [ ] create_campaign +- [ ] create_email_template +- [ ] create_export_job +- [ ] create_import_job +- [ ] create_in_app_template +- [ ] create_journey +- [ ] create_push_template +- [ ] create_recommender_configuration +- [ ] create_segment +- [ ] create_sms_template +- [ ] create_voice_template +- [ ] delete_adm_channel +- [ ] delete_apns_channel +- [ ] delete_apns_sandbox_channel +- [ ] delete_apns_voip_channel +- [ ] delete_apns_voip_sandbox_channel +- [X] delete_app +- [ ] delete_baidu_channel +- [ ] delete_campaign +- [ ] delete_email_channel +- [ ] delete_email_template +- [ ] delete_endpoint +- [X] delete_event_stream +- [ ] delete_gcm_channel +- [ ] delete_in_app_template +- [ ] delete_journey +- [ ] delete_push_template +- [ ] delete_recommender_configuration +- [ ] delete_segment +- [ ] delete_sms_channel +- [ ] delete_sms_template +- [ ] delete_user_endpoints +- [ ] delete_voice_channel +- [ ] delete_voice_template +- [ ] get_adm_channel +- [ ] get_apns_channel +- [ ] get_apns_sandbox_channel +- [ ] get_apns_voip_channel +- [ ] get_apns_voip_sandbox_channel +- [X] get_app +- [ ] get_application_date_range_kpi +- [X] get_application_settings +- [X] get_apps +- [ ] get_baidu_channel +- [ ] get_campaign +- [ ] get_campaign_activities +- [ ] get_campaign_date_range_kpi +- [ ] get_campaign_version +- [ ] get_campaign_versions +- [ ] get_campaigns +- [ ] get_channels +- [ ] get_email_channel +- [ ] get_email_template +- [ ] get_endpoint +- [X] get_event_stream +- [ ] get_export_job +- [ ] get_export_jobs +- [ ] get_gcm_channel +- [ ] get_import_job +- [ ] get_import_jobs +- [ ] get_in_app_messages +- [ ] get_in_app_template +- [ ] get_journey +- [ ] get_journey_date_range_kpi +- [ ] get_journey_execution_activity_metrics +- [ ] get_journey_execution_metrics +- [ ] get_push_template +- [ ] get_recommender_configuration +- [ ] get_recommender_configurations +- [ ] get_segment +- [ ] get_segment_export_jobs +- [ ] get_segment_import_jobs +- [ ] get_segment_version +- [ ] get_segment_versions +- [ ] get_segments +- [ ] get_sms_channel +- [ ] get_sms_template +- [ ] get_user_endpoints +- [ ] get_voice_channel +- [ ] get_voice_template +- [ ] list_journeys +- [X] list_tags_for_resource +- [ ] list_template_versions +- [ ] list_templates +- [ ] phone_number_validate +- [X] put_event_stream +- [ ] put_events +- [ ] remove_attributes +- [ ] send_messages +- [ ] send_otp_message +- [ ] send_users_messages +- [X] tag_resource +- [X] untag_resource +- [ ] update_adm_channel +- [ ] update_apns_channel +- [ ] update_apns_sandbox_channel +- [ ] update_apns_voip_channel +- [ ] update_apns_voip_sandbox_channel +- [X] update_application_settings +- [ ] update_baidu_channel +- [ ] update_campaign +- [ ] update_email_channel +- [ ] update_email_template +- [ ] update_endpoint +- [ ] update_endpoints_batch +- [ ] update_gcm_channel +- [ ] update_in_app_template +- [ ] update_journey +- [ ] update_journey_state +- [ ] update_push_template +- [ ] update_recommender_configuration +- [ ] update_segment +- [ ] update_sms_channel +- [ ] update_sms_template +- [ ] update_template_active_version +- [ ] update_voice_channel +- [ ] update_voice_template +- [ ] verify_otp_message +
+ ## polly
55% implemented @@ -5569,7 +5694,6 @@ - personalize-events - personalize-runtime - pi -- pinpoint - pinpoint-email - pinpoint-sms-voice - pricing diff --git a/docs/docs/services/pinpoint.rst b/docs/docs/services/pinpoint.rst new file mode 100644 index 000000000..2671def63 --- /dev/null +++ b/docs/docs/services/pinpoint.rst @@ -0,0 +1,153 @@ +.. _implementedservice_pinpoint: + +.. |start-h3| raw:: html + +

+ +.. |end-h3| raw:: html + +

+ +======== +pinpoint +======== + +.. autoclass:: moto.pinpoint.models.PinpointBackend + +|start-h3| Example usage |end-h3| + +.. sourcecode:: python + + @mock_pinpoint + def test_pinpoint_behaviour: + boto3.client("pinpoint") + ... + + + +|start-h3| Implemented features for this service |end-h3| + +- [X] create_app +- [ ] create_campaign +- [ ] create_email_template +- [ ] create_export_job +- [ ] create_import_job +- [ ] create_in_app_template +- [ ] create_journey +- [ ] create_push_template +- [ ] create_recommender_configuration +- [ ] create_segment +- [ ] create_sms_template +- [ ] create_voice_template +- [ ] delete_adm_channel +- [ ] delete_apns_channel +- [ ] delete_apns_sandbox_channel +- [ ] delete_apns_voip_channel +- [ ] delete_apns_voip_sandbox_channel +- [X] delete_app +- [ ] delete_baidu_channel +- [ ] delete_campaign +- [ ] delete_email_channel +- [ ] delete_email_template +- [ ] delete_endpoint +- [X] delete_event_stream +- [ ] delete_gcm_channel +- [ ] delete_in_app_template +- [ ] delete_journey +- [ ] delete_push_template +- [ ] delete_recommender_configuration +- [ ] delete_segment +- [ ] delete_sms_channel +- [ ] delete_sms_template +- [ ] delete_user_endpoints +- [ ] delete_voice_channel +- [ ] delete_voice_template +- [ ] get_adm_channel +- [ ] get_apns_channel +- [ ] get_apns_sandbox_channel +- [ ] get_apns_voip_channel +- [ ] get_apns_voip_sandbox_channel +- [X] get_app +- [ ] get_application_date_range_kpi +- [X] get_application_settings +- [X] get_apps + + Pagination is not yet implemented + + +- [ ] get_baidu_channel +- [ ] get_campaign +- [ ] get_campaign_activities +- [ ] get_campaign_date_range_kpi +- [ ] get_campaign_version +- [ ] get_campaign_versions +- [ ] get_campaigns +- [ ] get_channels +- [ ] get_email_channel +- [ ] get_email_template +- [ ] get_endpoint +- [X] get_event_stream +- [ ] get_export_job +- [ ] get_export_jobs +- [ ] get_gcm_channel +- [ ] get_import_job +- [ ] get_import_jobs +- [ ] get_in_app_messages +- [ ] get_in_app_template +- [ ] get_journey +- [ ] get_journey_date_range_kpi +- [ ] get_journey_execution_activity_metrics +- [ ] get_journey_execution_metrics +- [ ] get_push_template +- [ ] get_recommender_configuration +- [ ] get_recommender_configurations +- [ ] get_segment +- [ ] get_segment_export_jobs +- [ ] get_segment_import_jobs +- [ ] get_segment_version +- [ ] get_segment_versions +- [ ] get_segments +- [ ] get_sms_channel +- [ ] get_sms_template +- [ ] get_user_endpoints +- [ ] get_voice_channel +- [ ] get_voice_template +- [ ] list_journeys +- [X] list_tags_for_resource +- [ ] list_template_versions +- [ ] list_templates +- [ ] phone_number_validate +- [X] put_event_stream +- [ ] put_events +- [ ] remove_attributes +- [ ] send_messages +- [ ] send_otp_message +- [ ] send_users_messages +- [X] tag_resource +- [X] untag_resource +- [ ] update_adm_channel +- [ ] update_apns_channel +- [ ] update_apns_sandbox_channel +- [ ] update_apns_voip_channel +- [ ] update_apns_voip_sandbox_channel +- [X] update_application_settings +- [ ] update_baidu_channel +- [ ] update_campaign +- [ ] update_email_channel +- [ ] update_email_template +- [ ] update_endpoint +- [ ] update_endpoints_batch +- [ ] update_gcm_channel +- [ ] update_in_app_template +- [ ] update_journey +- [ ] update_journey_state +- [ ] update_push_template +- [ ] update_recommender_configuration +- [ ] update_segment +- [ ] update_sms_channel +- [ ] update_sms_template +- [ ] update_template_active_version +- [ ] update_voice_channel +- [ ] update_voice_template +- [ ] verify_otp_message + diff --git a/moto/__init__.py b/moto/__init__.py index 01b2cda49..2c235b707 100644 --- a/moto/__init__.py +++ b/moto/__init__.py @@ -107,6 +107,7 @@ mock_mediastoredata = lazy_load( mock_mq = lazy_load(".mq", "mock_mq", boto3_name="mq") mock_opsworks = lazy_load(".opsworks", "mock_opsworks") mock_organizations = lazy_load(".organizations", "mock_organizations") +mock_pinpoint = lazy_load(".pinpoint", "mock_pinpoint") mock_polly = lazy_load(".polly", "mock_polly") mock_ram = lazy_load(".ram", "mock_ram") mock_rds = lazy_load(".rds", "mock_rds", warn_repurpose=True) diff --git a/moto/awslambda/policy.py b/moto/awslambda/policy.py index 165b58551..42d169ca6 100644 --- a/moto/awslambda/policy.py +++ b/moto/awslambda/policy.py @@ -37,6 +37,9 @@ class Policy: " for the Lambda function or alias. Call the GetFunction or the GetAlias API to retrieve" " the latest RevisionId for your resource." ) + # Remove #LATEST from the Resource (Lambda ARN) + if policy.statements[0].get("Resource", "").endswith("$LATEST"): + policy.statements[0]["Resource"] = policy.statements[0]["Resource"][0:-8] self.statements.append(policy.statements[0]) self.revision = str(uuid.uuid4()) diff --git a/moto/backend_index.py b/moto/backend_index.py index 5ddfe8b0a..f75d767cd 100644 --- a/moto/backend_index.py +++ b/moto/backend_index.py @@ -95,6 +95,7 @@ backend_url_patterns = [ ("mq", re.compile("https?://mq\\.(.+)\\.amazonaws\\.com")), ("opsworks", re.compile("https?://opsworks\\.us-east-1\\.amazonaws.com")), ("organizations", re.compile("https?://organizations\\.(.+)\\.amazonaws\\.com")), + ("pinpoint", re.compile("https?://pinpoint\\.(.+)\\.amazonaws\\.com")), ("polly", re.compile("https?://polly\\.(.+)\\.amazonaws.com")), ("ram", re.compile("https?://ram\\.(.+)\\.amazonaws.com")), ("rds", re.compile("https?://rds\\.(.+)\\.amazonaws\\.com")), diff --git a/moto/moto_server/werkzeug_app.py b/moto/moto_server/werkzeug_app.py index cbfa64356..51f5f8ea2 100644 --- a/moto/moto_server/werkzeug_app.py +++ b/moto/moto_server/werkzeug_app.py @@ -33,6 +33,7 @@ SIGNING_ALIASES = { "eventbridge": "events", "execute-api": "iot", "iotdata": "data.iot", + "mobiletargeting": "pinpoint", } # Some services are only recognizable by the version diff --git a/moto/pinpoint/__init__.py b/moto/pinpoint/__init__.py new file mode 100644 index 000000000..106031bed --- /dev/null +++ b/moto/pinpoint/__init__.py @@ -0,0 +1,5 @@ +"""pinpoint module initialization; sets value for base decorator.""" +from .models import pinpoint_backends +from ..core.models import base_decorator + +mock_pinpoint = base_decorator(pinpoint_backends) diff --git a/moto/pinpoint/exceptions.py b/moto/pinpoint/exceptions.py new file mode 100644 index 000000000..4deebe359 --- /dev/null +++ b/moto/pinpoint/exceptions.py @@ -0,0 +1,20 @@ +"""Exceptions raised by the pinpoint service.""" +from moto.core.exceptions import JsonRESTError + + +class PinpointExceptions(JsonRESTError): + pass + + +class ApplicationNotFound(PinpointExceptions): + code = 404 + + def __init__(self): + super().__init__("NotFoundException", "Application not found") + + +class EventStreamNotFound(PinpointExceptions): + code = 404 + + def __init__(self): + super().__init__("NotFoundException", "Resource not found") diff --git a/moto/pinpoint/models.py b/moto/pinpoint/models.py new file mode 100644 index 000000000..cc62724d8 --- /dev/null +++ b/moto/pinpoint/models.py @@ -0,0 +1,155 @@ +from datetime import datetime +from moto.core import ACCOUNT_ID, BaseBackend, BaseModel +from moto.core.utils import BackendDict, unix_time +from moto.utilities.tagging_service import TaggingService +from uuid import uuid4 + +from .exceptions import ApplicationNotFound, EventStreamNotFound + + +class App(BaseModel): + def __init__(self, name): + self.application_id = str(uuid4()).replace("-", "") + self.arn = ( + f"arn:aws:mobiletargeting:us-east-1:{ACCOUNT_ID}:apps/{self.application_id}" + ) + self.name = name + self.created = unix_time() + self.settings = AppSettings() + self.event_stream = None + + def get_settings(self): + return self.settings + + def update_settings(self, settings): + self.settings.update(settings) + return self.settings + + def delete_event_stream(self): + stream = self.event_stream + self.event_stream = None + return stream + + def get_event_stream(self): + if self.event_stream is None: + raise EventStreamNotFound() + return self.event_stream + + def put_event_stream(self, stream_arn, role_arn): + self.event_stream = EventStream(stream_arn, role_arn) + return self.event_stream + + def to_json(self): + return { + "Arn": self.arn, + "Id": self.application_id, + "Name": self.name, + "CreationDate": self.created, + } + + +class AppSettings(BaseModel): + def __init__(self): + self.settings = dict() + self.last_modified = unix_time() + + def update(self, settings): + self.settings = settings + self.last_modified = datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ") + + def to_json(self): + return { + "CampaignHook": self.settings.get("CampaignHook", {}), + "CloudWatchMetricsEnabled": self.settings.get( + "CloudWatchMetricsEnabled", False + ), + "LastModifiedDate": self.last_modified, + "Limits": self.settings.get("Limits", {}), + "QuietTime": self.settings.get("QuietTime", {}), + } + + +class EventStream(BaseModel): + def __init__(self, stream_arn, role_arn): + self.stream_arn = stream_arn + self.role_arn = role_arn + self.last_modified = datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%fZ") + + def to_json(self): + return { + "DestinationStreamArn": self.stream_arn, + "RoleArn": self.role_arn, + "LastModifiedDate": self.last_modified, + } + + +class PinpointBackend(BaseBackend): + """Implementation of Pinpoint APIs.""" + + def __init__(self, region_name=None): + self.region_name = region_name + self.apps = {} + self.tagger = TaggingService() + + def reset(self): + """Re-initialize all attributes for this instance.""" + region_name = self.region_name + self.__dict__ = {} + self.__init__(region_name) + + def create_app(self, name, tags): + app = App(name) + self.apps[app.application_id] = app + tags = self.tagger.convert_dict_to_tags_input(tags) + self.tagger.tag_resource(app.arn, tags) + return app + + def delete_app(self, application_id): + self.get_app(application_id) + return self.apps.pop(application_id) + + def get_app(self, application_id): + if application_id not in self.apps: + raise ApplicationNotFound() + return self.apps[application_id] + + def get_apps(self): + """ + Pagination is not yet implemented + """ + return self.apps.values() + + def update_application_settings(self, application_id, settings): + app = self.get_app(application_id) + return app.update_settings(settings) + + def get_application_settings(self, application_id): + app = self.get_app(application_id) + return app.get_settings() + + def list_tags_for_resource(self, resource_arn): + tags = self.tagger.get_tag_dict_for_resource(resource_arn) + return {"tags": tags} + + def tag_resource(self, resource_arn, tags): + tags = TaggingService.convert_dict_to_tags_input(tags) + self.tagger.tag_resource(resource_arn, tags) + + def untag_resource(self, resource_arn, tag_keys): + self.tagger.untag_resource_using_names(resource_arn, tag_keys) + return + + def put_event_stream(self, application_id, stream_arn, role_arn): + app = self.get_app(application_id) + return app.put_event_stream(stream_arn, role_arn) + + def get_event_stream(self, application_id): + app = self.get_app(application_id) + return app.get_event_stream() + + def delete_event_stream(self, application_id): + app = self.get_app(application_id) + return app.delete_event_stream() + + +pinpoint_backends = BackendDict(PinpointBackend, "pinpoint") diff --git a/moto/pinpoint/responses.py b/moto/pinpoint/responses.py new file mode 100644 index 000000000..ce74b1716 --- /dev/null +++ b/moto/pinpoint/responses.py @@ -0,0 +1,161 @@ +"""Handles incoming pinpoint requests, invokes methods, returns responses.""" +import json + +from functools import wraps +from moto.core.responses import BaseResponse +from urllib.parse import unquote +from .exceptions import PinpointExceptions +from .models import pinpoint_backends + + +def error_handler(f): + @wraps(f) + def _wrapper(*args, **kwargs): + try: + return f(*args, **kwargs) + except PinpointExceptions as e: + return e.code, e.get_headers(), e.get_body() + + return _wrapper + + +class PinpointResponse(BaseResponse): + """Handler for Pinpoint requests and responses.""" + + @property + def pinpoint_backend(self): + """Return backend instance specific for this region.""" + return pinpoint_backends[self.region] + + @error_handler + def app(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "DELETE": + return self.delete_app() + if request.method == "GET": + return self.get_app() + + def apps(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "GET": + return self.get_apps() + if request.method == "POST": + return self.create_app() + + def app_settings(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "GET": + return self.get_application_settings() + if request.method == "PUT": + return self.update_application_settings() + + @error_handler + def eventstream(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "DELETE": + return self.delete_event_stream() + if request.method == "GET": + return self.get_event_stream() + if request.method == "POST": + return self.put_event_stream() + + def tags(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == "DELETE": + return self.untag_resource() + if request.method == "GET": + return self.list_tags_for_resource() + if request.method == "POST": + return self.tag_resource() + + def create_app(self): + params = json.loads(self.body) + name = params.get("Name") + tags = params.get("tags", {}) + app = self.pinpoint_backend.create_app(name=name, tags=tags) + return 201, {}, json.dumps(app.to_json()) + + def delete_app(self): + application_id = self.path.split("/")[-1] + app = self.pinpoint_backend.delete_app(application_id=application_id,) + return 200, {}, json.dumps(app.to_json()) + + def get_app(self): + application_id = self.path.split("/")[-1] + app = self.pinpoint_backend.get_app(application_id=application_id,) + return 200, {}, json.dumps(app.to_json()) + + def get_apps(self): + apps = self.pinpoint_backend.get_apps() + resp = {"Item": [a.to_json() for a in apps]} + return 200, {}, json.dumps(resp) + + def update_application_settings(self): + application_id = self.path.split("/")[-2] + settings = json.loads(self.body) + app_settings = self.pinpoint_backend.update_application_settings( + application_id=application_id, settings=settings + ) + app_settings = app_settings.to_json() + app_settings["ApplicationId"] = application_id + return 200, {}, json.dumps(app_settings) + + def get_application_settings(self): + application_id = self.path.split("/")[-2] + app_settings = self.pinpoint_backend.get_application_settings( + application_id=application_id, + ) + app_settings = app_settings.to_json() + app_settings["ApplicationId"] = application_id + return 200, {}, json.dumps(app_settings) + + def list_tags_for_resource(self): + resource_arn = unquote(self.path).split("/tags/")[-1] + tags = self.pinpoint_backend.list_tags_for_resource(resource_arn=resource_arn,) + return 200, {}, json.dumps(tags) + + def tag_resource(self): + resource_arn = unquote(self.path).split("/tags/")[-1] + tags = json.loads(self.body).get("tags", {}) + self.pinpoint_backend.tag_resource( + resource_arn=resource_arn, tags=tags, + ) + return 200, {}, "{}" + + def untag_resource(self): + resource_arn = unquote(self.path).split("/tags/")[-1] + tag_keys = self.querystring.get("tagKeys") + self.pinpoint_backend.untag_resource( + resource_arn=resource_arn, tag_keys=tag_keys, + ) + return 200, {}, "{}" + + def put_event_stream(self): + application_id = self.path.split("/")[-2] + params = json.loads(self.body) + stream_arn = params.get("DestinationStreamArn") + role_arn = params.get("RoleArn") + event_stream = self.pinpoint_backend.put_event_stream( + application_id=application_id, stream_arn=stream_arn, role_arn=role_arn + ) + resp = event_stream.to_json() + resp["ApplicationId"] = application_id + return 200, {}, json.dumps(resp) + + def get_event_stream(self): + application_id = self.path.split("/")[-2] + event_stream = self.pinpoint_backend.get_event_stream( + application_id=application_id, + ) + resp = event_stream.to_json() + resp["ApplicationId"] = application_id + return 200, {}, json.dumps(resp) + + def delete_event_stream(self): + application_id = self.path.split("/")[-2] + event_stream = self.pinpoint_backend.delete_event_stream( + application_id=application_id, + ) + resp = event_stream.to_json() + resp["ApplicationId"] = application_id + return 200, {}, json.dumps(resp) diff --git a/moto/pinpoint/urls.py b/moto/pinpoint/urls.py new file mode 100644 index 000000000..58e801af5 --- /dev/null +++ b/moto/pinpoint/urls.py @@ -0,0 +1,18 @@ +"""pinpoint base URL and path.""" +from .responses import PinpointResponse + +url_bases = [ + r"https?://pinpoint\.(.+)\.amazonaws\.com", +] + + +response = PinpointResponse() + + +url_paths = { + "{0}/v1/apps$": response.apps, + "{0}/v1/apps/(?P[^/]+)$": response.app, + "{0}/v1/apps/(?P[^/]+)/eventstream": response.eventstream, + "{0}/v1/apps/(?P[^/]+)/settings$": response.app_settings, + "{0}/v1/tags/(?P.+)$": response.tags, +} diff --git a/tests/terraform-tests.success.txt b/tests/terraform-tests.success.txt index 2cda44c39..1cfd9317e 100644 --- a/tests/terraform-tests.success.txt +++ b/tests/terraform-tests.success.txt @@ -99,6 +99,8 @@ TestAccAWSKmsSecretDataSource TestAccAWSMq TestAccAWSNatGateway TestAccAWSPartition +TestAccAWSPinpointApp +TestAccAWSPinpointEventStream TestAccAWSProvider TestAccAWSRedshiftServiceAccount TestAccAWSRolePolicyAttachment diff --git a/tests/test_awslambda/test_policy.py b/tests/test_awslambda/test_policy.py index d6ee9fda1..0ad83659e 100644 --- a/tests/test_awslambda/test_policy.py +++ b/tests/test_awslambda/test_policy.py @@ -26,7 +26,7 @@ def test_policy(): "FunctionName": "function_name", "Principal": {"Service": "events.amazonaws.com"}, "Effect": "Allow", - "Resource": "arn:$LATEST", + "Resource": "arn", "Sid": "statement0", "Condition": { "ArnLike": { @@ -40,7 +40,7 @@ def test_policy(): expected.should.be.equal(policy.statements[0]) sid = statement.get("StatementId", None) - if sid == None: + if sid is None: raise "TestCase.statement does not contain StatementId" policy.del_statement(sid) diff --git a/tests/test_pinpoint/__init__.py b/tests/test_pinpoint/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/test_pinpoint/test_pinpoint.py b/tests/test_pinpoint/test_pinpoint.py new file mode 100644 index 000000000..a5a2cbd7f --- /dev/null +++ b/tests/test_pinpoint/test_pinpoint.py @@ -0,0 +1,129 @@ +"""Unit tests for pinpoint-supported APIs.""" +import boto3 +import pytest +import sure # noqa # pylint: disable=unused-import + +from botocore.exceptions import ClientError +from moto import mock_pinpoint + +# See our Development Tips on writing tests for hints on how to write good tests: +# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html + + +@mock_pinpoint +def test_create_app(): + client = boto3.client("pinpoint", region_name="us-east-1") + resp = client.create_app(CreateApplicationRequest={"Name": "myfirstapp"}) + + resp.should.have.key("ApplicationResponse") + resp["ApplicationResponse"].should.have.key("Arn") + resp["ApplicationResponse"].should.have.key("Id") + resp["ApplicationResponse"].should.have.key("Name").equals("myfirstapp") + resp["ApplicationResponse"].should.have.key("CreationDate") + + +@mock_pinpoint +def test_delete_app(): + client = boto3.client("pinpoint", region_name="ap-southeast-1") + creation = client.create_app(CreateApplicationRequest={"Name": "myfirstapp"})[ + "ApplicationResponse" + ] + app_id = creation["Id"] + + deletion = client.delete_app(ApplicationId=app_id)["ApplicationResponse"] + deletion.should.equal(creation) + + with pytest.raises(ClientError) as exc: + client.get_app(ApplicationId=app_id) + err = exc.value.response["Error"] + err["Code"].should.equal("NotFoundException") + err["Message"].should.equal("Application not found") + + +@mock_pinpoint +def test_get_app(): + client = boto3.client("pinpoint", region_name="eu-west-1") + resp = client.create_app(CreateApplicationRequest={"Name": "myfirstapp"}) + app_id = resp["ApplicationResponse"]["Id"] + + resp = client.get_app(ApplicationId=app_id) + + resp.should.have.key("ApplicationResponse") + resp["ApplicationResponse"].should.have.key("Arn") + resp["ApplicationResponse"].should.have.key("Id") + resp["ApplicationResponse"].should.have.key("Name").equals("myfirstapp") + resp["ApplicationResponse"].should.have.key("CreationDate") + + +@mock_pinpoint +def test_get_apps_initial(): + client = boto3.client("pinpoint", region_name="us-east-1") + resp = client.get_apps() + + resp.should.have.key("ApplicationsResponse") + resp["ApplicationsResponse"].should.equal({"Item": []}) + + +@mock_pinpoint +def test_get_apps(): + client = boto3.client("pinpoint", region_name="us-east-1") + resp = client.create_app(CreateApplicationRequest={"Name": "myfirstapp"}) + app_id = resp["ApplicationResponse"]["Id"] + + resp = client.get_apps() + + resp.should.have.key("ApplicationsResponse").should.have.key("Item").length_of(1) + resp["ApplicationsResponse"]["Item"][0].should.have.key("Arn") + resp["ApplicationsResponse"]["Item"][0].should.have.key("Id").equals(app_id) + resp["ApplicationsResponse"]["Item"][0].should.have.key("Name").equals("myfirstapp") + resp["ApplicationsResponse"]["Item"][0].should.have.key("CreationDate") + + +@mock_pinpoint +def test_update_application_settings(): + client = boto3.client("pinpoint", region_name="eu-west-1") + resp = client.create_app(CreateApplicationRequest={"Name": "myfirstapp"}) + app_id = resp["ApplicationResponse"]["Id"] + + resp = client.update_application_settings( + ApplicationId=app_id, + WriteApplicationSettingsRequest={ + "CampaignHook": {"LambdaFunctionName": "lfn"}, + "CloudWatchMetricsEnabled": True, + "EventTaggingEnabled": True, + "Limits": {"Daily": 42}, + }, + ) + + resp.should.have.key("ApplicationSettingsResource") + app_settings = resp["ApplicationSettingsResource"] + app_settings.should.have.key("ApplicationId").equals(app_id) + app_settings.should.have.key("CampaignHook").equals({"LambdaFunctionName": "lfn"}) + app_settings.should.have.key("Limits").equals({"Daily": 42}) + app_settings.should.have.key("LastModifiedDate") + + +@mock_pinpoint +def test_get_application_settings(): + client = boto3.client("pinpoint", region_name="ap-southeast-1") + resp = client.create_app(CreateApplicationRequest={"Name": "myfirstapp"}) + app_id = resp["ApplicationResponse"]["Id"] + + client.update_application_settings( + ApplicationId=app_id, + WriteApplicationSettingsRequest={ + "CampaignHook": {"LambdaFunctionName": "lfn"}, + "CloudWatchMetricsEnabled": True, + "EventTaggingEnabled": True, + "Limits": {"Daily": 42}, + }, + ) + + resp = client.get_application_settings(ApplicationId=app_id) + + resp.should.have.key("ApplicationSettingsResource") + app_settings = resp["ApplicationSettingsResource"] + app_settings.should.have.key("ApplicationId").equals(app_id) + app_settings.should.have.key("CampaignHook").equals({"LambdaFunctionName": "lfn"}) + app_settings.should.have.key("Limits").equals({"Daily": 42}) + app_settings.should.have.key("LastModifiedDate") diff --git a/tests/test_pinpoint/test_pinpoint_application_tags.py b/tests/test_pinpoint/test_pinpoint_application_tags.py new file mode 100644 index 000000000..2d5a5eded --- /dev/null +++ b/tests/test_pinpoint/test_pinpoint_application_tags.py @@ -0,0 +1,73 @@ +"""Unit tests for pinpoint-supported APIs.""" +import boto3 +import sure # noqa # pylint: disable=unused-import + +from moto import mock_pinpoint + + +@mock_pinpoint +def test_list_tags_for_resource_empty(): + client = boto3.client("pinpoint", region_name="ap-southeast-1") + resp = client.create_app(CreateApplicationRequest={"Name": "myfirstapp"}) + app_arn = resp["ApplicationResponse"]["Arn"] + + resp = client.list_tags_for_resource(ResourceArn=app_arn) + resp.should.have.key("TagsModel").equals({"tags": {}}) + + +@mock_pinpoint +def test_list_tags_for_resource(): + client = boto3.client("pinpoint", region_name="ap-southeast-1") + resp = client.create_app( + CreateApplicationRequest={ + "Name": "myfirstapp", + "tags": {"key1": "value1", "key2": "value2"}, + } + ) + app_arn = resp["ApplicationResponse"]["Arn"] + + resp = client.list_tags_for_resource(ResourceArn=app_arn) + resp.should.have.key("TagsModel") + resp["TagsModel"].should.have.key("tags").equals( + {"key1": "value1", "key2": "value2"} + ) + + +@mock_pinpoint +def test_tag_resource(): + client = boto3.client("pinpoint", region_name="us-east-1") + resp = client.create_app(CreateApplicationRequest={"Name": "myfirstapp"}) + app_arn = resp["ApplicationResponse"]["Arn"] + + client.tag_resource( + ResourceArn=app_arn, TagsModel={"tags": {"key1": "value1", "key2": "value2"}} + ) + + resp = client.list_tags_for_resource(ResourceArn=app_arn) + resp.should.have.key("TagsModel") + resp["TagsModel"].should.have.key("tags").equals( + {"key1": "value1", "key2": "value2"} + ) + + +@mock_pinpoint +def test_untag_resource(): + client = boto3.client("pinpoint", region_name="eu-west-1") + resp = client.create_app( + CreateApplicationRequest={"Name": "myfirstapp", "tags": {"key1": "value1"}} + ) + app_arn = resp["ApplicationResponse"]["Arn"] + + client.tag_resource( + ResourceArn=app_arn, TagsModel={"tags": {"key2": "value2", "key3": "value3"}} + ) + + client.untag_resource( + ResourceArn=app_arn, TagKeys=["key2"], + ) + + resp = client.list_tags_for_resource(ResourceArn=app_arn) + resp.should.have.key("TagsModel") + resp["TagsModel"].should.have.key("tags").equals( + {"key1": "value1", "key3": "value3"} + ) diff --git a/tests/test_pinpoint/test_pinpoint_event_stream.py b/tests/test_pinpoint/test_pinpoint_event_stream.py new file mode 100644 index 000000000..32144169a --- /dev/null +++ b/tests/test_pinpoint/test_pinpoint_event_stream.py @@ -0,0 +1,74 @@ +"""Unit tests for pinpoint-supported APIs.""" +import boto3 +import pytest +import sure # noqa # pylint: disable=unused-import + +from botocore.exceptions import ClientError +from moto import mock_pinpoint + +# See our Development Tips on writing tests for hints on how to write good tests: +# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html + + +@mock_pinpoint +def test_put_event_stream(): + client = boto3.client("pinpoint", region_name="eu-west-1") + resp = client.create_app(CreateApplicationRequest={"Name": "myfirstapp"}) + app_id = resp["ApplicationResponse"]["Id"] + + resp = client.put_event_stream( + ApplicationId=app_id, + WriteEventStream={"DestinationStreamArn": "kinesis:arn", "RoleArn": "iam:arn"}, + ) + + resp.should.have.key("EventStream") + resp["EventStream"].should.have.key("ApplicationId").equals(app_id) + resp["EventStream"].should.have.key("DestinationStreamArn").equals("kinesis:arn") + resp["EventStream"].should.have.key("LastModifiedDate") + resp["EventStream"].should.have.key("RoleArn").equals("iam:arn") + + +@mock_pinpoint +def test_get_event_stream(): + client = boto3.client("pinpoint", region_name="us-east-1") + resp = client.create_app(CreateApplicationRequest={"Name": "myfirstapp"}) + app_id = resp["ApplicationResponse"]["Id"] + + client.put_event_stream( + ApplicationId=app_id, + WriteEventStream={"DestinationStreamArn": "kinesis:arn", "RoleArn": "iam:arn"}, + ) + + resp = client.get_event_stream(ApplicationId=app_id) + + resp.should.have.key("EventStream") + resp["EventStream"].should.have.key("ApplicationId").equals(app_id) + resp["EventStream"].should.have.key("DestinationStreamArn").equals("kinesis:arn") + resp["EventStream"].should.have.key("LastModifiedDate") + resp["EventStream"].should.have.key("RoleArn").equals("iam:arn") + + +@mock_pinpoint +def test_delete_event_stream(): + client = boto3.client("pinpoint", region_name="us-east-1") + resp = client.create_app(CreateApplicationRequest={"Name": "myfirstapp"}) + app_id = resp["ApplicationResponse"]["Id"] + + client.put_event_stream( + ApplicationId=app_id, + WriteEventStream={"DestinationStreamArn": "kinesis:arn", "RoleArn": "iam:arn"}, + ) + + resp = client.delete_event_stream(ApplicationId=app_id) + + resp.should.have.key("EventStream") + resp["EventStream"].should.have.key("ApplicationId").equals(app_id) + resp["EventStream"].should.have.key("DestinationStreamArn").equals("kinesis:arn") + resp["EventStream"].should.have.key("LastModifiedDate") + resp["EventStream"].should.have.key("RoleArn").equals("iam:arn") + + with pytest.raises(ClientError) as exc: + client.get_event_stream(ApplicationId=app_id) + err = exc.value.response["Error"] + err["Code"].should.equal("NotFoundException") + err["Message"].should.equal("Resource not found")