From 496431b4f0d9c7c430f6f2330434e51bfa080ef4 Mon Sep 17 00:00:00 2001 From: Xavier Morel Date: Tue, 5 Nov 2013 15:50:47 +0100 Subject: [PATCH] [ADD] CMS crawling case, with a bunch of hardcoded values to fix bzr revid: xmo@openerp.com-20131105145047-4ccrchj79lohry5q --- addons/website/tests/__init__.py | 2 +- addons/website/tests/cases.py | 32 ++++++++ addons/website/tests/test_requests.py | 111 ++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 addons/website/tests/cases.py create mode 100644 addons/website/tests/test_requests.py diff --git a/addons/website/tests/__init__.py b/addons/website/tests/__init__.py index b00c5984aa0..24de4d7ddad 100644 --- a/addons/website/tests/__init__.py +++ b/addons/website/tests/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- -import test_views, test_converter +import test_views, test_converter, test_requests checks = [ test_views, test_converter, ] diff --git a/addons/website/tests/cases.py b/addons/website/tests/cases.py new file mode 100644 index 00000000000..246fe6f2320 --- /dev/null +++ b/addons/website/tests/cases.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +import unittest2 + +class URLCase(unittest2.TestCase): + """ + URLCase moved out of test_requests, otherwise discovery attempts to + instantiate and run it + """ + def __init__(self, user, url, result): + super(URLCase, self).__init__() + self.user = user + self.url = url + self.result = result + + @property + def username(self): + return self.user or "Anonymous Coward" + + def __str__(self): + return "%s (as %s)" % (self.url, self.username) + + __repr__ = __str__ + + def shortDescription(self): + return "" + + def runTest(self): + code = self.result.getcode() + self.assertIn( + code, xrange(200, 300), + "Fetching %s as %s returned an error response (%d)" % ( + self.url, self.username, code)) diff --git a/addons/website/tests/test_requests.py b/addons/website/tests/test_requests.py new file mode 100644 index 00000000000..8d1e7baf469 --- /dev/null +++ b/addons/website/tests/test_requests.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +import collections +import urllib +import urlparse +import unittest2 +import urllib2 + +import lxml.html + +from openerp import tools + +from . import cases + +__all__ = ['load_tests', 'CrawlSuite'] + +class RedirectHandler(urllib2.HTTPRedirectHandler): + """ + HTTPRedirectHandler is predicated upon HTTPErrorProcessor being used and + works by intercepting 3xy "errors". + + Inherit from it to handle 3xy non-error responses instead, as we're not + using the error processor + """ + + def http_response(self, request, response): + code, msg, hdrs = response.code, response.msg, response.info() + + if 300 <= code < 400: + return self.parent.error( + 'http', request, response, code, msg, hdrs) + + return response + + https_response = http_response + +class CrawlSuite(unittest2.TestSuite): + """ Test suite crawling an openerp CMS instance and checking that all + internal links lead to a 200 response. + + If a username and a password are provided, authenticates the user before + starting the crawl + """ + + def __init__(self, user=None, password=None): + super(CrawlSuite, self).__init__() + + self.opener = urllib2.OpenerDirector() + self.opener.add_handler(urllib2.UnknownHandler()) + self.opener.add_handler(urllib2.HTTPHandler()) + self.opener.add_handler(urllib2.HTTPSHandler()) + self.opener.add_handler(RedirectHandler()) + self.opener.add_handler(urllib2.HTTPCookieProcessor()) + + self._authenticate(user, password) + self.user = user + + def _request(self, path): + # FIXME: host, db? + return self.opener.open(urlparse.urlunsplit([ + 'http', 'localhost:%s' % tools.config['xmlrpc_port'], + path, '', '' + ])) + + def _authenticate(self, user, password): + if user is None: return + + # FIXME: host, port, db? + auth = self.opener.open( + 'http://localhost:8069/login?' + urllib.urlencode( + {'db': 'website', 'login': user, 'key': password, })) + assert auth.getcode() < 400, "Auth failure %d" % auth.getcode() + + def _wrapped_run(self, result, debug=False): + paths = collections.deque(['/']) + seen = set(paths) + + while paths: + url = paths.popleft() + r = self._request(url) + cases.URLCase(self.user, url, r).run(result) + + if r.info().gettype() != 'text/html': + continue + + doc = lxml.html.fromstring(r.read()) + for link in doc.xpath('//a[@href]'): + href = link.get('href') + + # avoid repeats, even for links we won't crawl no need to + # bother splitting them if we've already ignored them + # previously + if href in seen: continue + seen.add(href) + + parts = urlparse.urlsplit(href) + + if parts.netloc or \ + not parts.path.startswith('/') or \ + parts.path == '/web' or\ + parts.path.startswith('/web/') or \ + (parts.scheme and parts.scheme not in ('http', 'https')): + continue + + paths.append(href) + +def load_tests(loader, base, _): + base.addTest(CrawlSuite()) + # blog duplicate (&al?) are on links + #base.addTest(CrawlSuite('admin', 'admin')) + #base.addTest(CrawlSuite('demo', 'demo')) + return base