diff --git a/Pipfile.lock b/Pipfile.lock index 41640ed04..8825f9462 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -406,11 +406,11 @@ }, "markdown": { "hashes": [ - "sha256:fc4a6f69a656b8d858d7503bda633f4dd63c2d70cf80abdc6eafa64c4ae8c250", - "sha256:fe463ff51e679377e3624984c829022e2cfb3be5518726b06f608a07a3aad680" + "sha256:2e50876bcdd74517e7b71f3e7a76102050edec255b3983403f1a63e7c8a41e7a", + "sha256:56a46ac655704b91e5b7e6326ce43d5ef72411376588afa1dd90e881b83c7e8c" ], "index": "pypi", - "version": "==3.1" + "version": "==3.1.1" }, "more-itertools": { "hashes": [ diff --git a/concordia/tests/test_1st_level_views.py b/concordia/tests/test_1st_level_views.py deleted file mode 100644 index 389fba8d2..000000000 --- a/concordia/tests/test_1st_level_views.py +++ /dev/null @@ -1,56 +0,0 @@ -from django.test import TestCase -from django.urls import reverse - - -class ViewTest_1st_level(TestCase): - """ - This is a test case for testing all the first level views originated - from home pages. - - """ - - def test_contact_us_get(self): - - response = self.client.get(reverse("contact")) - - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, "contact.html") - - def test_contact_us_get_pre_populate(self): - test_http_referrer = "http://foo/bar" - - response = self.client.get(reverse("contact"), HTTP_REFERER=test_http_referrer) - - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, "contact.html") - - self.assertEqual( - response.context["form"].initial["referrer"], test_http_referrer - ) - - def test_contact_us_post(self): - post_data = { - "email": "nobody@example.com", - "subject": "Problem found", - "link": "http://www.loc.gov/nowhere", - "story": "Houston, we got a problem", - } - - response = self.client.post(reverse("contact"), post_data) - - self.assertEqual(response.status_code, 302) - - def test_contact_us_post_invalid(self): - post_data = { - "email": "nobody@", - "subject": "Problem found", - "story": "Houston, we got a problem", - } - - response = self.client.post(reverse("contact"), post_data) - - self.assertEqual(response.status_code, 200) - - self.assertEqual( - {"email": ["Enter a valid email address."]}, response.context["form"].errors - ) diff --git a/concordia/tests/test_account_views.py b/concordia/tests/test_account_views.py new file mode 100644 index 000000000..f3ae25621 --- /dev/null +++ b/concordia/tests/test_account_views.py @@ -0,0 +1,102 @@ +""" +Tests for user account-related views +""" +from django.test import TestCase, override_settings +from django.urls import reverse + +from concordia.models import User + +from .utils import CacheControlAssertions, CreateTestUsers, JSONAssertMixin + + +@override_settings(RATELIMIT_ENABLE=False) +class ConcordiaViewTests( + CreateTestUsers, JSONAssertMixin, CacheControlAssertions, TestCase +): + """ + This class contains the unit tests for the view in the concordia app. + """ + + def test_AccountProfileView_get(self): + """ + Test the http GET on route account/profile + """ + + self.login_user() + + response = self.client.get(reverse("user-profile")) + + self.assertEqual(response.status_code, 200) + self.assertUncacheable(response) + self.assertTemplateUsed(response, template_name="account/profile.html") + + self.assertEqual(response.context["user"], self.user) + self.assertContains(response, self.user.username) + self.assertContains(response, self.user.email) + + def test_AccountProfileView_post(self): + """ + This unit test tests the post entry for the route account/profile + :param self: + """ + test_email = "tester@example.com" + + self.login_user() + + response = self.client.post( + reverse("user-profile"), {"email": test_email, "username": "tester"} + ) + + self.assertEqual(response.status_code, 302) + self.assertUncacheable(response) + self.assertEqual(response.url, reverse("user-profile")) + + # Verify the User was correctly updated + updated_user = User.objects.get(email=test_email) + self.assertEqual(updated_user.email, test_email) + + def test_AccountProfileView_post_invalid_form(self): + """ + This unit test tests the post entry for the route account/profile but + submits an invalid form + """ + self.login_user() + + response = self.client.post(reverse("user-profile"), {"first_name": "Jimmy"}) + + self.assertEqual(response.status_code, 200) + self.assertUncacheable(response) + + # Verify the User was not changed + updated_user = User.objects.get(id=self.user.id) + self.assertEqual(updated_user.first_name, "") + + def test_ajax_session_status_anon(self): + response = self.client.get(reverse("ajax-session-status")) + self.assertCachePrivate(response) + data = self.assertValidJSON(response) + self.assertEqual(data, {}) + + def test_ajax_session_status(self): + self.login_user() + + response = self.client.get(reverse("ajax-session-status")) + self.assertCachePrivate(response) + data = self.assertValidJSON(response) + + self.assertIn("links", data) + self.assertIn("username", data) + + self.assertEqual(data["username"], self.user.username) + + def test_ajax_messages(self): + self.login_user() + + response = self.client.get(reverse("ajax-messages")) + data = self.assertValidJSON(response) + + self.assertIn("messages", data) + + # This view cannot be cached because the messages would be displayed + # multiple times: + self.assertUncacheable(response) diff --git a/concordia/tests/test_top_level_views.py b/concordia/tests/test_top_level_views.py new file mode 100644 index 000000000..4fd2fb7fb --- /dev/null +++ b/concordia/tests/test_top_level_views.py @@ -0,0 +1,132 @@ +""" +Tests for for the top-level & “CMS” views +""" + +from django.test import TestCase +from django.urls import reverse + +from concordia.models import SimplePage + +from .utils import CacheControlAssertions, CreateTestUsers, JSONAssertMixin + + +class TopLevelViewTests( + JSONAssertMixin, CreateTestUsers, CacheControlAssertions, TestCase +): + def test_healthz(self): + data = self.assertValidJSON(self.client.get("/healthz")) + + for k in ( + "current_time", + "load_average", + "debug", + "database_has_data", + "application_version", + ): + self.assertIn(k, data) + + def test_homepage(self): + response = self.client.get(reverse("homepage")) + + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, "home.html") + + def test_contact_us_get(self): + response = self.client.get(reverse("contact")) + + self.assertEqual(response.status_code, 200) + self.assertUncacheable(response) + self.assertTemplateUsed(response, "contact.html") + + def test_contact_us_with_referrer(self): + test_http_referrer = "http://foo/bar" + + response = self.client.get(reverse("contact"), HTTP_REFERER=test_http_referrer) + + self.assertEqual(response.status_code, 200) + self.assertUncacheable(response) + self.assertTemplateUsed(response, "contact.html") + + self.assertEqual( + response.context["form"].initial["referrer"], test_http_referrer + ) + + def test_contact_us_as_a_logged_in_user(self): + """ + The contact form should pre-fill your email address if you're logged in + """ + + self.login_user() + + response = self.client.get(reverse("contact")) + + self.assertEqual(response.status_code, 200) + self.assertUncacheable(response) + self.assertTemplateUsed(response, "contact.html") + + self.assertEqual(response.context["form"].initial["email"], self.user.email) + + def test_contact_us_post(self): + post_data = { + "email": "nobody@example.com", + "subject": "Problem found", + "link": "http://www.loc.gov/nowhere", + "story": "Houston, we got a problem", + } + + response = self.client.post(reverse("contact"), post_data) + + self.assertEqual(response.status_code, 302) + self.assertUncacheable(response) + + def test_contact_us_post_invalid(self): + post_data = { + "email": "nobody@", + "subject": "Problem found", + "story": "Houston, we got a problem", + } + + response = self.client.post(reverse("contact"), post_data) + + self.assertEqual(response.status_code, 200) + + self.assertEqual( + {"email": ["Enter a valid email address."]}, response.context["form"].errors + ) + + def test_simple_page(self): + s = SimplePage.objects.create( + title="Help Center 123", + body="not the real body", + path=reverse("help-center"), + ) + + resp = self.client.get(reverse("help-center")) + self.assertEqual(200, resp.status_code) + self.assertEqual(s.title, resp.context["title"]) + self.assertEqual( + [(reverse("help-center"), s.title)], resp.context["breadcrumbs"] + ) + self.assertEqual(resp.context["body"], f"

