diff --git a/docs/tests/test_views.py b/docs/tests/test_views.py index b72603a95..003321dba 100644 --- a/docs/tests/test_views.py +++ b/docs/tests/test_views.py @@ -1,11 +1,13 @@ +import unittest from http import HTTPStatus +from django.contrib.contenttypes.models import ContentType from django.contrib.sites.models import Site from django.test import SimpleTestCase, TestCase from django.urls import reverse, set_urlconf -from django_hosts.resolvers import reverse as reverse_with_host +from django_hosts.resolvers import reverse as reverse_with_host, reverse_host -from djangoproject.urls import www as www_urls +from djangoproject.urls import docs as docs_urls, www as www_urls from releases.models import Release from ..models import Document, DocumentRelease @@ -29,7 +31,7 @@ def test_team_url(self): def test_internals_team(self): response = self.client.get( "/en/dev/internals/team/", - headers={"host": "docs.djangoproject.localhost:8000"}, + headers={"host": reverse_host("docs")}, ) self.assertRedirects( response, @@ -38,6 +40,146 @@ def test_internals_team(self): fetch_redirect_response=False, ) + def test_redirect_index_view(self): + response = self.client.get( + "/en/dev/index/", # Route without name + headers={"host": reverse_host("docs")}, + ) + self.assertRedirects(response, "/en/dev/", fetch_redirect_response=False) + + +class LangAndReleaseRedirectTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.release = Release.objects.create(version="5.2") + cls.doc_release = DocumentRelease.objects.create( + release=cls.release, is_default=True + ) + + def test_index_view_redirect_to_current_document_release(self): + response = self.client.get( + reverse_with_host("homepage", host="docs"), + headers={"host": reverse_host("docs")}, + ) + self.assertRedirects( + response, self.doc_release.get_absolute_url(), fetch_redirect_response=False + ) + + def test_language_view_redirect_to_current_document_release_with_the_same_language( + self, + ): + fr_doc_release = DocumentRelease.objects.create(release=self.release, lang="fr") + response = self.client.get( + "/fr/", # Route without name + headers={"host": reverse_host("docs")}, + ) + self.assertRedirects( + response, fr_doc_release.get_absolute_url(), fetch_redirect_response=False + ) + + def test_stable_view_redirect_to_current_document_release(self): + response = self.client.get( + reverse_with_host( + # The stable view doesn't have a name but it's basically + # the document-detail route with a version set to "stable" + "document-detail", + kwargs={ + "version": "stable", + "lang": self.doc_release.lang, + "url": "intro", + }, + host="docs", + ), + headers={"host": reverse_host("docs")}, + ) + # Using Django's `reverse()` over django-hosts's `reverse_host()` as the later + # one return an absolute URL but the view redirect only using the path component + expected_url = reverse( + # The stable view route doesn't have a name but it's basically + # the `document-detail` route with a version set to "stable" + "document-detail", + kwargs={ + "version": self.doc_release.version, + "lang": self.doc_release.lang, + "url": "intro", + }, + urlconf=docs_urls, + ) + self.assertRedirects(response, expected_url, fetch_redirect_response=False) + + +class DocumentViewTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.doc_release = DocumentRelease.objects.create(is_default=True) + + def test_document_index_view(self): + # Set up a release so we aren't in `dev` version + self.doc_release.release = Release.objects.create(version="5.2") + self.doc_release.save(update_fields=["release"]) + + response = self.client.get( + reverse_with_host( + "document-index", + kwargs={ + "lang": self.doc_release.lang, + "version": self.doc_release.version, + }, + host="docs", + ), + headers={"host": reverse_host("docs")}, + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context.get("docurl"), "") + self.assertEqual( + response.context.get("rtd_version"), f"{self.doc_release.version}.x" + ) + # Check the header used for Fastly + self.assertEqual(response.headers.get("Surrogate-Control"), "max-age=604800") + + def test_document_index_view_with_dev_version(self): + response = self.client.get( + reverse_with_host( + "document-index", + kwargs={"lang": self.doc_release.lang, "version": "dev"}, + host="docs", + ), + headers={"host": reverse_host("docs")}, + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context.get("rtd_version"), "latest") + + @unittest.expectedFailure + def test_document_index_view_with_stable_version(self): + response = self.client.get( + reverse_with_host( + "document-index", + kwargs={"lang": self.doc_release.lang, "version": "stable"}, + host="docs", + ), + headers={"host": reverse_host("docs")}, + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context.get("rtd_version"), "latest") + + def test_document_detail_view(self): + response = self.client.get( + reverse_with_host( + "document-detail", + kwargs={ + "lang": self.doc_release.lang, + "version": "dev", + "url": "intro", + }, + host="docs", + ), + headers={"host": reverse_host("docs")}, + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.context.get("docurl"), "intro") + # Check the header used for Fastly + self.assertEqual(response.headers.get("Surrogate-Control"), "max-age=604800") + class SearchFormTestCase(TestCase): fixtures = ["doc_test_fixtures"] @@ -80,14 +222,14 @@ def tearDownClass(cls): def test_empty_get(self): response = self.client.get( - "/en/dev/search/", headers={"host": "docs.djangoproject.localhost:8000"} + "/en/dev/search/", headers={"host": reverse_host("docs")} ) self.assertEqual(response.status_code, 200) def test_search_type_filter_all(self): response = self.client.get( "/en/5.1/search/?q=generic", - headers={"host": "docs.djangoproject.localhost:8000"}, + headers={"host": reverse_host("docs")}, ) self.assertEqual(response.status_code, 200) self.assertContains( @@ -101,7 +243,7 @@ def test_search_type_filter_by_doc_types(self): with self.subTest(category=category): response = self.client.get( f"/en/5.1/search/?q=generic&category={category.value}", - headers={"host": "docs.djangoproject.localhost:8000"}, + headers={"host": reverse_host("docs")}, ) self.assertEqual(response.status_code, 200) self.assertContains( @@ -118,7 +260,7 @@ def test_search_type_filter_by_doc_types(self): def test_search_category_filter_invalid_doc_categories(self): response = self.client.get( "/en/5.1/search/?q=generic&category=invalid-so-ignored", - headers={"host": "docs.djangoproject.localhost:8000"}, + headers={"host": reverse_host("docs")}, ) self.assertEqual(response.status_code, 200) self.assertContains( @@ -130,7 +272,7 @@ def test_search_category_filter_invalid_doc_categories(self): def test_search_category_filter_no_results(self): response = self.client.get( "/en/5.1/search/?q=potato&category=ref", - headers={"host": "docs.djangoproject.localhost:8000"}, + headers={"host": reverse_host("docs")}, ) self.assertEqual(response.status_code, 200) self.assertContains(response, self.active_filter, count=1) @@ -220,7 +362,7 @@ def test_code_links(self): with self.subTest(query=query): response = self.client.get( f"/en/5.1/search/?q={query}", - headers={"host": "docs.djangoproject.localhost:8000"}, + headers={"host": reverse_host("docs")}, ) self.assertEqual(response.status_code, 200) self.assertContains( @@ -231,6 +373,31 @@ def test_code_links(self): self.assertContains(response, expected_code_links, html=True) +class SearchRedirectTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.doc_release = DocumentRelease.objects.create(is_default=True) + + def test_redirect_search_view(self): + # With a `q` parameters + response = self.client.get( + "/search/?q=django", headers={"host": reverse_host("docs")} + ) + self.assertRedirects( + response, + "http://" + reverse_host("docs") + "/en/dev/search/?q=django", + fetch_redirect_response=False, + ) + + # Without a `q` parameters + response = self.client.get("/search/", headers={"host": reverse_host("docs")}) + self.assertRedirects( + response, + "http://" + reverse_host("docs") + "/en/dev/search/", + fetch_redirect_response=False, + ) + + class SitemapTests(TestCase): fixtures = ["doc_test_fixtures"] @@ -242,7 +409,7 @@ def tearDownClass(cls): def test_sitemap_index(self): response = self.client.get( - "/sitemap.xml", headers={"host": "docs.djangoproject.localhost:8000"} + "/sitemap.xml", headers={"host": reverse_host("docs")} ) self.assertContains(response, "", count=2) en_sitemap_url = reverse_with_host( @@ -261,9 +428,110 @@ def test_sitemap(self): def test_sitemap_404(self): response = self.client.get( - "/sitemap-xx.xml", headers={"host": "docs.djangoproject.localhost:8000"} + "/sitemap-xx.xml", headers={"host": reverse_host("docs")} ) self.assertEqual(response.status_code, 404) self.assertEqual( response.context["exception"], "No sitemap available for section: 'xx'" ) + + +class OpenSearchTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.doc_release = DocumentRelease.objects.create( + release=Release.objects.create(version="5.2"), is_default=True + ) + + def test_search_suggestions_view(self): + # Without `q` parameter + response = self.client.get( + reverse_with_host( + "document-search-suggestions", + kwargs={ + "lang": self.doc_release.lang, + "version": self.doc_release.version, + }, + host="docs", + ), + headers={"host": reverse_host("docs")}, + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.headers["Content-Type"], "application/json") + self.assertEqual(response.json(), []) + + # With `q` parameter but no Document + response = self.client.get( + reverse_with_host( + "document-search-suggestions", + kwargs={ + "lang": self.doc_release.lang, + "version": self.doc_release.version, + }, + host="docs", + ) + + "?q=test", + headers={"host": reverse_host("docs")}, + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.headers["Content-Type"], "application/json") + self.assertEqual(response.json(), ["test", [], [], []]) + + # # With `q` parameter and a Document + document = Document.objects.create( + release=self.doc_release, + path="test-document", + title="test title", + ) + response = self.client.get( + reverse_with_host( + "document-search-suggestions", + kwargs={ + "lang": self.doc_release.lang, + "version": self.doc_release.version, + }, + host="docs", + ) + + "?q=test", + headers={"host": reverse_host("docs")}, + ) + self.assertEqual(response.status_code, 200) + self.assertEqual(response.headers["Content-Type"], "application/json") + self.assertEqual( + response.json(), + [ + "test", + ["test title"], + [], + [ + reverse_with_host( + "contenttypes-shortcut", + kwargs={ + "content_type_id": ContentType.objects.get_for_model( + Document + ).pk, + "object_id": document.id, + }, + ) + ], + ], + ) + + def test_search_description(self): + response = self.client.get( + reverse_with_host( + "document-search-description", + kwargs={ + "lang": self.doc_release.lang, + "version": self.doc_release.version, + }, + host="docs", + ), + headers={"host": reverse_host("docs")}, + ) + self.assertEqual(response.status_code, 200) + self.assertEqual( + response.headers["Content-Type"], "application/opensearchdescription+xml" + ) + self.assertTemplateUsed("docs/search_description.html") + self.assertContains(response, f"{self.doc_release.lang}") diff --git a/docs/views.py b/docs/views.py index a525b3c1d..48cf43d7d 100644 --- a/docs/views.py +++ b/docs/views.py @@ -50,6 +50,9 @@ def document(request, lang, version, url): canonical_version = DocumentRelease.objects.current_version() canonical = version == canonical_version + # FIXME: I think it's dead code. + # The stable view route is higher than document-* ones, + # and test_document_index_view_with_stable_version failed with a 302 if version == "stable": version = canonical_version