tests.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. from datetime import datetime
  2. from django.test import SimpleTestCase, override_settings
  3. FULL_RESPONSE = "Test conditional get response"
  4. LAST_MODIFIED = datetime(2007, 10, 21, 23, 21, 47)
  5. LAST_MODIFIED_STR = "Sun, 21 Oct 2007 23:21:47 GMT"
  6. LAST_MODIFIED_NEWER_STR = "Mon, 18 Oct 2010 16:56:23 GMT"
  7. LAST_MODIFIED_INVALID_STR = "Mon, 32 Oct 2010 16:56:23 GMT"
  8. EXPIRED_LAST_MODIFIED_STR = "Sat, 20 Oct 2007 23:21:47 GMT"
  9. ETAG = '"b4246ffc4f62314ca13147c9d4f76974"'
  10. WEAK_ETAG = 'W/"b4246ffc4f62314ca13147c9d4f76974"' # weak match to ETAG
  11. EXPIRED_ETAG = '"7fae4cd4b0f81e7d2914700043aa8ed6"'
  12. @override_settings(ROOT_URLCONF="conditional_processing.urls")
  13. class ConditionalGet(SimpleTestCase):
  14. def assertFullResponse(self, response, check_last_modified=True, check_etag=True):
  15. self.assertEqual(response.status_code, 200)
  16. self.assertEqual(response.content, FULL_RESPONSE.encode())
  17. if response.request["REQUEST_METHOD"] in ("GET", "HEAD"):
  18. if check_last_modified:
  19. self.assertEqual(response.headers["Last-Modified"], LAST_MODIFIED_STR)
  20. if check_etag:
  21. self.assertEqual(response.headers["ETag"], ETAG)
  22. else:
  23. self.assertNotIn("Last-Modified", response.headers)
  24. self.assertNotIn("ETag", response.headers)
  25. def assertNotModified(self, response):
  26. self.assertEqual(response.status_code, 304)
  27. self.assertEqual(response.content, b"")
  28. def test_without_conditions(self):
  29. response = self.client.get("/condition/")
  30. self.assertFullResponse(response)
  31. def test_if_modified_since(self):
  32. self.client.defaults["HTTP_IF_MODIFIED_SINCE"] = LAST_MODIFIED_STR
  33. response = self.client.get("/condition/")
  34. self.assertNotModified(response)
  35. response = self.client.put("/condition/")
  36. self.assertFullResponse(response)
  37. self.client.defaults["HTTP_IF_MODIFIED_SINCE"] = LAST_MODIFIED_NEWER_STR
  38. response = self.client.get("/condition/")
  39. self.assertNotModified(response)
  40. response = self.client.put("/condition/")
  41. self.assertFullResponse(response)
  42. self.client.defaults["HTTP_IF_MODIFIED_SINCE"] = LAST_MODIFIED_INVALID_STR
  43. response = self.client.get("/condition/")
  44. self.assertFullResponse(response)
  45. self.client.defaults["HTTP_IF_MODIFIED_SINCE"] = EXPIRED_LAST_MODIFIED_STR
  46. response = self.client.get("/condition/")
  47. self.assertFullResponse(response)
  48. def test_if_unmodified_since(self):
  49. self.client.defaults["HTTP_IF_UNMODIFIED_SINCE"] = LAST_MODIFIED_STR
  50. response = self.client.get("/condition/")
  51. self.assertFullResponse(response)
  52. self.client.defaults["HTTP_IF_UNMODIFIED_SINCE"] = LAST_MODIFIED_NEWER_STR
  53. response = self.client.get("/condition/")
  54. self.assertFullResponse(response)
  55. self.client.defaults["HTTP_IF_UNMODIFIED_SINCE"] = LAST_MODIFIED_INVALID_STR
  56. response = self.client.get("/condition/")
  57. self.assertFullResponse(response)
  58. self.client.defaults["HTTP_IF_UNMODIFIED_SINCE"] = EXPIRED_LAST_MODIFIED_STR
  59. response = self.client.get("/condition/")
  60. self.assertEqual(response.status_code, 412)
  61. def test_if_none_match(self):
  62. self.client.defaults["HTTP_IF_NONE_MATCH"] = ETAG
  63. response = self.client.get("/condition/")
  64. self.assertNotModified(response)
  65. response = self.client.put("/condition/")
  66. self.assertEqual(response.status_code, 412)
  67. self.client.defaults["HTTP_IF_NONE_MATCH"] = EXPIRED_ETAG
  68. response = self.client.get("/condition/")
  69. self.assertFullResponse(response)
  70. # Several etags in If-None-Match is a bit exotic but why not?
  71. self.client.defaults["HTTP_IF_NONE_MATCH"] = "%s, %s" % (ETAG, EXPIRED_ETAG)
  72. response = self.client.get("/condition/")
  73. self.assertNotModified(response)
  74. def test_weak_if_none_match(self):
  75. """
  76. If-None-Match comparisons use weak matching, so weak and strong ETags
  77. with the same value result in a 304 response.
  78. """
  79. self.client.defaults["HTTP_IF_NONE_MATCH"] = ETAG
  80. response = self.client.get("/condition/weak_etag/")
  81. self.assertNotModified(response)
  82. response = self.client.put("/condition/weak_etag/")
  83. self.assertEqual(response.status_code, 412)
  84. self.client.defaults["HTTP_IF_NONE_MATCH"] = WEAK_ETAG
  85. response = self.client.get("/condition/weak_etag/")
  86. self.assertNotModified(response)
  87. response = self.client.put("/condition/weak_etag/")
  88. self.assertEqual(response.status_code, 412)
  89. response = self.client.get("/condition/")
  90. self.assertNotModified(response)
  91. response = self.client.put("/condition/")
  92. self.assertEqual(response.status_code, 412)
  93. def test_all_if_none_match(self):
  94. self.client.defaults["HTTP_IF_NONE_MATCH"] = "*"
  95. response = self.client.get("/condition/")
  96. self.assertNotModified(response)
  97. response = self.client.put("/condition/")
  98. self.assertEqual(response.status_code, 412)
  99. response = self.client.get("/condition/no_etag/")
  100. self.assertFullResponse(response, check_last_modified=False, check_etag=False)
  101. def test_if_match(self):
  102. self.client.defaults["HTTP_IF_MATCH"] = ETAG
  103. response = self.client.put("/condition/")
  104. self.assertFullResponse(response)
  105. self.client.defaults["HTTP_IF_MATCH"] = EXPIRED_ETAG
  106. response = self.client.put("/condition/")
  107. self.assertEqual(response.status_code, 412)
  108. def test_weak_if_match(self):
  109. """
  110. If-Match comparisons use strong matching, so any comparison involving
  111. a weak ETag return a 412 response.
  112. """
  113. self.client.defaults["HTTP_IF_MATCH"] = ETAG
  114. response = self.client.get("/condition/weak_etag/")
  115. self.assertEqual(response.status_code, 412)
  116. self.client.defaults["HTTP_IF_MATCH"] = WEAK_ETAG
  117. response = self.client.get("/condition/weak_etag/")
  118. self.assertEqual(response.status_code, 412)
  119. response = self.client.get("/condition/")
  120. self.assertEqual(response.status_code, 412)
  121. def test_all_if_match(self):
  122. self.client.defaults["HTTP_IF_MATCH"] = "*"
  123. response = self.client.get("/condition/")
  124. self.assertFullResponse(response)
  125. response = self.client.get("/condition/no_etag/")
  126. self.assertEqual(response.status_code, 412)
  127. def test_both_headers(self):
  128. # See RFC 9110 Section 13.2.2.
  129. self.client.defaults["HTTP_IF_MODIFIED_SINCE"] = LAST_MODIFIED_STR
  130. self.client.defaults["HTTP_IF_NONE_MATCH"] = ETAG
  131. response = self.client.get("/condition/")
  132. self.assertNotModified(response)
  133. self.client.defaults["HTTP_IF_MODIFIED_SINCE"] = EXPIRED_LAST_MODIFIED_STR
  134. self.client.defaults["HTTP_IF_NONE_MATCH"] = ETAG
  135. response = self.client.get("/condition/")
  136. self.assertNotModified(response)
  137. self.client.defaults["HTTP_IF_MODIFIED_SINCE"] = LAST_MODIFIED_STR
  138. self.client.defaults["HTTP_IF_NONE_MATCH"] = EXPIRED_ETAG
  139. response = self.client.get("/condition/")
  140. self.assertFullResponse(response)
  141. self.client.defaults["HTTP_IF_MODIFIED_SINCE"] = EXPIRED_LAST_MODIFIED_STR
  142. self.client.defaults["HTTP_IF_NONE_MATCH"] = EXPIRED_ETAG
  143. response = self.client.get("/condition/")
  144. self.assertFullResponse(response)
  145. def test_both_headers_2(self):
  146. self.client.defaults["HTTP_IF_UNMODIFIED_SINCE"] = LAST_MODIFIED_STR
  147. self.client.defaults["HTTP_IF_MATCH"] = ETAG
  148. response = self.client.get("/condition/")
  149. self.assertFullResponse(response)
  150. self.client.defaults["HTTP_IF_UNMODIFIED_SINCE"] = EXPIRED_LAST_MODIFIED_STR
  151. self.client.defaults["HTTP_IF_MATCH"] = ETAG
  152. response = self.client.get("/condition/")
  153. self.assertFullResponse(response)
  154. self.client.defaults["HTTP_IF_UNMODIFIED_SINCE"] = EXPIRED_LAST_MODIFIED_STR
  155. self.client.defaults["HTTP_IF_MATCH"] = EXPIRED_ETAG
  156. response = self.client.get("/condition/")
  157. self.assertEqual(response.status_code, 412)
  158. self.client.defaults["HTTP_IF_UNMODIFIED_SINCE"] = LAST_MODIFIED_STR
  159. self.client.defaults["HTTP_IF_MATCH"] = EXPIRED_ETAG
  160. response = self.client.get("/condition/")
  161. self.assertEqual(response.status_code, 412)
  162. def test_single_condition_1(self):
  163. self.client.defaults["HTTP_IF_MODIFIED_SINCE"] = LAST_MODIFIED_STR
  164. response = self.client.get("/condition/last_modified/")
  165. self.assertNotModified(response)
  166. response = self.client.get("/condition/etag/")
  167. self.assertFullResponse(response, check_last_modified=False)
  168. def test_single_condition_2(self):
  169. self.client.defaults["HTTP_IF_NONE_MATCH"] = ETAG
  170. response = self.client.get("/condition/etag/")
  171. self.assertNotModified(response)
  172. response = self.client.get("/condition/last_modified/")
  173. self.assertFullResponse(response, check_etag=False)
  174. def test_single_condition_3(self):
  175. self.client.defaults["HTTP_IF_MODIFIED_SINCE"] = EXPIRED_LAST_MODIFIED_STR
  176. response = self.client.get("/condition/last_modified/")
  177. self.assertFullResponse(response, check_etag=False)
  178. def test_single_condition_4(self):
  179. self.client.defaults["HTTP_IF_NONE_MATCH"] = EXPIRED_ETAG
  180. response = self.client.get("/condition/etag/")
  181. self.assertFullResponse(response, check_last_modified=False)
  182. def test_single_condition_5(self):
  183. self.client.defaults["HTTP_IF_MODIFIED_SINCE"] = LAST_MODIFIED_STR
  184. response = self.client.get("/condition/last_modified2/")
  185. self.assertNotModified(response)
  186. response = self.client.get("/condition/etag2/")
  187. self.assertFullResponse(response, check_last_modified=False)
  188. def test_single_condition_6(self):
  189. self.client.defaults["HTTP_IF_NONE_MATCH"] = ETAG
  190. response = self.client.get("/condition/etag2/")
  191. self.assertNotModified(response)
  192. response = self.client.get("/condition/last_modified2/")
  193. self.assertFullResponse(response, check_etag=False)
  194. def test_single_condition_7(self):
  195. self.client.defaults["HTTP_IF_UNMODIFIED_SINCE"] = EXPIRED_LAST_MODIFIED_STR
  196. response = self.client.get("/condition/last_modified/")
  197. self.assertEqual(response.status_code, 412)
  198. response = self.client.get("/condition/etag/")
  199. self.assertEqual(response.status_code, 412)
  200. def test_single_condition_8(self):
  201. self.client.defaults["HTTP_IF_UNMODIFIED_SINCE"] = LAST_MODIFIED_STR
  202. response = self.client.get("/condition/last_modified/")
  203. self.assertFullResponse(response, check_etag=False)
  204. def test_single_condition_9(self):
  205. self.client.defaults["HTTP_IF_UNMODIFIED_SINCE"] = EXPIRED_LAST_MODIFIED_STR
  206. response = self.client.get("/condition/last_modified2/")
  207. self.assertEqual(response.status_code, 412)
  208. response = self.client.get("/condition/etag2/")
  209. self.assertEqual(response.status_code, 412)
  210. def test_single_condition_head(self):
  211. self.client.defaults["HTTP_IF_MODIFIED_SINCE"] = LAST_MODIFIED_STR
  212. response = self.client.head("/condition/")
  213. self.assertNotModified(response)
  214. def test_unquoted(self):
  215. """
  216. The same quoted ETag should be set on the header regardless of whether
  217. etag_func() in condition() returns a quoted or an unquoted ETag.
  218. """
  219. response_quoted = self.client.get("/condition/etag/")
  220. response_unquoted = self.client.get("/condition/unquoted_etag/")
  221. self.assertEqual(response_quoted["ETag"], response_unquoted["ETag"])
  222. # It's possible that the matching algorithm could use the wrong value even
  223. # if the ETag header is set correctly correctly (as tested by
  224. # test_unquoted()), so check that the unquoted value is matched.
  225. def test_unquoted_if_none_match(self):
  226. self.client.defaults["HTTP_IF_NONE_MATCH"] = ETAG
  227. response = self.client.get("/condition/unquoted_etag/")
  228. self.assertNotModified(response)
  229. response = self.client.put("/condition/unquoted_etag/")
  230. self.assertEqual(response.status_code, 412)
  231. self.client.defaults["HTTP_IF_NONE_MATCH"] = EXPIRED_ETAG
  232. response = self.client.get("/condition/unquoted_etag/")
  233. self.assertFullResponse(response, check_last_modified=False)
  234. def test_invalid_etag(self):
  235. self.client.defaults["HTTP_IF_NONE_MATCH"] = '"""'
  236. response = self.client.get("/condition/etag/")
  237. self.assertFullResponse(response, check_last_modified=False)