From 5d7f6ee12c8931a9b625176a5d3a71673b6fc909 Mon Sep 17 00:00:00 2001 From: kakwa Date: Sun, 5 Jul 2015 16:35:32 +0200 Subject: [PATCH] adding html validation in ubit tests --- tests/html_validator.py | 243 +++++++++++++++++++++++++++++++++++++++ tests/test_LdapCherry.py | 31 +++++ 2 files changed, 274 insertions(+) create mode 100755 tests/html_validator.py diff --git a/tests/html_validator.py b/tests/html_validator.py new file mode 100755 index 0000000..269fc5b --- /dev/null +++ b/tests/html_validator.py @@ -0,0 +1,243 @@ +#!/usr/bin/python + +# Copyright (c) 2007-2008 Mozilla Foundation +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +from __future__ import print_function, with_statement + +import os +import sys +import re +import string +import gzip + +# Several "try" blocks for python2/3 differences (@secretrobotron) +try: + import httplib +except ImportError: + import http.client as httplib + +try: + import urlparse +except ImportError: + import urllib.parse as urlparse + +try: + from BytesIO import BytesIO +except ImportError: + from io import BytesIO + +try: + maketrans = str.maketrans +except AttributeError: + maketrans = string.maketrans + +# +# Begin +# +extPat = re.compile(r'^.*\.([A-Za-z]+)$') +extDict = { + 'html' : 'text/html', + 'htm' : 'text/html', + 'xhtml' : 'application/xhtml+xml', + 'xht' : 'application/xhtml+xml', + 'xml' : 'application/xml', +} + +forceXml = False +forceHtml = False +gnu = False +errorsOnly = False +encoding = None +fileName = None +contentType = None +inputHandle = None +service = 'https://html5.validator.nu/' + +argv = sys.argv[1:] + +# +# Parse command line input +# +for arg in argv: + if '--help' == arg: + print('-h : force text/html') + print('-x : force application/xhtml+xml') + print('-g : GNU output') + print('-e : errors only (no info or warnings)') + print('--encoding=foo : declare encoding foo') + print('--service=url : the address of the HTML5 validator') + print('One file argument allowed. Leave out to read from stdin.') + sys.exit(0) + elif arg.startswith('--encoding='): + encoding = arg[11:] + elif arg.startswith('--service='): + service = arg[10:] + elif arg.startswith('--'): + sys.stderr.write('Unknown argument %s.\n' % arg) + sys.exit(2) + elif arg.startswith('-'): + for c in arg[1:]: + if 'x' == c: + forceXml = True + elif 'h' == c: + forceHtml = True + elif 'g' == c: + gnu = True + elif 'e' == c: + errorsOnly = True + else: + sys.stderr.write('Unknown argument %s.\n' % arg) + sys.exit(3) + else: + if fileName: + sys.stderr.write('Cannot have more than one input file.\n') + sys.exit(1) + fileName = arg + +# +# Ensure a maximum of one forced output type +# +if forceXml and forceHtml: + sys.stderr.write('Cannot force HTML and XHTML at the same time.\n') + sys.exit(2) + +# +# Set contentType +# +if forceXml: + contentType = 'application/xhtml+xml' +elif forceHtml: + contentType = 'text/html' +elif fileName: + m = extPat.match(fileName) + if m: + ext = m.group(1) + ext = ext.translate(maketrans(string.ascii_uppercase, string.ascii_lowercase)) + if ext in extDict: + contentType = extDict[ext] + else: + sys.stderr.write('Unable to guess Content-Type from file name. Please force the type.\n') + sys.exit(3) + else: + sys.stderr.write('Could not extract a filename extension. Please force the type.\n') + sys.exit(6) +else: + sys.stderr.write('Need to force HTML or XHTML when reading from stdin.\n') + sys.exit(4) + +if encoding: + contentType = '%s; charset=%s' % (contentType, encoding) + +# +# Read the file argument (or STDIN) +# +if fileName: + inputHandle = fileName +else: + inputHandle = sys.stdin + +with open(inputHandle, mode='rb') as inFile: + data = inFile.read() + with BytesIO() as buf: + # we could use another with block here, but it requires Python 2.7+ + zipFile = gzip.GzipFile(fileobj=buf, mode='wb') + zipFile.write(data) + zipFile.close() + gzippeddata = buf.getvalue() + +# +# Prepare the request +# +url = service + +if gnu: + url = url + '?out=gnu' +else: + url = url + '?out=text' + +if errorsOnly: + url = url + '&level=error' + +connection = None +response = None +status = 302 +redirectCount = 0 + +# +# Make the request +# +while status in (302,301,307) and redirectCount < 10: + if redirectCount > 0: + url = response.getheader('Location') + parsed = urlparse.urlsplit(url) + + if redirectCount > 0: + connection.close() # previous connection + print('Redirecting to %s' % url) + print('Please press enter to continue or type \'stop\' followed by enter to stop.') + if raw_input() != '': + sys.exit(0) + + if parsed.scheme == 'https': + connection = httplib.HTTPSConnection(parsed[1]) + else: + connection = httplib.HTTPConnection(parsed[1]) + + headers = { + 'Accept-Encoding': 'gzip', + 'Content-Type': contentType, + 'Content-Encoding': 'gzip', + 'Content-Length': len(gzippeddata), + } + urlSuffix = '%s?%s' % (parsed[2], parsed[3]) + + connection.connect() + connection.request('POST', urlSuffix, body=gzippeddata, headers=headers) + + response = connection.getresponse() + status = response.status + + redirectCount += 1 + +# +# Handle the response +# +if status != 200: + sys.stderr.write('%s %s\n' % (status, response.reason)) + sys.exit(5) + +if response.getheader('Content-Encoding', 'identity').lower() == 'gzip': + response = gzip.GzipFile(fileobj=BytesIO(response.read())) + +if fileName and gnu: + quotedName = '"%s"' % fileName.replace("'", '\\042') + for line in response.read().split('\n'): + if line: + sys.stdout.write(quotedName) + sys.stdout.write(line + '\n') +else: + output = response.read() + # python2/3 difference in output's type + if not isinstance(output, str): + output = output.decode('utf-8') + sys.stdout.write(output) + +connection.close() diff --git a/tests/test_LdapCherry.py b/tests/test_LdapCherry.py index 5249b9c..8125335 100644 --- a/tests/test_LdapCherry.py +++ b/tests/test_LdapCherry.py @@ -6,6 +6,10 @@ from __future__ import unicode_literals import pytest import sys +import subprocess +from tempfile import NamedTemporaryFile as tempfile +import re + from sets import Set from ldapcherry import LdapCherry from ldapcherry.exceptions import * @@ -46,6 +50,24 @@ def loadconf(configfile, instance): cherrypy.config.update(configfile) instance.reload(app.config) +class HtmlValidationFailed(Exception): + def __init__(self, out): + self.errors = out + +def htmlvalidator(page): + f = tempfile() + stdout = tempfile() + f.write(page.encode("utf-8")) + f.seek(0) + ret = subprocess.call(['./tests/html_validator.py', '-h', f.name], stdout=stdout) + stdout.seek(0) + out = stdout.read() + f.close() + stdout.close() + print(out) + if not re.search(r'Error:.*', out) is None: + raise HtmlValidationFailed(out) + class BadModule(): pass @@ -161,6 +183,15 @@ class TestError(object): app._modify(modify_form) app._deleteuser('test') + def testHtml(self): + app = LdapCherry() + loadconf('./tests/cfg/ldapcherry_test.ini', app) + pages = [ + app.signin(), + ] + for page in pages: + htmlvalidator(page) + def testLogger(self): app = LdapCherry() loadconf('./tests/cfg/ldapcherry.ini', app)