mirror of
https://github.com/boostorg/website-v2.git
synced 2026-01-19 04:42:17 +00:00
Merge pull request #180 from cppalliance/153-caching
💾 Add caching logic to the static content view
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import pytest
|
||||
import random
|
||||
|
||||
from django.test.utils import override_settings
|
||||
|
||||
|
||||
def test_homepage(db, tp):
|
||||
"""Ensure we can hit the homepage"""
|
||||
@@ -27,8 +29,28 @@ def test_403_page(db, tp):
|
||||
tp.response_403(response)
|
||||
|
||||
|
||||
@override_settings(
|
||||
CACHES={
|
||||
"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"},
|
||||
"machina_attachments": {
|
||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache"
|
||||
},
|
||||
"static_content": {
|
||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
||||
"TIMEOUT": 86400,
|
||||
},
|
||||
}
|
||||
)
|
||||
def test_404_page(db, tp):
|
||||
"""Test a 404 error page"""
|
||||
"""
|
||||
Test a 404 error page
|
||||
|
||||
This test is a bit more complicated than the others because the
|
||||
this/should/not/exist URL will hit StaticContentTemplateView first
|
||||
to see if there is static content to serve, and cache it if so. To avoid
|
||||
errors in CI, we need to make sure that the cache is cleared before
|
||||
running this test.
|
||||
"""
|
||||
|
||||
rando = random.randint(1000, 20000)
|
||||
url = f"/this/should/not/exist/{rando}/"
|
||||
|
||||
@@ -267,8 +267,16 @@ CACHES = {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": f"redis://{REDIS_HOST}:6379",
|
||||
},
|
||||
"static_content": {
|
||||
"BACKEND": "django_redis.cache.RedisCache",
|
||||
"LOCATION": f"redis://{REDIS_HOST}:6379/2",
|
||||
"TIMEOUT": env(
|
||||
"STATIC_CACHE_TIMEOUT", default=86400
|
||||
), # Cache timeout in seconds: 1 day
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
HAYSTACK_CONNECTIONS = {
|
||||
"default": {
|
||||
"ENGINE": "haystack.backends.simple_backend.SimpleEngine",
|
||||
|
||||
@@ -1,11 +1,89 @@
|
||||
import pytest
|
||||
import time
|
||||
from unittest.mock import patch
|
||||
|
||||
from django.core.cache import caches
|
||||
from django.test import RequestFactory
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
|
||||
from core.views import StaticContentTemplateView
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def request_factory():
|
||||
"""Returns a RequestFactory instance."""
|
||||
return RequestFactory()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def content_path():
|
||||
"""Returns a sample content path."""
|
||||
return "/some/content/path"
|
||||
|
||||
|
||||
def call_view(request_factory, content_path):
|
||||
"""Calls the view with the given request_factory and content path."""
|
||||
request = request_factory.get(content_path)
|
||||
view = StaticContentTemplateView.as_view()
|
||||
response = view(request, content_path=content_path)
|
||||
return response
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(
|
||||
CACHES={
|
||||
"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"},
|
||||
"machina_attachments": {
|
||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache"
|
||||
},
|
||||
"static_content": {
|
||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
||||
"TIMEOUT": 86400,
|
||||
},
|
||||
}
|
||||
)
|
||||
def test_cache_behavior(request_factory, content_path):
|
||||
"""Tests the cache behavior of the StaticContentTemplateView view."""
|
||||
# Set up the mocked API call
|
||||
mock_content = "mocked content"
|
||||
mock_content_type = "text/plain"
|
||||
|
||||
# Clear the cache before testing
|
||||
cache = caches["static_content"]
|
||||
cache.clear()
|
||||
|
||||
with patch("core.views.get_content_from_s3") as mock_get_content_from_s3:
|
||||
mock_get_content_from_s3.return_value = (mock_content, mock_content_type)
|
||||
|
||||
# Cache miss scenario
|
||||
response = call_view(request_factory, content_path)
|
||||
assert response.status_code == 200
|
||||
assert response.content.decode() == mock_content
|
||||
assert response["Content-Type"] == mock_content_type
|
||||
mock_get_content_from_s3.assert_called_once_with(key=content_path)
|
||||
|
||||
# Cache hit scenario
|
||||
mock_get_content_from_s3.reset_mock()
|
||||
response = call_view(request_factory, content_path)
|
||||
assert response.status_code == 200
|
||||
assert response.content.decode() == mock_content
|
||||
assert response["Content-Type"] == mock_content_type
|
||||
mock_get_content_from_s3.assert_not_called()
|
||||
|
||||
# Cache expiration scenario
|
||||
cache.set(
|
||||
"static_content_" + content_path, (mock_content, mock_content_type), 1
|
||||
) # Set a 1-second cache timeout
|
||||
time.sleep(2) # Wait for the cache to expire
|
||||
mock_get_content_from_s3.reset_mock()
|
||||
response = call_view(request_factory, content_path)
|
||||
assert response.status_code == 200
|
||||
assert response.content.decode() == mock_content
|
||||
assert response["Content-Type"] == mock_content_type
|
||||
mock_get_content_from_s3.assert_called_once_with(key=content_path)
|
||||
|
||||
|
||||
# Define test cases with the paths based on the provided config file
|
||||
static_content_test_cases = [
|
||||
"/develop/libs/rst.css", # Test a site_path from the config file
|
||||
@@ -26,6 +104,19 @@ def mock_get_content_from_s3(key=None, bucket_name=None):
|
||||
return content_mapping.get(key, None)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
@override_settings(
|
||||
CACHES={
|
||||
"default": {"BACKEND": "django.core.cache.backends.locmem.LocMemCache"},
|
||||
"machina_attachments": {
|
||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache"
|
||||
},
|
||||
"static_content": {
|
||||
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
|
||||
"TIMEOUT": 86400,
|
||||
},
|
||||
}
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize("content_path", static_content_test_cases)
|
||||
@patch("core.views.get_content_from_s3", new=mock_get_content_from_s3)
|
||||
|
||||
@@ -3,6 +3,7 @@ import re
|
||||
import structlog
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.cache import caches
|
||||
from django.http import Http404, HttpResponse, HttpResponseNotFound
|
||||
from django.template.response import TemplateResponse
|
||||
from django.views.generic import TemplateView, View
|
||||
@@ -96,16 +97,31 @@ class StaticContentTemplateView(View):
|
||||
Verifies the file and returns the raw static content from S3
|
||||
mangling paths using the stage_static_config.json settings
|
||||
"""
|
||||
result = get_content_from_s3(key=kwargs.get("content_path"))
|
||||
if not result:
|
||||
logger.info(
|
||||
"get_content_from_s3_view_no_valid_object",
|
||||
key=kwargs.get("content_path"),
|
||||
status_code=404,
|
||||
)
|
||||
return HttpResponseNotFound("Page not found")
|
||||
content_path = kwargs.get("content_path")
|
||||
|
||||
content, content_type = result
|
||||
# Get the static content cache
|
||||
static_content_cache = caches["static_content"]
|
||||
|
||||
# Check if the content is in the cache
|
||||
cache_key = f"static_content_{content_path}"
|
||||
cached_result = static_content_cache.get(cache_key)
|
||||
|
||||
if cached_result:
|
||||
content, content_type = cached_result
|
||||
else:
|
||||
# Fetch content from S3 if not in cache
|
||||
result = get_content_from_s3(key=kwargs.get("content_path"))
|
||||
if not result:
|
||||
logger.info(
|
||||
"get_content_from_s3_view_no_valid_object",
|
||||
key=kwargs.get("content_path"),
|
||||
status_code=404,
|
||||
)
|
||||
return HttpResponseNotFound("Page not found")
|
||||
|
||||
content, content_type = result
|
||||
# Store the result in cache
|
||||
static_content_cache.set(cache_key, (content, content_type))
|
||||
|
||||
response = HttpResponse(content, content_type=content_type)
|
||||
logger.info(
|
||||
|
||||
Reference in New Issue
Block a user