Merge pull request #2506 from GSA/main

04/15/2025 Production Deploy
This commit is contained in:
ccostino
2025-04-16 18:10:42 -04:00
committed by GitHub
15 changed files with 810 additions and 732 deletions
+17 -1
View File
@@ -6,6 +6,7 @@ APP_VERSION_FILE = app/version.py
GIT_BRANCH ?= $(shell git symbolic-ref --short HEAD 2> /dev/null || echo "detached") GIT_BRANCH ?= $(shell git symbolic-ref --short HEAD 2> /dev/null || echo "detached")
GIT_COMMIT ?= $(shell git rev-parse HEAD 2> /dev/null || echo "") GIT_COMMIT ?= $(shell git rev-parse HEAD 2> /dev/null || echo "")
GIT_HOOKS_PATH ?= $(shell git config --global core.hooksPath || echo "")
VIRTUALENV_ROOT := $(shell [ -z $$VIRTUAL_ENV ] && echo $$(pwd)/venv || echo $$VIRTUAL_ENV) VIRTUALENV_ROOT := $(shell [ -z $$VIRTUAL_ENV ] && echo $$(pwd)/venv || echo $$VIRTUAL_ENV)
@@ -14,7 +15,8 @@ NVMSH := $(shell [ -f "$(HOME)/.nvm/nvm.sh" ] && echo "$(HOME)/.nvm/nvm.sh" || e
## DEVELOPMENT ## DEVELOPMENT
.PHONY: bootstrap .PHONY: bootstrap
bootstrap: generate-version-file ## Set up everything to run the app bootstrap: ## Set up everything to run the app
make generate-version-file
poetry self add poetry-dotenv-plugin poetry self add poetry-dotenv-plugin
poetry lock --no-update poetry lock --no-update
poetry install --sync --no-root poetry install --sync --no-root
@@ -24,6 +26,20 @@ bootstrap: generate-version-file ## Set up everything to run the app
source $(NVMSH) && npm ci --no-audit source $(NVMSH) && npm ci --no-audit
source $(NVMSH) && npm run build source $(NVMSH) && npm run build
.PHONY: bootstrap-with-git-hooks
bootstrap-with-git-hooks: ## Sets everything up and accounts for pre-existing git hooks
make generate-version-file
poetry self add poetry-dotenv-plugin
poetry lock --no-update
poetry install --sync --no-root
poetry run playwright install --with-deps
git config --global --unset-all core.hooksPath
poetry run pre-commit install
git config --global core.hookspath "${GIT_HOOKS_PATH}"
source $(NVMSH) --no-use && nvm install && npm install
source $(NVMSH) && npm ci --no-audit
source $(NVMSH) && npm run build
.PHONY: watch-frontend .PHONY: watch-frontend
watch-frontend: ## Build frontend and watch for changes watch-frontend: ## Build frontend and watch for changes
source $(NVMSH) && npm run watch source $(NVMSH) && npm run watch
+6
View File
@@ -145,6 +145,7 @@ def _csp(config):
"frame-src": [ "frame-src": [
"https://www.youtube.com", "https://www.youtube.com",
"https://www.youtube-nocookie.com", "https://www.youtube-nocookie.com",
"https://www.googletagmanager.com",
], ],
"frame-ancestors": "'none'", "frame-ancestors": "'none'",
"form-action": "'self'", "form-action": "'self'",
@@ -169,6 +170,11 @@ def _csp(config):
def create_app(application): def create_app(application):
@application.after_request
def add_csp_header(response):
existing_csp = response.headers.get("Content-Security-Policy", "")
response.headers["Content-Security-Policy"] = existing_csp + "; form-action 'self';"
return response
# @application.context_processor # @application.context_processor
# def inject_feature_flags(): # def inject_feature_flags():
# this is where feature flags can be easily added as a dictionary within context # this is where feature flags can be easily added as a dictionary within context
+2
View File
@@ -38,6 +38,7 @@ function attachValidation() {
const validatedRadioNames = new Set(); const validatedRadioNames = new Set();
inputs.forEach((input) => { inputs.forEach((input) => {
if (input.type === "hidden") return;
const errorId = input.type === "radio" ? `${input.name}-error` : `${input.id}-error`; const errorId = input.type === "radio" ? `${input.name}-error` : `${input.id}-error`;
let errorElement = document.getElementById(errorId); let errorElement = document.getElementById(errorId);
@@ -85,6 +86,7 @@ function attachValidation() {
}); });
inputs.forEach((input) => { inputs.forEach((input) => {
if (input.type === "hidden") return;
input.addEventListener("input", function () { input.addEventListener("input", function () {
const errorId = input.type === "radio" ? `${input.name}-error` : `${input.id}-error`; const errorId = input.type === "radio" ? `${input.name}-error` : `${input.id}-error`;
const errorElement = document.getElementById(errorId); const errorElement = document.getElementById(errorId);
+2 -2
View File
@@ -49,12 +49,12 @@
} }
.sms-message-sender, .sms-message-file-name, .sms-message-scheduler, .sms-message-template, .sms-message-sender { .sms-message-sender, .sms-message-file-name, .sms-message-scheduler, .sms-message-template, .sms-message-sender {
margin:0.25rem 0 0; margin: units(0.5) 0 0;
} }
.sms-message-recipient { .sms-message-recipient {
color: color('gray-cool-90'); color: color('gray-cool-90');
margin: units(1) 0 units(1); margin: units(0.5) 0 units(2);
} }
.sms-message-status { .sms-message-status {
+1 -1
View File
@@ -14,7 +14,7 @@
<script nonce="{{ csp_nonce() }}">document.body.className = ((document.body.className) ? document.body.className + ' js-enabled' : 'js-enabled');</script> <script nonce="{{ csp_nonce() }}">document.body.className = ((document.body.className) ? document.body.className + ' js-enabled' : 'js-enabled');</script>
{% block bodyStart %} {% block bodyStart %}
{% block extra_javascripts_before_body %} {% block extra_javascripts_before_body %}
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-WX5NGWF" <noscript><iframe sandbox src="https://www.googletagmanager.com/ns.html?id=GTM-WX5NGWF"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript> height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
{% endblock %} {% endblock %}
{% endblock %} {% endblock %}
+1 -1
View File
@@ -29,7 +29,7 @@
'class': 'file-upload-field', 'class': 'file-upload-field',
'accept': allowed_file_extensions|format_list_items('.{item}')|join(',')|e 'accept': allowed_file_extensions|format_list_items('.{item}')|join(',')|e
}) }} }) }}
<label class="file-upload-button" for="{{ field.name }}"> <label class="file-upload-button usa-button" for="{{ field.name }}">
{{ button_text }} {{ button_text }}
</label> </label>
{% if alternate_link and alternate_link_text %} {% if alternate_link and alternate_link_text %}
+105 -7
View File
@@ -7,7 +7,7 @@
<script type="text/javascript" src="{{ asset_url('js/setTimezone.js') }}"></script> <script type="text/javascript" src="{{ asset_url('js/setTimezone.js') }}"></script>
{% block service_page_title %} {% block service_page_title %}
Upload a list of {{ 999|recipient_count_label(template.template_type) }} Upload your bulk-sending spreadsheet
{% endblock %} {% endblock %}
@@ -15,20 +15,61 @@
{{ usaBackLink(params) }} {{ usaBackLink(params) }}
{% endblock %} {% endblock %}
{% set phone_numbers = [
{
"svg_src": "#check_circle",
"card_heading": "Label column A (the first column) as Phone number",
},
{
"svg_src": "#check_circle",
"card_heading": "Double check it's the only column with Phone number as its label",
},
{
"svg_src": "#check_circle",
"card_heading": "Make sure no duplicate phone numbers are listed in Column A",
}
]
%}
{% set additional_data = [
{
"svg_src": "#check_circle",
"card_heading": "Match column labels one-to-one to the message template placeholders",
},
{
"svg_src": "#check_circle",
"card_heading": "Label each additional <a class=\"usa-link\" href=\"/using-notify/how-to#personalize-content\">personalized placeholder</a> separately",
},
{
"svg_src": "#check_circle",
"card_heading": "Separate each word in a column label with a space or dash, but no commas",
},
{
"svg_src": "#check_circle",
"card_heading": "Fill in each personalized placeholder with the appropriate data or information",
},
{
"svg_src": "#check_circle",
"card_heading": "Fill in each <a class=\"usa-link\" href=\"/using-notify/how-to#conditional-content\">conditional placeholder</a> column with a Yes (Y) or No (N) to “answer” whether the recipient meets its criteria",
}
]
%}
{% block maincolumn_content %} {% block maincolumn_content %}
{{ page_header('Upload a list of {}'.format(999|recipient_count_label(template.template_type))) }} {{ page_header('Upload your bulk-sending spreadsheet')}}
<p class="font-sans-lg text-base">Organize phone numbers and information in a single spreadsheet and upload when you have multiple messages to send. Column headers in the spreadsheet will place the data in the right spots within the template that's selected.</p>
<div class="page-footer bottom-gutter"> <div class="page-footer bottom-gutter margin-top-3">
{{file_upload( {{file_upload(
form.file, form.file,
allowed_file_extensions=allowed_file_extensions, allowed_file_extensions=allowed_file_extensions,
button_text='Choose a file', button_text='Choose and upload a spreadsheet',
show_errors=False show_errors=False
)}} )}}
</div> </div>
<h2 class="font-body-lg">Your file needs to look like this example</h2> <h2 class="font-body-lg">A spreadsheet is available to use</h2>
<div class="spreadsheet" data-module="fullscreen-table"> <div class="spreadsheet" data-module="fullscreen-table">
{% call(item, row_number) list_table( {% call(item, row_number) list_table(
@@ -44,14 +85,71 @@
{% endcall %} {% endcall %}
</div> </div>
<p class="hint"> <p class="hint">
Save your spreadsheet as a <abbr title="Comma Separated Values">CSV</abbr> file for bulk messaging. It is the most reliable when uploading your contact list. Start by downloading this example for your message template. Each template saved in Notify is given an example <abbr title="Comma Separated Values">CSV</abbr> formatted spreadsheet like this. It's the most reliable file format for uploading your contact list. Use this example to populate your template with the right data items. Start by downloading this example for your message template. Then save it as a <abbr title="Comma Separated Values">CSV</abbr> file for bulk messaging.
</p> </p>
<p class="table-show-more-link"> <p class="table-show-more-link">
<a class="usa-link display-flex margin-top-1" href="{{ url_for('.get_example_csv', service_id=current_service.id, template_id=template.id) }}" download>Download this example (<abbr title="Comma separated values">CSV</abbr>) <a class="usa-link display-flex margin-top-1" href="{{ url_for('.get_example_csv', service_id=current_service.id, template_id=template.id) }}" download>Download this example (<abbr title="Comma separated values">CSV</abbr>)
<img class="margin-left-05" src="{{ asset_url('img/material-icons/download.svg') }}" alt="" /> <img class="margin-left-05" src="{{ asset_url('img/material-icons/download.svg') }}" alt="" />
</a> </a>
</p> </p>
<h2 class="font-body-lg margin-bottom-1">Your file will populate this template:<br><span class="font-body-lg">({{ template.name }})</span></h2> <h2 class="font-body-lg">Your bulk-sending spreadsheet checklist</h2>
<div class="grid-container margin-top-2 padding-0">
<div class="grid-row">
<div class="grid-col-12">
<h3>Phone numbers</h3>
<ul class="usa-icon-list">
{% for item in phone_numbers %}
<li class="usa-icon-list__item">
<div class="usa-icon-list__icon text-green">
<svg aria-hidden="true" focusable="false" role="img" class="usa-icon">
<use xlink:href="{{ asset_url('img/sprite.svg') }}{{ item.svg_src }}"></use>
</svg>
</div>
<div class="usa-icon-list__content">
{{item.card_heading | safe }}
</div>
</li>
{% endfor %}
</ul>
</div>
<div class="grid-col-12">
<h3>Additional data</h3>
<ul class="usa-icon-list">
{% for item in additional_data %}
<li class="usa-icon-list__item">
<div class="usa-icon-list__icon text-green">
<svg aria-hidden="true" focusable="false" role="img" class="usa-icon">
<use xlink:href="{{ asset_url('img/sprite.svg') }}{{ item.svg_src }}"></use>
</svg>
</div>
<div class="usa-icon-list__content">
{{item.card_heading | safe }}
</div>
</li>
{% endfor %}
</ul>
</div>
<div class="grid-col-12">
<h3>If an error occurs</h3>
<ul class="usa-icon-list">
<li class="usa-icon-list__item">
<div class="usa-icon-list__icon text-red">
<svg aria-hidden="true" focusable="false" role="img" class="usa-icon">
<use xlink:href="{{ asset_url('img/sprite.svg') }}#error"></use>
</svg>
</div>
<div class="usa-icon-list__content">
If you're receiving any errors, double check your file format and make sure your data is pasted in with values only
</div>
</li>
</ul>
</div>
</div>
</div>
<h2 class="font-body-lg margin-bottom-2">You are filling in this template</h2>
<p>Template: {{ template.name }}</p>
{{ template|string }} {{ template|string }}
{% endblock %} {% endblock %}
-2
View File
@@ -608,8 +608,6 @@ def validate_phone_number(number, international=False):
try: try:
parsed = phonenumbers.parse(number, None) parsed = phonenumbers.parse(number, None)
if parsed.country_code != 1:
raise InvalidPhoneError("Invalid country code")
number = f"{parsed.country_code}{parsed.national_number}" number = f"{parsed.country_code}{parsed.national_number}"
if len(number) < 8: if len(number) < 8:
raise InvalidPhoneError("Not enough digits") raise InvalidPhoneError("Not enough digits")
+288 -483
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -40,7 +40,7 @@
"playwright": "^1.51.1", "playwright": "^1.51.1",
"python": "^0.0.4", "python": "^0.0.4",
"query-command-supported": "1.0.0", "query-command-supported": "1.0.0",
"sass-embedded": "^1.86.1", "sass-embedded": "^1.86.3",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"timeago": "1.6.7", "timeago": "1.6.7",
"vinyl-buffer": "^1.0.1", "vinyl-buffer": "^1.0.1",
@@ -49,7 +49,7 @@
"devDependencies": { "devDependencies": {
"@babel/core": "^7.26.10", "@babel/core": "^7.26.10",
"@babel/preset-env": "^7.26.9", "@babel/preset-env": "^7.26.9",
"@uswds/compile": "^1.2.1", "@uswds/compile": "^1.2.2",
"backstopjs": "^6.3.25", "backstopjs": "^6.3.25",
"better-npm-audit": "^3.11.0", "better-npm-audit": "^3.11.0",
"gulp": "^5.0.0", "gulp": "^5.0.0",
@@ -66,7 +66,7 @@
"jest-environment-jsdom": "^29.2.2", "jest-environment-jsdom": "^29.2.2",
"jshint": "2.13.6", "jshint": "2.13.6",
"jshint-stylish": "2.2.1", "jshint-stylish": "2.2.1",
"rollup": "^4.38.0", "rollup": "^4.40.0",
"rollup-plugin-commonjs": "10.1.0", "rollup-plugin-commonjs": "10.1.0",
"rollup-plugin-node-resolve": "5.2.0" "rollup-plugin-node-resolve": "5.2.0"
} }
Generated
+286 -134
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -9,7 +9,7 @@ package-mode = false
[tool.poetry.dependencies] [tool.poetry.dependencies]
axe-core-python = "^0.1.0" axe-core-python = "^0.1.0"
python = "^3.12.2" python = "^3.12.2"
ago = "~=0.0.95" ago = "~=0.1.0"
beautifulsoup4 = "^4.13.3" beautifulsoup4 = "^4.13.3"
blinker = "~=1.8" blinker = "~=1.8"
exceptiongroup = "==1.2.2" exceptiongroup = "==1.2.2"
@@ -28,7 +28,7 @@ pyexcel = "==0.7.2"
pyexcel-io = "==0.6.7" pyexcel-io = "==0.6.7"
pyexcel-ods3 = "==0.6.1" pyexcel-ods3 = "==0.6.1"
pyexcel-xls = "==0.7.1" pyexcel-xls = "==0.7.1"
pyexcel-xlsx = "==0.6.0" pyexcel-xlsx = "==0.6.1"
openpyxl = "==3.0.10" openpyxl = "==3.0.10"
pyproj = "==3.7.1" pyproj = "==3.7.1"
python-dotenv = "==1.1.0" python-dotenv = "==1.1.0"
@@ -38,7 +38,7 @@ werkzeug = "^3.1.3"
wtforms = "~=3.1" wtforms = "~=3.1"
markdown = "^3.7" markdown = "^3.7"
mistune = "^3.1.3" mistune = "^3.1.3"
async-timeout = "^4.0.3" async-timeout = "^5.0.1"
bleach = "^6.1.0" bleach = "^6.1.0"
boto3 = "^1.35.99" boto3 = "^1.35.99"
botocore = "^1.35.99" botocore = "^1.35.99"
@@ -50,7 +50,7 @@ geojson = "^3.1.0"
jmespath = "^1.0.1" jmespath = "^1.0.1"
numpy = "^2.2.4" numpy = "^2.2.4"
ordered-set = "^4.1.0" ordered-set = "^4.1.0"
phonenumbers = "^8.13.52" phonenumbers = "^9.0.3"
pycparser = "^2.22" pycparser = "^2.22"
python-json-logger = "^3.3.0" python-json-logger = "^3.3.0"
redis = "^5.2.1" redis = "^5.2.1"
@@ -62,7 +62,7 @@ certifi = "^2025.1.31"
charset-normalizer = "^3.4.1" charset-normalizer = "^3.4.1"
click = "^8.1.8" click = "^8.1.8"
idna = "^3.7" idna = "^3.7"
markupsafe = "^2.1.5" markupsafe = "^3.0.2"
python-dateutil = "^2.9.0.post0" python-dateutil = "^2.9.0.post0"
pyyaml = "^6.0.1" pyyaml = "^6.0.1"
requests = "^2.32.3" requests = "^2.32.3"
+1 -1
View File
@@ -14,7 +14,7 @@ def test_owasp_useful_headers_set(
assert response.headers["X-Frame-Options"] == "deny" assert response.headers["X-Frame-Options"] == "deny"
assert response.headers["X-Content-Type-Options"] == "nosniff" assert response.headers["X-Content-Type-Options"] == "nosniff"
csp = response.headers["Content-Security-Policy"] csp = response.headers["Content-Security-Policy"]
assert search(r"default-src 'self' static\.example\.com;", csp) assert search(r"frame-src.*https://www\.googletagmanager\.com", csp)
assert search(r"frame-ancestors 'none';", csp) assert search(r"frame-ancestors 'none';", csp)
assert search(r"form-action 'self';", csp) assert search(r"form-action 'self';", csp)
assert search( assert search(
@@ -765,6 +765,10 @@ def test_bad_or_missing_data(
assert recipients.has_errors is True assert recipients.has_errors is True
# TODO in the first test where the expected result is {0,1}, the original
# expected result was {0,1,2}. Why? In restoring international capability
# for some reason +447900123 now looks legit. It may have more to do with
# formatting than the actually validity of the number, not sure.
@pytest.mark.parametrize( @pytest.mark.parametrize(
("file_contents", "rows_with_bad_recipients"), ("file_contents", "rows_with_bad_recipients"),
[ [
@@ -775,7 +779,7 @@ def test_bad_or_missing_data(
1234 1234
+447900123 +447900123
""", """,
{0, 1, 2}, {0, 1},
), ),
( (
""" """
@@ -784,7 +788,7 @@ def test_bad_or_missing_data(
+12022340104, USA +12022340104, USA
+23051234567, Mauritius +23051234567, Mauritius
""", """,
{2}, set(),
), ),
], ],
) )
@@ -23,21 +23,17 @@ valid_us_phone_numbers = [
"(202) 555-0104", "(202) 555-0104",
] ]
# TODO
# International phone number tests are commented out as a result of issue #943 in notifications-admin. We are
# deliberately eliminating the ability to send to numbers outside of country code 1. These tests should
# be removed at some point when we are sure we are never going to support international numbers
valid_international_phone_numbers = [ valid_international_phone_numbers = [
# "+71234567890", # Russia "+71234567890", # Russia
# "+447123456789", # UK "+447123456789", # UK
# "+4407123456789", # UK "+4407123456789", # UK
# "+4407123 456789", # UK "+4407123 456789", # UK
# "+4407123-456-789", # UK "+4407123-456-789", # UK
# "+23051234567", # Mauritius, "+23051234567", # Mauritius,
# "+682 12345", # Cook islands "+682 12345", # Cook islands
# "+3312345678", "+3312345678",
# "+9-2345-12345-12345", # 15 digits "+9-2345-12345-12345", # 15 digits
] ]
@@ -84,7 +80,7 @@ invalid_us_phone_numbers = sum(
invalid_phone_numbers = [ invalid_phone_numbers = [
("+80233456789", "Not a valid country prefix"), ("+80233456789", "Not a valid country prefix"),
("1234567", "Not enough digits"), ("1234567", "Not enough digits"),
("+682 1234", "Invalid country code"), # Cook Islands phone numbers can be 5 digits ("+682 1234", "Not enough digits"),
("+12345 12345 12345 6", "Too many digits"), ("+12345 12345 12345 6", "Too many digits"),
] ]
@@ -155,46 +151,46 @@ def test_detect_us_phone_numbers(phone_number):
@pytest.mark.parametrize( @pytest.mark.parametrize(
("phone_number", "expected_info"), ("phone_number", "expected_info"),
[ [
# ( (
# "+4407900900123", "+4407900900123",
# international_phone_info( international_phone_info(
# international=True, international=True,
# country_prefix="44", # UK country_prefix="44", # UK
# billable_units=1, billable_units=1,
# ), ),
# ), ),
# ( (
# "+4407700900123", "+4407700900123",
# international_phone_info( international_phone_info(
# international=True, international=True,
# country_prefix="44", # Number in TV range country_prefix="44", # Number in TV range
# billable_units=1, billable_units=1,
# ), ),
# ), ),
# ( (
# "+4407700800123", "+4407700800123",
# international_phone_info( international_phone_info(
# international=True, international=True,
# country_prefix="44", # UK Crown dependency, so prefix same as UK country_prefix="44", # UK Crown dependency, so prefix same as UK
# billable_units=1, billable_units=1,
# ), ),
# ), ),
# ( # ( #
# "+20-12-1234-1234", "+20-12-1234-1234",
# international_phone_info( international_phone_info(
# international=True, international=True,
# country_prefix="20", # Egypt country_prefix="20", # Egypt
# billable_units=1, billable_units=1,
# ), ),
# ), ),
# ( (
# "+201212341234", "+201212341234",
# international_phone_info( international_phone_info(
# international=True, international=True,
# country_prefix="20", # Egypt country_prefix="20", # Egypt
# billable_units=1, billable_units=1,
# ), ),
# ), ),
( (
"+1 664-491-3434", "+1 664-491-3434",
international_phone_info( international_phone_info(
@@ -203,14 +199,14 @@ def test_detect_us_phone_numbers(phone_number):
billable_units=1, billable_units=1,
), ),
), ),
# ( (
# "+71234567890", "+71234567890",
# international_phone_info( international_phone_info(
# international=True, international=True,
# country_prefix="7", # Russia country_prefix="7", # Russia
# billable_units=1, billable_units=1,
# ), ),
# ), ),
( (
"1-202-555-0104", "1-202-555-0104",
international_phone_info( international_phone_info(
@@ -227,14 +223,14 @@ def test_detect_us_phone_numbers(phone_number):
billable_units=1, billable_units=1,
), ),
), ),
# ( (
# "+23051234567", "+23051234567",
# international_phone_info( international_phone_info(
# international=True, international=True,
# country_prefix="230", # Mauritius country_prefix="230", # Mauritius
# billable_units=1, billable_units=1,
# ), ),
# ), ),
], ],
) )
def test_get_international_info(phone_number, expected_info): def test_get_international_info(phone_number, expected_info):
@@ -287,11 +283,11 @@ def test_valid_us_phone_number_can_be_formatted_consistently(phone_number):
@pytest.mark.parametrize( @pytest.mark.parametrize(
("phone_number", "expected_formatted"), ("phone_number", "expected_formatted"),
[ [
# ("+44071234567890", "+4471234567890"), ("+44071234567890", "+4471234567890"),
("1-202-555-0104", "+12025550104"), ("1-202-555-0104", "+12025550104"),
("+12025550104", "+12025550104"), ("+12025550104", "+12025550104"),
("12025550104", "+12025550104"), ("12025550104", "+12025550104"),
# ("+23051234567", "+23051234567"), ("+23051234567", "+23051234567"),
], ],
) )
def test_valid_international_phone_number_can_be_formatted_consistently( def test_valid_international_phone_number_can_be_formatted_consistently(
@@ -362,17 +358,17 @@ def test_validates_against_guestlist_of_phone_numbers(phone_number):
) )
# @pytest.mark.parametrize( @pytest.mark.parametrize(
# "recipient_number, allowlist_number", ("recipient_number", "allowlist_number"),
# [ [
# ["+4407123-456-789", "+4407123456789"], ("+4407123-456-789", "+4407123456789"),
# ["+4407123456789", "+4407123-456-789"], ("+4407123456789", "+4407123-456-789"),
# ], ],
# ) )
# def test_validates_against_guestlist_of_international_phone_numbers( def test_validates_against_guestlist_of_international_phone_numbers(
# recipient_number, allowlist_number recipient_number, allowlist_number
# ): ):
# assert allowed_to_send_to(recipient_number, [allowlist_number]) assert allowed_to_send_to(recipient_number, [allowlist_number])
@pytest.mark.parametrize("email_address", valid_email_addresses) @pytest.mark.parametrize("email_address", valid_email_addresses)
@@ -385,16 +381,17 @@ def test_validates_against_guestlist_of_email_addresses(email_address):
@pytest.mark.parametrize( @pytest.mark.parametrize(
("phone_number", "expected_formatted"), ("phone_number", "expected_formatted"),
[ [
# ("+4407900900123", "+44 7900 900123"), # UK ("+4407900900123", "+44 7900 900123"), # UK
# ("+44(0)7900900123", "+44 7900 900123"), # UK ("+44(0)7900900123", "+44 7900 900123"), # UK
# ("+447900900123", "+44 7900 900123"), # UK ("+447900900123", "+44 7900 900123"), # UK
# TODO these should be fixed, but affect readability, not sendability
# ("+20-12-1234-1234", "+20 121 234 1234"), # Egypt # ("+20-12-1234-1234", "+20 121 234 1234"), # Egypt
# ("+201212341234", "+20 121 234 1234"), # Egypt # ("+201212341234", "+20 121 234 1234"), # Egypt
("+1 664 491-3434", "+1 664-491-3434"), # Montserrat ("+1 664 491-3434", "+1 664-491-3434"), # Montserrat
# ("+7 499 1231212", "+7 499 123-12-12"), # Moscow (Russia) ("+7 499 1231212", "+7 499 123-12-12"), # Moscow (Russia)
("1-202-555-0104", "(202) 555-0104"), # Washington DC (USA) ("1-202-555-0104", "(202) 555-0104"), # Washington DC (USA)
# ("+23051234567", "+230 5123 4567"), # Mauritius ("+23051234567", "+230 5123 4567"), # Mauritius
# ("+33(0)1 12345678", "+33 1 12 34 56 78"), # Paris (France) ("+33(0)1 12345678", "+33 1 12 34 56 78"), # Paris (France)
], ],
) )
def test_format_us_and_international_phone_numbers(phone_number, expected_formatted): def test_format_us_and_international_phone_numbers(phone_number, expected_formatted):
@@ -411,7 +408,7 @@ def test_format_us_and_international_phone_numbers(phone_number, expected_format
(None, ""), (None, ""),
("foo", "foo"), ("foo", "foo"),
("TeSt@ExAmPl3.com", "test@exampl3.com"), ("TeSt@ExAmPl3.com", "test@exampl3.com"),
# ("+4407900 900 123", "+447900900123"), ("+4407900 900 123", "+447900900123"),
("+1 800 555 5555", "+18005555555"), ("+1 800 555 5555", "+18005555555"),
], ],
) )