diff --git a/tests/main/test_data_views.py b/tests/main/test_data_views.py index d4823396..83720a98 100644 --- a/tests/main/test_data_views.py +++ b/tests/main/test_data_views.py @@ -5,37 +5,40 @@ from http import HTTPStatus +import pytest from django.urls import reverse class TestFrameworkView: """Test suite for the FrameworkView.""" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("framework_json") - def test_framework_view_get(self, mocker, client): + def test_framework_view_get(self, mocker, client, url): """Test the GET returns the dictionary returned by `export_framework`.""" framework_dict = {"key": [{"sub-key": "value"}]} export_mock = mocker.patch( "main.views.data_views.export_framework", return_value=framework_dict ) - response = client.get(self._get_url()) + response = client.get(url) assert response.status_code == HTTPStatus.OK export_mock.assert_called_once() assert response.json() == framework_dict - def test_framework_view_can_only_get(self, client): + def test_framework_view_can_only_get(self, client, url): """Test to confirm it is only valid to GET the framework view.""" - response = client.post(self._get_url()) + response = client.post(url) assert response.status_code == HTTPStatus.METHOD_NOT_ALLOWED - response = client.put(self._get_url()) + response = client.put(url) assert response.status_code == HTTPStatus.METHOD_NOT_ALLOWED - response = client.patch(self._get_url()) + response = client.patch(url) assert response.status_code == HTTPStatus.METHOD_NOT_ALLOWED - response = client.delete(self._get_url()) + response = client.delete(url) assert response.status_code == HTTPStatus.METHOD_NOT_ALLOWED - response = client.trace(self._get_url()) + response = client.trace(url) assert response.status_code == HTTPStatus.METHOD_NOT_ALLOWED diff --git a/tests/main/test_main_views.py b/tests/main/test_main_views.py index 8082cdaa..173ee99d 100644 --- a/tests/main/test_main_views.py +++ b/tests/main/test_main_views.py @@ -29,12 +29,14 @@ class TestIndex(TemplateOkMixin, BS4Mixin): _template_name = "main/index.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("index") - def test_provides_required_context(self, admin_client, skill_level): + def test_provides_required_context(self, admin_client, skill_level, url): """Test the view provides skill_levels and sample_data context.""" - response = admin_client.get(self._get_url()) + response = admin_client.get(url) assert "chart_data" in response.context assert isinstance(response.context["chart_data"], str) assert response.context["skill_levels"] == json.dumps( @@ -147,7 +149,9 @@ class TestPrivacy(TemplateOkMixin): _template_name = "main/pages/policies/privacy.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("privacy") @@ -156,10 +160,12 @@ class TestUserUpdateView(TemplateOkMixin, LoginRequiredMixin): _template_name = "main/user-update-form.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("profile") - def test_post(self, client, user, django_user_model): + def test_post(self, client, user, django_user_model, url): """Test the view POST request updates the user and redirects correctly.""" client.force_login(user) @@ -167,9 +173,7 @@ def test_post(self, client, user, django_user_model): new_email = "new@mail.com" new_username = "newusername" - response = client.post( - self._get_url(), data={"username": new_username, "email": new_email} - ) + response = client.post(url, data={"username": new_username, "email": new_email}) # Assert the user email is updated updated_user = django_user_model.objects.get(id=user_id) @@ -179,7 +183,7 @@ def test_post(self, client, user, django_user_model): # Assert redirects to profile assert response.status_code == HTTPStatus.FOUND - assert response.url == self._get_url() + assert response.url == url class TestAboutPageView(TemplateOkMixin, BS4Mixin): @@ -187,7 +191,9 @@ class TestAboutPageView(TemplateOkMixin, BS4Mixin): _template_name = "main/pages/about.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("about") def test_page_content(self, soup): @@ -210,7 +216,9 @@ class TestTermsPageView(TemplateOkMixin): _template_name = "main/pages/policies/terms.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("terms") @@ -219,16 +227,18 @@ class TestTermsAcceptanceView(TemplateOkMixin, LoginRequiredMixin): _template_name = "main/terms_acceptance.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("terms_acceptance") - def test_post_updates_user_and_redirects_to_overview(self, client, user): + def test_post_updates_user_and_redirects_to_overview(self, client, user, url): """Test that posting acceptance updates user fields and redirects.""" user.agreed_to_tos = False user.save(update_fields=["agreed_to_tos"]) client.force_login(user) - response = client.post(self._get_url(), data={"tos": True}) + response = client.post(url, data={"tos": True}) user.refresh_from_db() assert user.agreed_to_tos is True @@ -236,13 +246,13 @@ def test_post_updates_user_and_redirects_to_overview(self, client, user): assert response.status_code == HTTPStatus.FOUND assert response.url == reverse("account-overview") - def test_post_redirects_to_overview_without_next(self, client, user): + def test_post_redirects_to_overview_without_next(self, client, user, url): """Test that posting acceptance without next redirects to account overview.""" user.agreed_to_tos = False user.save(update_fields=["agreed_to_tos"]) client.force_login(user) - response = client.post(self._get_url(), data={"tos": True}) + response = client.post(url, data={"tos": True}) assert response.status_code == HTTPStatus.FOUND assert response.url == reverse("account-overview") @@ -253,11 +263,13 @@ class TestSelfAssessPageView(TemplateOkMixin, LoginRequiredMixin): _template_name = "main/user-self-assess.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("self_assess") @pytest.mark.django_db - def test_post(self, client, user, competency, skill, skill_level): + def test_post(self, client, user, competency, skill, skill_level, url): """Test the view POST request creates new user skills and redirects.""" client.force_login(user) @@ -274,7 +286,7 @@ def test_post(self, client, user, competency, skill, skill_level): } # Make POST request - response = client.post(self._get_url(), data=form_data) + response = client.post(url, data=form_data) # Assert user skills are created user_skills = UserSkill.objects.filter(user=user) @@ -315,12 +327,14 @@ class TestUserSkillProfile(TemplateOkMixin, BS4Mixin): _template_name = "main/user-skills-profile.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("skills_profile") - def test_provides_required_context(self, admin_client, skill_level): + def test_provides_required_context(self, admin_client, skill_level, url): """Test that the skill profile view send the correct context data.""" - response = admin_client.get(self._get_url()) + response = admin_client.get(url) assert response.status_code == 200 assert "chart_data" in response.context assert isinstance(response.context["chart_data"], str) @@ -369,12 +383,14 @@ class TestRolesPageView(TemplateOkMixin): _template_name = "main/pages/roles.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("roles") - def test_provides_required_context(self, admin_client, skill_level): + def test_provides_required_context(self, admin_client, skill_level, url): """Test that the role profiles view renders the data visualization.""" - response = admin_client.get(self._get_url()) + response = admin_client.get(url) assert response.status_code == 200 assert "chart_data" in response.context assert isinstance(response.context["chart_data"], list) @@ -388,7 +404,9 @@ class TestSkillLevelsPageView(TemplateOkMixin): _template_name = "main/pages/skill-levels.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("skill_levels") @@ -397,7 +415,9 @@ class TestLearningResourcesPageView(TemplateOkMixin, BS4Mixin): _template_name = "main/pages/learning-resources.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("learning_resources") @pytest.mark.django_db @@ -431,7 +451,9 @@ class TestGetInvolvedPageView(TemplateOkMixin): _template_name = "main/pages/get-involved.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("get_involved") @@ -440,12 +462,14 @@ class TestEventsPageView(TemplateOkMixin): _template_name = "main/pages/events.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("events") - def test_provides_required_context(self, client): + def test_provides_required_context(self, client, url): """Test that the events view provides the events context.""" - response = client.get(self._get_url()) + response = client.get(url) assert response.status_code == HTTPStatus.OK assert "events" in response.context assert isinstance(response.context["events"], list) @@ -454,22 +478,26 @@ def test_provides_required_context(self, client): class TestAccountOverviewView(LoginRequiredMixin): """Test suite for the account_overview view.""" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("account-overview") - def test_redirects_to_self_assess_when_no_skills(self, client, user): + def test_redirects_to_self_assess_when_no_skills(self, client, user, url): """Test that users with no skills are redirected to self-assess.""" client.force_login(user) - response = client.get(self._get_url()) + response = client.get(url) assert response.status_code == HTTPStatus.FOUND assert response.url == reverse("self_assess") @pytest.mark.django_db - def test_redirects_to_skill_profile_when_skills_exist(self, client, user_skill): + def test_redirects_to_skill_profile_when_skills_exist( + self, client, user_skill, url + ): """Test that users with skills are redirected to their skill profile.""" client.force_login(user_skill.user) - response = client.get(self._get_url()) + response = client.get(url) assert response.status_code == HTTPStatus.FOUND assert response.url == reverse("skills_profile") @@ -479,13 +507,15 @@ class TestSkillsAndCompetenciesPageView(TemplateOkMixin): _template_name = "main/pages/skills-and-competencies.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("skills_and_competencies") @pytest.mark.django_db - def test_provides_required_context(self, client, competency_domain): + def test_provides_required_context(self, client, competency_domain, url): """Test that the competencies view provides the domains context.""" - response = client.get(self._get_url()) + response = client.get(url) assert response.status_code == HTTPStatus.OK assert "domains" in response.context assert isinstance(response.context["domains"], QuerySet) @@ -497,28 +527,38 @@ class TestSkillPageView(TemplateOkMixin): _template_name = "main/pages/skill.html" - def _get_url(self, **kwargs): - return reverse("skill_detail", kwargs=kwargs) + @pytest.fixture + def url(self, skill): + """Fixture to get the test url.""" + return reverse("skill_detail", kwargs={"slug": skill.slug}) - def test_template_used(self, admin_client, skill): + def test_template_used(self, admin_client, url): """Test the correct template is used by the GET request.""" with assertTemplateUsed(template_name=self._template_name): - response = admin_client.get(self._get_url(slug=skill.slug)) + response = admin_client.get(url) assert response.status_code == HTTPStatus.OK @pytest.mark.django_db - def test_provides_required_context(self, client, skill): + def test_provides_required_context(self, client, skill, url): """Test that the skill page view provides the skill context.""" - response = client.get(self._get_url(slug=skill.slug)) + response = client.get(url) assert response.status_code == HTTPStatus.OK assert "skill" in response.context assert response.context["skill"] == skill - @pytest.mark.django_db - def test_404_for_nonexistent_skill(self, client): - """Test that requesting a non-existent skill returns a 404.""" - response = client.get(self._get_url(slug="nonexistent-skill")) - assert response.status_code == HTTPStatus.NOT_FOUND + class TestSkillPageView404: + """Tests related to the 404 error for non-existent skills.""" + + @pytest.fixture + def url(self): + """Fixture to get the test url for a non-existent skill.""" + return reverse("skill_detail", kwargs={"slug": "nonexistent-skill"}) + + @pytest.mark.django_db + def test_404_for_nonexistent_skill(self, client, url): + """Test that requesting a non-existent skill returns a 404.""" + response = client.get(url) + assert response.status_code == HTTPStatus.NOT_FOUND def test_extract_and_combine_roles(): @@ -540,7 +580,9 @@ class TestFrameworkOverviewPageView(TemplateOkMixin): _template_name = "main/pages/framework-overview.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("framework_overview") @@ -549,7 +591,9 @@ class TestGovernancePageView(TemplateOkMixin): _template_name = "main/pages/policies/governance.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("governance") @@ -558,5 +602,7 @@ class TestLicensingPageView(TemplateOkMixin): _template_name = "main/pages/policies/licensing.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("licensing") diff --git a/tests/main/test_registration_views.py b/tests/main/test_registration_views.py index bf716624..60d9166e 100644 --- a/tests/main/test_registration_views.py +++ b/tests/main/test_registration_views.py @@ -42,13 +42,15 @@ class TestPasswordReset(TemplateOkMixin): _template_name = "registration/password_reset_form.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("password_reset") - def test_post(self, client, user): + def test_post(self, client, user, url): """Test the view POST request redirects correctly.""" # Request a password reset email - response = client.post(self._get_url(), data={"email": user.email}) + response = client.post(url, data={"email": user.email}) # Assert redirects to password_reset/done assert response.status_code == HTTPStatus.FOUND @@ -93,7 +95,9 @@ class TestPasswordResetDone(TemplateOkMixin): _template_name = "registration/password_reset_done.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("password_reset_done") @@ -102,15 +106,17 @@ class TestPasswordResetConfirm(TemplateOkMixin): _template_name = "registration/password_reset_confirm.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse( "password_reset_confirm", kwargs={"token": "some", "uidb64": "thing"} ) - def test_get(self, client, set_password_url): + def test_get(self, client, set_password_url, url): """Test the view GET request fails and succeeds depending on the token.""" # Expect our custom error page if an incorrect token/uid is used - response = client.get(self._get_url()) + response = client.get(url) assertContains(response, "

Password reset failed

") # Expect our custom password_reset_confirm page @@ -137,7 +143,9 @@ class TestPasswordResetComplete(TemplateOkMixin): _template_name = "registration/password_reset_complete.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("password_reset_complete") @@ -146,7 +154,9 @@ class TestPasswordChangeForm(LoginRequiredMixin, TemplateOkMixin): _template_name = "registration/password_change_form.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("password_change") @@ -155,7 +165,9 @@ class TestPasswordChangeDone(LoginRequiredMixin, TemplateOkMixin): _template_name = "registration/password_change_done.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("password_change_done") @@ -164,10 +176,12 @@ class TestRegisterUser(TemplateOkMixin): _template_name = "django_registration/registration_form.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("django_registration_register") - def test_post(self, client, django_user_model): + def test_post(self, client, django_user_model, url): """Test the view POST request creates a user and redirects correctly.""" from django.contrib import auth @@ -183,7 +197,7 @@ def test_post(self, client, django_user_model): django_user_model.objects.get(email=user.email) response = client.post( - self._get_url(), + url, data=dict( email=user.email, username=user.username, @@ -202,10 +216,10 @@ def test_post(self, client, django_user_model): assert logged_in_user.is_authenticated assert logged_in_user == django_user_model.objects.get(email=user.email) - def test_closed(self, client, settings): + def test_closed(self, client, settings, url): """Test the view when registration is closed.""" settings.REGISTRATION_OPEN = False - response = client.get(self._get_url()) + response = client.get(url) assert response.status_code == HTTPStatus.FOUND assert response.url == reverse("django_registration_disallowed") @@ -215,7 +229,9 @@ class TestRegistrationComplete(TemplateOkMixin): _template_name = "django_registration/registration_complete.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("django_registration_complete") @@ -224,5 +240,7 @@ class TestRegistrationClosed(TemplateOkMixin): _template_name = "django_registration/registration_closed.html" - def _get_url(self): + @pytest.fixture + def url(self): + """Fixture to get the test url.""" return reverse("django_registration_disallowed") diff --git a/tests/main/view_utils.py b/tests/main/view_utils.py index e1876ea2..00f40730 100644 --- a/tests/main/view_utils.py +++ b/tests/main/view_utils.py @@ -1,6 +1,6 @@ """Utility module for view tests.""" -from abc import ABC, abstractmethod +from abc import ABC from http import HTTPStatus import pytest @@ -13,16 +13,16 @@ class TemplateOkMixin(ABC): """Mixin for tests that verify the correct template usage. Note: Using this requires the test class to define: - - A `_get_url` method + - A `url` fixture - A `_template_name` variable """ _template_name: str - def test_template_used(self, admin_client): + def test_template_used(self, admin_client, url): """Test the correct template is used by the GET request.""" with assertTemplateUsed(template_name=self._template_name): - response = admin_client.get(self._get_url()) + response = admin_client.get(url) assert response.status_code == HTTPStatus.OK @@ -30,50 +30,42 @@ class LoginRequiredMixin(ABC): """Mixin for tests that require a user to be logged in. Note: Using this requires the test class to define: - - A `_get_url` method + - A `url` fixture """ _template_name: str - @abstractmethod - def _get_url(self) -> str: - return NotImplemented - - def test_login_required(self, client): + def test_login_required(self, client, url): """Test for redirect to the login page if the user is not logged in.""" - response = client.get(self._get_url()) + response = client.get(url) assert response.status_code == HTTPStatus.FOUND - assert response.url == settings.LOGIN_URL + "?next=" + self._get_url() + assert response.url == settings.LOGIN_URL + "?next=" + url class BS4Mixin(ABC): """Mixin for tests that use BeautifulSoup4. It makes the `soup` fixture available. Note: Using this requires the test class to define: - - A `_get_url` method + - A `url` fixture """ - @abstractmethod - def _get_url(self) -> str: - return NotImplemented - @pytest.fixture - def soup(self, client) -> BeautifulSoup: + def soup(self, client, url) -> BeautifulSoup: """A fixture of the BeautifulSoup4 object of the requested page.""" - response = client.get(self._get_url()) + response = client.get(url) return BeautifulSoup(response.content, "html.parser") @pytest.fixture - def auth_soup(self, client, user) -> BeautifulSoup: + def auth_soup(self, client, user, url) -> BeautifulSoup: """A BeautifulSoup4 object of the requested page viewed by a logged-in user.""" client.force_login(user) - response = client.get(self._get_url()) + response = client.get(url) return BeautifulSoup(response.content, "html.parser") @pytest.fixture - def admin_soup(self, admin_client) -> BeautifulSoup: + def admin_soup(self, admin_client, url) -> BeautifulSoup: """A BeautifulSoup4 object of the requested page viewed by an admin user.""" - response = admin_client.get(self._get_url()) + response = admin_client.get(url) return BeautifulSoup(response.content, "html.parser")