1 from __future__
import with_statement
3 from datetime
import datetime
, timedelta
9 from django
.conf
import settings
10 from django
.contrib
.sessions
.backends
.db
import SessionStore
as DatabaseSession
11 from django
.contrib
.sessions
.backends
.cache
import SessionStore
as CacheSession
12 from django
.contrib
.sessions
.backends
.cached_db
import SessionStore
as CacheDBSession
13 from django
.contrib
.sessions
.backends
.file import SessionStore
as FileSession
14 from django
.contrib
.sessions
.backends
.signed_cookies
import SessionStore
as CookieSession
15 from django
.contrib
.sessions
.models
import Session
16 from django
.contrib
.sessions
.middleware
import SessionMiddleware
17 from django
.core
.cache
.backends
.base
import CacheKeyWarning
18 from django
.core
.exceptions
import ImproperlyConfigured
, SuspiciousOperation
19 from django
.http
import HttpResponse
20 from django
.test
import TestCase
, RequestFactory
21 from django
.test
.utils
import override_settings
, get_warnings_state
, restore_warnings_state
22 from django
.utils
import timezone
23 from django
.utils
import unittest
26 class SessionTestsMixin(object):
27 # This does not inherit from TestCase to avoid any tests being run with this
28 # class, which wouldn't work, and to allow different TestCase subclasses to
31 backend
= None # subclasses must specify
34 self
.session
= self
.backend()
37 # NB: be careful to delete any sessions created; stale sessions fill up
38 # the /tmp (with some backends) and eventually overwhelm it after lots
39 # of runs (think buildbots)
42 def test_new_session(self
):
43 self
.assertFalse(self
.session
.modified
)
44 self
.assertFalse(self
.session
.accessed
)
46 def test_get_empty(self
):
47 self
.assertEqual(self
.session
.get('cat'), None)
50 self
.session
['cat'] = "dog"
51 self
.assertTrue(self
.session
.modified
)
52 self
.assertEqual(self
.session
.pop('cat'), 'dog')
55 self
.session
['some key'] = 'exists'
56 # Need to reset these to pretend we haven't accessed it:
60 self
.assertEqual(self
.session
.pop('some key'), 'exists')
61 self
.assertTrue(self
.session
.accessed
)
62 self
.assertTrue(self
.session
.modified
)
63 self
.assertEqual(self
.session
.get('some key'), None)
65 def test_pop_default(self
):
66 self
.assertEqual(self
.session
.pop('some key', 'does not exist'),
68 self
.assertTrue(self
.session
.accessed
)
69 self
.assertFalse(self
.session
.modified
)
71 def test_setdefault(self
):
72 self
.assertEqual(self
.session
.setdefault('foo', 'bar'), 'bar')
73 self
.assertEqual(self
.session
.setdefault('foo', 'baz'), 'bar')
74 self
.assertTrue(self
.session
.accessed
)
75 self
.assertTrue(self
.session
.modified
)
77 def test_update(self
):
78 self
.session
.update({'update key': 1})
79 self
.assertTrue(self
.session
.accessed
)
80 self
.assertTrue(self
.session
.modified
)
81 self
.assertEqual(self
.session
.get('update key', None), 1)
83 def test_has_key(self
):
84 self
.session
['some key'] = 1
85 self
.session
.modified
= False
86 self
.session
.accessed
= False
87 self
.assertTrue('some key' in self
.session
)
88 self
.assertTrue(self
.session
.accessed
)
89 self
.assertFalse(self
.session
.modified
)
91 def test_values(self
):
92 self
.assertEqual(self
.session
.values(), [])
93 self
.assertTrue(self
.session
.accessed
)
94 self
.session
['some key'] = 1
95 self
.assertEqual(self
.session
.values(), [1])
97 def test_iterkeys(self
):
99 self
.session
.modified
= False
100 self
.session
.accessed
= False
101 i
= self
.session
.iterkeys()
102 self
.assertTrue(hasattr(i
, '__iter__'))
103 self
.assertTrue(self
.session
.accessed
)
104 self
.assertFalse(self
.session
.modified
)
105 self
.assertEqual(list(i
), ['x'])
107 def test_itervalues(self
):
108 self
.session
['x'] = 1
109 self
.session
.modified
= False
110 self
.session
.accessed
= False
111 i
= self
.session
.itervalues()
112 self
.assertTrue(hasattr(i
, '__iter__'))
113 self
.assertTrue(self
.session
.accessed
)
114 self
.assertFalse(self
.session
.modified
)
115 self
.assertEqual(list(i
), [1])
117 def test_iteritems(self
):
118 self
.session
['x'] = 1
119 self
.session
.modified
= False
120 self
.session
.accessed
= False
121 i
= self
.session
.iteritems()
122 self
.assertTrue(hasattr(i
, '__iter__'))
123 self
.assertTrue(self
.session
.accessed
)
124 self
.assertFalse(self
.session
.modified
)
125 self
.assertEqual(list(i
), [('x', 1)])
127 def test_clear(self
):
128 self
.session
['x'] = 1
129 self
.session
.modified
= False
130 self
.session
.accessed
= False
131 self
.assertEqual(self
.session
.items(), [('x', 1)])
133 self
.assertEqual(self
.session
.items(), [])
134 self
.assertTrue(self
.session
.accessed
)
135 self
.assertTrue(self
.session
.modified
)
139 self
.assertTrue(self
.session
.exists(self
.session
.session_key
))
141 def test_delete(self
):
143 self
.session
.delete(self
.session
.session_key
)
144 self
.assertFalse(self
.session
.exists(self
.session
.session_key
))
146 def test_flush(self
):
147 self
.session
['foo'] = 'bar'
149 prev_key
= self
.session
.session_key
151 self
.assertFalse(self
.session
.exists(prev_key
))
152 self
.assertNotEqual(self
.session
.session_key
, prev_key
)
153 self
.assertTrue(self
.session
.modified
)
154 self
.assertTrue(self
.session
.accessed
)
156 def test_cycle(self
):
157 self
.session
['a'], self
.session
['b'] = 'c', 'd'
159 prev_key
= self
.session
.session_key
160 prev_data
= self
.session
.items()
161 self
.session
.cycle_key()
162 self
.assertNotEqual(self
.session
.session_key
, prev_key
)
163 self
.assertEqual(self
.session
.items(), prev_data
)
165 def test_invalid_key(self
):
166 # Submitting an invalid session key (either by guessing, or if the db has
167 # removed the key) results in a new key being generated.
169 session
= self
.backend('1')
172 except AttributeError:
173 self
.fail("The session object did not save properly. Middleware may be saving cache items without namespaces.")
174 self
.assertNotEqual(session
.session_key
, '1')
175 self
.assertEqual(session
.get('cat'), None)
178 # Some backends leave a stale cache entry for the invalid
179 # session key; make sure that entry is manually deleted
182 def test_session_key_is_read_only(self
):
183 def set_session_key(session
):
184 session
.session_key
= session
._get
_new
_session
_key
()
185 self
.assertRaises(AttributeError, set_session_key
, self
.session
)
187 # Custom session expiry
188 def test_default_expiry(self
):
189 # A normal session has a max age equal to settings
190 self
.assertEqual(self
.session
.get_expiry_age(), settings
.SESSION_COOKIE_AGE
)
192 # So does a custom session with an idle expiration time of 0 (but it'll
193 # expire at browser close)
194 self
.session
.set_expiry(0)
195 self
.assertEqual(self
.session
.get_expiry_age(), settings
.SESSION_COOKIE_AGE
)
197 def test_custom_expiry_seconds(self
):
199 self
.session
.set_expiry(10)
200 delta
= self
.session
.get_expiry_date() - timezone
.now()
201 self
.assertTrue(delta
.seconds
in (9, 10))
203 age
= self
.session
.get_expiry_age()
204 self
.assertTrue(age
in (9, 10))
206 def test_custom_expiry_timedelta(self
):
208 self
.session
.set_expiry(timedelta(seconds
=10))
209 delta
= self
.session
.get_expiry_date() - timezone
.now()
210 self
.assertTrue(delta
.seconds
in (9, 10))
212 age
= self
.session
.get_expiry_age()
213 self
.assertTrue(age
in (9, 10))
215 def test_custom_expiry_datetime(self
):
216 # Using fixed datetime
217 self
.session
.set_expiry(timezone
.now() + timedelta(seconds
=10))
218 delta
= self
.session
.get_expiry_date() - timezone
.now()
219 self
.assertTrue(delta
.seconds
in (9, 10))
221 age
= self
.session
.get_expiry_age()
222 self
.assertTrue(age
in (9, 10))
224 def test_custom_expiry_reset(self
):
225 self
.session
.set_expiry(None)
226 self
.session
.set_expiry(10)
227 self
.session
.set_expiry(None)
228 self
.assertEqual(self
.session
.get_expiry_age(), settings
.SESSION_COOKIE_AGE
)
230 def test_get_expire_at_browser_close(self
):
231 # Tests get_expire_at_browser_close with different settings and different
233 with
override_settings(SESSION_EXPIRE_AT_BROWSER_CLOSE
=False):
234 self
.session
.set_expiry(10)
235 self
.assertFalse(self
.session
.get_expire_at_browser_close())
237 self
.session
.set_expiry(0)
238 self
.assertTrue(self
.session
.get_expire_at_browser_close())
240 self
.session
.set_expiry(None)
241 self
.assertFalse(self
.session
.get_expire_at_browser_close())
243 with
override_settings(SESSION_EXPIRE_AT_BROWSER_CLOSE
=True):
244 self
.session
.set_expiry(10)
245 self
.assertFalse(self
.session
.get_expire_at_browser_close())
247 self
.session
.set_expiry(0)
248 self
.assertTrue(self
.session
.get_expire_at_browser_close())
250 self
.session
.set_expiry(None)
251 self
.assertTrue(self
.session
.get_expire_at_browser_close())
253 def test_decode(self
):
254 # Ensure we can decode what we encode
255 data
= {'a test key': 'a test value'}
256 encoded
= self
.session
.encode(data
)
257 self
.assertEqual(self
.session
.decode(encoded
), data
)
260 class DatabaseSessionTests(SessionTestsMixin
, TestCase
):
262 backend
= DatabaseSession
264 def test_session_get_decoded(self
):
266 Test we can use Session.get_decoded to retrieve data stored
269 self
.session
['x'] = 1
272 s
= Session
.objects
.get(session_key
=self
.session
.session_key
)
274 self
.assertEqual(s
.get_decoded(), {'x': 1})
276 def test_sessionmanager_save(self
):
278 Test SessionManager.save method
281 self
.session
['y'] = 1
284 s
= Session
.objects
.get(session_key
=self
.session
.session_key
)
286 Session
.objects
.save(s
.session_key
, {'y': 2}, s
.expire_date
)
287 # Clear cache, so that it will be retrieved from DB
288 del self
.session
._session
_cache
289 self
.assertEqual(self
.session
['y'], 2)
292 DatabaseSessionWithTimeZoneTests
= override_settings(USE_TZ
=True)(DatabaseSessionTests
)
295 class CacheDBSessionTests(SessionTestsMixin
, TestCase
):
297 backend
= CacheDBSession
299 def test_exists_searches_cache_first(self
):
301 with self
.assertNumQueries(0):
302 self
.assertTrue(self
.session
.exists(self
.session
.session_key
))
304 def test_load_overlong_key(self
):
305 warnings_state
= get_warnings_state()
306 warnings
.filterwarnings('ignore',
307 category
=CacheKeyWarning
)
308 self
.session
._session
_key
= (string
.ascii_letters
+ string
.digits
) * 20
309 self
.assertEqual(self
.session
.load(), {})
310 restore_warnings_state(warnings_state
)
313 CacheDBSessionWithTimeZoneTests
= override_settings(USE_TZ
=True)(CacheDBSessionTests
)
316 # Don't need DB flushing for these tests, so can use unittest.TestCase as base class
317 class FileSessionTests(SessionTestsMixin
, unittest
.TestCase
):
319 backend
= FileSession
322 super(FileSessionTests
, self
).setUp()
323 # Do file session tests in an isolated directory, and kill it after we're done.
324 self
.original_session_file_path
= settings
.SESSION_FILE_PATH
325 self
.temp_session_store
= settings
.SESSION_FILE_PATH
= tempfile
.mkdtemp()
328 settings
.SESSION_FILE_PATH
= self
.original_session_file_path
329 shutil
.rmtree(self
.temp_session_store
)
330 super(FileSessionTests
, self
).tearDown()
333 SESSION_FILE_PATH
="/if/this/directory/exists/you/have/a/weird/computer")
334 def test_configuration_check(self
):
335 # Make sure the file backend checks for a good storage dir
336 self
.assertRaises(ImproperlyConfigured
, self
.backend
)
338 def test_invalid_key_backslash(self
):
339 # Ensure we don't allow directory-traversal
340 self
.assertRaises(SuspiciousOperation
,
341 self
.backend("a\\b\\c").load
)
343 def test_invalid_key_forwardslash(self
):
344 # Ensure we don't allow directory-traversal
345 self
.assertRaises(SuspiciousOperation
,
346 self
.backend("a/b/c").load
)
349 class CacheSessionTests(SessionTestsMixin
, unittest
.TestCase
):
351 backend
= CacheSession
353 def test_load_overlong_key(self
):
354 warnings_state
= get_warnings_state()
355 warnings
.filterwarnings('ignore',
356 category
=CacheKeyWarning
)
357 self
.session
._session
_key
= (string
.ascii_letters
+ string
.digits
) * 20
358 self
.assertEqual(self
.session
.load(), {})
359 restore_warnings_state(warnings_state
)
362 class SessionMiddlewareTests(unittest
.TestCase
):
364 @override_settings(SESSION_COOKIE_SECURE
=True)
365 def test_secure_session_cookie(self
):
366 request
= RequestFactory().get('/')
367 response
= HttpResponse('Session test')
368 middleware
= SessionMiddleware()
370 # Simulate a request the modifies the session
371 middleware
.process_request(request
)
372 request
.session
['hello'] = 'world'
374 # Handle the response through the middleware
375 response
= middleware
.process_response(request
, response
)
377 response
.cookies
[settings
.SESSION_COOKIE_NAME
]['secure'])
379 @override_settings(SESSION_COOKIE_HTTPONLY
=True)
380 def test_httponly_session_cookie(self
):
381 request
= RequestFactory().get('/')
382 response
= HttpResponse('Session test')
383 middleware
= SessionMiddleware()
385 # Simulate a request the modifies the session
386 middleware
.process_request(request
)
387 request
.session
['hello'] = 'world'
389 # Handle the response through the middleware
390 response
= middleware
.process_response(request
, response
)
392 response
.cookies
[settings
.SESSION_COOKIE_NAME
]['httponly'])
393 self
.assertIn('httponly',
394 str(response
.cookies
[settings
.SESSION_COOKIE_NAME
]))
396 @override_settings(SESSION_COOKIE_HTTPONLY
=False)
397 def test_no_httponly_session_cookie(self
):
398 request
= RequestFactory().get('/')
399 response
= HttpResponse('Session test')
400 middleware
= SessionMiddleware()
402 # Simulate a request the modifies the session
403 middleware
.process_request(request
)
404 request
.session
['hello'] = 'world'
406 # Handle the response through the middleware
407 response
= middleware
.process_response(request
, response
)
408 # If it isn't in the cookie, that's fine (Python 2.5)
409 if 'httponly' in settings
.SESSION_COOKIE_NAME
:
411 response
.cookies
[settings
.SESSION_COOKIE_NAME
]['httponly'])
413 self
.assertNotIn('httponly',
414 str(response
.cookies
[settings
.SESSION_COOKIE_NAME
]))
417 class CookieSessionTests(SessionTestsMixin
, TestCase
):
419 backend
= CookieSession
423 This test tested exists() in the other session backends, but that
424 doesn't make sense for us.
428 def test_cycle(self
):
430 This test tested cycle_key() which would create a new session
431 key for the same session data. But we can't invalidate previously
432 signed cookies (other than letting them expire naturally) so
433 testing for this behavior is meaningless.