mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-11 10:28:41 -04:00
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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 %}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
Generated
+288
-483
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -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
File diff suppressed because it is too large
Load Diff
+5
-5
@@ -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"
|
||||||
|
|||||||
@@ -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"),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user