Skip to content

Commit

Permalink
New user wizard tests (#959)
Browse files Browse the repository at this point in the history
* Move account tests to a folder

* Add new user flow tests

* Added more tests to check for conditional buttons

* Add comments to tests

* Update test password
  • Loading branch information
okaycj authored Apr 12, 2022
1 parent 77c74c0 commit c4c8f0a
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .isort.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ include_trailing_comma=True
force_grid_wrap=0
use_parentheses=True
line_length=88
known_third_party=ace_overlay,bitfield,boto3,botocore,celery,ciso8601,dateutil,django,django_countries,django_dynamic_fixture,django_filters,django_pandas,docker,fleep,google,guardian,inflection,invoke,kombu,lark,localflavor,model_utils,more_itertools,multiselectfield,pandas,parameterized,psycopg2,pydenticon,pyotp,pytz,qrcode,requests,rest_framework,rest_framework_json_api,rest_framework_nested,revproxy,sendgrid,sgbackend,storages,transitions
known_third_party=ace_overlay,bitfield,boto3,botocore,bs4,celery,ciso8601,dateutil,django,django_countries,django_dynamic_fixture,django_filters,django_pandas,docker,fleep,google,guardian,inflection,invoke,kombu,lark,localflavor,model_utils,more_itertools,multiselectfield,pandas,parameterized,psycopg2,pydenticon,pyotp,pytz,qrcode,requests,rest_framework,rest_framework_json_api,rest_framework_nested,revproxy,sendgrid,sgbackend,storages,transitions
4 changes: 2 additions & 2 deletions accounts/templates/accounts/_account-navigation.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<a class="{% if current_page == 'demographic-update' %} active {% endif %} btn btn-default btn-md btn-block" href="{% url 'web:demographic-data-update' %}"><strong>{% trans "Demographic Survey" %}</strong><br>{% trans "Tell us more about yourself." %}</a>
<a class="{% if current_page == 'children-list' %} active {% endif %} btn btn-default btn-md btn-block" href="{% url 'web:children-list' %}"><strong>{% trans "Children Information" %}</strong><br>{% trans "Add or edit participant information." %}</a>
{% if request.session.study_name %}
<a class="btn btn-default btn-md btn-block btn-primary{% if not has_study_child or not user.has_demographics %} disabled{% endif %}" href="{% url 'web:study-detail' uuid=request.session.study_uuid %}"><strong>{% trans "Continue to Study" %}</strong><br>{% trans "Go on to" %} "{{ request.session.study_name|truncatechars:40 }}".</a>
<a class="btn-has-study btn btn-default btn-md btn-block btn-primary{% if not has_study_child or not user.has_demographics %} disabled{% endif %}" href="{% url 'web:study-detail' uuid=request.session.study_uuid %}"><strong>{% trans "Continue to Study" %}</strong><br>{% trans "Go on to" %} "{{ request.session.study_name|truncatechars:40 }}".</a>
{% endif %}
<a class="btn btn-default btn-md btn-block {% if not request.session.study_name %}btn-primary{% endif %}{% if not user.has_any_child or not user.has_demographics %} disabled{% endif %}" href="{% url 'web:studies-list' %}"><strong>{% if request.session.study_name %}{% trans "Find Another Study" %}{% else %}{% trans "Find a Study Now" %}{% endif %}</strong><br>{% trans "See all available studies." %}</a>
<a class="btn-study-list btn btn-default btn-md btn-block {% if not request.session.study_name %}btn-primary{% endif %}{% if not user.has_any_child or not user.has_demographics %} disabled{% endif %}" href="{% url 'web:studies-list' %}"><strong>{% if request.session.study_name %}{% trans "Find Another Study" %}{% else %}{% trans "Find a Study Now" %}{% endif %}</strong><br>{% trans "See all available studies." %}</a>
<a class="{% if current_page == 'email-preferences' %} active {% endif %} btn btn-default btn-md btn-block" href="{% url 'web:email-preferences' %}"><strong>{% trans "Email Preferences" %}</strong><br>{% trans "Edit when you can be contacted." %}</a>
Empty file added accounts/tests/__init__.py
Empty file.
File renamed without changes.
259 changes: 259 additions & 0 deletions accounts/tests/test_new_user_flow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
import datetime
import itertools
from typing import Any, Tuple
from urllib.parse import urlencode

from bs4 import BeautifulSoup as BS
from django.http import HttpResponse
from django.test import TestCase
from django.urls import reverse
from django_dynamic_fixture import G

from accounts.models import Child, DemographicData, User
from studies.models import Study


class NewUserAccountTestCase(TestCase):
def setUp(self):
"""Set up each test case."""

# Study and User to be used in each case.
self.study = self.get_study()
self.user = G(User, is_active=True)

# The details view for the above study
self.study_details_url = reverse(
"web:study-detail", kwargs={"uuid": self.study.uuid}
)

# List of my account urls that we should check the state of our new buttons. This is
# missing the update child view.
self.my_account_urls = [
reverse("web:demographic-data-update"),
reverse("accounts:manage-account"),
reverse("web:children-list"),
reverse("web:child-add"),
]

def get_study(self):
"""Create study
Returns:
Study: The study object after it's been set active.
"""
study = G(
Study,
image="asdf",
name="study name",
min_age_days=6 * 30,
max_age_days=12 * 30,
criteria_expression="",
)
study.state = "active"
study.save()
return study

def get_soup(self, response: HttpResponse) -> BS:
"""Return the beautiful soup object for a response.
Args:
response (HttpResponse): Response returned from client
Returns:
BS: BeautifulSoup object
"""
return BS(response.content, "html.parser")

def get_study_buttons(self, response: HttpResponse) -> Tuple[Any, Any]:
"""Use Beautiful Soup to find and return the two buttons whose state we're checking.
Args:
response (HttpResponse): Response returned from client
Returns:
Tuple[Button, Button]: Returns a tuple of our two buttons
"""
soup = self.get_soup(response)
study_button = soup.find("a", class_="btn-has-study")
study_list_button = soup.find("a", class_="btn-study-list")
return (study_button, study_list_button)

def get_my_account_urls(self, child: Child) -> itertools.chain:
"""Get account urls with update child url.
Args:
child (Child): Child model object
Returns:
itertools.chain: Generator
"""
return itertools.chain(
self.my_account_urls,
(reverse("web:child-update", kwargs={"uuid": child.uuid}),),
)

def set_session(self) -> None:
"""Setup session to have study values."""
session = self.client.session
session["study_name"] = self.study.name
session["study_uuid"] = str(self.study.uuid)
session.save()

def login_user(self) -> None:
"""Login our user."""
user = self.user
self.client.force_login(user)
self.set_session()

def test_valid_study_detail(self) -> None:
"""Check if study returns a valid details page."""
response = self.client.get(self.study_details_url)
self.assertEqual(response.status_code, 200)

def test_login_and_create_buttons_exist(self) -> None:
"""Check if login and create new account button exist on page when user is logged out."""

# get our response
response = self.client.get(self.study_details_url)

# use response to get forms. Login and Sign up buttons are wrapped in forms.
soup = self.get_soup(response)
forms = soup.find_all("form")

# The two urls we need to check for.
login_url = reverse("login")
signup_url = reverse("web:participant-signup")

# There are a few forms on the page, we'll iterate through the list to check if at least
# one has what we're looking for.
self.assertTrue(
any(
f.button.text == "Log in to participate"
and f.attrs["action"] == login_url
and f.input.attrs["value"] == self.study_details_url
for f in forms
)
)
self.assertTrue(
any(
f.button.text == "Create a new account"
and f.attrs["action"] == signup_url
and f.input.attrs["value"] == self.study_details_url
for f in forms
)
)

def test_create_account_has_study(self) -> None:
"""Check if when user is created,that the study is stored in session."""

# Set up url with the query string
query = {"next": self.study_details_url}
qs = urlencode(query, doseq=False)
url = f"{reverse('web:participant-signup')}?{qs}"

# Sign up a user
nickname = "user_asdf"
pw = "asdfasdfasdfasdf"
response = self.client.post(
url,
{
"username": "user@email.com",
"nickname": nickname,
"password1": pw,
"password2": pw,
},
follow=True,
)

# confirm we ended up in the correct view
self.assertEqual(response.status_code, 200)
self.assertIn(
(reverse("web:demographic-data-update"), 302), response.redirect_chain
)

# confirm user was created
self.assertEqual(
User.objects.filter(username="user@email.com").first().nickname, nickname
)

# confirm session data was set
self.assertEqual(self.client.session["study_uuid"], str(self.study.uuid))
self.assertEqual(self.client.session["study_name"], self.study.name)

def test_no_demo_no_child(self) -> None:
"""Check buttons when user has no Demo or any child."""

# Login user
self.login_user()

# confirm user has no demo and no children
self.assertFalse(self.user.has_demographics)
self.assertFalse(self.user.has_any_child)

# iterate over list of urls and check the state of the buttons
for url in self.my_account_urls:
response = self.client.get(url)
study_button, study_list_button = self.get_study_buttons(response)

self.assertEqual(response.status_code, 200)
self.assertIn("disabled", study_button.attrs["class"])
self.assertIn("disabled", study_list_button.attrs["class"])

def test_has_demo_no_child(self) -> None:
"""Check buttons when user has Demo but no children."""

# Create demo and log in
G(DemographicData, user=self.user)
self.login_user()

# confirm user has demo and no children
self.assertTrue(self.user.has_demographics)
self.assertFalse(self.user.has_any_child)

# iterate over list of urls and check the state of the buttons
for url in self.my_account_urls:
response = self.client.get(url)
study_button, study_list_button = self.get_study_buttons(response)

self.assertEqual(response.status_code, 200)
self.assertIn("disabled", study_button.attrs["class"])
self.assertIn("disabled", study_list_button.attrs["class"])

def test_has_demo_ineligible_child(self) -> None:
"""Check buttons when user has Demo but an ineligible child."""

# Create Demo, Child and log user in
G(DemographicData, user=self.user)
child = G(Child, user=self.user, birthday=datetime.datetime.now())
self.login_user()

# confirm user has demo and has a child
self.assertTrue(self.user.has_demographics)
self.assertTrue(self.user.has_any_child)

# in addition the my account views, lets check the update child view as well.
for url in self.get_my_account_urls(child):
response = self.client.get(url)
study_button, study_list_button = self.get_study_buttons(response)

self.assertEqual(response.status_code, 200)
self.assertIn("disabled", study_button.attrs["class"])
self.assertNotIn("disabled", study_list_button.attrs["class"])

def test_has_demo_eligible_child(self) -> None:
"""Check buttons when user has Denmo and an eligible child."""

# Create Demo, Child and log user in
G(DemographicData, user=self.user)
seven_months_old = datetime.datetime.now() - datetime.timedelta(30 * 7)
child = G(Child, user=self.user, birthday=seven_months_old)
self.login_user()

# in addition the my account views, lets check the update child view as well.
for url in self.get_my_account_urls(child):
response = self.client.get(url)
study_button, study_list_button = self.get_study_buttons(response)

self.assertEqual(response.status_code, 200)
self.assertNotIn("disabled", study_button.attrs["class"])
self.assertNotIn("disabled", study_list_button.attrs["class"])
33 changes: 32 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ pyopenssl = "19.1.0"
python_dotenv = "0.18.0"
django-dynamic-fixture = "^3.1.2"
black = "^22.3.0"
beautifulsoup4 = "^4.10.0"

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
10 changes: 10 additions & 0 deletions web/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,11 @@ def form_valid(self, form):
def get_success_url(self):
return reverse("web:children-list")

def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
context["has_study_child"] = self.request.user.has_study_child(self.request)
return context


class ChildUpdateView(LoginRequiredMixin, generic.UpdateView):
"""
Expand Down Expand Up @@ -286,6 +291,11 @@ def post(self, request, *args, **kwargs):
messages.success(self.request, _("Child updated."))
return super().post(request, *args, **kwargs)

def get_context_data(self, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
context["has_study_child"] = self.request.user.has_study_child(self.request)
return context


class ParticipantEmailPreferencesView(LoginRequiredMixin, generic.UpdateView):
"""
Expand Down

0 comments on commit c4c8f0a

Please sign in to comment.