2 # Copyright 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 from HTMLParser
import HTMLParser
9 from fake_fetchers
import ConfigureFakeFetchers
10 from host_file_system_provider
import HostFileSystemProvider
11 from patch_servlet
import PatchServlet
12 from render_servlet
import RenderServlet
13 from server_instance
import ServerInstance
14 from servlet
import Request
15 from test_branch_utility
import TestBranchUtility
16 from test_util
import DisableLogging
20 _ALLOWED_HOST
= 'chrome-apps-doc.appspot.com'
23 def _CheckURLsArePatched(content
, patch_servlet_path
):
25 class LinkChecker(HTMLParser
):
26 def handle_starttag(self
, tag
, attrs
):
29 tag_description
= '<a %s .../>' % ' '.join('%s="%s"' % (key
, val
)
30 for key
, val
in attrs
)
32 if ('href' in attrs
and
33 attrs
['href'].startswith('/') and
34 not attrs
['href'].startswith('/%s/' % patch_servlet_path
)):
35 errors
.append('%s has an unqualified href' % tag_description
)
36 LinkChecker().feed(content
)
40 class _RenderServletDelegate(RenderServlet
.Delegate
):
41 def CreateServerInstance(self
):
42 return ServerInstance
.ForLocal()
44 class _PatchServletDelegate(RenderServlet
.Delegate
):
45 def CreateBranchUtility(self
, object_store_creator
):
46 return TestBranchUtility
.CreateWithCannedData()
48 def CreateHostFileSystemProvider(self
, object_store_creator
, **optargs
):
49 return HostFileSystemProvider
.ForLocal(object_store_creator
, **optargs
)
51 def CreateGithubFileSystemProvider(self
, object_store_creator
):
52 return GithubFileSystemProvider
.ForEmpty()
55 class PatchServletTest(unittest
.TestCase
):
57 ConfigureFakeFetchers()
59 def _RenderWithPatch(self
, path
, issue
):
60 path_with_issue
= '%s/%s' % (issue
, path
)
61 return PatchServlet(Request
.ForTest(path_with_issue
, host
=_ALLOWED_HOST
),
62 _PatchServletDelegate()).Get()
64 def _RenderWithoutPatch(self
, path
):
65 return RenderServlet(Request
.ForTest(path
, host
=_ALLOWED_HOST
),
66 _RenderServletDelegate()).Get()
68 def _RenderAndCheck(self
, path
, issue
, expected_equal
):
69 '''Renders |path| with |issue| patched in and asserts that the result is
70 the same as |expected_equal| modulo any links that get rewritten to
73 patched_response
= self
._RenderWithPatch
(path
, issue
)
74 unpatched_response
= self
._RenderWithoutPatch
(path
)
75 for header
in ('Cache-Control', 'ETag'):
76 patched_response
.headers
.pop(header
, None)
77 unpatched_response
.headers
.pop(header
, None)
78 unpatched_content
= unpatched_response
.content
.ToString()
80 # Check that all links in the patched content are qualified with
81 # the patch URL, then strip them out for checking (in)equality.
82 patched_content
= patched_response
.content
.ToString()
83 patch_servlet_path
= '_patch/%s' % issue
84 errors
= _CheckURLsArePatched(patched_content
, patch_servlet_path
)
85 self
.assertFalse(errors
,
86 '%s\nFound errors:\n * %s' % (patched_content
, '\n * '.join(errors
)))
87 patched_content
= patched_content
.replace('/%s' % patch_servlet_path
, '')
89 self
.assertEqual(patched_response
.status
, unpatched_response
.status
)
90 self
.assertEqual(patched_response
.headers
, unpatched_response
.headers
)
92 self
.assertEqual(patched_content
, unpatched_content
)
94 self
.assertNotEqual(patched_content
, unpatched_content
)
96 def _RenderAndAssertEqual(self
, path
, issue
):
97 self
._RenderAndCheck
(path
, issue
, True)
99 def _RenderAndAssertNotEqual(self
, path
, issue
):
100 self
._RenderAndCheck
(path
, issue
, False)
102 @DisableLogging('warning')
103 def _AssertNotFound(self
, path
, issue
):
104 response
= self
._RenderWithPatch
(path
, issue
)
105 self
.assertEqual(response
.status
, 404,
106 'Path %s with issue %s should have been removed for %s.' % (
107 path
, issue
, response
))
109 def _AssertOk(self
, path
, issue
):
110 response
= self
._RenderWithPatch
(path
, issue
)
111 self
.assertEqual(response
.status
, 200,
112 'Failed to render path %s with issue %s.' % (path
, issue
))
113 self
.assertTrue(len(response
.content
.ToString()) > 0,
114 'Rendered result for path %s with issue %s should not be empty.' %
117 def _AssertRedirect(self
, path
, issue
, redirect_path
):
118 response
= self
._RenderWithPatch
(path
, issue
)
119 self
.assertEqual(302, response
.status
)
120 self
.assertEqual('/_patch/%s/%s' % (issue
, redirect_path
),
121 response
.headers
['Location'])
123 def testRender(self
):
124 # '_patch' is not included in paths below because it's stripped by Handler.
127 # TODO(kalman): Test with chrome_sidenav.json once the sidenav logic has
130 # extensions/runtime.html is removed in the patch, should redirect to the
132 self
._AssertRedirect
('extensions/runtime', issue
, 'apps/runtime')
134 # apps/runtime.html is not removed.
135 self
._RenderAndAssertEqual
('apps/runtime', issue
)
137 # test_foo.html is added in the patch.
138 self
._AssertOk
('extensions/test_foo', issue
)
140 # Invalid issue number results in a 404.
141 self
._AssertNotFound
('extensions/index', '11111')
143 def testXssRedirect(self
):
144 def is_redirect(from_host
, from_path
, to_url
):
145 response
= PatchServlet(Request
.ForTest(from_path
, host
=from_host
),
146 _PatchServletDelegate()).Get()
147 redirect_url
, _
= response
.GetRedirect()
148 if redirect_url
is None:
149 return (False, '%s/%s did not cause a redirect' % (
150 from_host
, from_path
))
151 if redirect_url
!= to_url
:
152 return (False, '%s/%s redirected to %s not %s' % (
153 from_host
, from_path
, redirect_url
, to_url
))
154 return (True, '%s/%s redirected to %s' % (
155 from_host
, from_path
, redirect_url
))
156 self
.assertTrue(*is_redirect('developer.chrome.com', '12345',
157 'https://%s/_patch/12345' % _ALLOWED_HOST
))
158 self
.assertTrue(*is_redirect('developers.google.com', '12345',
159 'https://%s/_patch/12345' % _ALLOWED_HOST
))
160 self
.assertFalse(*is_redirect('chrome-apps-doc.appspot.com', '12345',
162 self
.assertFalse(*is_redirect('some-other-app.appspot.com', '12345',
165 if __name__
== '__main__':