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")