18
18
import urlparse
19
19
20
20
import appengine_config
21
+ import requests
21
22
import webapp2
22
23
23
24
from google .appengine .datastore import datastore_stub_util
@@ -40,79 +41,120 @@ def get_task_eta(task):
40
41
float (dict (task ['headers' ])['X-AppEngine-TaskETA' ]))
41
42
42
43
43
- class HandlerTest (mox .MoxTestBase ):
44
- """Base test class for webapp2 request handlers.
45
-
46
- Uses App Engine's testbed to set up API stubs:
47
- http://code.google.com/appengine/docs/python/tools/localunittesting.html
48
-
49
- Attributes:
50
- application: WSGIApplication
51
- handler: webapp2.RequestHandler
44
+ class UrlopenResult (object ):
45
+ """A fake urllib2.urlopen() result object. Also works for urlfetch.fetch().
52
46
"""
47
+ def __init__ (self , status_code , content , url = None , headers = {}):
48
+ self .status_code = status_code
49
+ self .content = StringIO .StringIO (content )
50
+ self .url = url
51
+ self .headers = headers
53
52
54
- class UrlopenResult (object ):
55
- """A fake urllib2.urlopen() result object. Also works for urlfetch.fetch().
56
- """
57
- def __init__ (self , status_code , content , url = None , headers = {}):
58
- self .status_code = status_code
59
- self .content = StringIO .StringIO (content )
60
- self .url = url
61
- self .headers = headers
53
+ def read (self , length = - 1 ):
54
+ return self .content .read (length )
62
55
63
- def read (self , length = - 1 ):
64
- return self .content . read ( length )
56
+ def getcode (self ):
57
+ return self .status_code
65
58
66
- def getcode (self ):
67
- return self .status_code
59
+ def geturl (self ):
60
+ return self .url
68
61
69
- def geturl (self ):
70
- return self .url
62
+ def info (self ):
63
+ return rfc822 .Message (StringIO .StringIO (
64
+ '\n ' .join ('%s: %s' % item for item in self .headers .items ())))
71
65
72
- def info (self ):
73
- return rfc822 .Message (StringIO .StringIO (
74
- '\n ' .join ('%s: %s' % item for item in self .headers .items ())))
75
66
67
+ class TestCase (mox .MoxTestBase ):
68
+ """Test case class with lots of extra helpers."""
76
69
77
70
def setUp (self ):
78
- super (HandlerTest , self ).setUp ()
71
+ super (TestCase , self ).setUp ()
72
+ for method in 'get' , 'post' :
73
+ self .mox .StubOutWithMock (requests , method , use_mock_anything = True )
74
+ self .stub_requests_head ()
79
75
80
- logging . getLogger (). removeHandler ( appengine_config . ereporter_logging_handler )
76
+ self . mox . StubOutWithMock ( urllib2 , 'urlopen' )
81
77
82
- os .environ ['APPLICATION_ID' ] = 'app_id'
83
- self .current_user_id = '123'
84
- self .
current_user_email = '[email protected] '
78
+ # set time zone to UTC so that tests don't depend on local time zone
79
+ os .environ ['TZ' ] = 'UTC'
85
80
86
- self .testbed = testbed .Testbed ()
87
- self .testbed .setup_env (user_id = self .current_user_id ,
88
- user_email = self .current_user_email )
89
- self .testbed .activate ()
81
+ def stub_requests_head (self ):
82
+ """Automatically return 200 to outgoing HEAD requests."""
83
+ def fake_head (url , ** kwargs ):
84
+ resp = requests .Response ()
85
+ resp .url = url
86
+ if '.' in url or url .startswith ('http' ):
87
+ resp .headers ['content-type' ] = 'text/html; charset=UTF-8'
88
+ resp .status_code = 200
89
+ else :
90
+ resp .status_code = 404
91
+ return resp
92
+ self .mox .stubs .Set (requests , 'head' , fake_head )
90
93
91
- hrd_policy = datastore_stub_util .PseudoRandomHRConsistencyPolicy (probability = .5 )
92
- self .testbed .init_datastore_v3_stub (consistency_policy = hrd_policy )
93
- self .testbed .init_taskqueue_stub (root_path = '.' )
94
- self .testbed .init_user_stub ()
95
- self .testbed .init_mail_stub ()
96
- self .testbed .init_memcache_stub ()
97
- self .testbed .init_logservice_stub ()
94
+ self ._is_head_mocked = False # expect_requests_head() sets this to True
98
95
99
- self .mox .StubOutWithMock (urllib2 , 'urlopen' )
96
+ def unstub_requests_head (self ):
97
+ """Mock outgoing HEAD requests so they must be expected individually."""
98
+ if not self ._is_head_mocked :
99
+ self .mox .StubOutWithMock (requests , 'head' , use_mock_anything = True )
100
+ self ._is_head_mocked = True
100
101
101
- # unofficial API, whee! this is so we can call
102
- # TaskQueueServiceStub.GetTasks() in tests. see
103
- # google/appengine/api/taskqueue/taskqueue_stub.py
104
- self .taskqueue_stub = self .testbed .get_stub ('taskqueue' )
102
+ def expect_requests_head (self , * args , ** kwargs ):
103
+ self .unstub_requests_head ()
104
+ return self ._expect_requests_call (* args , method = requests .head , ** kwargs )
105
105
106
- self .request = webapp2 .Request .blank ('/' )
107
- self .response = webapp2 .Response ()
108
- self .handler = webapp2 .RequestHandler (self .request , self .response )
106
+ def expect_requests_get (self , * args , ** kwargs ):
107
+ return self ._expect_requests_call (* args , method = requests .get , ** kwargs )
109
108
110
- # set time zone to UTC so that tests don't depend on local time zone
111
- os . environ [ 'TZ' ] = 'UTC'
109
+ def expect_requests_post ( self , * args , ** kwargs ):
110
+ return self . _expect_requests_call ( * args , method = requests . post , ** kwargs )
112
111
113
- def tearDown (self ):
114
- self .testbed .deactivate ()
115
- super (HandlerTest , self ).tearDown ()
112
+ def _expect_requests_call (self , url , response = '' , status_code = 200 ,
113
+ content_type = 'text/html' , method = requests .get ,
114
+ redirected_url = None , response_headers = None ,
115
+ ** kwargs ):
116
+ """
117
+ Args:
118
+ redirected_url: string URL or sequence of string URLs for multiple redirects
119
+ """
120
+ resp = requests .Response ()
121
+
122
+ resp ._text = response
123
+ resp ._content = (response .encode ('utf-8' ) if isinstance (response , unicode )
124
+ else response )
125
+ resp .encoding = 'utf-8'
126
+
127
+ resp .url = url
128
+ if redirected_url is not None :
129
+ if isinstance (redirected_url , basestring ):
130
+ redirected_url = [redirected_url ]
131
+ assert isinstance (redirected_url , (list , tuple ))
132
+ resp .url = redirected_url [- 1 ]
133
+ for u in [url ] + redirected_url [:- 1 ]:
134
+ resp .history .append (requests .Response ())
135
+ resp .history [- 1 ].url = u
136
+
137
+ resp .status_code = status_code
138
+ resp .headers ['content-type' ] = content_type
139
+ if response_headers is not None :
140
+ resp .headers .update (response_headers )
141
+
142
+ kwargs .setdefault ('timeout' , appengine_config .HTTP_TIMEOUT )
143
+ if method is requests .head :
144
+ kwargs ['allow_redirects' ] = True
145
+
146
+ files = kwargs .get ('files' )
147
+ if files :
148
+ def check_files (actual ):
149
+ self .assertEqual (actual .keys (), files .keys ())
150
+ for name , expected in files .items ():
151
+ self .assertEqual (expected , actual [name ].read ())
152
+ return True
153
+ kwargs ['files' ] = mox .Func (check_files )
154
+
155
+ call = method (url , ** kwargs )
156
+ call .AndReturn (resp )
157
+ return call
116
158
117
159
def expect_urlopen (self , url , response = None , status = 200 , data = None ,
118
160
headers = None , response_headers = {}, ** kwargs ):
@@ -170,8 +212,8 @@ def check_request(req):
170
212
call .AndRaise (urllib2 .HTTPError ('url' , status , 'message' ,
171
213
response_headers , response ))
172
214
elif response is not None :
173
- call .AndReturn (self . UrlopenResult (status , response , url = url ,
174
- headers = response_headers ))
215
+ call .AndReturn (UrlopenResult (status , response , url = url ,
216
+ headers = response_headers ))
175
217
176
218
return call
177
219
@@ -304,3 +346,49 @@ def _normalize_lines(val):
304
346
lines = [l .strip () + '\n ' for l in val .splitlines (True )]
305
347
return [l for i , l in enumerate (lines )
306
348
if i <= 1 or not (lines [i - 1 ] == l == '\n ' )]
349
+
350
+
351
+ class HandlerTest (TestCase ):
352
+ """Base test class for webapp2 request handlers.
353
+
354
+ Uses App Engine's testbed to set up API stubs:
355
+ http://code.google.com/appengine/docs/python/tools/localunittesting.html
356
+
357
+ Attributes:
358
+ application: WSGIApplication
359
+ handler: webapp2.RequestHandler
360
+ """
361
+ def setUp (self ):
362
+ super (HandlerTest , self ).setUp ()
363
+
364
+ logging .getLogger ().removeHandler (appengine_config .ereporter_logging_handler )
365
+
366
+ os .environ ['APPLICATION_ID' ] = 'app_id'
367
+ self .current_user_id = '123'
368
+ self .
current_user_email = '[email protected] '
369
+
370
+ self .testbed = testbed .Testbed ()
371
+ self .testbed .setup_env (user_id = self .current_user_id ,
372
+ user_email = self .current_user_email )
373
+ self .testbed .activate ()
374
+
375
+ hrd_policy = datastore_stub_util .PseudoRandomHRConsistencyPolicy (probability = .5 )
376
+ self .testbed .init_datastore_v3_stub (consistency_policy = hrd_policy )
377
+ self .testbed .init_taskqueue_stub (root_path = '.' )
378
+ self .testbed .init_user_stub ()
379
+ self .testbed .init_mail_stub ()
380
+ self .testbed .init_memcache_stub ()
381
+ self .testbed .init_logservice_stub ()
382
+
383
+ # unofficial API, whee! this is so we can call
384
+ # TaskQueueServiceStub.GetTasks() in tests. see
385
+ # google/appengine/api/taskqueue/taskqueue_stub.py
386
+ self .taskqueue_stub = self .testbed .get_stub ('taskqueue' )
387
+
388
+ self .request = webapp2 .Request .blank ('/' )
389
+ self .response = webapp2 .Response ()
390
+ self .handler = webapp2 .RequestHandler (self .request , self .response )
391
+
392
+ def tearDown (self ):
393
+ self .testbed .deactivate ()
394
+ super (HandlerTest , self ).tearDown ()
0 commit comments