{s.body}

") + + def test_nested_simple_page(self): + l1 = SimplePage.objects.create( + title="Help Center", body="not the real body", path=reverse("help-center") + ) + + l2 = SimplePage.objects.create( + title="Help Center Welcome Guide", + body="This is _not_ the real page", + path=reverse("welcome-guide"), + ) + + resp = self.client.get(reverse("welcome-guide")) + self.assertEqual(200, resp.status_code) + self.assertEqual(l2.title, resp.context["title"]) + self.assertEqual( + resp.context["breadcrumbs"], + [(reverse("help-center"), l1.title), (reverse("welcome-guide"), l2.title)], + ) + self.assertHTMLEqual( + resp.context["body"], f"

This is not the real page

" + ) diff --git a/concordia/tests/test_view.py b/concordia/tests/test_views.py similarity index 88% rename from concordia/tests/test_view.py rename to concordia/tests/test_views.py index c637704d3..71aed1f27 100644 --- a/concordia/tests/test_view.py +++ b/concordia/tests/test_views.py @@ -1,3 +1,7 @@ +""" +Tests for the core application features +""" + from datetime import datetime, timedelta from captcha.models import CaptchaStore @@ -9,14 +13,13 @@ from concordia.models import ( Asset, AssetTranscriptionReservation, - SimplePage, Transcription, TranscriptionStatus, - User, ) from concordia.utils import get_anonymous_user from .utils import ( + CreateTestUsers, JSONAssertMixin, create_asset, create_campaign, @@ -26,73 +29,11 @@ @override_settings(RATELIMIT_ENABLE=False) -class ConcordiaViewTests(JSONAssertMixin, TestCase): +class ConcordiaViewTests(CreateTestUsers, JSONAssertMixin, TestCase): """ This class contains the unit tests for the view in the concordia app. """ - def login_user(self): - """ - Create a user and log the user in - """ - - # create user and login - self.user = User.objects.create_user( - username="tester", email="tester@example.com" - ) - self.user.set_password("top_secret") - self.user.save() - - self.client.login(username="tester", password="top_secret") - - def test_AccountProfileView_get(self): - """ - Test the http GET on route account/profile - """ - - self.login_user() - - response = self.client.get(reverse("user-profile")) - - # validate the web page has the "tester" and "tester@example.com" as values - self.assertEqual(response.status_code, 200) - self.assertTemplateUsed(response, template_name="account/profile.html") - - def test_AccountProfileView_post(self): - """ - This unit test tests the post entry for the route account/profile - :param self: - """ - test_email = "tester@example.com" - - self.login_user() - - response = self.client.post( - reverse("user-profile"), {"email": test_email, "username": "tester"} - ) - - self.assertEqual(response.status_code, 302) - self.assertEqual(response.url, reverse("user-profile")) - - # Verify the User was correctly updated - updated_user = User.objects.get(email=test_email) - self.assertEqual(updated_user.email, test_email) - - def test_AccountProfileView_post_invalid_form(self): - """ - This unit test tests the post entry for the route account/profile but - submits an invalid form - """ - self.login_user() - - response = self.client.post(reverse("user-profile"), {"first_name": "Jimmy"}) - - self.assertEqual(response.status_code, 200) - - # Verify the User was not changed - updated_user = User.objects.get(id=self.user.id) - self.assertEqual(updated_user.first_name, "") - def test_campaign_list_view(self): """ Test the GET method for route /campaigns @@ -316,68 +257,21 @@ def test_campaign_report(self): self.assertEqual(ctx["title"], item.project.campaign.title) self.assertEqual(ctx["total_asset_count"], 10) - def test_simple_page(self): - s = SimplePage.objects.create( - title="Help Center 123", - body="not the real body", - path=reverse("help-center"), - ) - - resp = self.client.get(reverse("help-center")) - self.assertEqual(200, resp.status_code) - self.assertEqual(s.title, resp.context["title"]) - self.assertEqual( - [(reverse("help-center"), s.title)], resp.context["breadcrumbs"] - ) - self.assertEqual(resp.context["body"], f"

{s.body}

") - - def test_ajax_session_status_anon(self): - resp = self.client.get(reverse("ajax-session-status")) - data = self.assertValidJSON(resp) - self.assertEqual(data, {}) - - def test_ajax_session_status(self): - self.login_user() - - resp = self.client.get(reverse("ajax-session-status")) - data = self.assertValidJSON(resp) - - self.assertIn("links", data) - self.assertIn("username", data) - - self.assertEqual(data["username"], self.user.username) - - self.assertIn("private", resp["Cache-Control"]) + @override_settings(FLAGS={"ACTIVITY_UI_ENABLED": [("boolean", True)]}) + def test_activity_ui_anonymous(self): + response = self.client.get(reverse("action-app")) + self.assertRedirects(response, "/account/login/?next=/act/") - def test_ajax_messages(self): + @override_settings(FLAGS={"ACTIVITY_UI_ENABLED": [("boolean", True)]}) + def test_activity_ui_logged_in(self): self.login_user() - - resp = self.client.get(reverse("ajax-messages")) - data = self.assertValidJSON(resp) - - self.assertIn("messages", data) - - # This view cannot be cached because the messages would be displayed - # multiple times: - self.assertIn("no-cache", resp["Cache-Control"]) + response = self.client.get(reverse("action-app")) + self.assertTemplateUsed(response, "action-app.html") + self.assertContains(response, "new ActionApp") @override_settings(RATELIMIT_ENABLE=False) -class TransactionalViewTests(JSONAssertMixin, TransactionTestCase): - def login_user(self): - """ - Create a user and log the user in - """ - - # create user and login - self.user = User.objects.create_user( - username="tester", email="tester@example.com" - ) - self.user.set_password("top_secret") - self.user.save() - - self.client.login(username="tester", password="top_secret") - +class TransactionalViewTests(CreateTestUsers, JSONAssertMixin, TransactionTestCase): def completeCaptcha(self, key=None): """Submit a CAPTCHA response using the provided challenge key""" @@ -868,12 +762,10 @@ def test_duplicate_tag_submission(self): ) data = self.assertValidJSON(resp, expected_status=200) - second_user = User.objects.create_user( + second_user = self.create_test_user( username="second_tester", email="second_tester@example.com" ) - second_user.set_password("secret") - second_user.save() - self.client.login(username="second_tester", password="secret") + self.client.login(username=second_user.username, password=second_user.password) resp = self.client.post( reverse("submit-tags", kwargs={"asset_pk": asset.pk}), diff --git a/concordia/tests/utils.py b/concordia/tests/utils.py index f0d642113..ddb726504 100644 --- a/concordia/tests/utils.py +++ b/concordia/tests/utils.py @@ -1,9 +1,10 @@ import json from functools import wraps +from secrets import token_hex from django.utils.text import slugify -from concordia.models import Asset, Campaign, Item, MediaType, Project +from concordia.models import Asset, Campaign, Item, MediaType, Project, User def ensure_slug(original_function): @@ -132,3 +133,43 @@ def assertValidJSON(self, response, expected_status=200): raise return data + + +class CreateTestUsers(object): + def login_user(self): + """ + Create a user and log the user in + """ + + if not hasattr(self, "user"): + self.user = self.create_test_user("tester") + + self.client.login(username=self.user.username, password=self.user.password) + + def create_test_user(self, username, **kwargs): + """ + Creates a test User account + """ + + if "email" not in kwargs: + kwargs["email"] = f"{username}@example.com" + + user = User.objects.create_user(username=username, **kwargs) + fake_pw = token_hex(24) + user.set_password(fake_pw) + user.save() + + user.password = fake_pw + + return user + + +class CacheControlAssertions(object): + def assertUncacheable(self, response): + self.assertIn("Cache-Control", response) + self.assertIn("no-cache", response["Cache-Control"]) + self.assertIn("no-store", response["Cache-Control"]) + + def assertCachePrivate(self, response): + self.assertIn("Cache-Control", response) + self.assertIn("private", response["Cache-Control"]) diff --git a/concordia/views.py b/concordia/views.py index c35ab928d..8b48e0c0e 100644 --- a/concordia/views.py +++ b/concordia/views.py @@ -115,13 +115,11 @@ def healthz(request): @default_cache_control def simple_page(request, path=None): """ - Serve static content from Markdown files + Basic content management using Markdown managed in the SimplePage model - Expects the request path with the addition of ".md" to match a file under - the top-level static-pages directory or the url dispatcher configuration to - pass a base_name parameter: + This expects a pre-existing URL path matching the path specified in the database:: - path("foobar/", static_page, {"base_name": "some-weird-filename.md"}) + path("about/", views.simple_page, name="about"), """ if not path: diff --git a/exporter/tests/test_view.py b/exporter/tests/test_views.py similarity index 100% rename from exporter/tests/test_view.py rename to exporter/tests/test_views.py