|
1 from logilab.common.testlib import TestCase, unittest_main, tag, Tags |
|
2 |
|
3 from cubicweb.web import StatusResponse |
|
4 from cubicweb.devtools.fake import FakeRequest |
|
5 |
|
6 |
|
7 def _test_cache(hin, hout, method='GET'): |
|
8 """forge and process a request |
|
9 |
|
10 return status code and the request object |
|
11 |
|
12 status is None is no cache is involved |
|
13 """ |
|
14 # forge request |
|
15 req = FakeRequest(method=method) |
|
16 for key, value in hin: |
|
17 req._headers_in.addRawHeader(key, str(value)) |
|
18 for key, value in hout: |
|
19 req.headers_out.addRawHeader(key, str(value)) |
|
20 # process |
|
21 status = None |
|
22 try: |
|
23 req.validate_cache() |
|
24 except StatusResponse, ex: |
|
25 status = ex.status |
|
26 return status, req |
|
27 |
|
28 class HTTPCache(TestCase): |
|
29 """Check that the http cache logiac work as expected |
|
30 (as far as we understood the RFC) |
|
31 |
|
32 """ |
|
33 tags = TestCase.tags | Tags('http', 'cache') |
|
34 |
|
35 |
|
36 def assertCache(self, expected, status, situation=''): |
|
37 """simple assert for nicer message""" |
|
38 if expected != status: |
|
39 if expected is None: |
|
40 expected = "MODIFIED" |
|
41 if status is None: |
|
42 status = "MODIFIED" |
|
43 msg = 'expected %r got %r' % (expected, status) |
|
44 if situation: |
|
45 msg = "%s - when: %s" % (msg, situation) |
|
46 self.fail(msg) |
|
47 |
|
48 def test_IN_none_OUT_none(self): |
|
49 #: test that no caching is requested when not data is available |
|
50 #: on any side |
|
51 status, req =_test_cache((),()) |
|
52 self.assertIsNone(status) |
|
53 |
|
54 def test_IN_Some_OUT_none(self): |
|
55 #: test that no caching is requested when no data is available |
|
56 #: server (origin) side |
|
57 hin = [('if-modified-since','Sat, 14 Apr 2012 14:39:32 GM'), |
|
58 ] |
|
59 status, req = _test_cache(hin, ()) |
|
60 self.assertIsNone(status) |
|
61 hin = [('if-none-match','babar/huitre'), |
|
62 ] |
|
63 status, req = _test_cache(hin, ()) |
|
64 self.assertIsNone(status) |
|
65 hin = [('if-modified-since','Sat, 14 Apr 2012 14:39:32 GM'), |
|
66 ('if-none-match','babar/huitre'), |
|
67 ] |
|
68 status, req = _test_cache(hin, ()) |
|
69 self.assertIsNone(status) |
|
70 |
|
71 def test_IN_none_OUT_Some(self): |
|
72 #: test that no caching is requested when no data is provided |
|
73 #: by the client |
|
74 hout = [('last-modified','Sat, 14 Apr 2012 14:39:32 GM'), |
|
75 ] |
|
76 status, req = _test_cache((), hout) |
|
77 self.assertIsNone(status) |
|
78 hout = [('etag','babar/huitre'), |
|
79 ] |
|
80 status, req = _test_cache((), hout) |
|
81 self.assertIsNone(status) |
|
82 hout = [('last-modified', 'Sat, 14 Apr 2012 14:39:32 GM'), |
|
83 ('etag','babar/huitre'), |
|
84 ] |
|
85 status, req = _test_cache((), hout) |
|
86 self.assertIsNone(status) |
|
87 |
|
88 @tag('last_modified') |
|
89 def test_last_modified_newer(self): |
|
90 #: test the proper behavior of modification date only |
|
91 # newer |
|
92 hin = [('if-modified-since', 'Sat, 13 Apr 2012 14:39:32 GM'), |
|
93 ] |
|
94 hout = [('last-modified', 'Sat, 14 Apr 2012 14:39:32 GM'), |
|
95 ] |
|
96 status, req = _test_cache(hin, hout) |
|
97 self.assertCache(None, status, 'origin is newer than client') |
|
98 |
|
99 @tag('last_modified') |
|
100 def test_last_modified_older(self): |
|
101 # older |
|
102 hin = [('if-modified-since', 'Sat, 15 Apr 2012 14:39:32 GM'), |
|
103 ] |
|
104 hout = [('last-modified', 'Sat, 14 Apr 2012 14:39:32 GM'), |
|
105 ] |
|
106 status, req = _test_cache(hin, hout) |
|
107 self.assertCache(304, status, 'origin is older than client') |
|
108 |
|
109 @tag('last_modified') |
|
110 def test_last_modified_same(self): |
|
111 # same |
|
112 hin = [('if-modified-since', 'Sat, 14 Apr 2012 14:39:32 GM'), |
|
113 ] |
|
114 hout = [('last-modified', 'Sat, 14 Apr 2012 14:39:32 GM'), |
|
115 ] |
|
116 status, req = _test_cache(hin, hout) |
|
117 self.assertCache(304, status, 'origin is equal to client') |
|
118 |
|
119 @tag('etag') |
|
120 def test_etag_mismatch(self): |
|
121 #: test the proper behavior of etag only |
|
122 # etag mismatch |
|
123 hin = [('if-none-match', 'babar'), |
|
124 ] |
|
125 hout = [('etag', 'celestine'), |
|
126 ] |
|
127 status, req = _test_cache(hin, hout) |
|
128 self.assertCache(None, status, 'etag mismatch') |
|
129 |
|
130 @tag('etag') |
|
131 def test_etag_match(self): |
|
132 # etag match |
|
133 hin = [('if-none-match', 'babar'), |
|
134 ] |
|
135 hout = [('etag', 'babar'), |
|
136 ] |
|
137 status, req = _test_cache(hin, hout) |
|
138 self.assertCache(304, status, 'etag match') |
|
139 # etag match in multiple |
|
140 hin = [('if-none-match', 'loutre'), |
|
141 ('if-none-match', 'babar'), |
|
142 ] |
|
143 hout = [('etag', 'babar'), |
|
144 ] |
|
145 status, req = _test_cache(hin, hout) |
|
146 self.assertCache(304, status, 'etag match in multiple') |
|
147 # client use "*" as etag |
|
148 hin = [('if-none-match', '*'), |
|
149 ] |
|
150 hout = [('etag', 'babar'), |
|
151 ] |
|
152 status, req = _test_cache(hin, hout) |
|
153 self.assertCache(304, status, 'client use "*" as etag') |
|
154 |
|
155 @tag('etag', 'last_modified') |
|
156 def test_both(self): |
|
157 #: test the proper behavior of etag only |
|
158 # both wrong |
|
159 hin = [('if-none-match', 'babar'), |
|
160 ('if-modified-since', 'Sat, 14 Apr 2012 14:39:32 GM'), |
|
161 ] |
|
162 hout = [('etag', 'loutre'), |
|
163 ('last-modified', 'Sat, 15 Apr 2012 14:39:32 GM'), |
|
164 ] |
|
165 status, req = _test_cache(hin, hout) |
|
166 self.assertCache(None, status, 'both wrong') |
|
167 |
|
168 @tag('etag', 'last_modified') |
|
169 def test_both_etag_mismatch(self): |
|
170 # both etag mismatch |
|
171 hin = [('if-none-match', 'babar'), |
|
172 ('if-modified-since', 'Sat, 14 Apr 2012 14:39:32 GM'), |
|
173 ] |
|
174 hout = [('etag', 'loutre'), |
|
175 ('last-modified', 'Sat, 13 Apr 2012 14:39:32 GM'), |
|
176 ] |
|
177 status, req = _test_cache(hin, hout) |
|
178 self.assertCache(None, status, 'both but etag mismatch') |
|
179 |
|
180 @tag('etag', 'last_modified') |
|
181 def test_both_but_modified(self): |
|
182 # both but modified |
|
183 hin = [('if-none-match', 'babar'), |
|
184 ('if-modified-since', 'Sat, 14 Apr 2012 14:39:32 GM'), |
|
185 ] |
|
186 hout = [('etag', 'babar'), |
|
187 ('last-modified', 'Sat, 15 Apr 2012 14:39:32 GM'), |
|
188 ] |
|
189 status, req = _test_cache(hin, hout) |
|
190 self.assertCache(None, status, 'both but modified') |
|
191 |
|
192 @tag('etag', 'last_modified') |
|
193 def test_both_ok(self): |
|
194 # both ok |
|
195 hin = [('if-none-match', 'babar'), |
|
196 ('if-modified-since', 'Sat, 14 Apr 2012 14:39:32 GM'), |
|
197 ] |
|
198 hout = [('etag', 'babar'), |
|
199 ('last-modified', 'Sat, 13 Apr 2012 14:39:32 GM'), |
|
200 ] |
|
201 status, req = _test_cache(hin, hout) |
|
202 self.assertCache(304, status, 'both ok') |
|
203 |
|
204 @tag('etag', 'HEAD') |
|
205 def test_head_verb(self): |
|
206 #: check than FOUND 200 is properly raise without content on HEAD request |
|
207 #: This logic does not really belong here :-/ |
|
208 # modified |
|
209 hin = [('if-none-match', 'babar'), |
|
210 ] |
|
211 hout = [('etag', 'rhino/really-not-babar'), |
|
212 ] |
|
213 status, req = _test_cache(hin, hout, method='HEAD') |
|
214 self.assertCache(200, status, 'modifier HEAD verb') |
|
215 # not modified |
|
216 hin = [('if-none-match', 'babar'), |
|
217 ] |
|
218 hout = [('etag', 'babar'), |
|
219 ] |
|
220 status, req = _test_cache(hin, hout, method='HEAD') |
|
221 self.assertCache(304, status, 'not modifier HEAD verb') |
|
222 |
|
223 @tag('etag', 'POST') |
|
224 def test_post_verb(self): |
|
225 # modified |
|
226 hin = [('if-none-match', 'babar'), |
|
227 ] |
|
228 hout = [('etag', 'rhino/really-not-babar'), |
|
229 ] |
|
230 status, req = _test_cache(hin, hout, method='POST') |
|
231 self.assertCache(None, status, 'modifier HEAD verb') |
|
232 # not modified |
|
233 hin = [('if-none-match', 'babar'), |
|
234 ] |
|
235 hout = [('etag', 'babar'), |
|
236 ] |
|
237 status, req = _test_cache(hin, hout, method='POST') |
|
238 self.assertCache(412, status, 'not modifier HEAD verb') |
|
239 |
|
240 @tag('expires') |
|
241 def test_expires_added(self): |
|
242 #: Check that Expires header is added: |
|
243 #: - when the page is modified |
|
244 #: - when none was already present |
|
245 hin = [('if-none-match', 'babar'), |
|
246 ] |
|
247 hout = [('etag', 'rhino/really-not-babar'), |
|
248 ] |
|
249 status, req = _test_cache(hin, hout) |
|
250 self.assertCache(None, status, 'modifier HEAD verb') |
|
251 value = req.headers_out.getHeader('expires') |
|
252 self.assertIsNotNone(value) |
|
253 |
|
254 @tag('expires') |
|
255 def test_expires_not_added(self): |
|
256 #: Check that Expires header is not added if NOT-MODIFIED |
|
257 hin = [('if-none-match', 'babar'), |
|
258 ] |
|
259 hout = [('etag', 'babar'), |
|
260 ] |
|
261 status, req = _test_cache(hin, hout) |
|
262 self.assertCache(304, status, 'not modifier HEAD verb') |
|
263 value = req.headers_out.getHeader('expires') |
|
264 self.assertIsNone(value) |
|
265 |
|
266 @tag('expires') |
|
267 def test_expires_no_overwrite(self): |
|
268 #: Check that cache does not overwrite existing Expires header |
|
269 hin = [('if-none-match', 'babar'), |
|
270 ] |
|
271 DATE = 'Sat, 13 Apr 2012 14:39:32 GM' |
|
272 hout = [('etag', 'rhino/really-not-babar'), |
|
273 ('expires', DATE), |
|
274 ] |
|
275 status, req = _test_cache(hin, hout) |
|
276 self.assertCache(None, status, 'not modifier HEAD verb') |
|
277 value = req.headers_out.getRawHeaders('expires') |
|
278 self.assertEqual(value, [DATE]) |
|
279 |
|
280 |
|
281 if __name__ == '__main__': |
|
282 unittest_main() |