blob: 694e49363023b2a148663c4eb0700a4981bac751 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# Copyright 2016 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style
3# license that can be found in the LICENSE file or at
4# https://developers.google.com/open-source/licenses/bsd
5
6"""Unit tests for servlet base class module."""
7from __future__ import print_function
8from __future__ import division
9from __future__ import absolute_import
10
11import time
12import mock
13import unittest
14
15from google.appengine.api import app_identity
16from google.appengine.ext import testbed
17
18import webapp2
19
Adrià Vilanova Martínezac4a6442022-05-15 19:05:13 +020020from framework import framework_constants, servlet_helpers
Copybara854996b2021-09-07 19:36:02 +000021from framework import servlet
22from framework import xsrf
23from proto import project_pb2
24from proto import tracker_pb2
25from proto import user_pb2
26from services import service_manager
27from testing import fake
28from testing import testing_helpers
29
30
31class TestableServlet(servlet.Servlet):
32 """A tiny concrete subclass of abstract class Servlet."""
33
34 def __init__(self, request, response, services=None, do_post_redirect=True):
35 super(TestableServlet, self).__init__(request, response, services=services)
36 self.do_post_redirect = do_post_redirect
37 self.seen_post_data = None
38
39 def ProcessFormData(self, _mr, post_data):
40 self.seen_post_data = post_data
41 if self.do_post_redirect:
42 return '/This/Is?The=Next#Page'
43 else:
44 self.response.write('sending raw data to browser')
45
46
47class ServletTest(unittest.TestCase):
48
49 def setUp(self):
50 services = service_manager.Services(
51 project=fake.ProjectService(),
52 project_star=fake.ProjectStarService(),
53 user=fake.UserService(),
54 usergroup=fake.UserGroupService())
55 services.user.TestAddUser('user@example.com', 111)
56 self.page_class = TestableServlet(
57 webapp2.Request.blank('/'), webapp2.Response(), services=services)
58 self.testbed = testbed.Testbed()
59 self.testbed.activate()
60 self.testbed.init_user_stub()
61 self.testbed.init_memcache_stub()
62 self.testbed.init_datastore_v3_stub()
63
64 def tearDown(self):
65 self.testbed.deactivate()
66
67 def testDefaultValues(self):
68 self.assertEqual(None, self.page_class._MAIN_TAB_MODE)
69 self.assertTrue(self.page_class._TEMPLATE_PATH.endswith('/templates/'))
70 self.assertEqual(None, self.page_class._PAGE_TEMPLATE)
71
72 def testGatherBaseData(self):
73 project = self.page_class.services.project.TestAddProject(
74 'testproj', state=project_pb2.ProjectState.LIVE)
75 project.cached_content_timestamp = 12345
76
77 (_request, mr) = testing_helpers.GetRequestObjects(
78 path='/p/testproj/feeds', project=project)
79 nonce = '1a2b3c4d5e6f7g'
80
81 base_data = self.page_class.GatherBaseData(mr, nonce)
82
83 self.assertEqual(base_data['nonce'], nonce)
84 self.assertEqual(base_data['projectname'], 'testproj')
85 self.assertEqual(base_data['project'].cached_content_timestamp, 12345)
86 self.assertEqual(base_data['project_alert'], None)
87
88 self.assertTrue(base_data['currentPageURL'].endswith('/p/testproj/feeds'))
89 self.assertTrue(
90 base_data['currentPageURLEncoded'].endswith('%2Fp%2Ftestproj%2Ffeeds'))
91
92 def testFormHandlerURL(self):
93 self.assertEqual('/edit.do', self.page_class._FormHandlerURL('/'))
94 self.assertEqual(
95 '/something/edit.do',
96 self.page_class._FormHandlerURL('/something/'))
97 self.assertEqual(
98 '/something/edit.do',
99 self.page_class._FormHandlerURL('/something/edit.do'))
100 self.assertEqual(
101 '/something/detail_ezt.do',
102 self.page_class._FormHandlerURL('/something/detail_ezt'))
103
104 def testProcessForm_BadToken(self):
105 user_id = 111
106 token = 'no soup for you'
107
108 request, mr = testing_helpers.GetRequestObjects(
109 path='/we/we/we?so=excited',
110 params={
111 'yesterday': 'thursday',
112 'today': 'friday',
113 'token': token
114 },
115 user_info={'user_id': user_id},
116 method='POST',
117 )
118 self.assertRaises(
119 xsrf.TokenIncorrect, self.page_class._DoFormProcessing, request, mr)
120 self.assertEqual(None, self.page_class.seen_post_data)
121
122 def testProcessForm_XhrAllowed_BadToken(self):
123 user_id = 111
124 token = 'no soup for you'
125
126 self.page_class.ALLOW_XHR = True
127
128 request, mr = testing_helpers.GetRequestObjects(
129 path='/we/we/we?so=excited',
130 params={
131 'yesterday': 'thursday',
132 'today': 'friday',
133 'token': token
134 },
135 user_info={'user_id': user_id},
136 method='POST',
137 )
138 self.assertRaises(
139 xsrf.TokenIncorrect, self.page_class._DoFormProcessing, request, mr)
140 self.assertEqual(None, self.page_class.seen_post_data)
141
142 def testProcessForm_XhrAllowed_AcceptsPathToken(self):
143 user_id = 111
144 token = xsrf.GenerateToken(user_id, '/we/we/we')
145
146 self.page_class.ALLOW_XHR = True
147
148 request, mr = testing_helpers.GetRequestObjects(
149 path='/we/we/we?so=excited',
150 params={
151 'yesterday': 'thursday',
152 'today': 'friday',
153 'token': token
154 },
155 user_info={'user_id': user_id},
156 method='POST',
157 )
158 with self.assertRaises(webapp2.HTTPException) as cm:
159 self.page_class._DoFormProcessing(request, mr)
160 self.assertEqual(302, cm.exception.code) # forms redirect on success
161
162 self.assertDictEqual(
163 {
164 'yesterday': 'thursday',
165 'today': 'friday',
166 'token': token
167 }, dict(self.page_class.seen_post_data))
168
169 def testProcessForm_XhrAllowed_AcceptsXhrToken(self):
170 user_id = 111
171 token = xsrf.GenerateToken(user_id, 'xhr')
172
173 self.page_class.ALLOW_XHR = True
174
175 request, mr = testing_helpers.GetRequestObjects(
176 path='/we/we/we?so=excited',
177 params={'yesterday': 'thursday', 'today': 'friday', 'token': token},
178 user_info={'user_id': user_id},
179 method='POST',
180 )
181 with self.assertRaises(webapp2.HTTPException) as cm:
182 self.page_class._DoFormProcessing(request, mr)
183 self.assertEqual(302, cm.exception.code) # forms redirect on success
184
185 self.assertDictEqual(
186 {
187 'yesterday': 'thursday',
188 'today': 'friday',
189 'token': token
190 }, dict(self.page_class.seen_post_data))
191
192 def testProcessForm_RawResponse(self):
193 user_id = 111
194 token = xsrf.GenerateToken(user_id, '/we/we/we')
195
196 request, mr = testing_helpers.GetRequestObjects(
197 path='/we/we/we?so=excited',
198 params={'yesterday': 'thursday', 'today': 'friday', 'token': token},
199 user_info={'user_id': user_id},
200 method='POST',
201 )
202 self.page_class.do_post_redirect = False
203 self.page_class._DoFormProcessing(request, mr)
204 self.assertEqual(
205 'sending raw data to browser',
206 self.page_class.response.body)
207
208 def testProcessForm_Normal(self):
209 user_id = 111
210 token = xsrf.GenerateToken(user_id, '/we/we/we')
211
212 request, mr = testing_helpers.GetRequestObjects(
213 path='/we/we/we?so=excited',
214 params={'yesterday': 'thursday', 'today': 'friday', 'token': token},
215 user_info={'user_id': user_id},
216 method='POST',
217 )
218 with self.assertRaises(webapp2.HTTPException) as cm:
219 self.page_class._DoFormProcessing(request, mr)
220 self.assertEqual(302, cm.exception.code) # forms redirect on success
221
222 self.assertDictEqual(
223 {'yesterday': 'thursday', 'today': 'friday', 'token': token},
224 dict(self.page_class.seen_post_data))
225
226 def testCalcProjectAlert(self):
227 project = fake.Project(
228 project_name='alerttest', state=project_pb2.ProjectState.LIVE)
229
Adrià Vilanova Martínezac4a6442022-05-15 19:05:13 +0200230 project_alert = servlet_helpers.CalcProjectAlert(project)
Copybara854996b2021-09-07 19:36:02 +0000231 self.assertEqual(project_alert, None)
232
233 project.state = project_pb2.ProjectState.ARCHIVED
Adrià Vilanova Martínezac4a6442022-05-15 19:05:13 +0200234 project_alert = servlet_helpers.CalcProjectAlert(project)
Copybara854996b2021-09-07 19:36:02 +0000235 self.assertEqual(
236 project_alert,
237 'Project is archived: read-only by members only.')
238
239 delete_time = int(time.time() + framework_constants.SECS_PER_DAY * 1.5)
240 project.delete_time = delete_time
Adrià Vilanova Martínezac4a6442022-05-15 19:05:13 +0200241 project_alert = servlet_helpers.CalcProjectAlert(project)
Copybara854996b2021-09-07 19:36:02 +0000242 self.assertEqual(project_alert, 'Scheduled for deletion in 1 day.')
243
244 delete_time = int(time.time() + framework_constants.SECS_PER_DAY * 2.5)
245 project.delete_time = delete_time
Adrià Vilanova Martínezac4a6442022-05-15 19:05:13 +0200246 project_alert = servlet_helpers.CalcProjectAlert(project)
Copybara854996b2021-09-07 19:36:02 +0000247 self.assertEqual(project_alert, 'Scheduled for deletion in 2 days.')
248
249 def testCheckForMovedProject_NoRedirect(self):
250 project = fake.Project(
251 project_name='proj', state=project_pb2.ProjectState.LIVE)
252 request, mr = testing_helpers.GetRequestObjects(
253 path='/p/proj', project=project)
254 self.page_class._CheckForMovedProject(mr, request)
255
256 request, mr = testing_helpers.GetRequestObjects(
257 path='/p/proj/source/browse/p/adminAdvanced', project=project)
258 self.page_class._CheckForMovedProject(mr, request)
259
260 def testCheckForMovedProject_Redirect(self):
261 project = fake.Project(project_name='proj', moved_to='http://example.com')
262 request, mr = testing_helpers.GetRequestObjects(
263 path='/p/proj', project=project)
264 with self.assertRaises(webapp2.HTTPException) as cm:
265 self.page_class._CheckForMovedProject(mr, request)
266 self.assertEqual(302, cm.exception.code) # redirect because project moved
267
268 request, mr = testing_helpers.GetRequestObjects(
269 path='/p/proj/source/browse/p/adminAdvanced', project=project)
270 with self.assertRaises(webapp2.HTTPException) as cm:
271 self.page_class._CheckForMovedProject(mr, request)
272 self.assertEqual(302, cm.exception.code) # redirect because project moved
273
274 def testCheckForMovedProject_AdminAdvanced(self):
275 """We do not redirect away from the page that edits project state."""
276 project = fake.Project(project_name='proj', moved_to='http://example.com')
277 request, mr = testing_helpers.GetRequestObjects(
278 path='/p/proj/adminAdvanced', project=project)
279 self.page_class._CheckForMovedProject(mr, request)
280
281 request, mr = testing_helpers.GetRequestObjects(
282 path='/p/proj/adminAdvanced?ts=123234', project=project)
283 self.page_class._CheckForMovedProject(mr, request)
284
285 request, mr = testing_helpers.GetRequestObjects(
286 path='/p/proj/adminAdvanced.do', project=project)
287 self.page_class._CheckForMovedProject(mr, request)
288
289 @mock.patch('settings.branded_domains',
290 {'proj': 'branded.example.com', '*': 'bugs.chromium.org'})
291 def testMaybeRedirectToBrandedDomain_RedirBrandedProject(self):
292 """We redirect for a branded project if the user typed a different host."""
293 project = fake.Project(project_name='proj')
294 request, _mr = testing_helpers.GetRequestObjects(
295 path='/p/proj/path', project=project)
296 with self.assertRaises(webapp2.HTTPException) as cm:
297 self.page_class._MaybeRedirectToBrandedDomain(request, 'proj')
298 self.assertEqual(302, cm.exception.code) # forms redirect on success
299 self.assertEqual('https://branded.example.com/p/proj/path?redir=1',
300 cm.exception.location)
301
302 request, _mr = testing_helpers.GetRequestObjects(
303 path='/p/proj/path?query', project=project)
304 with self.assertRaises(webapp2.HTTPException) as cm:
305 self.page_class._MaybeRedirectToBrandedDomain(request, 'proj')
306 self.assertEqual(302, cm.exception.code) # forms redirect on success
307 self.assertEqual('https://branded.example.com/p/proj/path?query&redir=1',
308 cm.exception.location)
309
310 @mock.patch('settings.branded_domains',
311 {'proj': 'branded.example.com', '*': 'bugs.chromium.org'})
312 def testMaybeRedirectToBrandedDomain_AvoidRedirLoops(self):
313 """Don't redirect for a branded project if already redirected."""
314 project = fake.Project(project_name='proj')
315 request, _mr = testing_helpers.GetRequestObjects(
316 path='/p/proj/path?redir=1', project=project)
317 # No redirect happens.
318 self.page_class._MaybeRedirectToBrandedDomain(request, 'proj')
319
320 @mock.patch('settings.branded_domains',
321 {'proj': 'branded.example.com', '*': 'bugs.chromium.org'})
322 def testMaybeRedirectToBrandedDomain_NonProjectPage(self):
323 """Don't redirect for a branded project if not in any project."""
324 request, _mr = testing_helpers.GetRequestObjects(
325 path='/u/user@example.com')
326 # No redirect happens.
327 self.page_class._MaybeRedirectToBrandedDomain(request, None)
328
329 @mock.patch('settings.branded_domains',
330 {'proj': 'branded.example.com', '*': 'bugs.chromium.org'})
331 def testMaybeRedirectToBrandedDomain_AlreadyOnBrandedHost(self):
332 """Don't redirect for a branded project if already on branded domain."""
333 project = fake.Project(project_name='proj')
334 request, _mr = testing_helpers.GetRequestObjects(
335 path='/p/proj/path', project=project)
336 request.host = 'branded.example.com'
337 # No redirect happens.
338 self.page_class._MaybeRedirectToBrandedDomain(request, 'proj')
339
340 @mock.patch('settings.branded_domains',
341 {'proj': 'branded.example.com', '*': 'bugs.chromium.org'})
342 def testMaybeRedirectToBrandedDomain_Localhost(self):
343 """Don't redirect for a branded project on localhost."""
344 project = fake.Project(project_name='proj')
345 request, _mr = testing_helpers.GetRequestObjects(
346 path='/p/proj/path', project=project)
347 request.host = 'localhost:8080'
348 # No redirect happens.
349 self.page_class._MaybeRedirectToBrandedDomain(request, 'proj')
350
351 request.host = '0.0.0.0:8080'
352 # No redirect happens.
353 self.page_class._MaybeRedirectToBrandedDomain(request, 'proj')
354
355 @mock.patch('settings.branded_domains',
356 {'proj': 'branded.example.com', '*': 'bugs.chromium.org'})
357 def testMaybeRedirectToBrandedDomain_NotBranded(self):
358 """Don't redirect for a non-branded project."""
359 project = fake.Project(project_name='other')
360 request, _mr = testing_helpers.GetRequestObjects(
361 path='/p/other/path?query', project=project)
362 request.host = 'branded.example.com' # But other project is unbranded.
363
364 with self.assertRaises(webapp2.HTTPException) as cm:
365 self.page_class._MaybeRedirectToBrandedDomain(request, 'other')
366 self.assertEqual(302, cm.exception.code) # forms redirect on success
367 self.assertEqual('https://bugs.chromium.org/p/other/path?query&redir=1',
368 cm.exception.location)
369
370 def testGatherHelpData_Normal(self):
371 project = fake.Project(project_name='proj')
372 _request, mr = testing_helpers.GetRequestObjects(
373 path='/p/proj', project=project)
374 help_data = self.page_class.GatherHelpData(mr, {})
375 self.assertEqual(None, help_data['cue'])
376 self.assertEqual(None, help_data['account_cue'])
377
378 def testGatherHelpData_VacationReminder(self):
379 project = fake.Project(project_name='proj')
380 _request, mr = testing_helpers.GetRequestObjects(
381 path='/p/proj', project=project)
382 mr.auth.user_id = 111
383 mr.auth.user_pb.vacation_message = 'Gone skiing'
384 help_data = self.page_class.GatherHelpData(mr, {})
385 self.assertEqual('you_are_on_vacation', help_data['cue'])
386
387 self.page_class.services.user.SetUserPrefs(
388 'cnxn', 111,
389 [user_pb2.UserPrefValue(name='you_are_on_vacation', value='true')])
390 help_data = self.page_class.GatherHelpData(mr, {})
391 self.assertEqual(None, help_data['cue'])
392 self.assertEqual(None, help_data['account_cue'])
393
394 def testGatherHelpData_YouAreBouncing(self):
395 project = fake.Project(project_name='proj')
396 _request, mr = testing_helpers.GetRequestObjects(
397 path='/p/proj', project=project)
398 mr.auth.user_id = 111
399 mr.auth.user_pb.email_bounce_timestamp = 1497647529
400 help_data = self.page_class.GatherHelpData(mr, {})
401 self.assertEqual('your_email_bounced', help_data['cue'])
402
403 self.page_class.services.user.SetUserPrefs(
404 'cnxn', 111,
405 [user_pb2.UserPrefValue(name='your_email_bounced', value='true')])
406 help_data = self.page_class.GatherHelpData(mr, {})
407 self.assertEqual(None, help_data['cue'])
408 self.assertEqual(None, help_data['account_cue'])
409
410 def testGatherHelpData_ChildAccount(self):
411 """Display a warning when user is signed in to a child account."""
412 project = fake.Project(project_name='proj')
413 _request, mr = testing_helpers.GetRequestObjects(
414 path='/p/proj', project=project)
415 mr.auth.user_pb.linked_parent_id = 111
416 help_data = self.page_class.GatherHelpData(mr, {})
417 self.assertEqual(None, help_data['cue'])
418 self.assertEqual('switch_to_parent_account', help_data['account_cue'])
419 self.assertEqual('user@example.com', help_data['parent_email'])
420
421 def testGatherDebugData_Visibility(self):
422 project = fake.Project(
423 project_name='testtest', state=project_pb2.ProjectState.LIVE)
424 _request, mr = testing_helpers.GetRequestObjects(
425 path='/p/foo/servlet_path', project=project)
426 debug_data = self.page_class.GatherDebugData(mr, {})
427 self.assertEqual('off', debug_data['dbg'])
428
429 _request, mr = testing_helpers.GetRequestObjects(
430 path='/p/foo/servlet_path?debug=1', project=project)
431 debug_data = self.page_class.GatherDebugData(mr, {})
432 self.assertEqual('on', debug_data['dbg'])
433
434
435class ProjectIsRestrictedTest(unittest.TestCase):
436
437 def testNonRestrictedProject(self):
438 proj = project_pb2.Project()
439 mr = testing_helpers.MakeMonorailRequest()
440 mr.project = proj
441
442 proj.access = project_pb2.ProjectAccess.ANYONE
443 proj.state = project_pb2.ProjectState.LIVE
Adrià Vilanova Martínezac4a6442022-05-15 19:05:13 +0200444 self.assertFalse(servlet_helpers.ProjectIsRestricted(mr))
Copybara854996b2021-09-07 19:36:02 +0000445
446 proj.state = project_pb2.ProjectState.ARCHIVED
Adrià Vilanova Martínezac4a6442022-05-15 19:05:13 +0200447 self.assertFalse(servlet_helpers.ProjectIsRestricted(mr))
Copybara854996b2021-09-07 19:36:02 +0000448
449 def testRestrictedProject(self):
450 proj = project_pb2.Project()
451 mr = testing_helpers.MakeMonorailRequest()
452 mr.project = proj
453
454 proj.state = project_pb2.ProjectState.LIVE
455 proj.access = project_pb2.ProjectAccess.MEMBERS_ONLY
Adrià Vilanova Martínezac4a6442022-05-15 19:05:13 +0200456 self.assertTrue(servlet_helpers.ProjectIsRestricted(mr))
457
Copybara854996b2021-09-07 19:36:02 +0000458
459class VersionBaseTest(unittest.TestCase):
460
461 @mock.patch('settings.local_mode', True)
462 def testLocalhost(self):
463 request = webapp2.Request.blank('/', base_url='http://localhost:8080')
Adrià Vilanova Martínezac4a6442022-05-15 19:05:13 +0200464 actual = servlet_helpers.VersionBaseURL(request)
Copybara854996b2021-09-07 19:36:02 +0000465 expected = 'http://localhost:8080'
466 self.assertEqual(expected, actual)
467
468 @mock.patch('settings.local_mode', False)
469 @mock.patch('google.appengine.api.app_identity.get_default_version_hostname')
470 def testProd(self, mock_gdvh):
471 mock_gdvh.return_value = 'monorail-prod.appspot.com'
472 request = webapp2.Request.blank('/', base_url='https://bugs.chromium.org')
Adrià Vilanova Martínezac4a6442022-05-15 19:05:13 +0200473 actual = servlet_helpers.VersionBaseURL(request)
Copybara854996b2021-09-07 19:36:02 +0000474 expected = 'https://test-dot-monorail-prod.appspot.com'
475 self.assertEqual(expected, actual)