From 2b392f8538d0270f9c82af20f82e2bc87c1f55fd Mon Sep 17 00:00:00 2001 From: daveoconnor Date: Wed, 8 Oct 2025 14:48:45 -0700 Subject: [PATCH] Add /libraries/x.x.x redirect (#1937) (#1949) --- config/urls.py | 5 +++ core/tests/test_views.py | 78 +++++++++++++++++++++++++++++++++++ core/views.py | 27 ++++++------ libraries/tests/test_views.py | 15 +++++++ 4 files changed, 112 insertions(+), 13 deletions(-) diff --git a/config/urls.py b/config/urls.py index 8a8bcf35..5edd2e62 100755 --- a/config/urls.py +++ b/config/urls.py @@ -387,6 +387,11 @@ urlpatterns = ( ), ] + [ + path( + "libraries//", + RedirectToLibrariesView.as_view(), + name="redirect-to-library-list-view", + ), # Redirects for old boost.org urls. re_path( r"^libs/(?P[^/]+)/(?P.*)/?$", diff --git a/core/tests/test_views.py b/core/tests/test_views.py index ad159d9c..8432f971 100644 --- a/core/tests/test_views.py +++ b/core/tests/test_views.py @@ -149,6 +149,84 @@ def test_markdown_view_trailing_slash(tp): tp.response_200(res) +def test_doc_libs_get_content_with_db_cache(request_factory): + """Test DocLibsTemplateView.get_content with database caching enabled.""" + from core.views import DocLibsTemplateView + + # Mock S3 returning content + mock_s3_result = { + "content": b"Test content", + "content_type": "text/html", + } + + with patch("core.views.ENABLE_DB_CACHE", True), patch( + "core.views.DocLibsTemplateView.get_from_database", return_value=None + ) as mock_get_from_db, patch( + "core.views.DocLibsTemplateView.get_from_s3", return_value=mock_s3_result + ) as mock_get_from_s3, patch( + "core.views.DocLibsTemplateView.save_to_database" + ) as mock_save_to_db: + + view = DocLibsTemplateView() + view.request = request_factory.get("/doc/libs/test/") + result = view.get_content("test/path") + + # verify database was checked first + mock_get_from_db.assert_called_once_with("static_content_test/path") + # verify S3 was called after cache miss + mock_get_from_s3.assert_called_once_with("test/path") + # verify content was saved to database + mock_save_to_db.assert_called_once_with( + "static_content_test/path", mock_s3_result + ) + + assert result["content"] == mock_s3_result["content"] + assert result["content_type"] == mock_s3_result["content_type"] + assert "redirect" in result + + +def test_doc_libs_get_content_without_db_cache(request_factory): + """Test DocLibsTemplateView.get_content with database caching disabled.""" + from core.views import DocLibsTemplateView + + # Mock S3 returning content + mock_s3_result = { + "content": b"Test content", + "content_type": "text/html", + } + + with patch("core.views.ENABLE_DB_CACHE", False), patch( + "core.views.DocLibsTemplateView.get_from_s3", return_value=mock_s3_result + ) as mock_get_from_s3: + + view = DocLibsTemplateView() + view.request = request_factory.get("/doc/libs/test/") + result = view.get_content("test/path") + + # verify S3 was called directly + mock_get_from_s3.assert_called_once_with("test/path") + + assert result["content"] == mock_s3_result["content"] + assert result["content_type"] == mock_s3_result["content_type"] + assert "redirect" in result + + +@patch("core.views.DocLibsTemplateView.get_from_s3") +def test_doc_libs_get_content_not_found(mock_get_from_s3, request_factory): + """Test DocLibsTemplateView.get_content when content is not found.""" + from core.views import DocLibsTemplateView, ContentNotFoundException + + # mock S3 returning None (content not found) + mock_get_from_s3.return_value = None + + request = request_factory.get("/doc/libs/test/") + view = DocLibsTemplateView() + view.request = request + + with pytest.raises(ContentNotFoundException, match="Content not found"): + view.get_content("nonexistent/path") + + def test_markdown_view_top_level_includes_extension(tp): res = tp.get("/markdown/foo.html") tp.response_200(res) diff --git a/core/views.py b/core/views.py index 53eb8ed3..c4ba2272 100644 --- a/core/views.py +++ b/core/views.py @@ -559,21 +559,22 @@ class DocLibsTemplateView(BaseStaticContentTemplateView): # For now at least we're only going to cache docs this way, user guides and # will continue to be cached as they were - if not ENABLE_DB_CACHE: - return self.get_from_s3(content_path) - - cache_key = f"static_content_{content_path}" - # check to see if in db, if not retrieve from s3 and save to db - result = self.get_from_database(cache_key) - if not result and (result := self.get_from_s3(content_path)): - self.save_to_database(cache_key, result) + result = None + if ENABLE_DB_CACHE: + cache_key = f"static_content_{content_path}" + # check to see if in db, if not retrieve from s3 and save to db + result = self.get_from_database(cache_key) + if not result and (result := self.get_from_s3(content_path)): + self.save_to_database(cache_key, result) + elif content_data := self.get_from_s3(content_path): + # structure is to allow for redirect/return to be handled in a unified way + result = { + "content": content_data.get("content"), + "content_type": content_data.get("content_type"), + } if result is None: - logger.info( - "get_content_from_s3_view_no_valid_object", - key=content_path, - status_code=404, - ) + logger.info(f"get_content_from_s3_view_no_valid_object {content_path=}") raise ContentNotFoundException("Content not found") result["redirect"] = get_meta_redirect_from_html(result["content"]) diff --git a/libraries/tests/test_views.py b/libraries/tests/test_views.py index 0ddfcb7c..4fa0e40d 100644 --- a/libraries/tests/test_views.py +++ b/libraries/tests/test_views.py @@ -335,3 +335,18 @@ def test_library_detail_context_missing_readme(tp, user, library_version): tp.response_200(response) assert "description" in response.context assert response.context["description"] == README_MISSING + + +def test_redirect_to_library_list_view(library_version, tp): + """ + GET /libraries/{version_string}/ + Test that redirection occurs to the proper libraries list view + """ + url = tp.reverse("redirect-to-library-list-view", "1.79.0") + + response = tp.get(url, follow=False) + tp.response_302(response) + + # Should redirect to the libraries-list view with the version slug + expected_redirect = "/libraries/1.79.0/grid/" + assert response.url == expected_redirect