This repository was archived by the owner on Mar 23, 2026. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 4.7k
implemented phone ops, added tests #13449
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,7 @@ | |
| from localstack.aws.api.sns import ( | ||
| AmazonResourceName, | ||
| BatchEntryIdsNotDistinctException, | ||
| CheckIfPhoneNumberIsOptedOutResponse, | ||
| ConfirmSubscriptionResponse, | ||
| CreateEndpointResponse, | ||
| CreatePlatformApplicationResponse, | ||
|
|
@@ -26,6 +27,7 @@ | |
| InvalidParameterException, | ||
| InvalidParameterValueException, | ||
| ListEndpointsByPlatformApplicationResponse, | ||
| ListPhoneNumbersOptedOutResponse, | ||
| ListPlatformApplicationsResponse, | ||
| ListString, | ||
| ListSubscriptionsByTopicResponse, | ||
|
|
@@ -35,6 +37,7 @@ | |
| MapStringToString, | ||
| MessageAttributeMap, | ||
| NotFoundException, | ||
| OptInPhoneNumberResponse, | ||
| PhoneNumber, | ||
| PlatformApplication, | ||
| PublishBatchRequestEntryList, | ||
|
|
@@ -61,6 +64,7 @@ | |
| messageStructure, | ||
| nextToken, | ||
| protocol, | ||
| string, | ||
| subject, | ||
| subscriptionARN, | ||
| topicARN, | ||
|
|
@@ -1073,6 +1077,39 @@ def get_sms_attributes( | |
|
|
||
| return GetSMSAttributesResponse(attributes=return_attributes) | ||
|
|
||
| # | ||
| # Phone number operations | ||
| # | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question about Are we expecting to add a new internal route in the future to allow users to add values to this field? if yes, maybe putting it back to lowercase would make sense |
||
| def check_if_phone_number_is_opted_out( | ||
| self, context: RequestContext, phone_number: PhoneNumber, **kwargs | ||
| ) -> CheckIfPhoneNumberIsOptedOutResponse: | ||
| store = sns_stores[context.account_id][context.region] | ||
| return CheckIfPhoneNumberIsOptedOutResponse( | ||
| isOptedOut=phone_number in store.PHONE_NUMBERS_OPTED_OUT | ||
| ) | ||
|
|
||
| def list_phone_numbers_opted_out( | ||
| self, context: RequestContext, next_token: string | None = None, **kwargs | ||
| ) -> ListPhoneNumbersOptedOutResponse: | ||
| store = self.get_store(context.account_id, context.region) | ||
| numbers_opted_out = PaginatedList(store.PHONE_NUMBERS_OPTED_OUT) | ||
| page, nxt = numbers_opted_out.get_page( | ||
| token_generator=lambda x: x, | ||
| next_token=next_token, | ||
| page_size=100, | ||
| ) | ||
| phone_numbers = {"phoneNumbers": page, "nextToken": nxt} | ||
| return ListPhoneNumbersOptedOutResponse(**phone_numbers) | ||
|
|
||
| def opt_in_phone_number( | ||
| self, context: RequestContext, phone_number: PhoneNumber, **kwargs | ||
| ) -> OptInPhoneNumberResponse: | ||
| store = self.get_store(context.account_id, context.region) | ||
| if phone_number in store.PHONE_NUMBERS_OPTED_OUT: | ||
| store.PHONE_NUMBERS_OPTED_OUT.remove(phone_number) | ||
| return OptInPhoneNumberResponse() | ||
|
|
||
| def list_tags_for_resource( | ||
| self, context: RequestContext, resource_arn: AmazonResourceName, **kwargs | ||
| ) -> ListTagsForResourceResponse: | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,6 +37,7 @@ | |
| from localstack.testing.aws.util import is_aws_cloud | ||
| from localstack.testing.config import TEST_AWS_ACCESS_KEY_ID, TEST_AWS_SECRET_ACCESS_KEY | ||
| from localstack.testing.pytest import markers | ||
| from localstack.testing.snapshots.transformer_utility import TransformerUtility | ||
| from localstack.utils import testutil | ||
| from localstack.utils.aws.arns import get_partition, parse_arn, sqs_queue_arn | ||
| from localstack.utils.net import wait_for_port_closed, wait_for_port_open | ||
|
|
@@ -3665,6 +3666,15 @@ def platform_credentials() -> tuple[str, str]: | |
| return client_id, client_secret | ||
|
|
||
|
|
||
| @pytest.fixture(scope="class") | ||
| def phone_number() -> str: | ||
| # if you want to test phone number operations against AWS and a real phone number, replace this value | ||
| # and use this fixture. | ||
| # note: you might need to verify that number first in your AWS account due to the sms sandbox | ||
| phone_number = "+430000000000" | ||
| return phone_number | ||
|
|
||
|
|
||
| class TestSNSPlatformApplicationCrud: | ||
| @markers.aws.manual_setup_required | ||
| def test_create_platform_application( | ||
|
|
@@ -4680,6 +4690,85 @@ def test_set_invalid_sms_attributes(self, aws_client, snapshot, attribute_key_va | |
| ) | ||
| snapshot.match("invalid-attribute", e.value.response) | ||
|
|
||
| @markers.aws.manual_setup_required | ||
| @pytest.mark.skipif(is_sns_v1_provider(), reason="Not correctly implemented in v1") | ||
| def test_is_phone_number_opted_out( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice work validating those tests 👌 |
||
| self, phone_number, aws_client, snapshot, sns_provider, account_id, region_name, cleanups | ||
| ): | ||
| # this test expects the fixture-provided phone number to be opted out | ||
| # if you want to test against AWS, you need to manually opt out a number | ||
| # https://us-east-1.console.aws.amazon.com/sms-voice/home?region=us-east-1#/opt-out-lists?name=Default&tab=opt-out-list-opted-out-numbers | ||
| sns_store = sns_provider().get_store(account_id, region_name) | ||
|
|
||
| def cleanup_store(): | ||
| sns_store.PHONE_NUMBERS_OPTED_OUT.remove(phone_number) | ||
|
|
||
| if not is_aws_cloud(): | ||
| sns_store.PHONE_NUMBERS_OPTED_OUT.append(phone_number) | ||
| cleanups.append(cleanup_store) | ||
|
|
||
| response = aws_client.sns.check_if_phone_number_is_opted_out(phoneNumber=phone_number) | ||
| snapshot.match("phone-number-opted-out", response) | ||
|
|
||
| @markers.aws.manual_setup_required | ||
| @pytest.mark.skipif(is_sns_v1_provider(), reason="Not correctly implemented in v1") | ||
| def test_list_phone_numbers_opted_out( | ||
| self, phone_number, aws_client, snapshot, sns_provider, account_id, region_name, cleanups | ||
| ): | ||
| # this test expects exactly one phone number opted out | ||
| # if you want to test against AWS, you need to manually opt out a number | ||
| # https://us-east-1.console.aws.amazon.com/sms-voice/home?region=us-east-1#/opt-out-lists?name=Default&tab=opt-out-list-opted-out-numbers | ||
| sns_store = sns_provider().get_store(account_id, region_name) | ||
|
|
||
| def cleanup_store(): | ||
| sns_store.PHONE_NUMBERS_OPTED_OUT.remove(phone_number) | ||
|
|
||
| if not is_aws_cloud(): | ||
| sns_store.PHONE_NUMBERS_OPTED_OUT.append(phone_number) | ||
| cleanups.append(cleanup_store) | ||
|
|
||
| snapshot.add_transformer( | ||
| TransformerUtility.jsonpath( | ||
| jsonpath="$..phoneNumbers[*]", | ||
| value_replacement="phone-number", | ||
| ) | ||
| ) | ||
| response = aws_client.sns.list_phone_numbers_opted_out() | ||
| snapshot.match("list-phone-numbers-opted-out", response) | ||
|
|
||
| @markers.aws.manual_setup_required | ||
| @pytest.mark.skipif(is_sns_v1_provider(), reason="Not correctly implemented in v1") | ||
| def test_opt_in_phone_number( | ||
| self, phone_number, aws_client, snapshot, sns_provider, account_id, region_name, cleanups | ||
| ): | ||
| # this test expects exactly one phone number opted out | ||
| # if you want to test against AWS, you need to manually opt out a number | ||
| # https://us-east-1.console.aws.amazon.com/sms-voice/home?region=us-east-1#/opt-out-lists?name=Default&tab=opt-out-list-opted-out-numbers | ||
| # IMPORTANT: a phone number can only be opted in once every 30 days on AWS. | ||
| # Make sure everything else is set up and taken care of properly before trying to validate this. | ||
| sns_store = sns_provider().get_store(account_id, region_name) | ||
|
|
||
| def cleanup_store(): | ||
| sns_store.PHONE_NUMBERS_OPTED_OUT.remove(phone_number) | ||
|
|
||
| if not is_aws_cloud(): | ||
| sns_store.PHONE_NUMBERS_OPTED_OUT.append(phone_number) | ||
| cleanups.append(cleanup_store) | ||
| response = aws_client.sns.check_if_phone_number_is_opted_out(phoneNumber=phone_number) | ||
| assert response["isOptedOut"] | ||
|
|
||
| response = aws_client.sns.opt_in_phone_number(phoneNumber=phone_number) | ||
| snapshot.match("opt-in-phone-number", response) | ||
|
|
||
| @markers.aws.validated | ||
| def test_opt_in_non_existing_phone_number( | ||
| self, phone_number, aws_client, snapshot, sns_provider, account_id, region_name | ||
| ): | ||
| non_existing_number = "+4411111111" | ||
| response = aws_client.sns.opt_in_phone_number(phoneNumber=non_existing_number) | ||
|
|
||
| snapshot.match("opt-in-non-existing-number", response) | ||
|
|
||
|
|
||
| class TestSNSSubscriptionHttp: | ||
| @markers.aws.validated | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: is this in uppercase because it is supposed to be a constant? if that's the case, does it make sense to be in the store?
if it's not a constant, then maybe it could be a lowercase variable