Merge pull request #180 from cppalliance/153-caching

💾 Add caching logic to the static content view
This commit is contained in:
Lacey Williams Henschel
2023-05-03 14:25:48 -07:00
committed by GitHub
4 changed files with 147 additions and 10 deletions

View File

@@ -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}/"

View File

@@ -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",

View File

@@ -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)

View File

@@ -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(