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 github_file_system_provider
import GithubFileSystemProvider
11 from host_file_system_provider
import HostFileSystemProvider
12 from patch_servlet
import PatchServlet
13 from render_servlet
import RenderServlet
14 from server_instance
import ServerInstance
15 from servlet
import Request
16 from test_branch_utility
import TestBranchUtility
17 from test_util
import DisableLogging
21 _ALLOWED_HOST
= 'https://chrome-apps-doc.appspot.com'
24 def _CheckURLsArePatched(content
, patch_servlet_path
):
26 class LinkChecker(HTMLParser
):
27 def handle_starttag(self
, tag
, attrs
):
30 tag_description
= '<a %s .../>' % ' '.join('%s="%s"' % (key
, val
)
31 for key
, val
in attrs
)
33 if ('href' in attrs
and
34 attrs
['href'].startswith('/') and
35 not attrs
['href'].startswith('/%s/' % patch_servlet_path
)):
36 errors
.append('%s has an unqualified href' % tag_description
)
37 LinkChecker().feed(content
)
41 class _RenderServletDelegate(RenderServlet
.Delegate
):
42 def CreateServerInstance(self
):
43 return ServerInstance
.ForLocal()
45 class _PatchServletDelegate(RenderServlet
.Delegate
):
46 def CreateBranchUtility(self
, object_store_creator
):
47 return TestBranchUtility
.CreateWithCannedData()
49 def CreateHostFileSystemProvider(self
, object_store_creator
, **optargs
):
50 return HostFileSystemProvider
.ForLocal(object_store_creator
, **optargs
)
52 def CreateGithubFileSystemProvider(self
, object_store_creator
):
53 return GithubFileSystemProvider
.ForEmpty()
56 class PatchServletTest(unittest
.TestCase
):
58 ConfigureFakeFetchers()
60 def _RenderWithPatch(self
, path
, issue
):
61 path_with_issue
= '%s/%s' % (issue
, path
)
62 return PatchServlet(Request
.ForTest(path_with_issue
, host
=_ALLOWED_HOST
),
63 _PatchServletDelegate()).Get()
65 def _RenderWithoutPatch(self
, path
):
66 return RenderServlet(Request
.ForTest(path
, host
=_ALLOWED_HOST
),
67 _RenderServletDelegate()).Get()
69 def _RenderAndCheck(self
, path
, issue
, expected_equal
):
70 '''Renders |path| with |issue| patched in and asserts that the result is
71 the same as |expected_equal| modulo any links that get rewritten to
74 patched_response
= self
._RenderWithPatch
(path
, issue
)
75 unpatched_response
= self
._RenderWithoutPatch
(path
)
76 patched_response
.headers
.pop('cache-control', None)
77 unpatched_response
.headers
.pop('cache-control', 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.html', issue
,
135 # apps/runtime.html is not removed.
136 self
._RenderAndAssertEqual
('apps/runtime.html', issue
)
138 # test_foo.html is added in the patch.
139 self
._AssertOk
('extensions/test_foo.html', issue
)
141 # Invalid issue number results in a 404.
142 self
._AssertNotFound
('extensions/index.html', '11111')
144 def testXssRedirect(self
):
145 def is_redirect(from_host
, from_path
, to_url
):
146 response
= PatchServlet(Request
.ForTest(from_path
, host
=from_host
),
147 _PatchServletDelegate()).Get()
148 redirect_url
, _
= response
.GetRedirect()
149 if redirect_url
is None:
150 return (False, '%s/%s did not cause a redirect' % (
151 from_host
, from_path
))
152 if redirect_url
!= to_url
:
153 return (False, '%s/%s redirected to %s not %s' % (
154 from_host
, from_path
, redirect_url
, to_url
))
155 return (True, '%s/%s redirected to %s' % (
156 from_host
, from_path
, redirect_url
))
157 self
.assertTrue(*is_redirect('http://developer.chrome.com', '12345',
158 '%s/_patch/12345' % _ALLOWED_HOST
))
159 self
.assertTrue(*is_redirect('http://developers.google.com', '12345',
160 '%s/_patch/12345' % _ALLOWED_HOST
))
161 self
.assertFalse(*is_redirect('http://chrome-apps-doc.appspot.com', '12345',
163 self
.assertFalse(*is_redirect('http://some-other-app.appspot.com', '12345',
166 if __name__
== '__main__':