diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 8f192c4a0..db78bfbcb 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -20,7 +20,6 @@
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
- "bungcip.better-toml",
"batisteo.vscode-django",
"bpruitt-goddard.mermaid-markdown-syntax-highlighting",
"eamodio.gitlens",
@@ -28,10 +27,12 @@
"hashicorp.terraform",
"mhutchie.git-graph",
"monosans.djlint",
- "ms-python.python",
- "ms-python.vscode-pylance",
"mrorz.language-gettext",
- "qwtel.sqlite-viewer"
+ "ms-python.python",
+ "ms-python.black-formatter",
+ "ms-python.flake8",
+ "qwtel.sqlite-viewer",
+ "tamasfe.even-better-toml"
]
}
}
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index e9625fb06..0eff8aa05 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -22,6 +22,21 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
+ - uses: actions/setup-python@v4
+ with:
+ python-version-file: .github/workflows/.python-version
+ cache: pip
+ cache-dependency-path: "**/pyproject.toml"
+
+ - name: Write python packages to file
+ run: |
+ python -m venv .venv
+ source .venv/bin/activate
+ pip install pipdeptree
+ pip install -e .
+ pipdeptree
+ pipdeptree >> benefits/static/requirements.txt
+
- name: Write commit SHA to file
run: echo "${{ github.sha }}" >> benefits/static/sha.txt
diff --git a/.github/workflows/tests-pytest.yml b/.github/workflows/tests-pytest.yml
index 0817edf99..531c318c3 100644
--- a/.github/workflows/tests-pytest.yml
+++ b/.github/workflows/tests-pytest.yml
@@ -5,6 +5,14 @@ on: [push, pull_request]
jobs:
pytest:
runs-on: ubuntu-latest
+ permissions:
+ # Gives the action the necessary permissions for publishing new
+ # comments in pull requests.
+ pull-requests: write
+ # Gives the action the necessary permissions for pushing data to the
+ # python-coverage-comment-action branch, and for editing existing
+ # comments (to avoid publishing multiple comments in the same PR)
+ contents: write
steps:
- name: Check out code
uses: actions/checkout@v4
@@ -34,3 +42,10 @@ jobs:
with:
name: coverage-report
path: benefits/static/coverage
+
+ - name: Coverage comment
+ uses: py-cov-action/python-coverage-comment-action@v3
+ with:
+ GITHUB_TOKEN: ${{ github.token }}
+ MINIMUM_GREEN: 90
+ MINIMUM_ORANGE: 80
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e73768a6b..0f67df768 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -15,13 +15,13 @@ default_stages:
repos:
- repo: https://github.com/compilerla/conventional-pre-commit
- rev: v2.4.0
+ rev: v3.0.0
hooks:
- id: conventional-pre-commit
stages: [commit-msg]
- repo: https://github.com/pre-commit/pre-commit-hooks
- rev: v4.4.0
+ rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: mixed-line-ending
@@ -34,7 +34,7 @@ repos:
args: ["--maxkb=1500"]
- repo: https://github.com/psf/black
- rev: 23.9.1
+ rev: 23.11.0
hooks:
- id: black
types:
@@ -55,12 +55,12 @@ repos:
files: .py$
- repo: https://github.com/pre-commit/mirrors-prettier
- rev: v3.0.3
+ rev: v3.1.0
hooks:
- id: prettier
types_or: [javascript, css]
- repo: https://github.com/Riverside-Healthcare/djLint
- rev: v1.32.1
+ rev: v1.34.0
hooks:
- id: djlint-django
diff --git a/.vscode/settings.json b/.vscode/settings.json
index b3ff8e30d..78ec3acc2 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -12,17 +12,10 @@
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true,
"[python]": {
- "editor.defaultFormatter": "ms-python.python"
+ "editor.defaultFormatter": "ms-python.black-formatter"
},
- "python.formatting.provider": "black",
"python.languageServer": "Pylance",
- "python.linting.enabled": true,
- "python.linting.flake8Enabled": true,
- "python.testing.pytestArgs": [
- "tests/pytest",
- "--import-mode=importlib",
- "--no-migrations"
- ],
+ "python.testing.pytestArgs": ["tests/pytest"],
"python.testing.pytestEnabled": true,
"python.testing.unittestEnabled": false,
"[terraform]": {
diff --git a/appcontainer/nginx.conf b/appcontainer/nginx.conf
index 585f6b669..27f322248 100644
--- a/appcontainer/nginx.conf
+++ b/appcontainer/nginx.conf
@@ -52,7 +52,7 @@ http {
# 404 known scraping file targets
# case-insensitive regex matches the given file extension anywhere in the request path
- location ~* /.*\.(ash|asp|axd|cgi|com|env|json|php|ping|xml|ya?ml) {
+ location ~* /.*\.(ash|asp|axd|cgi|com|db|env|json|php|ping|sqlite|xml|ya?ml) {
access_log off;
log_not_found off;
return 404;
diff --git a/benefits/core/migrations/0002_data.py b/benefits/core/migrations/0002_data.py
index e95b5c781..2b2b6def1 100644
--- a/benefits/core/migrations/0002_data.py
+++ b/benefits/core/migrations/0002_data.py
@@ -28,13 +28,25 @@ def load_data(app, *args, **kwargs):
sbmtd_senior_type = EligibilityType.objects.create(
name="senior", label="Senior Discount (SBMTD)", group_id=os.environ.get("SBMTD_SENIOR_GROUP_ID", "group4")
)
+ sbmtd_mobility_pass_type = EligibilityType.objects.create(
+ name="mobility_pass",
+ label="Mobility Pass Discount (SBMTD)",
+ group_id=os.environ.get("SBMTD_MOBILITY_PASS_GROUP_ID", "group5"),
+ )
PemData = app.get_model("core", "PemData")
- server_public_key = PemData.objects.create(
+ mst_server_public_key = PemData.objects.create(
label="Eligibility server public key",
remote_url=os.environ.get(
- "SERVER_PUBLIC_KEY_URL", "https://raw.githubusercontent.com/cal-itp/eligibility-server/dev/keys/server.pub"
+ "MST_SERVER_PUBLIC_KEY_URL", "https://raw.githubusercontent.com/cal-itp/eligibility-server/dev/keys/server.pub"
+ ),
+ )
+
+ sbmtd_server_public_key = PemData.objects.create(
+ label="Eligibility server public key",
+ remote_url=os.environ.get(
+ "SBMTD_SERVER_PUBLIC_KEY_URL", "https://raw.githubusercontent.com/cal-itp/eligibility-server/dev/keys/server.pub"
),
)
@@ -192,7 +204,7 @@ def load_data(app, *args, **kwargs):
api_auth_header=os.environ.get("COURTESY_CARD_VERIFIER_API_AUTH_HEADER", "X-Server-API-Key"),
api_auth_key=os.environ.get("COURTESY_CARD_VERIFIER_API_AUTH_KEY", "server-auth-token"),
eligibility_type=mst_courtesy_card_type,
- public_key=server_public_key,
+ public_key=mst_server_public_key,
jwe_cek_enc=os.environ.get("COURTESY_CARD_VERIFIER_JWE_CEK_ENC", "A256CBC-HS512"),
jwe_encryption_alg=os.environ.get("COURTESY_CARD_VERIFIER_JWE_ENCRYPTION_ALG", "RSA-OAEP"),
jws_signing_alg=os.environ.get("COURTESY_CARD_VERIFIER_JWS_SIGNING_ALG", "RS256"),
@@ -220,6 +232,23 @@ def load_data(app, *args, **kwargs):
start_template="eligibility/start--senior.html",
)
+ sbmtd_mobility_pass_verifier = EligibilityVerifier.objects.create(
+ name=os.environ.get("MOBILITY_PASS_VERIFIER_NAME", "Eligibility Server Verifier"),
+ active=os.environ.get("MOBILITY_PASS_VERIFIER_ACTIVE", "True").lower() == "true",
+ api_url=os.environ.get("MOBILITY_PASS_VERIFIER_API_URL", "http://server:8000/verify"),
+ api_auth_header=os.environ.get("MOBILITY_PASS_VERIFIER_API_AUTH_HEADER", "X-Server-API-Key"),
+ api_auth_key=os.environ.get("MOBILITY_PASS_VERIFIER_API_AUTH_KEY", "server-auth-token"),
+ eligibility_type=sbmtd_mobility_pass_type,
+ public_key=sbmtd_server_public_key,
+ jwe_cek_enc=os.environ.get("MOBILITY_PASS_VERIFIER_JWE_CEK_ENC", "A256CBC-HS512"),
+ jwe_encryption_alg=os.environ.get("MOBILITY_PASS_VERIFIER_JWE_ENCRYPTION_ALG", "RSA-OAEP"),
+ jws_signing_alg=os.environ.get("MOBILITY_PASS_VERIFIER_JWS_SIGNING_ALG", "RS256"),
+ auth_provider=None,
+ selection_label_template="eligibility/includes/selection-label--sbmtd-mobility-pass.html",
+ start_template="eligibility/start--sbmtd-mobility-pass.html",
+ form_class="benefits.eligibility.forms.SBMTDMobilityPass",
+ )
+
PaymentProcessor = app.get_model("core", "PaymentProcessor")
mst_payment_processor = PaymentProcessor.objects.create(
@@ -337,9 +366,10 @@ def load_data(app, *args, **kwargs):
index_template="core/index--sbmtd.html",
eligibility_index_template="eligibility/index--sbmtd.html",
enrollment_success_template="enrollment/success--sbmtd.html",
+ help_template="core/includes/help--sbmtd.html",
)
- sbmtd_agency.eligibility_types.set([sbmtd_senior_type])
- sbmtd_agency.eligibility_verifiers.set([sbmtd_senior_verifier])
+ sbmtd_agency.eligibility_types.set([sbmtd_senior_type, sbmtd_mobility_pass_type])
+ sbmtd_agency.eligibility_verifiers.set([sbmtd_senior_verifier, sbmtd_mobility_pass_verifier])
class Migration(migrations.Migration):
diff --git a/benefits/core/templates/core/includes/help--sbmtd.html b/benefits/core/templates/core/includes/help--sbmtd.html
new file mode 100644
index 000000000..d4f5c5a73
--- /dev/null
+++ b/benefits/core/templates/core/includes/help--sbmtd.html
@@ -0,0 +1,8 @@
+{% load i18n %}
+
+
{% translate "What is a Mobility Pass?" %}
+
+ {% blocktranslate trimmed %}
+ The Santa Barbara Metropolitan Transit District issues Mobility Pass cards to eligible riders. This transit benefit may need to be renewed in the future based on the expiration date of the Mobility Pass. Learn more at the SBMTD Fares & Passes.
+ {% endblocktranslate %}
+
diff --git a/benefits/eligibility/forms.py b/benefits/eligibility/forms.py
index 02844377c..a1a4f675f 100644
--- a/benefits/eligibility/forms.py
+++ b/benefits/eligibility/forms.py
@@ -156,3 +156,26 @@ def __init__(self, *args, **kwargs):
*args,
**kwargs,
)
+
+
+class SBMTDMobilityPass(EligibilityVerificationForm):
+ """EligibilityVerification form for the SBMTD Mobility Pass."""
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(
+ title=_("Agency card information"),
+ headline=_("Let’s see if we can confirm your eligibility."),
+ blurb=_("Please input your Mobility Pass number and last name below to confirm your eligibility."),
+ name_label=_("Last name (as it appears on Mobility Pass card)"),
+ name_placeholder="Garcia",
+ name_help_text=_("We use this to help confirm your Mobility Pass."),
+ sub_label=_("SBMTD Mobility Pass number"),
+ sub_help_text=_("This is a 4-digit number on the back of your card."),
+ sub_placeholder="1234",
+ name_max_length=255,
+ sub_input_mode="numeric",
+ sub_max_length=4,
+ sub_pattern=r"\d{4}",
+ *args,
+ **kwargs,
+ )
diff --git a/benefits/eligibility/templates/eligibility/includes/media-item--idcardcheck--start--sbmtd-mobility-pass.html b/benefits/eligibility/templates/eligibility/includes/media-item--idcardcheck--start--sbmtd-mobility-pass.html
new file mode 100644
index 000000000..2117c9f62
--- /dev/null
+++ b/benefits/eligibility/templates/eligibility/includes/media-item--idcardcheck--start--sbmtd-mobility-pass.html
@@ -0,0 +1,15 @@
+{% extends "eligibility/includes/media-item--idcardcheck.html" %}
+
+{% load i18n %}
+
+{% block heading %}
+ {% translate "Your current Mobility Pass number" %}
+{% endblock heading %}
+
+{% block body %}
+
+
+ {% translate "You do not need to have your physical card, but you will need your 4-digit Mobility Pass number on the back of the card." %}
+
+
+{% endblock body %}
diff --git a/benefits/eligibility/templates/eligibility/includes/selection-label--sbmtd-mobility-pass.html b/benefits/eligibility/templates/eligibility/includes/selection-label--sbmtd-mobility-pass.html
new file mode 100644
index 000000000..884348614
--- /dev/null
+++ b/benefits/eligibility/templates/eligibility/includes/selection-label--sbmtd-mobility-pass.html
@@ -0,0 +1,10 @@
+{% extends "eligibility/includes/selection-label.html" %}
+{% load i18n %}
+
+{% block label %}
+ {% translate "SBMTD Mobility Pass" %}
+{% endblock label %}
+
+{% block description %}
+ {% translate "This option is for people who have a current SBMTD Mobility Pass." %}
+{% endblock description %}
diff --git a/benefits/eligibility/templates/eligibility/start--sbmtd-mobility-pass.html b/benefits/eligibility/templates/eligibility/start--sbmtd-mobility-pass.html
new file mode 100644
index 000000000..ace24b397
--- /dev/null
+++ b/benefits/eligibility/templates/eligibility/start--sbmtd-mobility-pass.html
@@ -0,0 +1,25 @@
+{% extends "eligibility/start.html" %}
+{% load i18n %}
+
+{% block page-title %}
+ {% translate "Agency card overview" %}
+{% endblock page-title %}
+
+{% block headline %}
+
+
{% translate "You selected a Mobility Pass transit benefit." %}
+
+{% endblock headline %}
+
+{% block media-item %}
+ {% include "eligibility/includes/media-item--idcardcheck--start--sbmtd-mobility-pass.html" %}
+{% endblock media-item %}
+
+{% block call-to-action %}
+
+{% endblock call-to-action %}
diff --git a/benefits/enrollment/templates/enrollment/retry.html b/benefits/enrollment/templates/enrollment/retry.html
index 7cd0ea9c1..4efb402f8 100644
--- a/benefits/enrollment/templates/enrollment/retry.html
+++ b/benefits/enrollment/templates/enrollment/retry.html
@@ -26,14 +26,12 @@
{% include "core/includes/agency-links.html" %}
- {% if retry_button %}
-
-
- {% translate "Try again" as button_text %}
- {% include "core/includes/button--origin.html" with button_text=button_text %}
-
+
+
+ {% translate "Try again" as button_text %}
+ {% include "core/includes/button--origin.html" with button_text=button_text %}
- {% endif %}
+
{% endblock main-content %}
diff --git a/benefits/locale/en/LC_MESSAGES/django.po b/benefits/locale/en/LC_MESSAGES/django.po
index e2bd8568c..0457ee0a3 100644
--- a/benefits/locale/en/LC_MESSAGES/django.po
+++ b/benefits/locale/en/LC_MESSAGES/django.po
@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: https://github.com/cal-itp/benefits/issues \n"
-"POT-Creation-Date: 2023-09-11 17:23+0000\n"
+"POT-Creation-Date: 2023-11-16 23:40+0000\n"
"Language: English\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -194,6 +194,17 @@ msgid ""
"target=\"_blank\" rel=\"noopener noreferrer\">MST Riders Guide."
msgstr ""
+msgid "What is a Mobility Pass?"
+msgstr ""
+
+msgid ""
+"The Santa Barbara Metropolitan Transit District issues Mobility Pass cards "
+"to eligible riders. This transit benefit may need to be renewed in the "
+"future based on the expiration date of the Mobility Pass. Learn more at the "
+"SBMTD Fares & Passes."
+msgstr ""
+
msgid "Sign out of Login.gov"
msgstr ""
@@ -291,6 +302,23 @@ msgstr ""
msgid "This is a 5-digit number on the front and back of your card."
msgstr ""
+msgid ""
+"Please input your Mobility Pass number and last name below to confirm your "
+"eligibility."
+msgstr ""
+
+msgid "Last name (as it appears on Mobility Pass card)"
+msgstr ""
+
+msgid "We use this to help confirm your Mobility Pass."
+msgstr ""
+
+msgid "SBMTD Mobility Pass number"
+msgstr ""
+
+msgid "This is a 4-digit number on the back of your card."
+msgstr ""
+
msgid "Your contactless card details"
msgstr ""
@@ -309,6 +337,14 @@ msgid ""
"number. The number starts with a number sign (#) followed by five digits."
msgstr ""
+msgid "Your current Mobility Pass number"
+msgstr ""
+
+msgid ""
+"You do not need to have your physical card, but you will need your 4-digit "
+"Mobility Pass number on the back of the card."
+msgstr ""
+
msgid "A Login.gov account with identity verification"
msgstr ""
@@ -335,21 +371,21 @@ msgstr ""
msgid "Go back"
msgstr ""
-msgid "Verify your identity with "
+msgid "Learn more about"
msgstr ""
msgctxt "image alt text"
msgid "Login.gov"
msgstr ""
+msgid "Verify your identity with "
+msgstr ""
+
msgid ""
"We use Login.gov to verify your identity to make sure you are eligible for "
"the transit benefit you selected."
msgstr ""
-msgid "Learn more about"
-msgstr ""
-
msgid "MST Courtesy Card"
msgstr ""
@@ -358,6 +394,12 @@ msgid ""
"eligibility card."
msgstr ""
+msgid "SBMTD Mobility Pass"
+msgstr ""
+
+msgid "This option is for people who have a current SBMTD Mobility Pass."
+msgstr ""
+
msgid "Older Adult"
msgstr ""
@@ -406,6 +448,9 @@ msgstr ""
msgid "Continue"
msgstr ""
+msgid "You selected a Mobility Pass transit benefit."
+msgstr ""
+
msgid "Older Adult benefit overview"
msgstr ""
diff --git a/benefits/locale/es/LC_MESSAGES/django.po b/benefits/locale/es/LC_MESSAGES/django.po
index a36b72922..e84abd0a0 100644
--- a/benefits/locale/es/LC_MESSAGES/django.po
+++ b/benefits/locale/es/LC_MESSAGES/django.po
@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: https://github.com/cal-itp/benefits/issues \n"
-"POT-Creation-Date: 2023-09-11 17:23+0000\n"
+"POT-Creation-Date: 2023-11-16 23:40+0000\n"
"Language: Español\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
@@ -259,8 +259,24 @@ msgstr ""
"califican para una serie de programas de tarifas reducidas. Este beneficio "
"de tránsito debe renovarse en el futuro en función de la fecha de "
"vencimiento de la tarjeta de cortesía. Obtenga más información en el MST Riders Guide."
+"href='https://mst.org/riders-guide/how-to-ride/courtesy-card/' "
+"target=\"_blank\" rel=\"noopener noreferrer\">MST Riders Guide."
+
+msgid "What is a Mobility Pass?"
+msgstr "¿Qué es un Mobility Pass?"
+
+msgid ""
+"The Santa Barbara Metropolitan Transit District issues Mobility Pass cards "
+"to eligible riders. This transit benefit may need to be renewed in the "
+"future based on the expiration date of the Mobility Pass. Learn more at the "
+"SBMTD Fares & Passes."
+msgstr ""
+"Santa Barbara Metropolitan Transit District emite tarjetas Mobility Pass a "
+"pasajeros que califican. Este beneficio de tránsito debe renovarse en el "
+"futuro en función de la fecha de vencimiento del Mobility Pass. Obtenga más "
+"información en el SBMTD Fares & Passes."
msgid "Sign out of Login.gov"
msgstr "Cierre sesión de Login.gov"
@@ -374,6 +390,25 @@ msgstr "Número de tarjeta de cortesía de MST"
msgid "This is a 5-digit number on the front and back of your card."
msgstr "Este es un número de 5 dígitos en el anverso y reverso de su tarjeta."
+msgid ""
+"Please input your Mobility Pass number and last name below to confirm your "
+"eligibility."
+msgstr ""
+"Ingrese el número de cuatro dígitos de su Mobility Pass y su apellido a "
+"continuación para confirmar su elegibilidad."
+
+msgid "Last name (as it appears on Mobility Pass card)"
+msgstr "Apellido (tal como aparece en la tarjeta de Mobility Pass)"
+
+msgid "We use this to help confirm your Mobility Pass."
+msgstr "Usamos esto para ayudar a confirmar su Mobility Pass."
+
+msgid "SBMTD Mobility Pass number"
+msgstr "Número de SBMTD Mobility Pass"
+
+msgid "This is a 4-digit number on the back of your card."
+msgstr "Este es un número de 4 dígitos en el reverso de su tarjeta."
+
msgid "Your contactless card details"
msgstr "Los datos de su tarjeta sin contacto"
@@ -396,6 +431,16 @@ msgstr ""
"No necesita tener su tarjeta física, pero necesitará saber el número. El "
"número comienza con un signo de número (#) seguido de cinco dígitos."
+msgid "Your current Mobility Pass number"
+msgstr "Su número actual de Mobility Pass"
+
+msgid ""
+"You do not need to have your physical card, but you will need your 4-digit "
+"Mobility Pass number on the back of the card."
+msgstr ""
+"No necesita tener su tarjeta física, pero necesitará su número de cuatro "
+"dígitos de Mobility Pass en el reverso de la tarjeta."
+
msgid "A Login.gov account with identity verification"
msgstr "Una cuenta de Login.gov con verificación de identidad"
@@ -426,13 +471,16 @@ msgstr ""
msgid "Go back"
msgstr "Volver"
-msgid "Verify your identity with "
-msgstr "Verifique su identidad con "
+msgid "Learn more about"
+msgstr "Más información sobre"
msgctxt "image alt text"
msgid "Login.gov"
msgstr "Login.gov"
+msgid "Verify your identity with "
+msgstr "Verifique su identidad con "
+
msgid ""
"We use Login.gov to verify your identity to make sure you are eligible for "
"the transit benefit you selected."
@@ -440,9 +488,6 @@ msgstr ""
"Utilizamos Login.gov para verificar su identidad para asegurarnos de que "
"seas elegible para el beneficio de tránsito que seleccionaste."
-msgid "Learn more about"
-msgstr "Más información sobre"
-
msgid "MST Courtesy Card"
msgstr "Tarjeta de cortesía de MST"
@@ -453,6 +498,14 @@ msgstr ""
"Esta opción es para personas que cuentan actualmente con una tarjeta de "
"cortesía o una tarjeta de elegibilidad MST RIDES."
+msgid "SBMTD Mobility Pass"
+msgstr ""
+
+msgid "This option is for people who have a current SBMTD Mobility Pass."
+msgstr ""
+"Esta opción es para personas que cuentan actualmente con un Mobility Pass de "
+"SBMTD."
+
msgid "Older Adult"
msgstr "Adulto mayor"
@@ -510,6 +563,9 @@ msgstr "Ha seleccionado un beneficio de tránsito de tarjeta de cortesía."
msgid "Continue"
msgstr "Continuar"
+msgid "You selected a Mobility Pass transit benefit."
+msgstr "Ha seleccionado un beneficio de tránsito de Mobility Pass."
+
msgid "Older Adult benefit overview"
msgstr "Descripción de beneficios para adultos mayores"
diff --git a/benefits/settings.py b/benefits/settings.py
index 0d274c093..fbd9e7e01 100644
--- a/benefits/settings.py
+++ b/benefits/settings.py
@@ -2,6 +2,7 @@
Django settings for benefits project.
"""
import os
+
from benefits import sentry
@@ -147,10 +148,11 @@ def _filter_empty(ls):
WSGI_APPLICATION = "benefits.wsgi.application"
+DATABASE_DIR = os.environ.get("DJANGO_DB_DIR", BASE_DIR)
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
- "NAME": "django.db",
+ "NAME": os.path.join(DATABASE_DIR, "django.db"),
}
}
diff --git a/benefits/static/css/styles.css b/benefits/static/css/styles.css
index a889df384..d3ed3a3af 100644
--- a/benefits/static/css/styles.css
+++ b/benefits/static/css/styles.css
@@ -53,6 +53,7 @@
}
html {
+ overflow-y: auto;
scroll-padding-top: 101px; /* Header Height (80px) + Skip Nav Height (21px) = 101px */
}
diff --git a/bin/init.sh b/bin/init.sh
index 2fe3fdec9..3a1e2b8bb 100755
--- a/bin/init.sh
+++ b/bin/init.sh
@@ -2,9 +2,15 @@
set -eux
# remove existing (old) database file
-# -f forces the delete (and avoids an error when the file doesn't exist)
-rm -f django.db
+if [[ ${DJANGO_DB_RESET:-true} = true ]]; then
+ # construct the path to the database file from environment or default
+ DB_DIR="${DJANGO_DB_DIR:-.}"
+ DB_FILE="${DB_DIR}/django.db"
+
+ # -f forces the delete (and avoids an error when the file doesn't exist)
+ rm -f "${DB_FILE}"
+fi
# run database migrations
@@ -14,7 +20,7 @@ python manage.py migrate
# check DJANGO_ADMIN = true, default to false if empty or unset
if [[ ${DJANGO_ADMIN:-false} = true ]]; then
- python manage.py createsuperuser
+ python manage.py createsuperuser --no-input
else
echo "superuser: Django not configured for Admin access"
fi
diff --git a/compose.yml b/compose.yml
index c5e2dea99..6d284afd5 100644
--- a/compose.yml
+++ b/compose.yml
@@ -8,7 +8,6 @@ services:
dockerfile: appcontainer/Dockerfile
image: benefits_client:latest
env_file: .env
- platform: linux/amd64
ports:
- "${DJANGO_LOCAL_PORT:-8000}:8000"
@@ -22,7 +21,6 @@ services:
entrypoint: sleep infinity
depends_on:
- server
- platform: linux/amd64
ports:
- "${DJANGO_LOCAL_PORT:-8000}:8000"
volumes:
@@ -32,7 +30,6 @@ services:
image: benefits_client:dev
entrypoint: mkdocs
command: serve --dev-addr "0.0.0.0:8001"
- platform: linux/amd64
ports:
- "8001"
volumes:
diff --git a/docs/configuration/environment-variables.md b/docs/configuration/environment-variables.md
index 80b7fac44..574ca3f4b 100644
--- a/docs/configuration/environment-variables.md
+++ b/docs/configuration/environment-variables.md
@@ -6,20 +6,6 @@ The sections below outline in more detail the application environment variables
See other topic pages in this section for more specific environment variable configurations.
-## Docker
-
-### `COMPOSE_PROJECT_NAME`
-
-!!! info "Local configuration"
-
- This setting only affects the app running on localhost
-
-!!! tldr "Docker docs"
-
- Read more at
-
-Name that Docker Compose prefixes to the project for container runs.
-
## Amplitude
!!! tldr "Amplitude API docs"
@@ -57,6 +43,28 @@ Boolean:
A list of strings representing the host/domain names that this Django site can serve.
+### `DJANGO_DB_DIR`
+
+!!! warning "Deployment configuration"
+
+ You may change this setting when deploying the app to a non-localhost domain
+
+The directory where Django creates its Sqlite database file. _Must exist and be
+writable by the Django process._
+
+By default, the base project directory (i.e. the root of the repository).
+
+### `DJANGO_DB_RESET`
+
+!!! warning "Deployment configuration"
+
+ You may change this setting when deploying the app to a non-localhost domain
+
+Boolean:
+
+- `True` (default): deletes the existing database file and runs fresh Django migrations.
+- `False`: Django uses the existing database file.
+
### `DJANGO_DEBUG`
!!! warning "Deployment configuration"
@@ -112,6 +120,42 @@ By default the application sends logs to `stdout`.
Django's primary secret, keep this safe!
+### `DJANGO_SUPERUSER_EMAIL`
+
+!!! warning "Deployment configuration"
+
+ You may change this setting when deploying the app to a non-localhost domain
+
+!!! danger "Required configuration"
+
+ This setting is required when `DJANGO_ADMIN` is `true`
+
+The email address of the Django Admin superuser created during initialization.
+
+### `DJANGO_SUPERUSER_PASSWORD`
+
+!!! warning "Deployment configuration"
+
+ You may change this setting when deploying the app to a non-localhost domain
+
+!!! danger "Required configuration"
+
+ This setting is required when `DJANGO_ADMIN` is `true`
+
+The password of the Django Admin superuser created during initialization.
+
+### `DJANGO_SUPERUSER_USERNAME`
+
+!!! warning "Deployment configuration"
+
+ You may change this setting when deploying the app to a non-localhost domain
+
+!!! danger "Required configuration"
+
+ This setting is required when `DJANGO_ADMIN` is `true`
+
+The username of the Django Admin superuser created during initialization.
+
### `DJANGO_TRUSTED_ORIGINS`
!!! warning "Deployment configuration"
diff --git a/docs/use-cases/Veterans.md b/docs/use-cases/Veterans.md
new file mode 100644
index 000000000..658d3e9e3
--- /dev/null
+++ b/docs/use-cases/Veterans.md
@@ -0,0 +1,85 @@
+# Veterans enrollment pathway
+
+## Overview
+
+This use case describes a feature in the [Cal-ITP Benefits app](https://benefits.calitp.org) that allows US veterans who use public transit to verify their veteran status and receive reduced fares when paying by contactless debit or credit card at participating transit providers in California.
+
+**Actor:** A US veteran who uses public transit in California. For benefit eligibility, a veteran is defined as “a person who served in the active military, naval, or air service, and was discharged or released therefrom under conditions other than dishonorable.” ([source](https://www.ssa.gov/OP_Home/comp2/D-USC-38.html))
+
+**Goal:** To verify a transit rider’s veteran status and enable the rider to receive reduced fares when paying by contactless debit or credit card.
+
+**Precondition:** The California transit provider delivering fixed route service has installed and tested validator hardware necessary to collect fares using contactless payment on bus or rail lines, and the provider has a policy to offer a transit discount for US veterans.
+
+## Basic flow
+
+```mermaid
+sequenceDiagram
+%% Veteran Enrollment Pathway
+ actor Transit Rider
+ participant Benefits as Benefits app
+ participant IdG as Identity Gateway
+ participant Login.gov
+ participant VA.gov
+ participant Littlepay
+Transit Rider->>Benefits: visits benefits.calitp.org
+ activate Benefits
+Benefits-->>IdG: eligibility verification
+ activate IdG
+Transit Rider->>Login.gov: account authentication
+ activate Login.gov
+IdG-->>Login.gov: requests required PII
+ activate Login.gov
+ Note right of Login.gov: transit rider first name
transit rider last name
home address
date of birth
+Login.gov-->>IdG: returns required PII
+ deactivate Login.gov
+IdG-->>VA.gov: check veteran status
+ activate VA.gov
+VA.gov-->>IdG: return veteran status
+ deactivate VA.gov
+IdG-->>Benefits: eligibility response
+ deactivate IdG
+ deactivate Login.gov
+Benefits-->>Littlepay: payment enrollment start
+ activate Littlepay
+Transit Rider->>Littlepay: provides debit or credit card details
+Littlepay-->>Benefits: payment method enrollment confirmation
+ deactivate Littlepay
+ deactivate Benefits
+```
+
+1. The transit rider visits the web application at [benefits.calitp.org](https://benefits.calitp.org) in a browser on their desktop computer.
+2. The transit rider chooses the transit provider that serves their area.
+3. The transit rider selects the option to receive a reduced fare for veterans.
+4. The transit rider authenticates with their existing [Login.gov](https://Login.gov) account or creates a [Login.gov](https://Login.gov) account if they don’t have one.
+5. The Cal-ITP Benefits app interfaces with the [California Department of Technology](https://cdt.ca.gov/) Identity Gateway (IdG) to verify benefit eligibility. The IdG requests the required personal information to verify veteran status from [Login.gov](https://Login.gov).
+6. The IdG utilizes the [Veteran Confirmation API](https://developer.va.gov/explore/api/veteran-confirmation) provided by the US Department of Veterans Affairs to determine the rider’s veteran status.
+7. The IdG passes the response from [VA.gov](https://VA.gov) as veteran status = TRUE to the Cal-ITP Benefits app to indicate the person is eligible for a benefit.
+8. The transit rider provides the debit or credit card details they use to pay for transit to Littlepay, the payment processor that facilitates transit fare collection.
+9. The app registers the veteran benefit with the transit rider’s debit or credit card.
+
+## Alternative flows
+
+* If the transit rider does not have a desktop computer, they can open the web application at [benefits.calitp.org](https://benefits.calitp.org) in a mobile browser on their iOS or Android tablet or mobile device to complete enrollment using the basic flow.
+* Suppose the transit rider cannot authenticate with [Login.gov](https://Login.gov), or will not create an account. In either case, the app cannot determine their veteran status and, thus, cannot enroll their contactless debit or credit card for a reduced fare.
+* If [VA.gov](http://VA.gov) determines the person does not meet the definition of a veteran (IdG returns a veteran status of FALSE), the Cal-ITP Benefits app will not allow the transit rider to enroll their contactless debit or credit card for a reduced fare.
+* If the debit or credit card expires or is canceled by the issuer, the transit rider must repeat the basic flow to register a new debit or credit card.
+* If the transit rider uses more than one debit or credit card to pay for transit, they repeat the basic flow for each card.
+
+## Postcondition
+
+The transit rider receives a fare reduction each time they use the debit or credit card they registered to pay for transit rides. The number of times they can use the card to pay for transit is unlimited and the benefit never expires.
+
+## Benefits
+
+* The transit rider no longer needs cash to pay for transit rides.
+* The transit rider doesn’t have to lock up funds on a closed-loop card offered by the transit provider.
+* The transit rider pays for transit rides with their debit or credit card, just as they pay for any other good or service that accepts contactless payment.
+* The transit rider can enroll in a transit benefit from home when convenient; they do not have to visit a transit provider in person.
+* Secure state and federal solutions manage the transit rider’s personal identifiable information (PII): [Login.gov](https://Login.gov) and the California Department of Technology Identity Gateway (IdG). Transit riders do not have to share personal information with local transit agencies.
+* Benefits enrollment takes minutes rather than days or weeks.
+
+## Example scenario
+
+A veteran in California uses public transit regularly. They don’t have a car and depend on buses to get to appointments and do errands that take too long to use their bicycle. They receive a 50% fare reduction for being a US veteran but have to pay for transit rides using the closed loop card provided by the agency to receive the reduced fare. It’s frustrating and inconvenient to reload this agency card in $10 payments every week, especially because they sometimes need the money tied up on the card to pay for groceries and medication.
+
+The transit provider serving their part of California implements contactless payments on fixed bus routes throughout the service area. This rider uses [benefits.calitp.org](https://benefits.calitp.org) to confirm their veteran status and register their debit card for reduced fares. They tap to pay when boarding buses in their area and are automatically charged the reduced fare. They no longer need to carry one card to pay for transit and another for other purchases. Best of all, they have complete access to all funds in their weekly budget. If food and medication costs are higher one week, they can allocate additional funds to those areas and ride transit less.
diff --git a/pyproject.toml b/pyproject.toml
index 780a8a3c4..2906458c6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,11 +8,11 @@ classifiers = ["Programming Language :: Python :: 3 :: Only"]
requires-python = ">=3.9"
dependencies = [
"Authlib==1.2.1",
- "Django==4.2.5",
+ "Django==4.2.7",
"django-csp==3.7",
"eligibility-api==2023.9.1",
"requests==2.31.0",
- "sentry-sdk==1.31.0",
+ "sentry-sdk==1.35.0",
"six==1.16.0",
]
@@ -24,8 +24,8 @@ dev = [
"pre-commit",
]
test = [
+ "coverage",
"pytest",
- "pytest-cov",
"pytest-django",
"pytest-mock",
"pytest-socket",
@@ -46,9 +46,12 @@ target-version = ['py311']
include = '\.pyi?$'
[tool.coverage.run]
+branch = true
+relative_files = true
omit = [
"benefits/core/migrations/*"
]
+source = ["benefits"]
[tool.djlint]
ignore = "H017,H031"
diff --git a/terraform/app_service.tf b/terraform/app_service.tf
index c1d775640..37db1d5ac 100644
--- a/terraform/app_service.tf
+++ b/terraform/app_service.tf
@@ -10,6 +10,10 @@ resource "azurerm_service_plan" "main" {
}
}
+locals {
+ data_mount = "/home/calitp/app/data"
+}
+
resource "azurerm_linux_web_app" "main" {
name = "AS-CDT-PUB-VIP-CALITP-${local.env_letter}-001"
location = data.azurerm_resource_group.main.location
@@ -62,10 +66,15 @@ resource "azurerm_linux_web_app" "main" {
"REQUESTS_READ_TIMEOUT" = "${local.secret_prefix}requests-read-timeout)",
# Django settings
- "DJANGO_ADMIN" = (local.is_prod || local.is_test) ? null : "${local.secret_prefix}django-admin)",
- "DJANGO_ALLOWED_HOSTS" = "${local.secret_prefix}django-allowed-hosts)",
- "DJANGO_DEBUG" = local.is_prod ? null : "${local.secret_prefix}django-debug)",
- "DJANGO_LOG_LEVEL" = "${local.secret_prefix}django-log-level)",
+ "DJANGO_ADMIN" = "${local.secret_prefix}django-admin)",
+ "DJANGO_ALLOWED_HOSTS" = "${local.secret_prefix}django-allowed-hosts)",
+ "DJANGO_DB_DIR" = "${local.secret_prefix}django-db-dir)",
+ "DJANGO_DB_RESET" = "${local.secret_prefix}django-db-reset)",
+ "DJANGO_DEBUG" = local.is_prod ? null : "${local.secret_prefix}django-debug)",
+ "DJANGO_LOG_LEVEL" = "${local.secret_prefix}django-log-level)",
+ "DJANGO_SUPERUSER_EMAIL" = "${local.secret_prefix}django-superuser-email)",
+ "DJANGO_SUPERUSER_PASSWORD" = "${local.secret_prefix}django-superuser-password)",
+ "DJANGO_SUPERUSER_USERNAME" = "${local.secret_prefix}django-superuser-username)",
"DJANGO_RECAPTCHA_SECRET_KEY" = local.is_dev ? null : "${local.secret_prefix}django-recaptcha-secret-key)",
"DJANGO_RECAPTCHA_SITE_KEY" = local.is_dev ? null : "${local.secret_prefix}django-recaptcha-site-key)",
@@ -87,9 +96,11 @@ resource "azurerm_linux_web_app" "main" {
"MST_COURTESY_CARD_GROUP_ID" = "${local.secret_prefix}mst-courtesy-card-group-id)"
"SACRT_SENIOR_GROUP_ID" = "${local.secret_prefix}sacrt-senior-group-id)"
"SBMTD_SENIOR_GROUP_ID" = "${local.secret_prefix}sbmtd-senior-group-id)",
+ "SBMTD_MOBILITY_PASS_GROUP_ID" = "${local.secret_prefix}sbmtd-mobility-pass-group-id)"
"CLIENT_PRIVATE_KEY" = "${local.secret_prefix}client-private-key)"
"CLIENT_PUBLIC_KEY" = "${local.secret_prefix}client-public-key)"
- "SERVER_PUBLIC_KEY_URL" = "${local.secret_prefix}server-public-key-url)"
+ "MST_SERVER_PUBLIC_KEY_URL" = "${local.secret_prefix}mst-server-public-key-url)"
+ "SBMTD_SERVER_PUBLIC_KEY_URL" = "${local.secret_prefix}sbmtd-server-public-key-url)"
"MST_PAYMENT_PROCESSOR_CLIENT_CERT" = "${local.secret_prefix}mst-payment-processor-client-cert)"
"MST_PAYMENT_PROCESSOR_CLIENT_CERT_PRIVATE_KEY" = "${local.secret_prefix}mst-payment-processor-client-cert-private-key)"
"MST_PAYMENT_PROCESSOR_CLIENT_CERT_ROOT_CA" = "${local.secret_prefix}mst-payment-processor-client-cert-root-ca)"
@@ -149,6 +160,14 @@ resource "azurerm_linux_web_app" "main" {
"SBMTD_PAYMENT_PROCESSOR_CARD_TOKENIZE_URL" = "${local.secret_prefix}sbmtd-payment-processor-card-tokenize-url)"
"SBMTD_PAYMENT_PROCESSOR_CARD_TOKENIZE_FUNC" = "${local.secret_prefix}sbmtd-payment-processor-card-tokenize-func)"
"SBMTD_PAYMENT_PROCESSOR_CARD_TOKENIZE_ENV" = "${local.secret_prefix}sbmtd-payment-processor-card-tokenize-env)"
+ "MOBILITY_PASS_VERIFIER_NAME" = "${local.secret_prefix}mobility-pass-verifier-name)"
+ "MOBILITY_PASS_VERIFIER_ACTIVE" = "${local.secret_prefix}mobility-pass-verifier-active)"
+ "MOBILITY_PASS_VERIFIER_API_URL" = "${local.secret_prefix}mobility-pass-verifier-api-url)"
+ "MOBILITY_PASS_VERIFIER_API_AUTH_HEADER" = "${local.secret_prefix}mobility-pass-verifier-api-auth-header)"
+ "MOBILITY_PASS_VERIFIER_API_AUTH_KEY" = "${local.secret_prefix}mobility-pass-verifier-api-auth-key)"
+ "MOBILITY_PASS_VERIFIER_JWE_CEK_ENC" = "${local.secret_prefix}mobility-pass-verifier-jwe-cek-enc)"
+ "MOBILITY_PASS_VERIFIER_JWE_ENCRYPTION_ALG" = "${local.secret_prefix}mobility-pass-verifier-jwe-encryption-alg)"
+ "MOBILITY_PASS_VERIFIER_JWS_SIGNING_ALG" = "${local.secret_prefix}mobility-pass-verifier-jws-signing-alg)"
"MST_AGENCY_SHORT_NAME" = "${local.secret_prefix}mst-agency-short-name)"
"MST_AGENCY_LONG_NAME" = "${local.secret_prefix}mst-agency-long-name)"
"MST_AGENCY_JWS_SIGNING_ALG" = "${local.secret_prefix}mst-agency-jws-signing-alg)"
@@ -164,6 +183,15 @@ resource "azurerm_linux_web_app" "main" {
"SBMTD_AGENCY_JWS_SIGNING_ALG" = "${local.secret_prefix}sbmtd-agency-jws-signing-alg)"
}
+ storage_account {
+ access_key = azurerm_storage_account.main.primary_access_key
+ account_name = azurerm_storage_account.main.name
+ name = "benefits-data"
+ type = "AzureFiles"
+ share_name = azurerm_storage_share.data.name
+ mount_path = local.data_mount
+ }
+
lifecycle {
prevent_destroy = true
ignore_changes = [tags]
diff --git a/terraform/restart-app.sh b/terraform/restart-app.sh
new file mode 100755
index 000000000..efe8fd5a9
--- /dev/null
+++ b/terraform/restart-app.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+set -e
+
+if [ $# -ne 1 ]; then
+ echo "Usage: $0 D|T|P"
+ exit 1
+fi
+
+ENV="$1"
+APP="AS-CDT-PUB-VIP-CALITP-$ENV-001"
+RG="RG-CDT-PUB-VIP-CALITP-$ENV-001"
+NOW=$(date --utc)
+
+az webapp config appsettings set --name "$APP" --resource-group "$RG" --settings change_me_to_refresh_secrets="$NOW"
diff --git a/terraform/secrets/file.sh b/terraform/secrets/file.sh
old mode 100644
new mode 100755
diff --git a/terraform/secrets/read.sh b/terraform/secrets/read.sh
old mode 100644
new mode 100755
diff --git a/terraform/secrets/value.sh b/terraform/secrets/value.sh
old mode 100644
new mode 100755
diff --git a/terraform/storage.tf b/terraform/storage.tf
index 04761fbd7..b69c44159 100644
--- a/terraform/storage.tf
+++ b/terraform/storage.tf
@@ -18,8 +18,20 @@ resource "azurerm_storage_account" "main" {
}
}
-
lifecycle {
ignore_changes = [tags]
}
}
+
+resource "azurerm_storage_share" "data" {
+ name = "benefits-data"
+ storage_account_name = azurerm_storage_account.main.name
+ quota = 5
+ enabled_protocol = "SMB"
+ acl {
+ id = "benefits-data-rwdl"
+ access_policy {
+ permissions = "rwdl"
+ }
+ }
+}
diff --git a/tests/cypress/package-lock.json b/tests/cypress/package-lock.json
index 4038eb226..e7d5945d3 100644
--- a/tests/cypress/package-lock.json
+++ b/tests/cypress/package-lock.json
@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "AGPL-3.0-or-later",
"devDependencies": {
- "cypress": "^13.2.0"
+ "cypress": "^13.5.1"
}
},
"node_modules/@colors/colors": {
@@ -536,9 +536,9 @@
}
},
"node_modules/cypress": {
- "version": "13.2.0",
- "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.2.0.tgz",
- "integrity": "sha512-AvDQxBydE771GTq0TR4ZUBvv9m9ffXuB/ueEtpDF/6gOcvFR96amgwSJP16Yhqw6VhmwqspT5nAGzoxxB+D89g==",
+ "version": "13.5.1",
+ "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.5.1.tgz",
+ "integrity": "sha512-yqLViT0D/lPI8Kkm7ciF/x/DCK/H/DnogdGyiTnQgX4OVR2aM30PtK+kvklTOD1u3TuItiD9wUQAF8EYWtyZug==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
@@ -2357,9 +2357,9 @@
}
},
"cypress": {
- "version": "13.2.0",
- "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.2.0.tgz",
- "integrity": "sha512-AvDQxBydE771GTq0TR4ZUBvv9m9ffXuB/ueEtpDF/6gOcvFR96amgwSJP16Yhqw6VhmwqspT5nAGzoxxB+D89g==",
+ "version": "13.5.1",
+ "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.5.1.tgz",
+ "integrity": "sha512-yqLViT0D/lPI8Kkm7ciF/x/DCK/H/DnogdGyiTnQgX4OVR2aM30PtK+kvklTOD1u3TuItiD9wUQAF8EYWtyZug==",
"dev": true,
"requires": {
"@cypress/request": "^3.0.0",
diff --git a/tests/cypress/package.json b/tests/cypress/package.json
index 590196d48..d667c439d 100644
--- a/tests/cypress/package.json
+++ b/tests/cypress/package.json
@@ -12,6 +12,6 @@
"license": "AGPL-3.0-or-later",
"private": true,
"devDependencies": {
- "cypress": "^13.2.0"
+ "cypress": "^13.5.1"
}
}
diff --git a/tests/pytest/core/test_models.py b/tests/pytest/core/test_models.py
index 1ba7f2854..6df589ce0 100644
--- a/tests/pytest/core/test_models.py
+++ b/tests/pytest/core/test_models.py
@@ -121,7 +121,7 @@ def test_EligibilityVerifier_str(model_EligibilityVerifier):
assert str(model_EligibilityVerifier) == model_EligibilityVerifier.name
-class TestFormClass:
+class SampleFormClass:
"""A class for testing EligibilityVerifier form references."""
def __init__(self, *args, **kwargs):
@@ -131,14 +131,14 @@ def __init__(self, *args, **kwargs):
@pytest.mark.django_db
def test_EligibilityVerifier_form_instance(model_EligibilityVerifier):
- model_EligibilityVerifier.form_class = f"{__name__}.TestFormClass"
+ model_EligibilityVerifier.form_class = f"{__name__}.SampleFormClass"
model_EligibilityVerifier.save()
args = (1, "2")
kwargs = {"one": 1, "two": "2"}
form_instance = model_EligibilityVerifier.form_instance(*args, **kwargs)
- assert isinstance(form_instance, TestFormClass)
+ assert isinstance(form_instance, SampleFormClass)
assert form_instance.args == args
assert form_instance.kwargs == kwargs
@@ -329,16 +329,18 @@ def test_TransitAgency_by_slug_nonmatching():
@pytest.mark.django_db
def test_TransitAgency_all_active(model_TransitAgency):
- assert TransitAgency.objects.count() == 1
+ count = TransitAgency.objects.count()
+ assert count >= 1
inactive_agency = TransitAgency.by_id(model_TransitAgency.id)
inactive_agency.pk = None
inactive_agency.active = False
inactive_agency.save()
- assert TransitAgency.objects.count() == 2
+ assert TransitAgency.objects.count() == count + 1
result = TransitAgency.all_active()
- assert len(result) == 1
+ assert len(result) > 0
assert model_TransitAgency in result
+ assert inactive_agency not in result
diff --git a/tests/pytest/eligibility/test_views.py b/tests/pytest/eligibility/test_views.py
index f7c6e3df5..3a8a430e1 100644
--- a/tests/pytest/eligibility/test_views.py
+++ b/tests/pytest/eligibility/test_views.py
@@ -53,7 +53,7 @@ def invalid_form_data():
return {"invalid": "data"}
-class TestVerificationForm(EligibilityVerificationForm):
+class SampleVerificationForm(EligibilityVerificationForm):
def __init__(self, *args, **kwargs):
super().__init__(
"title",
@@ -72,7 +72,7 @@ def __init__(self, *args, **kwargs):
@pytest.fixture
def model_EligibilityVerifier_with_form_class(mocker, model_EligibilityVerifier):
- model_EligibilityVerifier.form_class = f"{__name__}.TestVerificationForm"
+ model_EligibilityVerifier.form_class = f"{__name__}.SampleVerificationForm"
model_EligibilityVerifier.save()
mocker.patch("benefits.eligibility.views.session.verifier", return_value=model_EligibilityVerifier)
return model_EligibilityVerifier
diff --git a/tests/pytest/run.sh b/tests/pytest/run.sh
index afc4c5aff..8d06749e6 100755
--- a/tests/pytest/run.sh
+++ b/tests/pytest/run.sh
@@ -1,8 +1,10 @@
#!/usr/bin/env bash
set -eu
-pytest --cov=benefits --cov-branch --import-mode=importlib --no-migrations
+# run normal pytests
+coverage run -m pytest
# clean out old coverage results
rm -rf benefits/static/coverage
+
coverage html --directory benefits/static/coverage