|
2 | 2 | import json |
3 | 3 | import logging |
4 | 4 | import warnings |
| 5 | +from datetime import timedelta |
5 | 6 | from io import StringIO |
6 | 7 |
|
7 | 8 | import pandas as pd |
8 | 9 | from bs4 import BeautifulSoup |
9 | 10 | from django.contrib.auth.models import User |
| 11 | +from django.core.cache import cache |
10 | 12 | from django.db.models import F |
11 | 13 | from django.urls import reverse |
| 14 | +from django.utils.http import http_date |
| 15 | +from django.utils.timezone import now |
12 | 16 | from rest_framework.test import APITestCase |
13 | 17 |
|
14 | 18 | from baseline.models import ( |
@@ -677,6 +681,43 @@ def test_as_of_date_filter_returns_valid_baselines(self): |
677 | 681 | self.assertIn(baseline_no_to.id, baseline_ids) |
678 | 682 | self.assertIn(baseline_no_dates.id, baseline_ids) |
679 | 683 |
|
| 684 | + def test_conditional_request_headers(self): |
| 685 | + cache.clear() # Clear cache to ensure clean state |
| 686 | + |
| 687 | + # Test that 200 response includes ETag, Last-Modified, Cache-Control, and Expires headers |
| 688 | + response = self.client.get(self.url) |
| 689 | + self.assertEqual(response.status_code, 200) |
| 690 | + self.assertIn("ETag", response.headers) |
| 691 | + self.assertIn("Last-Modified", response.headers) |
| 692 | + self.assertIn("Cache-Control", response.headers) |
| 693 | + self.assertIn("Expires", response.headers) |
| 694 | + self.assertTrue(response.headers["ETag"].startswith('W/"')) # Weak ETag format |
| 695 | + |
| 696 | + # Test If-None-Match returns 304 when not modified |
| 697 | + etag = response.headers["ETag"] |
| 698 | + cache.clear() |
| 699 | + response = self.client.get(self.url, HTTP_IF_NONE_MATCH=etag) |
| 700 | + self.assertEqual(response.status_code, 304) |
| 701 | + self.assertIn("Cache-Control", response.headers) |
| 702 | + self.assertIn("Expires", response.headers) |
| 703 | + |
| 704 | + # Test If-None-Match returns 200 when data is modified |
| 705 | + cache.clear() # Clear cache before testing modified data |
| 706 | + baseline = self.data[0] |
| 707 | + baseline.population_source = "Updated source" |
| 708 | + baseline.save() |
| 709 | + response = self.client.get(self.url, HTTP_IF_NONE_MATCH=etag) |
| 710 | + self.assertEqual(response.status_code, 200) |
| 711 | + self.assertNotEqual(response.headers["ETag"], etag) |
| 712 | + |
| 713 | + # Test If-Modified-Since with future date returns 304 |
| 714 | + cache.clear() |
| 715 | + future_date = http_date((now() + timedelta(days=1)).timestamp()) |
| 716 | + response = self.client.get(self.url, HTTP_IF_MODIFIED_SINCE=future_date) |
| 717 | + self.assertEqual(response.status_code, 304) |
| 718 | + self.assertIn("Cache-Control", response.headers) |
| 719 | + self.assertIn("Expires", response.headers) |
| 720 | + |
680 | 721 |
|
681 | 722 | class LivelihoodZoneBaselineFacetedSearchViewTestCase(APITestCase): |
682 | 723 | def setUp(self): |
|
0 commit comments