From d0813eddacaf1030b8e5e6c83f77c436ce3dc676 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Wed, 11 Sep 2013 22:06:17 +0100 Subject: [PATCH 01/18] Start to remove CherryPy --- openlp/plugins/remotes/lib/__init__.py | 5 +- openlp/plugins/remotes/lib/httprouter.py | 526 +++++++++++++++++ openlp/plugins/remotes/lib/httpserver.py | 705 ++++------------------- openlp/plugins/remotes/remoteplugin.py | 10 +- 4 files changed, 631 insertions(+), 615 deletions(-) create mode 100644 openlp/plugins/remotes/lib/httprouter.py diff --git a/openlp/plugins/remotes/lib/__init__.py b/openlp/plugins/remotes/lib/__init__.py index 72a090647..873e651d8 100644 --- a/openlp/plugins/remotes/lib/__init__.py +++ b/openlp/plugins/remotes/lib/__init__.py @@ -28,6 +28,7 @@ ############################################################################### from .remotetab import RemoteTab -from .httpserver import HttpServer +from .httprouter import HttpRouter +from .httpserver import OpenLPServer -__all__ = ['RemoteTab', 'HttpServer'] +__all__ = ['RemoteTab', 'OpenLPServer', 'HttpRouter'] diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py new file mode 100644 index 000000000..77927723c --- /dev/null +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -0,0 +1,526 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### + +""" +The :mod:`http` module contains the API web server. This is a lightweight web +server used by remotes to interact with OpenLP. It uses JSON to communicate with +the remotes. + +*Routes:* + +``/`` + Go to the web interface. + +``/stage`` + Show the stage view. + +``/files/{filename}`` + Serve a static file. + +``/stage/api/poll`` + Poll to see if there are any changes. Returns a JSON-encoded dict of + any changes that occurred:: + + {"results": {"type": "controller"}} + + Or, if there were no results, False:: + + {"results": False} + +``/api/display/{hide|show}`` + Blank or unblank the screen. + +``/api/alert`` + Sends an alert message to the alerts plugin. This method expects a + JSON-encoded dict like this:: + + {"request": {"text": ""}} + +``/api/controller/{live|preview}/{action}`` + Perform ``{action}`` on the live or preview controller. Valid actions + are: + + ``next`` + Load the next slide. + + ``previous`` + Load the previous slide. + + ``set`` + Set a specific slide. Requires an id return in a JSON-encoded dict like + this:: + + {"request": {"id": 1}} + + ``first`` + Load the first slide. + + ``last`` + Load the last slide. + + ``text`` + Fetches the text of the current song. The output is a JSON-encoded + dict which looks like this:: + + {"result": {"slides": ["...", "..."]}} + +``/api/service/{action}`` + Perform ``{action}`` on the service manager (e.g. go live). Data is + passed as a json-encoded ``data`` parameter. Valid actions are: + + ``next`` + Load the next item in the service. + + ``previous`` + Load the previews item in the service. + + ``set`` + Set a specific item in the service. Requires an id returned in a + JSON-encoded dict like this:: + + {"request": {"id": 1}} + + ``list`` + Request a list of items in the service. Returns a list of items in the + current service in a JSON-encoded dict like this:: + + {"results": {"items": [{...}, {...}]}} +""" + +import json +import logging +import os +import re +import urllib.request +import urllib.parse +import urllib.error +import urllib.parse + + +from mako.template import Template +from PyQt4 import QtCore + +from openlp.core.lib import Registry, Settings, PluginStatus, StringContent, image_to_byte +from openlp.core.utils import AppLocation, translate + +from hashlib import sha1 + +log = logging.getLogger(__name__) + + +class HttpRouter(object): + """ + This code is called by the HttpServer upon a request and it processes it based on the routing table. + """ + def initialise(self): + """ + Initialise the router + """ + self.routes = [ + ('^/$', self.serve_file), + ('^/(stage)$', self.serve_file), + ('^/(main)$', self.serve_file), + (r'^/files/(.*)$', self.serve_file), + (r'^/api/poll$', self.poll), + (r'^/stage/poll$', self.poll), + (r'^/main/poll$', self.main_poll), + (r'^/main/image$', self.main_image), + (r'^/api/controller/(live|preview)/(.*)$', self.controller), + (r'^/stage/controller/(live|preview)/(.*)$', self.controller), + (r'^/api/service/(.*)$', self.service), + (r'^/stage/service/(.*)$', self.service), + (r'^/api/display/(hide|show|blank|theme|desktop)$', self.display), + (r'^/api/alert$', self.alert), + (r'^/api/plugin/(search)$', self.plugin_info), + (r'^/api/(.*)/search$', self.search), + (r'^/api/(.*)/live$', self.go_live), + (r'^/api/(.*)/add$', self.add_to_service) + ] + self.translate() + self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html') + + def process_http_request(self, url_path, *args): + """ + Common function to process HTTP requests + + ``url_path`` + The requested URL. + + ``*args`` + Any passed data. + """ + response = None + for route, func in self.routes: + match = re.match(route, url_path) + if match: + log.debug('Route "%s" matched "%s"', route, url_path) + args = [] + for param in match.groups(): + args.append(param) + response = func(*args) + break + if response: + return response + else: + log.debug('Path not found %s', url_path) + return self._http_not_found() + + def _get_service_items(self): + """ + Read the service item in use and return the data as a json object + """ + service_items = [] + if self.live_controller.service_item: + current_unique_identifier = self.live_controller.service_item.unique_identifier + else: + current_unique_identifier = None + for item in self.service_manager.service_items: + service_item = item['service_item'] + service_items.append({ + 'id': str(service_item.unique_identifier), + 'title': str(service_item.get_display_title()), + 'plugin': str(service_item.name), + 'notes': str(service_item.notes), + 'selected': (service_item.unique_identifier == current_unique_identifier) + }) + return service_items + + def translate(self): + """ + Translate various strings in the mobile app. + """ + self.template_vars = { + 'app_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Remote'), + 'stage_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Stage View'), + 'live_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Live View'), + 'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'), + 'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'), + 'alerts': translate('RemotePlugin.Mobile', 'Alerts'), + 'search': translate('RemotePlugin.Mobile', 'Search'), + 'home': translate('RemotePlugin.Mobile', 'Home'), + 'refresh': translate('RemotePlugin.Mobile', 'Refresh'), + 'blank': translate('RemotePlugin.Mobile', 'Blank'), + 'theme': translate('RemotePlugin.Mobile', 'Theme'), + 'desktop': translate('RemotePlugin.Mobile', 'Desktop'), + 'show': translate('RemotePlugin.Mobile', 'Show'), + 'prev': translate('RemotePlugin.Mobile', 'Prev'), + 'next': translate('RemotePlugin.Mobile', 'Next'), + 'text': translate('RemotePlugin.Mobile', 'Text'), + 'show_alert': translate('RemotePlugin.Mobile', 'Show Alert'), + 'go_live': translate('RemotePlugin.Mobile', 'Go Live'), + 'add_to_service': translate('RemotePlugin.Mobile', 'Add to Service'), + 'add_and_go_to_service': translate('RemotePlugin.Mobile', 'Add & Go to Service'), + 'no_results': translate('RemotePlugin.Mobile', 'No Results'), + 'options': translate('RemotePlugin.Mobile', 'Options'), + 'service': translate('RemotePlugin.Mobile', 'Service'), + 'slides': translate('RemotePlugin.Mobile', 'Slides') + } + + def serve_file(self, file_name=None): + """ + Send a file to the socket. For now, just a subset of file types and must be top level inside the html folder. + If subfolders requested return 404, easier for security for the present. + + Ultimately for i18n, this could first look for xx/file.html before falling back to file.html. + where xx is the language, e.g. 'en' + """ + log.debug('serve file request %s' % file_name) + if not file_name: + file_name = 'index.html' + elif file_name == 'stage': + file_name = 'stage.html' + elif file_name == 'main': + file_name = 'main.html' + path = os.path.normpath(os.path.join(self.html_dir, file_name)) + if not path.startswith(self.html_dir): + return self._http_not_found() + ext = os.path.splitext(file_name)[1] + html = None + if ext == '.html': + mimetype = 'text/html' + variables = self.template_vars + html = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables) + elif ext == '.css': + mimetype = 'text/css' + elif ext == '.js': + mimetype = 'application/x-javascript' + elif ext == '.jpg': + mimetype = 'image/jpeg' + elif ext == '.gif': + mimetype = 'image/gif' + elif ext == '.png': + mimetype = 'image/png' + else: + mimetype = 'text/plain' + file_handle = None + try: + if html: + content = html + else: + file_handle = open(path, 'rb') + log.debug('Opened %s' % path) + content = file_handle.read() + except IOError: + log.exception('Failed to open %s' % path) + return self._http_not_found() + finally: + if file_handle: + file_handle.close() + return content + + def poll(self): + """ + Poll OpenLP to determine the current slide number and item name. + """ + result = { + 'service': self.service_manager.service_id, + 'slide': self.live_controller.selected_row or 0, + 'item': self.live_controller.service_item.unique_identifier if self.live_controller.service_item else '', + 'twelve': Settings().value('remotes/twelve hour'), + 'blank': self.live_controller.blank_screen.isChecked(), + 'theme': self.live_controller.theme_screen.isChecked(), + 'display': self.live_controller.desktop_screen.isChecked() + } + return json.dumps({'results': result}).encode() + + def main_poll(self): + """ + Poll OpenLP to determine the current slide count. + """ + result = { + 'slide_count': self.live_controller.slide_count + } + return json.dumps({'results': result}).encode() + + def main_image(self): + """ + Return the latest display image as a byte stream. + """ + result = { + 'slide_image': 'data:image/png;base64,' + str(image_to_byte(self.live_controller.slide_image)) + } + return json.dumps({'results': result}).encode() + + def display(self, action): + """ + Hide or show the display screen. + This is a cross Thread call and UI is updated so Events need to be used. + + ``action`` + This is the action, either ``hide`` or ``show``. + """ + self.live_controller.emit(QtCore.SIGNAL('slidecontroller_toggle_display'), action) + return json.dumps({'results': {'success': True}}).encode() + + def alert(self): + """ + Send an alert. + """ + plugin = self.plugin_manager.get_plugin_by_name("alerts") + if plugin.status == PluginStatus.Active: + try: + text = json.loads(self.request_data)['request']['text'] + except KeyError as ValueError: + return self._http_bad_request() + text = urllib.parse.unquote(text) + self.alerts_manager.emit(QtCore.SIGNAL('alerts_text'), [text]) + success = True + else: + success = False + return json.dumps({'results': {'success': success}}).encode() + + def controller(self, display_type, action): + """ + Perform an action on the slide controller. + + ``display_type`` + This is the type of slide controller, either ``preview`` or ``live``. + + ``action`` + The action to perform. + """ + event = 'slidecontroller_%s_%s' % (display_type, action) + if action == 'text': + current_item = self.live_controller.service_item + data = [] + if current_item: + for index, frame in enumerate(current_item.get_frames()): + item = {} + if current_item.is_text(): + if frame['verseTag']: + item['tag'] = str(frame['verseTag']) + else: + item['tag'] = str(index + 1) + item['text'] = str(frame['text']) + item['html'] = str(frame['html']) + else: + item['tag'] = str(index + 1) + item['text'] = str(frame['title']) + item['html'] = str(frame['title']) + item['selected'] = (self.live_controller.selected_row == index) + data.append(item) + json_data = {'results': {'slides': data}} + if current_item: + json_data['results']['item'] = self.live_controller.service_item.unique_identifier + else: + if self.request_data: + try: + data = json.loads(self.request_data)['request']['id'] + except KeyError as ValueError: + return self._http_bad_request() + log.info(data) + # This slot expects an int within a list. + self.live_controller.emit(QtCore.SIGNAL(event), [data]) + else: + self.live_controller.emit(QtCore.SIGNAL(event)) + json_data = {'results': {'success': True}} + return json.dumps(json_data).encode() + + def service(self, action): + """ + Handles requests for service items in the service manager + + ``action`` + The action to perform. + """ + event = 'servicemanager_%s' % action + if action == 'list': + return json.dumps({'results': {'items': self._get_service_items()}}).encode() + event += '_item' + if self.request_data: + try: + data = json.loads(self.request_data)['request']['id'] + except KeyError: + return self._http_bad_request() + self.service_manager.emit(QtCore.SIGNAL(event), data) + else: + Registry().execute(event) + return json.dumps({'results': {'success': True}}).encode() + + def plugin_info(self, action): + """ + Return plugin related information, based on the action. + + ``action`` + The action to perform. If *search* return a list of plugin names + which support search. + """ + if action == 'search': + searches = [] + for plugin in self.plugin_manager.plugins: + if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search: + searches.append([plugin.name, str(plugin.text_strings[StringContent.Name]['plural'])]) + return json.dumps({'results': {'items': searches}}).encode() + + def search(self, plugin_name): + """ + Return a list of items that match the search text. + + ``plugin`` + The plugin name to search in. + """ + try: + text = json.loads(self.request_data)['request']['text'] + except KeyError as ValueError: + return self._http_bad_request() + text = urllib.parse.unquote(text) + plugin = self.plugin_manager.get_plugin_by_name(plugin_name) + if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search: + results = plugin.media_item.search(text, False) + else: + results = [] + return json.dumps({'results': {'items': results}}).encode() + + def go_live(self, plugin_name): + """ + Go live on an item of type ``plugin``. + """ + try: + id = json.loads(self.request_data)['request']['id'] + except KeyError as ValueError: + return self._http_bad_request() + plugin = self.plugin_manager.get_plugin_by_name(plugin_name) + if plugin.status == PluginStatus.Active and plugin.media_item: + plugin.media_item.emit(QtCore.SIGNAL('%s_go_live' % plugin_name), [id, True]) + return self._http_success() + + def add_to_service(self, plugin_name): + """ + Add item of type ``plugin_name`` to the end of the service. + """ + try: + id = json.loads(self.request_data)['request']['id'] + except KeyError as ValueError: + return self._http_bad_request() + plugin = self.plugin_manager.get_plugin_by_name(plugin_name) + if plugin.status == PluginStatus.Active and plugin.media_item: + item_id = plugin.media_item.create_item_from_id(id) + plugin.media_item.emit(QtCore.SIGNAL('%s_add_to_service' % plugin_name), [item_id, True]) + self._http_success() + + def _get_service_manager(self): + """ + Adds the service manager to the class dynamically + """ + if not hasattr(self, '_service_manager'): + self._service_manager = Registry().get('service_manager') + return self._service_manager + + service_manager = property(_get_service_manager) + + def _get_live_controller(self): + """ + Adds the live controller to the class dynamically + """ + if not hasattr(self, '_live_controller'): + self._live_controller = Registry().get('live_controller') + return self._live_controller + + live_controller = property(_get_live_controller) + + def _get_plugin_manager(self): + """ + Adds the plugin manager to the class dynamically + """ + if not hasattr(self, '_plugin_manager'): + self._plugin_manager = Registry().get('plugin_manager') + return self._plugin_manager + + plugin_manager = property(_get_plugin_manager) + + def _get_alerts_manager(self): + """ + Adds the alerts manager to the class dynamically + """ + if not hasattr(self, '_alerts_manager'): + self._alerts_manager = Registry().get('alerts_manager') + return self._alerts_manager + + alerts_manager = property(_get_alerts_manager) + diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index 89cb3a64b..cee58b9ab 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -31,101 +31,23 @@ The :mod:`http` module contains the API web server. This is a lightweight web server used by remotes to interact with OpenLP. It uses JSON to communicate with the remotes. - -*Routes:* - -``/`` - Go to the web interface. - -``/stage`` - Show the stage view. - -``/files/{filename}`` - Serve a static file. - -``/stage/api/poll`` - Poll to see if there are any changes. Returns a JSON-encoded dict of - any changes that occurred:: - - {"results": {"type": "controller"}} - - Or, if there were no results, False:: - - {"results": False} - -``/api/display/{hide|show}`` - Blank or unblank the screen. - -``/api/alert`` - Sends an alert message to the alerts plugin. This method expects a - JSON-encoded dict like this:: - - {"request": {"text": ""}} - -``/api/controller/{live|preview}/{action}`` - Perform ``{action}`` on the live or preview controller. Valid actions - are: - - ``next`` - Load the next slide. - - ``previous`` - Load the previous slide. - - ``set`` - Set a specific slide. Requires an id return in a JSON-encoded dict like - this:: - - {"request": {"id": 1}} - - ``first`` - Load the first slide. - - ``last`` - Load the last slide. - - ``text`` - Fetches the text of the current song. The output is a JSON-encoded - dict which looks like this:: - - {"result": {"slides": ["...", "..."]}} - -``/api/service/{action}`` - Perform ``{action}`` on the service manager (e.g. go live). Data is - passed as a json-encoded ``data`` parameter. Valid actions are: - - ``next`` - Load the next item in the service. - - ``previous`` - Load the previews item in the service. - - ``set`` - Set a specific item in the service. Requires an id returned in a - JSON-encoded dict like this:: - - {"request": {"id": 1}} - - ``list`` - Request a list of items in the service. Returns a list of items in the - current service in a JSON-encoded dict like this:: - - {"results": {"items": [{...}, {...}]}} """ -import json -import logging +import ssl +import socket import os -import re -import urllib.request, urllib.parse, urllib.error -import urllib.parse -import cherrypy +import logging +from urllib.parse import urlparse, parse_qs -from mako.template import Template from PyQt4 import QtCore -from openlp.core.lib import Registry, Settings, PluginStatus, StringContent, image_to_byte -from openlp.core.utils import AppLocation, translate +from openlp.core.lib import Settings +from openlp.core.utils import AppLocation + +from openlp.plugins.remotes.lib import HttpRouter + +from socketserver import BaseServer, ThreadingMixIn +from http.server import BaseHTTPRequestHandler, HTTPServer from hashlib import sha1 @@ -150,542 +72,111 @@ def fetch_password(username): return make_sha_hash(Settings().value('remotes/password')) -class HttpServer(object): +class CustomHandler(BaseHTTPRequestHandler, HttpRouter): """ - Ability to control OpenLP via a web browser. - This class controls the Cherrypy server and configuration. + Main class to present webpages and authentication. """ - _cp_config = { - 'tools.sessions.on': True, - 'tools.auth.on': True - } + def do_POST(self): + self.do_GET() + + def do_GET(self): + """ + Present frontpage with user authentication. + """ + if self.path == '/favicon.ico': + return + print(self.headers['content-type']) + if self.headers['content-type'] == 'application/json': + length = int(self.headers['content-length']) + postvars = parse_qs(self.rfile.read(length), keep_blank_values=1) + for var in postvars: + print(var.decode("utf-8")) + if not hasattr(self, 'auth'): + self.initialise() + function, args = self.process_http_request(self.path) + if not function: + self.do_http_error() + return + if function['secure']: + if self.headers['Authorization'] is None: + self.do_authorisation() + self.wfile.write(bytes('no auth header received', 'UTF-8')) + elif self.headers['Authorization'] == 'Basic %s' % self.auth: + self.do_http_success() + self.call_function(function, *args) + else: + self.do_authorisation() + self.wfile.write(bytes(self.headers['Authorization'], 'UTF-8')) + self.wfile.write(bytes(' not authenticated', 'UTF-8')) + else: + self.call_function(function, *args) + + +class ThreadingHTTPServer(ThreadingMixIn, HTTPServer): + pass + + +class HttpThread(QtCore.QThread): + """ + A special Qt thread class to allow the HTTP server to run at the same time as the UI. + """ + def __init__(self, server): + """ + Constructor for the thread class. + + ``server`` + The http server class. + """ + super(HttpThread, self).__init__(None) + self.http_server = server + + def run(self): + """ + Run the thread. + """ + self.http_server.start_server() + + +class OpenLPServer(): def __init__(self): """ Initialise the http server, and start the server. """ log.debug('Initialise httpserver') self.settings_section = 'remotes' - self.router = HttpRouter() + self.http_thread = HttpThread(self) + self.http_thread.start() def start_server(self): - """ - Start the http server based on configuration. - """ - log.debug('Start CherryPy server') - # Define to security levels and inject the router code - self.root = self.Public() - self.root.files = self.Files() - self.root.stage = self.Stage() - self.root.main = self.Main() - self.root.router = self.router - self.root.files.router = self.router - self.root.stage.router = self.router - self.root.main.router = self.router - cherrypy.tree.mount(self.root, '/', config=self.define_config()) - # Turn off the flood of access messages cause by poll - cherrypy.log.access_log.propagate = False - cherrypy.engine.start() - - def define_config(self): - """ - Define the configuration of the server. - """ + address = Settings().value(self.settings_section + '/ip address') if Settings().value(self.settings_section + '/https enabled'): port = Settings().value(self.settings_section + '/https port') - address = Settings().value(self.settings_section + '/ip address') - local_data = AppLocation.get_directory(AppLocation.DataDir) - cherrypy.config.update({'server.socket_host': str(address), - 'server.socket_port': port, - 'server.ssl_certificate': os.path.join(local_data, 'remotes', 'openlp.crt'), - 'server.ssl_private_key': os.path.join(local_data, 'remotes', 'openlp.key')}) + self.httpd = HTTPSServer((address, port), CustomHandler) + print('started ssl httpd...') else: port = Settings().value(self.settings_section + '/port') - address = Settings().value(self.settings_section + '/ip address') - cherrypy.config.update({'server.socket_host': str(address)}) - cherrypy.config.update({'server.socket_port': port}) - cherrypy.config.update({'environment': 'embedded'}) - cherrypy.config.update({'engine.autoreload_on': False}) - directory_config = {'/': {'tools.staticdir.on': True, - 'tools.staticdir.dir': self.router.html_dir, - 'tools.basic_auth.on': Settings().value('remotes/authentication enabled'), - 'tools.basic_auth.realm': 'OpenLP Remote Login', - 'tools.basic_auth.users': fetch_password, - 'tools.basic_auth.encrypt': make_sha_hash}, - '/files': {'tools.staticdir.on': True, - 'tools.staticdir.dir': self.router.html_dir, - 'tools.basic_auth.on': False}, - '/stage': {'tools.staticdir.on': True, - 'tools.staticdir.dir': self.router.html_dir, - 'tools.basic_auth.on': False}, - '/main': {'tools.staticdir.on': True, - 'tools.staticdir.dir': self.router.html_dir, - 'tools.basic_auth.on': False}} - return directory_config + self.httpd = ThreadingHTTPServer((address, port), CustomHandler) + print('started non ssl httpd...') + self.httpd.serve_forever() - class Public(object): - """ - Main access class with may have security enabled on it. - """ - @cherrypy.expose - def default(self, *args, **kwargs): - self.router.request_data = None - if isinstance(kwargs, dict): - self.router.request_data = kwargs.get('data', None) - url = urllib.parse.urlparse(cherrypy.url()) - return self.router.process_http_request(url.path, *args) - - class Files(object): - """ - Provides access to files and has no security available. These are read only accesses - """ - @cherrypy.expose - def default(self, *args, **kwargs): - url = urllib.parse.urlparse(cherrypy.url()) - return self.router.process_http_request(url.path, *args) - - class Stage(object): - """ - Stage view is read only so security is not relevant and would reduce it's usability - """ - @cherrypy.expose - def default(self, *args, **kwargs): - url = urllib.parse.urlparse(cherrypy.url()) - return self.router.process_http_request(url.path, *args) - - class Main(object): - """ - Main view is read only so security is not relevant and would reduce it's usability - """ - @cherrypy.expose - def default(self, *args, **kwargs): - url = urllib.parse.urlparse(cherrypy.url()) - return self.router.process_http_request(url.path, *args) - - def close(self): - """ - Close down the http server. - """ - log.debug('close http server') - cherrypy.engine.exit() + def stop_server(self): + self.httpd.socket.close() + self.httpd = None -class HttpRouter(object): - """ - This code is called by the HttpServer upon a request and it processes it based on the routing table. - """ - def __init__(self): - """ - Initialise the router - """ - self.routes = [ - ('^/$', self.serve_file), - ('^/(stage)$', self.serve_file), - ('^/(main)$', self.serve_file), - (r'^/files/(.*)$', self.serve_file), - (r'^/api/poll$', self.poll), - (r'^/stage/poll$', self.poll), - (r'^/main/poll$', self.main_poll), - (r'^/main/image$', self.main_image), - (r'^/api/controller/(live|preview)/(.*)$', self.controller), - (r'^/stage/controller/(live|preview)/(.*)$', self.controller), - (r'^/api/service/(.*)$', self.service), - (r'^/stage/service/(.*)$', self.service), - (r'^/api/display/(hide|show|blank|theme|desktop)$', self.display), - (r'^/api/alert$', self.alert), - (r'^/api/plugin/(search)$', self.plugin_info), - (r'^/api/(.*)/search$', self.search), - (r'^/api/(.*)/live$', self.go_live), - (r'^/api/(.*)/add$', self.add_to_service) - ] - self.translate() - self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html') +class HTTPSServer(HTTPServer): + def __init__(self, address, handler): + BaseServer.__init__(self, address, handler) + local_data = AppLocation.get_directory(AppLocation.DataDir) + self.socket = ssl.SSLSocket( + sock=socket.socket(self.address_family, self.socket_type), + ssl_version=ssl.PROTOCOL_TLSv1, + certfile=os.path.join(local_data, 'remotes', 'openlp.crt'), + keyfile=os.path.join(local_data, 'remotes', 'openlp.key'), + server_side=True) + self.server_bind() + self.server_activate() - def process_http_request(self, url_path, *args): - """ - Common function to process HTTP requests - ``url_path`` - The requested URL. - ``*args`` - Any passed data. - """ - response = None - for route, func in self.routes: - match = re.match(route, url_path) - if match: - log.debug('Route "%s" matched "%s"', route, url_path) - args = [] - for param in match.groups(): - args.append(param) - response = func(*args) - break - if response: - return response - else: - log.debug('Path not found %s', url_path) - return self._http_not_found() - - def _get_service_items(self): - """ - Read the service item in use and return the data as a json object - """ - service_items = [] - if self.live_controller.service_item: - current_unique_identifier = self.live_controller.service_item.unique_identifier - else: - current_unique_identifier = None - for item in self.service_manager.service_items: - service_item = item['service_item'] - service_items.append({ - 'id': str(service_item.unique_identifier), - 'title': str(service_item.get_display_title()), - 'plugin': str(service_item.name), - 'notes': str(service_item.notes), - 'selected': (service_item.unique_identifier == current_unique_identifier) - }) - return service_items - - def translate(self): - """ - Translate various strings in the mobile app. - """ - self.template_vars = { - 'app_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Remote'), - 'stage_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Stage View'), - 'live_title': translate('RemotePlugin.Mobile', 'OpenLP 2.1 Live View'), - 'service_manager': translate('RemotePlugin.Mobile', 'Service Manager'), - 'slide_controller': translate('RemotePlugin.Mobile', 'Slide Controller'), - 'alerts': translate('RemotePlugin.Mobile', 'Alerts'), - 'search': translate('RemotePlugin.Mobile', 'Search'), - 'home': translate('RemotePlugin.Mobile', 'Home'), - 'refresh': translate('RemotePlugin.Mobile', 'Refresh'), - 'blank': translate('RemotePlugin.Mobile', 'Blank'), - 'theme': translate('RemotePlugin.Mobile', 'Theme'), - 'desktop': translate('RemotePlugin.Mobile', 'Desktop'), - 'show': translate('RemotePlugin.Mobile', 'Show'), - 'prev': translate('RemotePlugin.Mobile', 'Prev'), - 'next': translate('RemotePlugin.Mobile', 'Next'), - 'text': translate('RemotePlugin.Mobile', 'Text'), - 'show_alert': translate('RemotePlugin.Mobile', 'Show Alert'), - 'go_live': translate('RemotePlugin.Mobile', 'Go Live'), - 'add_to_service': translate('RemotePlugin.Mobile', 'Add to Service'), - 'add_and_go_to_service': translate('RemotePlugin.Mobile', 'Add & Go to Service'), - 'no_results': translate('RemotePlugin.Mobile', 'No Results'), - 'options': translate('RemotePlugin.Mobile', 'Options'), - 'service': translate('RemotePlugin.Mobile', 'Service'), - 'slides': translate('RemotePlugin.Mobile', 'Slides') - } - - def serve_file(self, file_name=None): - """ - Send a file to the socket. For now, just a subset of file types and must be top level inside the html folder. - If subfolders requested return 404, easier for security for the present. - - Ultimately for i18n, this could first look for xx/file.html before falling back to file.html. - where xx is the language, e.g. 'en' - """ - log.debug('serve file request %s' % file_name) - if not file_name: - file_name = 'index.html' - elif file_name == 'stage': - file_name = 'stage.html' - elif file_name == 'main': - file_name = 'main.html' - path = os.path.normpath(os.path.join(self.html_dir, file_name)) - if not path.startswith(self.html_dir): - return self._http_not_found() - ext = os.path.splitext(file_name)[1] - html = None - if ext == '.html': - mimetype = 'text/html' - variables = self.template_vars - html = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables) - elif ext == '.css': - mimetype = 'text/css' - elif ext == '.js': - mimetype = 'application/x-javascript' - elif ext == '.jpg': - mimetype = 'image/jpeg' - elif ext == '.gif': - mimetype = 'image/gif' - elif ext == '.png': - mimetype = 'image/png' - else: - mimetype = 'text/plain' - file_handle = None - try: - if html: - content = html - else: - file_handle = open(path, 'rb') - log.debug('Opened %s' % path) - content = file_handle.read() - except IOError: - log.exception('Failed to open %s' % path) - return self._http_not_found() - finally: - if file_handle: - file_handle.close() - cherrypy.response.headers['Content-Type'] = mimetype - return content - - def poll(self): - """ - Poll OpenLP to determine the current slide number and item name. - """ - result = { - 'service': self.service_manager.service_id, - 'slide': self.live_controller.selected_row or 0, - 'item': self.live_controller.service_item.unique_identifier if self.live_controller.service_item else '', - 'twelve': Settings().value('remotes/twelve hour'), - 'blank': self.live_controller.blank_screen.isChecked(), - 'theme': self.live_controller.theme_screen.isChecked(), - 'display': self.live_controller.desktop_screen.isChecked() - } - cherrypy.response.headers['Content-Type'] = 'application/json' - return json.dumps({'results': result}).encode() - - def main_poll(self): - """ - Poll OpenLP to determine the current slide count. - """ - result = { - 'slide_count': self.live_controller.slide_count - } - cherrypy.response.headers['Content-Type'] = 'application/json' - return json.dumps({'results': result}).encode() - - def main_image(self): - """ - Return the latest display image as a byte stream. - """ - result = { - 'slide_image': 'data:image/png;base64,' + str(image_to_byte(self.live_controller.slide_image)) - } - cherrypy.response.headers['Content-Type'] = 'application/json' - return json.dumps({'results': result}).encode() - - def display(self, action): - """ - Hide or show the display screen. - This is a cross Thread call and UI is updated so Events need to be used. - - ``action`` - This is the action, either ``hide`` or ``show``. - """ - self.live_controller.emit(QtCore.SIGNAL('slidecontroller_toggle_display'), action) - cherrypy.response.headers['Content-Type'] = 'application/json' - return json.dumps({'results': {'success': True}}).encode() - - def alert(self): - """ - Send an alert. - """ - plugin = self.plugin_manager.get_plugin_by_name("alerts") - if plugin.status == PluginStatus.Active: - try: - text = json.loads(self.request_data)['request']['text'] - except KeyError as ValueError: - return self._http_bad_request() - text = urllib.parse.unquote(text) - self.alerts_manager.emit(QtCore.SIGNAL('alerts_text'), [text]) - success = True - else: - success = False - cherrypy.response.headers['Content-Type'] = 'application/json' - return json.dumps({'results': {'success': success}}).encode() - - def controller(self, display_type, action): - """ - Perform an action on the slide controller. - - ``display_type`` - This is the type of slide controller, either ``preview`` or ``live``. - - ``action`` - The action to perform. - """ - event = 'slidecontroller_%s_%s' % (display_type, action) - if action == 'text': - current_item = self.live_controller.service_item - data = [] - if current_item: - for index, frame in enumerate(current_item.get_frames()): - item = {} - if current_item.is_text(): - if frame['verseTag']: - item['tag'] = str(frame['verseTag']) - else: - item['tag'] = str(index + 1) - item['text'] = str(frame['text']) - item['html'] = str(frame['html']) - else: - item['tag'] = str(index + 1) - item['text'] = str(frame['title']) - item['html'] = str(frame['title']) - item['selected'] = (self.live_controller.selected_row == index) - data.append(item) - json_data = {'results': {'slides': data}} - if current_item: - json_data['results']['item'] = self.live_controller.service_item.unique_identifier - else: - if self.request_data: - try: - data = json.loads(self.request_data)['request']['id'] - except KeyError as ValueError: - return self._http_bad_request() - log.info(data) - # This slot expects an int within a list. - self.live_controller.emit(QtCore.SIGNAL(event), [data]) - else: - self.live_controller.emit(QtCore.SIGNAL(event)) - json_data = {'results': {'success': True}} - cherrypy.response.headers['Content-Type'] = 'application/json' - return json.dumps(json_data).encode() - - def service(self, action): - """ - Handles requests for service items in the service manager - - ``action`` - The action to perform. - """ - event = 'servicemanager_%s' % action - if action == 'list': - cherrypy.response.headers['Content-Type'] = 'application/json' - return json.dumps({'results': {'items': self._get_service_items()}}).encode() - event += '_item' - if self.request_data: - try: - data = json.loads(self.request_data)['request']['id'] - except KeyError: - return self._http_bad_request() - self.service_manager.emit(QtCore.SIGNAL(event), data) - else: - Registry().execute(event) - cherrypy.response.headers['Content-Type'] = 'application/json' - return json.dumps({'results': {'success': True}}).encode() - - def plugin_info(self, action): - """ - Return plugin related information, based on the action. - - ``action`` - The action to perform. If *search* return a list of plugin names - which support search. - """ - if action == 'search': - searches = [] - for plugin in self.plugin_manager.plugins: - if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search: - searches.append([plugin.name, str(plugin.text_strings[StringContent.Name]['plural'])]) - cherrypy.response.headers['Content-Type'] = 'application/json' - return json.dumps({'results': {'items': searches}}).encode() - - def search(self, plugin_name): - """ - Return a list of items that match the search text. - - ``plugin`` - The plugin name to search in. - """ - try: - text = json.loads(self.request_data)['request']['text'] - except KeyError as ValueError: - return self._http_bad_request() - text = urllib.parse.unquote(text) - plugin = self.plugin_manager.get_plugin_by_name(plugin_name) - if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search: - results = plugin.media_item.search(text, False) - else: - results = [] - cherrypy.response.headers['Content-Type'] = 'application/json' - return json.dumps({'results': {'items': results}}).encode() - - def go_live(self, plugin_name): - """ - Go live on an item of type ``plugin``. - """ - try: - id = json.loads(self.request_data)['request']['id'] - except KeyError as ValueError: - return self._http_bad_request() - plugin = self.plugin_manager.get_plugin_by_name(plugin_name) - if plugin.status == PluginStatus.Active and plugin.media_item: - plugin.media_item.emit(QtCore.SIGNAL('%s_go_live' % plugin_name), [id, True]) - return self._http_success() - - def add_to_service(self, plugin_name): - """ - Add item of type ``plugin_name`` to the end of the service. - """ - try: - id = json.loads(self.request_data)['request']['id'] - except KeyError as ValueError: - return self._http_bad_request() - plugin = self.plugin_manager.get_plugin_by_name(plugin_name) - if plugin.status == PluginStatus.Active and plugin.media_item: - item_id = plugin.media_item.create_item_from_id(id) - plugin.media_item.emit(QtCore.SIGNAL('%s_add_to_service' % plugin_name), [item_id, True]) - self._http_success() - - def _http_success(self): - """ - Set the HTTP success return code. - """ - cherrypy.response.status = 200 - - def _http_bad_request(self): - """ - Set the HTTP bad response return code. - """ - cherrypy.response.status = 400 - - def _http_not_found(self): - """ - Set the HTTP not found return code. - """ - cherrypy.response.status = 404 - cherrypy.response.body = [b'Sorry, an error occurred '] - - def _get_service_manager(self): - """ - Adds the service manager to the class dynamically - """ - if not hasattr(self, '_service_manager'): - self._service_manager = Registry().get('service_manager') - return self._service_manager - - service_manager = property(_get_service_manager) - - def _get_live_controller(self): - """ - Adds the live controller to the class dynamically - """ - if not hasattr(self, '_live_controller'): - self._live_controller = Registry().get('live_controller') - return self._live_controller - - live_controller = property(_get_live_controller) - - def _get_plugin_manager(self): - """ - Adds the plugin manager to the class dynamically - """ - if not hasattr(self, '_plugin_manager'): - self._plugin_manager = Registry().get('plugin_manager') - return self._plugin_manager - - plugin_manager = property(_get_plugin_manager) - - def _get_alerts_manager(self): - """ - Adds the alerts manager to the class dynamically - """ - if not hasattr(self, '_alerts_manager'): - self._alerts_manager = Registry().get('alerts_manager') - return self._alerts_manager - - alerts_manager = property(_get_alerts_manager) diff --git a/openlp/plugins/remotes/remoteplugin.py b/openlp/plugins/remotes/remoteplugin.py index 6d51ae28d..0c23978b6 100644 --- a/openlp/plugins/remotes/remoteplugin.py +++ b/openlp/plugins/remotes/remoteplugin.py @@ -29,10 +29,8 @@ import logging -from PyQt4 import QtGui - from openlp.core.lib import Plugin, StringContent, translate, build_icon -from openlp.plugins.remotes.lib import RemoteTab, HttpServer +from openlp.plugins.remotes.lib import RemoteTab, OpenLPServer log = logging.getLogger(__name__) @@ -67,8 +65,8 @@ class RemotesPlugin(Plugin): """ log.debug('initialise') super(RemotesPlugin, self).initialise() - self.server = HttpServer() - self.server.start_server() + self.server = OpenLPServer() + #self.server.start_server() def finalise(self): """ @@ -77,7 +75,7 @@ class RemotesPlugin(Plugin): log.debug('finalise') super(RemotesPlugin, self).finalise() if self.server: - self.server.close() + self.server.stop_server() self.server = None def about(self): From 448da916e9f820e8d9333579a68f3b9c09cb19d9 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Thu, 12 Sep 2013 17:46:57 +0100 Subject: [PATCH 02/18] Readonly works - next comes updates --- openlp/plugins/remotes/lib/httprouter.py | 120 ++++++++++++++++------- openlp/plugins/remotes/lib/httpserver.py | 46 ++++----- 2 files changed, 108 insertions(+), 58 deletions(-) diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 77927723c..4d9133e65 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -112,15 +112,14 @@ the remotes. {"results": {"items": [{...}, {...}]}} """ - +import base64 import json import logging import os import re import urllib.request -import urllib.parse import urllib.error -import urllib.parse +from urllib.parse import urlparse, parse_qs from mako.template import Template @@ -129,46 +128,77 @@ from PyQt4 import QtCore from openlp.core.lib import Registry, Settings, PluginStatus, StringContent, image_to_byte from openlp.core.utils import AppLocation, translate -from hashlib import sha1 - log = logging.getLogger(__name__) class HttpRouter(object): """ This code is called by the HttpServer upon a request and it processes it based on the routing table. + This code is stateless so need """ def initialise(self): """ - Initialise the router + Initialise the router stack and any other varables. """ + authcode = "%s:%s" % (Settings().value('remotes/user id'), Settings().value('remotes/password')) + try: + self.auth = base64.b64encode(authcode) + except TypeError: + self.auth = base64.b64encode(authcode.encode()).decode() self.routes = [ - ('^/$', self.serve_file), - ('^/(stage)$', self.serve_file), - ('^/(main)$', self.serve_file), - (r'^/files/(.*)$', self.serve_file), - (r'^/api/poll$', self.poll), - (r'^/stage/poll$', self.poll), - (r'^/main/poll$', self.main_poll), - (r'^/main/image$', self.main_image), - (r'^/api/controller/(live|preview)/(.*)$', self.controller), - (r'^/stage/controller/(live|preview)/(.*)$', self.controller), - (r'^/api/service/(.*)$', self.service), - (r'^/stage/service/(.*)$', self.service), - (r'^/api/display/(hide|show|blank|theme|desktop)$', self.display), - (r'^/api/alert$', self.alert), - (r'^/api/plugin/(search)$', self.plugin_info), - (r'^/api/(.*)/search$', self.search), - (r'^/api/(.*)/live$', self.go_live), - (r'^/api/(.*)/add$', self.add_to_service) + ('^/$', {'function': self.serve_file, 'secure': False}), + ('^/(stage)$', {'function': self.serve_file, 'secure': False}), + ('^/(main)$', {'function': self.serve_file, 'secure': False}), + (r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}), + (r'^/api/poll$', {'function': self.poll, 'secure': False}), + (r'^/stage/poll$', {'function': self.poll, 'secure': False}), + (r'^/main/poll$', {'function': self.poll, 'secure': False}), + (r'^/main/image$', {'function': self.main_poll, 'secure': False}), + (r'^/api/controller/(live|preview)/(.*)$', {'function': self.controller, 'secure': False}), + (r'^/stage/controller/(live|preview)/(.*)$', {'function': self.controller, 'secure': False}), + (r'^/api/service/(.*)$', {'function':self.service, 'secure': False}), + (r'^/stage/service/(.*)$', {'function': self.service, 'secure': False}), + (r'^/api/display/(hide|show|blank|theme|desktop)$', {'function': self.display, 'secure': True}), + (r'^/api/alert$', {'function': self.alert, 'secure': True}), + (r'^/api/plugin/(search)$', {'function': self.plugin_info, 'secure': False}), + (r'^/api/(.*)/search$', {'function': self.search, 'secure': False}), + (r'^/api/(.*)/live$', {'function': self.go_live, 'secure': True}), + (r'^/api/(.*)/add$', {'function': self.add_to_service, 'secure': True}) ] self.translate() self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html') + def call_function(self, function, *args): + response = function['function'](*args) + if response: + self.wfile.write(response) + return + def process_http_request(self, url_path, *args): """ Common function to process HTTP requests + ``url_path`` + The requested URL. + + ``*args`` + Any passed data. + """ + url_path_split = urlparse(url_path) + for route, func in self.routes: + match = re.match(route, url_path_split.path) + if match: + print('Route "%s" matched "%s"', route, url_path) + args = [] + for param in match.groups(): + args.append(param) + return func, args + return None, None + + def _process_http_request(self, url_path, *args): + """ + Common function to process HTTP requests + ``url_path`` The requested URL. @@ -189,7 +219,29 @@ class HttpRouter(object): return response else: log.debug('Path not found %s', url_path) - return self._http_not_found() + return self.do_not_found() + + def do_http_success(self): + self.send_response(200) + self.send_header('Content-type', 'text/html') + self.end_headers() + + def do_http_error(self): + self.send_response(404) + self.send_header('Content-type', 'text/html') + self.end_headers() + + def do_authorisation(self): + self.send_response(401) + self.send_header('WWW-Authenticate', 'Basic realm=\"Test\"') + self.send_header('Content-type', 'text/html') + self.end_headers() + + def do_notfound(self): + self.send_response(404) + self.send_header('Content-type', 'text/html') + self.end_headers() + self.wfile.write(bytes('Sorry, an error occurred ', 'UTF-8')) def _get_service_items(self): """ @@ -259,25 +311,27 @@ class HttpRouter(object): file_name = 'main.html' path = os.path.normpath(os.path.join(self.html_dir, file_name)) if not path.startswith(self.html_dir): - return self._http_not_found() + return self.http_not_found() ext = os.path.splitext(file_name)[1] html = None if ext == '.html': - mimetype = 'text/html' + self.send_header('Content-type', 'text/html') variables = self.template_vars html = Template(filename=path, input_encoding='utf-8', output_encoding='utf-8').render(**variables) elif ext == '.css': - mimetype = 'text/css' + self.send_header('Content-type', 'text/css') elif ext == '.js': - mimetype = 'application/x-javascript' + self.send_header('Content-type', 'application/x-javascript') elif ext == '.jpg': - mimetype = 'image/jpeg' + self.send_header('Content-type', 'image/jpeg') elif ext == '.gif': - mimetype = 'image/gif' + self.send_header('Content-type', 'image/gif') + elif ext == '.ico': + self.send_header('Content-type', 'image/ico') elif ext == '.png': - mimetype = 'image/png' + self.send_header('Content-type', 'image/png') else: - mimetype = 'text/plain' + self.send_header('Content-type', 'text/plain') file_handle = None try: if html: diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index cee58b9ab..a819d8ac2 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -49,40 +49,26 @@ from openlp.plugins.remotes.lib import HttpRouter from socketserver import BaseServer, ThreadingMixIn from http.server import BaseHTTPRequestHandler, HTTPServer -from hashlib import sha1 - log = logging.getLogger(__name__) -def make_sha_hash(password): - """ - Create an encrypted password for the given password. - """ - log.debug("make_sha_hash") - return sha1(password.encode()).hexdigest() - - -def fetch_password(username): - """ - Fetch the password for a provided user. - """ - log.debug("Fetch Password") - if username != Settings().value('remotes/user id'): - return None - return make_sha_hash(Settings().value('remotes/password')) - - class CustomHandler(BaseHTTPRequestHandler, HttpRouter): """ - Main class to present webpages and authentication. + Stateless session handler to handle the HTTP request and process it. + This class handles just the overrides to the base methods and the logic to invoke the + methods within the HttpRouter class. + DO not try change the structure as this is as per the documentation. """ def do_POST(self): + """ + Present pages / data and invoke URL level user authentication. + """ self.do_GET() def do_GET(self): """ - Present frontpage with user authentication. + Present pages / data and invoke URL level user authentication. """ if self.path == '/favicon.ico': return @@ -141,7 +127,7 @@ class HttpThread(QtCore.QThread): class OpenLPServer(): def __init__(self): """ - Initialise the http server, and start the server. + Initialise the http server, and start the server of the correct type http / https """ log.debug('Initialise httpserver') self.settings_section = 'remotes' @@ -149,24 +135,34 @@ class OpenLPServer(): self.http_thread.start() def start_server(self): + """ + Start the correct server and save the handler + """ address = Settings().value(self.settings_section + '/ip address') if Settings().value(self.settings_section + '/https enabled'): port = Settings().value(self.settings_section + '/https port') self.httpd = HTTPSServer((address, port), CustomHandler) - print('started ssl httpd...') + log.debug('Started ssl httpd...') else: port = Settings().value(self.settings_section + '/port') self.httpd = ThreadingHTTPServer((address, port), CustomHandler) - print('started non ssl httpd...') + log.debug('Started non ssl httpd...') self.httpd.serve_forever() def stop_server(self): + """ + Stop the server + """ self.httpd.socket.close() self.httpd = None + log.debug('Stopped the server.') class HTTPSServer(HTTPServer): def __init__(self, address, handler): + """ + Initialise the secure handlers for the SSL server if required.s + """ BaseServer.__init__(self, address, handler) local_data = AppLocation.get_directory(AppLocation.DataDir) self.socket = ssl.SSLSocket( From cca76a90bd340b8812d425a886b8f371c15aed0f Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 14 Sep 2013 08:22:01 +0100 Subject: [PATCH 03/18] pass data to functions --- openlp/plugins/remotes/lib/httprouter.py | 38 ++++++------------------ openlp/plugins/remotes/lib/httpserver.py | 7 +++-- 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 4d9133e65..799396454 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -154,7 +154,7 @@ class HttpRouter(object): (r'^/stage/poll$', {'function': self.poll, 'secure': False}), (r'^/main/poll$', {'function': self.poll, 'secure': False}), (r'^/main/image$', {'function': self.main_poll, 'secure': False}), - (r'^/api/controller/(live|preview)/(.*)$', {'function': self.controller, 'secure': False}), + (r'^/api/controller/(live|preview)/(.*)$', {'function': self.controller, 'secure': True}), (r'^/stage/controller/(live|preview)/(.*)$', {'function': self.controller, 'secure': False}), (r'^/api/service/(.*)$', {'function':self.service, 'secure': False}), (r'^/stage/service/(.*)$', {'function': self.service, 'secure': False}), @@ -184,42 +184,20 @@ class HttpRouter(object): ``*args`` Any passed data. """ + self.request_data = None url_path_split = urlparse(url_path) + url_query = parse_qs(url_path_split.query) + if 'data' in url_query.keys(): + self.request_data = url_query['data'][0] for route, func in self.routes: match = re.match(route, url_path_split.path) - if match: - print('Route "%s" matched "%s"', route, url_path) - args = [] - for param in match.groups(): - args.append(param) - return func, args - return None, None - - def _process_http_request(self, url_path, *args): - """ - Common function to process HTTP requests - - ``url_path`` - The requested URL. - - ``*args`` - Any passed data. - """ - response = None - for route, func in self.routes: - match = re.match(route, url_path) if match: log.debug('Route "%s" matched "%s"', route, url_path) args = [] for param in match.groups(): args.append(param) - response = func(*args) - break - if response: - return response - else: - log.debug('Path not found %s', url_path) - return self.do_not_found() + return func, args + return None, None def do_http_success(self): self.send_response(200) @@ -469,6 +447,8 @@ class HttpRouter(object): event += '_item' if self.request_data: try: +# print(json.loads(self.request_data['data'])) + print(json.loads(self.request_data)) data = json.loads(self.request_data)['request']['id'] except KeyError: return self._http_bad_request() diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index a819d8ac2..5535847a6 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -72,12 +72,13 @@ class CustomHandler(BaseHTTPRequestHandler, HttpRouter): """ if self.path == '/favicon.ico': return - print(self.headers['content-type']) - if self.headers['content-type'] == 'application/json': + #print(self.headers['content-type'],self.headers['content-length']) + if self.headers['content-type'] == 'application/text': length = int(self.headers['content-length']) postvars = parse_qs(self.rfile.read(length), keep_blank_values=1) for var in postvars: - print(var.decode("utf-8")) + print(var) + #{"request": {"id": 1}} if not hasattr(self, 'auth'): self.initialise() function, args = self.process_http_request(self.path) From 2531513ec58229e626a881a3affaef9b848d8c70 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 14 Sep 2013 19:46:49 +0100 Subject: [PATCH 04/18] basic working model --- openlp/plugins/remotes/html/openlp.js | 6 +- openlp/plugins/remotes/html/stage.js | 6 +- openlp/plugins/remotes/lib/httprouter.py | 120 ++++++++++++----------- openlp/plugins/remotes/lib/httpserver.py | 10 +- 4 files changed, 75 insertions(+), 67 deletions(-) diff --git a/openlp/plugins/remotes/html/openlp.js b/openlp/plugins/remotes/html/openlp.js index 10bc9e328..dd9e7a98b 100644 --- a/openlp/plugins/remotes/html/openlp.js +++ b/openlp/plugins/remotes/html/openlp.js @@ -40,6 +40,8 @@ window.OpenLP = { // defeat Safari bug targ = targ.parentNode; } + var isSecure = false; + var isAuthorised = false; return $(targ); }, getSearchablePlugins: function () { @@ -147,11 +149,13 @@ window.OpenLP = { }, pollServer: function () { $.getJSON( - "/stage/poll", + "/api/poll", function (data, status) { var prevItem = OpenLP.currentItem; OpenLP.currentSlide = data.results.slide; OpenLP.currentItem = data.results.item; + OpenLP.isSecure = data.results.isSecure; + OpenLP.isAuthorised = data.results.isAuthorised; if ($("#service-manager").is(":visible")) { if (OpenLP.currentService != data.results.service) { OpenLP.currentService = data.results.service; diff --git a/openlp/plugins/remotes/html/stage.js b/openlp/plugins/remotes/html/stage.js index 42b7712f9..dcc2e4b70 100644 --- a/openlp/plugins/remotes/html/stage.js +++ b/openlp/plugins/remotes/html/stage.js @@ -26,7 +26,7 @@ window.OpenLP = { loadService: function (event) { $.getJSON( - "/stage/service/list", + "/api/service/list", function (data, status) { OpenLP.nextSong = ""; $("#notes").html(""); @@ -46,7 +46,7 @@ window.OpenLP = { }, loadSlides: function (event) { $.getJSON( - "/stage/controller/live/text", + "/api/controller/live/text", function (data, status) { OpenLP.currentSlides = data.results.slides; OpenLP.currentSlide = 0; @@ -137,7 +137,7 @@ window.OpenLP = { }, pollServer: function () { $.getJSON( - "/stage/poll", + "/api/poll", function (data, status) { OpenLP.updateClock(data); if (OpenLP.currentItem != data.results.item || diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 799396454..11e1900ef 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -151,13 +151,12 @@ class HttpRouter(object): ('^/(main)$', {'function': self.serve_file, 'secure': False}), (r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}), (r'^/api/poll$', {'function': self.poll, 'secure': False}), - (r'^/stage/poll$', {'function': self.poll, 'secure': False}), (r'^/main/poll$', {'function': self.poll, 'secure': False}), (r'^/main/image$', {'function': self.main_poll, 'secure': False}), + (r'^/api/controller/(live|preview)/text$', {'function': self.controller_text, 'secure': False}), (r'^/api/controller/(live|preview)/(.*)$', {'function': self.controller, 'secure': True}), - (r'^/stage/controller/(live|preview)/(.*)$', {'function': self.controller, 'secure': False}), - (r'^/api/service/(.*)$', {'function':self.service, 'secure': False}), - (r'^/stage/service/(.*)$', {'function': self.service, 'secure': False}), + (r'^/api/service/list$', {'function': self.service_list, 'secure': False}), + (r'^/api/service/(.*)$', {'function': self.service, 'secure': True}), (r'^/api/display/(hide|show|blank|theme|desktop)$', {'function': self.display, 'secure': True}), (r'^/api/alert$', {'function': self.alert, 'secure': True}), (r'^/api/plugin/(search)$', {'function': self.plugin_info, 'secure': False}), @@ -165,6 +164,7 @@ class HttpRouter(object): (r'^/api/(.*)/live$', {'function': self.go_live, 'secure': True}), (r'^/api/(.*)/add$', {'function': self.add_to_service, 'secure': True}) ] + self.settings_section = 'remotes' self.translate() self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html') @@ -215,7 +215,7 @@ class HttpRouter(object): self.send_header('Content-type', 'text/html') self.end_headers() - def do_notfound(self): + def do_not_found(self): self.send_response(404) self.send_header('Content-type', 'text/html') self.end_headers() @@ -289,7 +289,7 @@ class HttpRouter(object): file_name = 'main.html' path = os.path.normpath(os.path.join(self.html_dir, file_name)) if not path.startswith(self.html_dir): - return self.http_not_found() + return self.do_not_found() ext = os.path.splitext(file_name)[1] html = None if ext == '.html': @@ -320,7 +320,7 @@ class HttpRouter(object): content = file_handle.read() except IOError: log.exception('Failed to open %s' % path) - return self._http_not_found() + return self.do_not_found() finally: if file_handle: file_handle.close() @@ -337,7 +337,10 @@ class HttpRouter(object): 'twelve': Settings().value('remotes/twelve hour'), 'blank': self.live_controller.blank_screen.isChecked(), 'theme': self.live_controller.theme_screen.isChecked(), - 'display': self.live_controller.desktop_screen.isChecked() + 'display': self.live_controller.desktop_screen.isChecked(), + 'version': 2, + 'isSecure': Settings().value(self.settings_section + '/authentication enabled'), + 'isAuthorised': self.authorised } return json.dumps({'results': result}).encode() @@ -379,7 +382,7 @@ class HttpRouter(object): try: text = json.loads(self.request_data)['request']['text'] except KeyError as ValueError: - return self._http_bad_request() + return self.do_http_error() text = urllib.parse.unquote(text) self.alerts_manager.emit(QtCore.SIGNAL('alerts_text'), [text]) success = True @@ -387,6 +390,33 @@ class HttpRouter(object): success = False return json.dumps({'results': {'success': success}}).encode() + def controller_text(self, var): + """ + Perform an action on the slide controller. + """ + current_item = self.live_controller.service_item + data = [] + if current_item: + for index, frame in enumerate(current_item.get_frames()): + item = {} + if current_item.is_text(): + if frame['verseTag']: + item['tag'] = str(frame['verseTag']) + else: + item['tag'] = str(index + 1) + item['text'] = str(frame['text']) + item['html'] = str(frame['html']) + else: + item['tag'] = str(index + 1) + item['text'] = str(frame['title']) + item['html'] = str(frame['title']) + item['selected'] = (self.live_controller.selected_row == index) + data.append(item) + json_data = {'results': {'slides': data}} + if current_item: + json_data['results']['item'] = self.live_controller.service_item.unique_identifier + return json.dumps(json_data).encode() + def controller(self, display_type, action): """ Perform an action on the slide controller. @@ -398,42 +428,28 @@ class HttpRouter(object): The action to perform. """ event = 'slidecontroller_%s_%s' % (display_type, action) - if action == 'text': - current_item = self.live_controller.service_item - data = [] - if current_item: - for index, frame in enumerate(current_item.get_frames()): - item = {} - if current_item.is_text(): - if frame['verseTag']: - item['tag'] = str(frame['verseTag']) - else: - item['tag'] = str(index + 1) - item['text'] = str(frame['text']) - item['html'] = str(frame['html']) - else: - item['tag'] = str(index + 1) - item['text'] = str(frame['title']) - item['html'] = str(frame['title']) - item['selected'] = (self.live_controller.selected_row == index) - data.append(item) - json_data = {'results': {'slides': data}} - if current_item: - json_data['results']['item'] = self.live_controller.service_item.unique_identifier + if self.request_data: + try: + data = json.loads(self.request_data)['request']['id'] + except KeyError as ValueError: + return self.do_http_error() + log.info(data) + # This slot expects an int within a list. + self.live_controller.emit(QtCore.SIGNAL(event), [data]) else: - if self.request_data: - try: - data = json.loads(self.request_data)['request']['id'] - except KeyError as ValueError: - return self._http_bad_request() - log.info(data) - # This slot expects an int within a list. - self.live_controller.emit(QtCore.SIGNAL(event), [data]) - else: - self.live_controller.emit(QtCore.SIGNAL(event)) - json_data = {'results': {'success': True}} + self.live_controller.emit(QtCore.SIGNAL(event)) + json_data = {'results': {'success': True}} return json.dumps(json_data).encode() + def service_list(self): + """ + Handles requests for service items in the service manager + + ``action`` + The action to perform. + """ + return json.dumps({'results': {'items': self._get_service_items()}}).encode() + def service(self, action): """ Handles requests for service items in the service manager @@ -441,17 +457,12 @@ class HttpRouter(object): ``action`` The action to perform. """ - event = 'servicemanager_%s' % action - if action == 'list': - return json.dumps({'results': {'items': self._get_service_items()}}).encode() - event += '_item' + event = 'servicemanager_%s_item' % action if self.request_data: try: -# print(json.loads(self.request_data['data'])) - print(json.loads(self.request_data)) data = json.loads(self.request_data)['request']['id'] except KeyError: - return self._http_bad_request() + return self.do_http_error() self.service_manager.emit(QtCore.SIGNAL(event), data) else: Registry().execute(event) @@ -482,7 +493,7 @@ class HttpRouter(object): try: text = json.loads(self.request_data)['request']['text'] except KeyError as ValueError: - return self._http_bad_request() + return self.do_http_error() text = urllib.parse.unquote(text) plugin = self.plugin_manager.get_plugin_by_name(plugin_name) if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search: @@ -498,11 +509,11 @@ class HttpRouter(object): try: id = json.loads(self.request_data)['request']['id'] except KeyError as ValueError: - return self._http_bad_request() + return self.do_http_error() plugin = self.plugin_manager.get_plugin_by_name(plugin_name) if plugin.status == PluginStatus.Active and plugin.media_item: plugin.media_item.emit(QtCore.SIGNAL('%s_go_live' % plugin_name), [id, True]) - return self._http_success() + return self.do_http_success() def add_to_service(self, plugin_name): """ @@ -511,12 +522,12 @@ class HttpRouter(object): try: id = json.loads(self.request_data)['request']['id'] except KeyError as ValueError: - return self._http_bad_request() + return self.do_http_error() plugin = self.plugin_manager.get_plugin_by_name(plugin_name) if plugin.status == PluginStatus.Active and plugin.media_item: item_id = plugin.media_item.create_item_from_id(id) plugin.media_item.emit(QtCore.SIGNAL('%s_add_to_service' % plugin_name), [item_id, True]) - self._http_success() + self.do_http_success() def _get_service_manager(self): """ @@ -557,4 +568,3 @@ class HttpRouter(object): return self._alerts_manager alerts_manager = property(_get_alerts_manager) - diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index 5535847a6..932a4af69 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -72,20 +72,14 @@ class CustomHandler(BaseHTTPRequestHandler, HttpRouter): """ if self.path == '/favicon.ico': return - #print(self.headers['content-type'],self.headers['content-length']) - if self.headers['content-type'] == 'application/text': - length = int(self.headers['content-length']) - postvars = parse_qs(self.rfile.read(length), keep_blank_values=1) - for var in postvars: - print(var) - #{"request": {"id": 1}} if not hasattr(self, 'auth'): self.initialise() function, args = self.process_http_request(self.path) if not function: self.do_http_error() return - if function['secure']: + self.authorised = self.headers['Authorization'] is None + if function['secure'] and Settings().value(self.settings_section + '/authentication enabled'): if self.headers['Authorization'] is None: self.do_authorisation() self.wfile.write(bytes('no auth header received', 'UTF-8')) From b868ecc57dffab2835b5e2d0939289876d6a7a3f Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 14 Sep 2013 20:16:14 +0100 Subject: [PATCH 05/18] remove reminances of cherrypy --- openlp/core/ui/exceptionform.py | 7 ------- openlp/plugins/remotes/remoteplugin.py | 7 ++++--- scripts/check_dependencies.py | 14 +++++++++----- .../openlp_plugins/remotes/test_server.py | 4 +--- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/openlp/core/ui/exceptionform.py b/openlp/core/ui/exceptionform.py index 758fc6ecc..2dc034f71 100644 --- a/openlp/core/ui/exceptionform.py +++ b/openlp/core/ui/exceptionform.py @@ -75,12 +75,6 @@ try: ICU_VERSION = 'OK' except ImportError: ICU_VERSION = '-' -try: - import cherrypy - CHERRYPY_VERSION = cherrypy.__version__ -except ImportError: - CHERRYPY_VERSION = '-' - try: WEBKIT_VERSION = QtWebKit.qWebKitVersion() except AttributeError: @@ -140,7 +134,6 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog): 'Chardet: %s\n' % CHARDET_VERSION + \ 'PyEnchant: %s\n' % ENCHANT_VERSION + \ 'Mako: %s\n' % MAKO_VERSION + \ - 'CherryPy: %s\n' % CHERRYPY_VERSION + \ 'pyICU: %s\n' % ICU_VERSION + \ 'pyUNO bridge: %s\n' % self._pyuno_import() + \ 'VLC: %s\n' % VLC_VERSION diff --git a/openlp/plugins/remotes/remoteplugin.py b/openlp/plugins/remotes/remoteplugin.py index 0c23978b6..d5b5545ba 100644 --- a/openlp/plugins/remotes/remoteplugin.py +++ b/openlp/plugins/remotes/remoteplugin.py @@ -28,6 +28,7 @@ ############################################################################### import logging +import time from openlp.core.lib import Plugin, StringContent, translate, build_icon from openlp.plugins.remotes.lib import RemoteTab, OpenLPServer @@ -66,7 +67,6 @@ class RemotesPlugin(Plugin): log.debug('initialise') super(RemotesPlugin, self).initialise() self.server = OpenLPServer() - #self.server.start_server() def finalise(self): """ @@ -107,5 +107,6 @@ class RemotesPlugin(Plugin): Called when Config is changed to restart the server on new address or port """ log.debug('remote config changed') - self.main_window.information_message(translate('RemotePlugin', 'Configuration Change'), - translate('RemotePlugin', 'OpenLP will need to be restarted for the Remote changes to become active.')) + self.finalise() + time.sleep(0.5) + self.initialise() diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py index 348a809eb..698a65a96 100755 --- a/scripts/check_dependencies.py +++ b/scripts/check_dependencies.py @@ -48,6 +48,7 @@ except ImportError: IS_WIN = sys.platform.startswith('win') + VERS = { 'Python': '2.6', 'PyQt4': '4.6', @@ -82,7 +83,6 @@ MODULES = [ 'enchant', 'bs4', 'mako', - 'cherrypy', 'uno', 'icu', 'bs4', @@ -98,6 +98,7 @@ OPTIONAL_MODULES = [ w = sys.stdout.write + def check_vers(version, required, text): if not isinstance(version, str): version = '.'.join(map(str, version)) @@ -111,13 +112,16 @@ def check_vers(version, required, text): w('FAIL' + os.linesep) return False + def print_vers_fail(required, text): print(' %s >= %s ... FAIL' % (text, required)) + def verify_python(): if not check_vers(list(sys.version_info), VERS['Python'], text='Python'): exit(1) + def verify_versions(): print('Verifying version of modules...') try: @@ -138,6 +142,7 @@ def verify_versions(): except ImportError: print_vers_fail(VERS['enchant'], 'enchant') + def check_module(mod, text='', indent=' '): space = (30 - len(mod) - len(text)) * ' ' w(indent + '%s%s... ' % (mod, text) + space) @@ -148,6 +153,7 @@ def check_module(mod, text='', indent=' '): w('FAIL') w(os.linesep) + def verify_pyenchant(): w('Enchant (spell checker)... ') try: @@ -160,6 +166,7 @@ def verify_pyenchant(): except ImportError: w('FAIL' + os.linesep) + def verify_pyqt(): w('Qt4 image formats... ') try: @@ -174,22 +181,19 @@ def verify_pyqt(): except ImportError: w('FAIL' + os.linesep) + def main(): verify_python() - print('Checking for modules...') for m in MODULES: check_module(m) - print('Checking for optional modules...') for m in OPTIONAL_MODULES: check_module(m[0], text=m[1]) - if IS_WIN: print('Checking for Windows specific modules...') for m in WIN32_MODULES: check_module(m) - verify_versions() verify_pyqt() verify_pyenchant() diff --git a/tests/interfaces/openlp_plugins/remotes/test_server.py b/tests/interfaces/openlp_plugins/remotes/test_server.py index 63975370f..101444829 100644 --- a/tests/interfaces/openlp_plugins/remotes/test_server.py +++ b/tests/interfaces/openlp_plugins/remotes/test_server.py @@ -7,9 +7,8 @@ from unittest import TestCase from tempfile import mkstemp from mock import MagicMock import urllib.request, urllib.error, urllib.parse -import cherrypy -from BeautifulSoup import BeautifulSoup +from bs4 import BeautifulSoup from openlp.core.lib import Settings from openlp.plugins.remotes.lib.httpserver import HttpServer @@ -133,6 +132,5 @@ def process_http_request(url_path, *args): ``*args`` Some args. """ - cherrypy.response.status = 200 return None From 539ed55777097b74a1b121683684c3b61e08131c Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 14 Sep 2013 22:00:58 +0100 Subject: [PATCH 06/18] More cleanups --- openlp/plugins/remotes/lib/httprouter.py | 53 ++++++- openlp/plugins/remotes/lib/httpserver.py | 27 +--- .../openlp_plugins/remotes/test_router.py | 51 +++---- .../openlp_plugins/remotes/test_server.py | 136 ------------------ 4 files changed, 72 insertions(+), 195 deletions(-) delete mode 100644 tests/interfaces/openlp_plugins/remotes/test_server.py diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 11e1900ef..6f1268433 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -134,11 +134,12 @@ log = logging.getLogger(__name__) class HttpRouter(object): """ This code is called by the HttpServer upon a request and it processes it based on the routing table. - This code is stateless so need + This code is stateless and is created on each request. + Some variables may look incorrect but this extends BaseHTTPRequestHandler. """ def initialise(self): """ - Initialise the router stack and any other varables. + Initialise the router stack and any other variables. """ authcode = "%s:%s" % (Settings().value('remotes/user id'), Settings().value('remotes/password')) try: @@ -168,7 +169,43 @@ class HttpRouter(object): self.translate() self.html_dir = os.path.join(AppLocation.get_directory(AppLocation.PluginsDir), 'remotes', 'html') + def do_post_processor(self): + """ + Handle the POST amd GET requests placed on the server. + """ + if self.path == '/favicon.ico': + return + if not hasattr(self, 'auth'): + self.initialise() + function, args = self.process_http_request(self.path) + if not function: + self.do_http_error() + return + self.authorised = self.headers['Authorization'] is None + if function['secure'] and Settings().value(self.settings_section + '/authentication enabled'): + if self.headers['Authorization'] is None: + self.do_authorisation() + self.wfile.write(bytes('no auth header received', 'UTF-8')) + elif self.headers['Authorization'] == 'Basic %s' % self.auth: + self.do_http_success() + self.call_function(function, *args) + else: + self.do_authorisation() + self.wfile.write(bytes(self.headers['Authorization'], 'UTF-8')) + self.wfile.write(bytes(' not authenticated', 'UTF-8')) + else: + self.call_function(function, *args) + def call_function(self, function, *args): + """ + Invoke the route function passing the relevant values + + ``function`` + The function to be calledL. + + ``*args`` + Any passed data. + """ response = function['function'](*args) if response: self.wfile.write(response) @@ -200,22 +237,34 @@ class HttpRouter(object): return None, None def do_http_success(self): + """ + Create a success http header. + """ self.send_response(200) self.send_header('Content-type', 'text/html') self.end_headers() def do_http_error(self): + """ + Create a error http header. + """ self.send_response(404) self.send_header('Content-type', 'text/html') self.end_headers() def do_authorisation(self): + """ + Create a needs authorisation http header. + """ self.send_response(401) self.send_header('WWW-Authenticate', 'Basic realm=\"Test\"') self.send_header('Content-type', 'text/html') self.end_headers() def do_not_found(self): + """ + Create a not found http header. + """ self.send_response(404) self.send_header('Content-type', 'text/html') self.end_headers() diff --git a/openlp/plugins/remotes/lib/httpserver.py b/openlp/plugins/remotes/lib/httpserver.py index 932a4af69..7776812fa 100644 --- a/openlp/plugins/remotes/lib/httpserver.py +++ b/openlp/plugins/remotes/lib/httpserver.py @@ -64,34 +64,13 @@ class CustomHandler(BaseHTTPRequestHandler, HttpRouter): """ Present pages / data and invoke URL level user authentication. """ - self.do_GET() + self.do_post_processor() def do_GET(self): """ Present pages / data and invoke URL level user authentication. """ - if self.path == '/favicon.ico': - return - if not hasattr(self, 'auth'): - self.initialise() - function, args = self.process_http_request(self.path) - if not function: - self.do_http_error() - return - self.authorised = self.headers['Authorization'] is None - if function['secure'] and Settings().value(self.settings_section + '/authentication enabled'): - if self.headers['Authorization'] is None: - self.do_authorisation() - self.wfile.write(bytes('no auth header received', 'UTF-8')) - elif self.headers['Authorization'] == 'Basic %s' % self.auth: - self.do_http_success() - self.call_function(function, *args) - else: - self.do_authorisation() - self.wfile.write(bytes(self.headers['Authorization'], 'UTF-8')) - self.wfile.write(bytes(' not authenticated', 'UTF-8')) - else: - self.call_function(function, *args) + self.do_post_processor() class ThreadingHTTPServer(ThreadingMixIn, HTTPServer): @@ -148,7 +127,7 @@ class OpenLPServer(): """ Stop the server """ - self.httpd.socket.close() + self.http_thread.exit(0) self.httpd = None log.debug('Stopped the server.') diff --git a/tests/functional/openlp_plugins/remotes/test_router.py b/tests/functional/openlp_plugins/remotes/test_router.py index af1849a65..0e154a1a1 100644 --- a/tests/functional/openlp_plugins/remotes/test_router.py +++ b/tests/functional/openlp_plugins/remotes/test_router.py @@ -8,7 +8,7 @@ from tempfile import mkstemp from mock import MagicMock from openlp.core.lib import Settings -from openlp.plugins.remotes.lib.httpserver import HttpRouter, fetch_password, make_sha_hash +from openlp.plugins.remotes.lib.httpserver import HttpRouter from PyQt4 import QtGui __default_settings__ = { @@ -44,40 +44,22 @@ class TestRouter(TestCase): del self.application os.unlink(self.ini_file) - def fetch_password_unknown_test(self): + def password_encrypter_test(self): """ - Test the fetch password code with an unknown userid + Test hash userid and password function """ # GIVEN: A default configuration - # WHEN: called with the defined userid - password = fetch_password('itwinkle') + Settings().setValue('remotes/user id', 'openlp') + Settings().setValue('remotes/password', 'password') - # THEN: the function should return None - self.assertEqual(password, None, 'The result for fetch_password should be None') - - def fetch_password_known_test(self): - """ - Test the fetch password code with the defined userid - """ - # GIVEN: A default configuration # WHEN: called with the defined userid - password = fetch_password('openlp') - required_password = make_sha_hash('password') + router = HttpRouter() + router.initialise() + test_value = 'b3BlbmxwOnBhc3N3b3Jk' + print(router.auth) # THEN: the function should return the correct password - self.assertEqual(password, required_password, 'The result for fetch_password should be the defined password') - - def sha_password_encrypter_test(self): - """ - Test hash password function - """ - # GIVEN: A default configuration - # WHEN: called with the defined userid - required_password = make_sha_hash('password') - test_value = '5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8' - - # THEN: the function should return the correct password - self.assertEqual(required_password, test_value, + self.assertEqual(router.auth, test_value, 'The result for make_sha_hash should return the correct encrypted password') def process_http_request_test(self): @@ -85,15 +67,18 @@ class TestRouter(TestCase): Test the router control functionality """ # GIVEN: A testing set of Routes + router = HttpRouter() mocked_function = MagicMock() test_route = [ - (r'^/stage/api/poll$', mocked_function), + (r'^/stage/api/poll$', {'function': mocked_function, 'secure': False}), ] - self.router.routes = test_route + router.routes = test_route # WHEN: called with a poll route - self.router.process_http_request('/stage/api/poll', None) + function, args = router.process_http_request('/stage/api/poll', None) # THEN: the function should have been called only once - assert mocked_function.call_count == 1, \ - 'The mocked function should have been matched and called once.' + assert function['function'] == mocked_function, \ + 'The mocked function should match defined value.' + assert function['secure'] == False, \ + 'The mocked function should not require any security.' \ No newline at end of file diff --git a/tests/interfaces/openlp_plugins/remotes/test_server.py b/tests/interfaces/openlp_plugins/remotes/test_server.py deleted file mode 100644 index 101444829..000000000 --- a/tests/interfaces/openlp_plugins/remotes/test_server.py +++ /dev/null @@ -1,136 +0,0 @@ -""" -This module contains tests for the lib submodule of the Remotes plugin. -""" -import os - -from unittest import TestCase -from tempfile import mkstemp -from mock import MagicMock -import urllib.request, urllib.error, urllib.parse - -from bs4 import BeautifulSoup - -from openlp.core.lib import Settings -from openlp.plugins.remotes.lib.httpserver import HttpServer -from PyQt4 import QtGui - -__default_settings__ = { - 'remotes/twelve hour': True, - 'remotes/port': 4316, - 'remotes/https port': 4317, - 'remotes/https enabled': False, - 'remotes/user id': 'openlp', - 'remotes/password': 'password', - 'remotes/authentication enabled': False, - 'remotes/ip address': '0.0.0.0' -} - - -class TestRouter(TestCase): - """ - Test the functions in the :mod:`lib` module. - """ - def setUp(self): - """ - Create the UI - """ - fd, self.ini_file = mkstemp('.ini') - Settings().set_filename(self.ini_file) - self.application = QtGui.QApplication.instance() - Settings().extend_default_settings(__default_settings__) - self.server = HttpServer() - - def tearDown(self): - """ - Delete all the C++ objects at the end so that we don't have a segfault - """ - del self.application - os.unlink(self.ini_file) - self.server.close() - - def start_server(self): - """ - Common function to start server then mock out the router. CherryPy crashes if you mock before you start - """ - self.server.start_server() - self.server.router = MagicMock() - self.server.router.process_http_request = process_http_request - - def start_default_server_test(self): - """ - Test the default server serves the correct initial page - """ - # GIVEN: A default configuration - Settings().setValue('remotes/authentication enabled', False) - self.start_server() - - # WHEN: called the route location - code, page = call_remote_server('http://localhost:4316') - - # THEN: default title will be returned - self.assertEqual(BeautifulSoup(page).title.text, 'OpenLP 2.1 Remote', - 'The default menu should be returned') - - def start_authenticating_server_test(self): - """ - Test the default server serves the correctly with authentication - """ - # GIVEN: A default authorised configuration - Settings().setValue('remotes/authentication enabled', True) - self.start_server() - - # WHEN: called the route location with no user details - code, page = call_remote_server('http://localhost:4316') - - # THEN: then server will ask for details - self.assertEqual(code, 401, 'The basic authorisation request should be returned') - - # WHEN: called the route location with user details - code, page = call_remote_server('http://localhost:4316', 'openlp', 'password') - - # THEN: default title will be returned - self.assertEqual(BeautifulSoup(page).title.text, 'OpenLP 2.1 Remote', - 'The default menu should be returned') - - # WHEN: called the route location with incorrect user details - code, page = call_remote_server('http://localhost:4316', 'itwinkle', 'password') - - # THEN: then server will ask for details - self.assertEqual(code, 401, 'The basic authorisation request should be returned') - - -def call_remote_server(url, username=None, password=None): - """ - Helper function - - ``username`` - The username. - - ``password`` - The password. - """ - if username: - passman = urllib.request.HTTPPasswordMgrWithDefaultRealm() - passman.add_password(None, url, username, password) - authhandler = urllib.request.HTTPBasicAuthHandler(passman) - opener = urllib.request.build_opener(authhandler) - urllib.request.install_opener(opener) - try: - page = urllib.request.urlopen(url) - return 0, page.read() - except urllib.error.HTTPError as e: - return e.code, '' - - -def process_http_request(url_path, *args): - """ - Override function to make the Mock work but does nothing. - - ``Url_path`` - The url_path. - - ``*args`` - Some args. - """ - return None - From 870a232e4be1cdf8a38a82f2739dd21439e46b7e Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Thu, 19 Sep 2013 18:05:07 +0100 Subject: [PATCH 07/18] Fix mime types add json debugging --- openlp/plugins/remotes/lib/httprouter.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 6f1268433..1b29bc416 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -175,6 +175,14 @@ class HttpRouter(object): """ if self.path == '/favicon.ico': return + ########### + print(self.headers['content-type']) + if self.headers['content-type'] == 'application/json': + length = int(self.headers['content-length']) + postvars = parse_qs(self.rfile.read(length), keep_blank_values=1) + for var in postvars: + print(var.decode("utf-8")) + ############## if not hasattr(self, 'auth'): self.initialise() function, args = self.process_http_request(self.path) @@ -348,13 +356,13 @@ class HttpRouter(object): elif ext == '.css': self.send_header('Content-type', 'text/css') elif ext == '.js': - self.send_header('Content-type', 'application/x-javascript') + self.send_header('Content-type', 'application/javascript') elif ext == '.jpg': self.send_header('Content-type', 'image/jpeg') elif ext == '.gif': self.send_header('Content-type', 'image/gif') elif ext == '.ico': - self.send_header('Content-type', 'image/ico') + self.send_header('Content-type', 'image/x-icon') elif ext == '.png': self.send_header('Content-type', 'image/png') else: From 148da9724570dfa57d0d60e7179d605940a425e1 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Thu, 19 Sep 2013 23:02:28 +0200 Subject: [PATCH 08/18] Update the tests to use a conditionally imported mock and cleaned things up a little. --- tests/functional/__init__.py | 6 ++ tests/functional/openlp_core_lib/test_db.py | 30 +++++- .../openlp_core_lib/test_formattingtags.py | 31 +++++- .../openlp_core_lib/test_image_manager.py | 36 ++++++- tests/functional/openlp_core_lib/test_lib.py | 32 +++++- .../openlp_core_lib/test_pluginmanager.py | 31 +++++- .../openlp_core_lib/test_registry.py | 31 +++++- .../functional/openlp_core_lib/test_screen.py | 32 +++++- .../openlp_core_lib/test_serviceitem.py | 33 ++++++- .../openlp_core_lib/test_settings.py | 34 ++++++- .../openlp_core_lib/test_uistrings.py | 29 +++++- .../openlp_core_utils/test_actions.py | 31 ++++++ .../openlp_core_utils/test_applocation.py | 53 +++++++--- .../openlp_core_utils/test_utils.py | 99 ++++++++++++++----- .../openlp_plugins/bibles/test_lib.py | 28 ++++++ .../bibles/test_versereferencelist.py | 88 +++++++++++------ .../openlp_plugins/images/test_lib.py | 90 ++++++++++------- .../presentations/test_mediaitem.py | 35 +++++-- .../openlp_plugins/remotes/test_remotetab.py | 54 +++++++--- .../openlp_plugins/remotes/test_router.py | 34 ++++++- .../openlp_plugins/songs/test_ewimport.py | 30 +++++- .../songs/test_foilpresenterimport.py | 5 +- .../openlp_plugins/songs/test_lib.py | 48 ++++++--- .../openlp_plugins/songs/test_mediaitem.py | 4 +- .../songs/test_songshowplusimport.py | 30 +++++- .../songs/test_worshipcenterproimport.py | 37 ++++++- 26 files changed, 819 insertions(+), 172 deletions(-) diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py index 24d8d5b28..f311052f9 100644 --- a/tests/functional/__init__.py +++ b/tests/functional/__init__.py @@ -7,7 +7,13 @@ sip.setapi('QTime', 2) sip.setapi('QUrl', 2) sip.setapi('QVariant', 2) +import sys from PyQt4 import QtGui +if sys.version_info[1] >= 3: + from unittest.mock import patch, MagicMock +else: + from mock import patch, MagicMockv + # Only one QApplication can be created. Use QtGui.QApplication.instance() when you need to "create" a QApplication. application = QtGui.QApplication([]) diff --git a/tests/functional/openlp_core_lib/test_db.py b/tests/functional/openlp_core_lib/test_db.py index b485f61e2..cdb046642 100644 --- a/tests/functional/openlp_core_lib/test_db.py +++ b/tests/functional/openlp_core_lib/test_db.py @@ -1,14 +1,42 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ Package to test the openlp.core.lib package. """ from unittest import TestCase -from mock import MagicMock, patch from sqlalchemy.pool import NullPool from sqlalchemy.orm.scoping import ScopedSession from sqlalchemy import MetaData from openlp.core.lib.db import init_db, get_upgrade_op +from tests.functional import patch, MagicMock class TestDB(TestCase): diff --git a/tests/functional/openlp_core_lib/test_formattingtags.py b/tests/functional/openlp_core_lib/test_formattingtags.py index 5d04544fa..a200318ff 100644 --- a/tests/functional/openlp_core_lib/test_formattingtags.py +++ b/tests/functional/openlp_core_lib/test_formattingtags.py @@ -1,12 +1,39 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ Package to test the openlp.core.lib.formattingtags package. """ import copy from unittest import TestCase -from mock import patch - from openlp.core.lib import FormattingTags +from tests.functional import patch TAG = { diff --git a/tests/functional/openlp_core_lib/test_image_manager.py b/tests/functional/openlp_core_lib/test_image_manager.py index a1bc7624a..74f699e8e 100644 --- a/tests/functional/openlp_core_lib/test_image_manager.py +++ b/tests/functional/openlp_core_lib/test_image_manager.py @@ -1,15 +1,41 @@ -""" - Package to test the openlp.core.ui package. -""" +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +""" +Package to test the openlp.core.ui package. +""" import os from unittest import TestCase -from PyQt4 import QtCore, QtGui +from PyQt4 import QtGui from openlp.core.lib import Registry, ImageManager, ScreenList - TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources')) diff --git a/tests/functional/openlp_core_lib/test_lib.py b/tests/functional/openlp_core_lib/test_lib.py index fa7fe92e9..4b67cee2b 100644 --- a/tests/functional/openlp_core_lib/test_lib.py +++ b/tests/functional/openlp_core_lib/test_lib.py @@ -1,3 +1,31 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ Package to test the openlp.core.lib package. """ @@ -6,17 +34,15 @@ import os from unittest import TestCase from datetime import datetime, timedelta -from mock import MagicMock, patch from PyQt4 import QtCore, QtGui from openlp.core.lib import str_to_bool, create_thumb, translate, check_directory_exists, get_text_file_string, \ build_icon, image_to_byte, check_item_selected, validate_thumb, create_separated_list, clean_tags, expand_tags - +from tests.functional import MagicMock, patch TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources')) - class TestLib(TestCase): def str_to_bool_with_bool_test(self): diff --git a/tests/functional/openlp_core_lib/test_pluginmanager.py b/tests/functional/openlp_core_lib/test_pluginmanager.py index 17e3c3e67..a3114c9e5 100644 --- a/tests/functional/openlp_core_lib/test_pluginmanager.py +++ b/tests/functional/openlp_core_lib/test_pluginmanager.py @@ -1,12 +1,39 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ Package to test the openlp.core.lib.pluginmanager package. """ from unittest import TestCase -from mock import MagicMock - from openlp.core.lib.pluginmanager import PluginManager from openlp.core.lib import Settings, Registry, PluginStatus +from tests.functional import MagicMock class TestPluginManager(TestCase): diff --git a/tests/functional/openlp_core_lib/test_registry.py b/tests/functional/openlp_core_lib/test_registry.py index c8003559f..06307630b 100644 --- a/tests/functional/openlp_core_lib/test_registry.py +++ b/tests/functional/openlp_core_lib/test_registry.py @@ -1,12 +1,39 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ Package to test the openlp.core.lib package. """ import os from unittest import TestCase -from mock import MagicMock - from openlp.core.lib import Registry +from tests.functional import MagicMock TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources')) diff --git a/tests/functional/openlp_core_lib/test_screen.py b/tests/functional/openlp_core_lib/test_screen.py index a73883ab9..b8b617d3c 100644 --- a/tests/functional/openlp_core_lib/test_screen.py +++ b/tests/functional/openlp_core_lib/test_screen.py @@ -1,14 +1,40 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ Package to test the openlp.core.lib.screenlist package. """ - from unittest import TestCase -from mock import MagicMock from PyQt4 import QtGui, QtCore from openlp.core.lib import Registry, ScreenList - +from tests.functional import MagicMock SCREEN = { 'primary': False, diff --git a/tests/functional/openlp_core_lib/test_serviceitem.py b/tests/functional/openlp_core_lib/test_serviceitem.py index c387d83ad..ce176c82c 100644 --- a/tests/functional/openlp_core_lib/test_serviceitem.py +++ b/tests/functional/openlp_core_lib/test_serviceitem.py @@ -1,16 +1,40 @@ # -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ - Package to test the openlp.core.lib package. +Package to test the openlp.core.lib package. """ import os import json -import tempfile from unittest import TestCase -from mock import MagicMock, patch from openlp.core.lib import ItemCapabilities, ServiceItem, Registry -from lxml import objectify, etree +from tests.functional import MagicMock, patch VERSE = 'The Lord said to {r}Noah{/r}: \n'\ 'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n'\ @@ -20,7 +44,6 @@ VERSE = 'The Lord said to {r}Noah{/r}: \n'\ '{r}C{/r}{b}h{/b}{bl}i{/bl}{y}l{/y}{g}d{/g}{pk}'\ 'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n' FOOTER = ['Arky Arky (Unknown)', 'Public Domain', 'CCLI 123456'] - TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources')) diff --git a/tests/functional/openlp_core_lib/test_settings.py b/tests/functional/openlp_core_lib/test_settings.py index 444b206d6..e9ee02443 100644 --- a/tests/functional/openlp_core_lib/test_settings.py +++ b/tests/functional/openlp_core_lib/test_settings.py @@ -1,14 +1,42 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ - Package to test the openlp.core.lib.settings package. +Package to test the openlp.core.lib.settings package. """ import os from unittest import TestCase from tempfile import mkstemp -from openlp.core.lib import Settings - from PyQt4 import QtGui +from openlp.core.lib import Settings + class TestSettings(TestCase): """ diff --git a/tests/functional/openlp_core_lib/test_uistrings.py b/tests/functional/openlp_core_lib/test_uistrings.py index 0070533db..a69905680 100644 --- a/tests/functional/openlp_core_lib/test_uistrings.py +++ b/tests/functional/openlp_core_lib/test_uistrings.py @@ -1,7 +1,34 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ Package to test the openlp.core.lib.uistrings package. """ - from unittest import TestCase from openlp.core.lib import UiStrings diff --git a/tests/functional/openlp_core_utils/test_actions.py b/tests/functional/openlp_core_utils/test_actions.py index 0fa096b07..42a7c7079 100644 --- a/tests/functional/openlp_core_utils/test_actions.py +++ b/tests/functional/openlp_core_utils/test_actions.py @@ -1,3 +1,31 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ Package to test the openlp.core.utils.actions package. """ @@ -12,6 +40,9 @@ from openlp.core.utils import ActionList class TestActionList(TestCase): + """ + Test the ActionList class + """ def setUp(self): """ diff --git a/tests/functional/openlp_core_utils/test_applocation.py b/tests/functional/openlp_core_utils/test_applocation.py index 997ff7417..d34c0cfa5 100644 --- a/tests/functional/openlp_core_utils/test_applocation.py +++ b/tests/functional/openlp_core_utils/test_applocation.py @@ -1,13 +1,39 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ Functional tests to test the AppLocation class and related methods. """ import copy from unittest import TestCase -from mock import patch - from openlp.core.utils import AppLocation - +from tests.functional import patch FILE_LIST = ['file1', 'file2', 'file3.txt', 'file4.txt', 'file5.mp3', 'file6.mp3'] @@ -38,7 +64,7 @@ class TestAppLocation(TestCase): mocked_settings.contains.assert_called_with('advanced/data path') mocked_get_directory.assert_called_with(AppLocation.DataDir) mocked_check_directory_exists.assert_called_with('test/dir') - assert data_path == 'test/dir', 'Result should be "test/dir"' + self.assertEqual('test/dir', data_path, 'Result should be "test/dir"') def get_data_path_with_custom_location_test(self): """ @@ -58,7 +84,7 @@ class TestAppLocation(TestCase): # THEN: the mocked Settings methods were called and the value returned was our set up value mocked_settings.contains.assert_called_with('advanced/data path') mocked_settings.value.assert_called_with('advanced/data path') - assert data_path == 'custom/dir', 'Result should be "custom/dir"' + self.assertEqual('custom/dir', data_path, 'Result should be "custom/dir"') def get_files_no_section_no_extension_test(self): """ @@ -74,7 +100,7 @@ class TestAppLocation(TestCase): result = AppLocation.get_files() # Then: check if the file lists are identical. - assert result == FILE_LIST, 'The file lists should be identical.' + self.assertListEqual(FILE_LIST, result, 'The file lists should be identical.') def get_files_test(self): """ @@ -93,7 +119,7 @@ class TestAppLocation(TestCase): mocked_listdir.assert_called_with('test/dir/section') # Then: check if the file lists are identical. - assert result == ['file5.mp3', 'file6.mp3'], 'The file lists should be identical.' + self.assertListEqual(['file5.mp3', 'file6.mp3'], result, 'The file lists should be identical.') def get_section_data_path_test(self): """ @@ -110,25 +136,27 @@ class TestAppLocation(TestCase): # THEN: check that all the correct methods were called, and the result is correct mocked_check_directory_exists.assert_called_with('test/dir/section') - assert data_path == 'test/dir/section', 'Result should be "test/dir/section"' + self.assertEqual('test/dir/section', data_path, 'Result should be "test/dir/section"') def get_directory_for_app_dir_test(self): """ Test the AppLocation.get_directory() method for AppLocation.AppDir """ + # GIVEN: A mocked out _get_frozen_path function with patch('openlp.core.utils.applocation._get_frozen_path') as mocked_get_frozen_path: mocked_get_frozen_path.return_value = 'app/dir' # WHEN: We call AppLocation.get_directory directory = AppLocation.get_directory(AppLocation.AppDir) - # THEN: - assert directory == 'app/dir', 'Directory should be "app/dir"' + # THEN: check that the correct directory is returned + self.assertEqual('app/dir', directory, 'Directory should be "app/dir"') def get_directory_for_plugins_dir_test(self): """ Test the AppLocation.get_directory() method for AppLocation.PluginsDir """ + # GIVEN: _get_frozen_path, abspath, split and sys are mocked out with patch('openlp.core.utils.applocation._get_frozen_path') as mocked_get_frozen_path, \ patch('openlp.core.utils.applocation.os.path.abspath') as mocked_abspath, \ patch('openlp.core.utils.applocation.os.path.split') as mocked_split, \ @@ -142,6 +170,5 @@ class TestAppLocation(TestCase): # WHEN: We call AppLocation.get_directory directory = AppLocation.get_directory(AppLocation.PluginsDir) - # THEN: - assert directory == 'plugins/dir', 'Directory should be "plugins/dir"' - + # THEN: The correct directory should be returned + self.assertEqual('plugins/dir', directory, 'Directory should be "plugins/dir"') diff --git a/tests/functional/openlp_core_utils/test_utils.py b/tests/functional/openlp_core_utils/test_utils.py index 6eacb2e48..03f0f3654 100644 --- a/tests/functional/openlp_core_utils/test_utils.py +++ b/tests/functional/openlp_core_utils/test_utils.py @@ -1,25 +1,52 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ Functional tests to test the AppLocation class and related methods. """ from unittest import TestCase -from mock import patch - from openlp.core.utils import clean_filename, get_filesystem_encoding, _get_frozen_path, get_locale_key, \ get_natural_key, split_filename +from tests.functional import patch class TestUtils(TestCase): """ A test suite to test out various methods around the AppLocation class. """ - def get_filesystem_encoding_test(self): + def get_filesystem_encoding_sys_function_not_called_test(self): """ - Test the get_filesystem_encoding() function + Test the get_filesystem_encoding() function does not call the sys.getdefaultencoding() function """ + # GIVEN: sys.getfilesystemencoding returns "cp1252" with patch('openlp.core.utils.sys.getfilesystemencoding') as mocked_getfilesystemencoding, \ patch('openlp.core.utils.sys.getdefaultencoding') as mocked_getdefaultencoding: - # GIVEN: sys.getfilesystemencoding returns "cp1252" mocked_getfilesystemencoding.return_value = 'cp1252' # WHEN: get_filesystem_encoding() is called @@ -27,10 +54,16 @@ class TestUtils(TestCase): # THEN: getdefaultencoding should have been called mocked_getfilesystemencoding.assert_called_with() - assert not mocked_getdefaultencoding.called - assert result == 'cp1252', 'The result should be "cp1252"' + self.assertEqual(0, mocked_getdefaultencoding.called, 'getdefaultencoding should not have been called') + self.assertEqual('cp1252', result, 'The result should be "cp1252"') - # GIVEN: sys.getfilesystemencoding returns None and sys.getdefaultencoding returns "utf-8" + def get_filesystem_encoding_sys_function_is_called_test(self): + """ + Test the get_filesystem_encoding() function calls the sys.getdefaultencoding() function + """ + # GIVEN: sys.getfilesystemencoding returns None and sys.getdefaultencoding returns "utf-8" + with patch('openlp.core.utils.sys.getfilesystemencoding') as mocked_getfilesystemencoding, \ + patch('openlp.core.utils.sys.getdefaultencoding') as mocked_getdefaultencoding: mocked_getfilesystemencoding.return_value = None mocked_getdefaultencoding.return_value = 'utf-8' @@ -40,23 +73,35 @@ class TestUtils(TestCase): # THEN: getdefaultencoding should have been called mocked_getfilesystemencoding.assert_called_with() mocked_getdefaultencoding.assert_called_with() - assert result == 'utf-8', 'The result should be "utf-8"' + self.assertEqual('utf-8', result, 'The result should be "utf-8"') - def get_frozen_path_test(self): + def get_frozen_path_in_unfrozen_app_test(self): """ - Test the _get_frozen_path() function + Test the _get_frozen_path() function when the application is not frozen (compiled by PyInstaller) """ with patch('openlp.core.utils.sys') as mocked_sys: # GIVEN: The sys module "without" a "frozen" attribute mocked_sys.frozen = None + # WHEN: We call _get_frozen_path() with two parameters + frozen_path = _get_frozen_path('frozen', 'not frozen') + # THEN: The non-frozen parameter is returned - assert _get_frozen_path('frozen', 'not frozen') == 'not frozen', 'Should return "not frozen"' + self.assertEqual('not frozen', frozen_path, '_get_frozen_path should return "not frozen"') + + def get_frozen_path_in_frozen_app_test(self): + """ + Test the _get_frozen_path() function when the application is frozen (compiled by PyInstaller) + """ + with patch('openlp.core.utils.sys') as mocked_sys: # GIVEN: The sys module *with* a "frozen" attribute mocked_sys.frozen = 1 + # WHEN: We call _get_frozen_path() with two parameters + frozen_path = _get_frozen_path('frozen', 'not frozen') + # THEN: The frozen parameter is returned - assert _get_frozen_path('frozen', 'not frozen') == 'frozen', 'Should return "frozen"' + self.assertEqual('frozen', frozen_path, 'Should return "frozen"') def split_filename_with_file_path_test(self): """ @@ -72,7 +117,7 @@ class TestUtils(TestCase): result = split_filename(file_path) # THEN: A tuple should be returned. - assert result == wanted_result, 'A tuple with the directory and file name should have been returned.' + self.assertEqual(wanted_result, result, 'A tuple with the dir and file name should have been returned') def split_filename_with_dir_path_test(self): """ @@ -88,8 +133,8 @@ class TestUtils(TestCase): result = split_filename(file_path) # THEN: A tuple should be returned. - assert result == wanted_result, \ - 'A two-entry tuple with the directory and file name (empty) should have been returned.' + self.assertEqual(wanted_result, result, + 'A two-entry tuple with the directory and file name (empty) should have been returned.') def clean_filename_test(self): """ @@ -103,7 +148,7 @@ class TestUtils(TestCase): result = clean_filename(invalid_name) # THEN: The file name should be cleaned. - assert result == wanted_name, 'The file name should not contain any special characters.' + self.assertEqual(wanted_name, result, 'The file name should not contain any special characters.') def get_locale_key_windows_test(self): """ @@ -116,13 +161,15 @@ class TestUtils(TestCase): mocked_get_language.return_value = 'de' mocked_os.name = 'nt' unsorted_list = ['Auszug', 'Aushang', '\u00C4u\u00DFerung'] + # WHEN: We sort the list and use get_locale_key() to generate the sorting keys + sorted_list = sorted(unsorted_list, key=get_locale_key) + # THEN: We get a properly sorted list - test_passes = sorted(unsorted_list, key=get_locale_key) == ['Aushang', '\u00C4u\u00DFerung', 'Auszug'] - assert test_passes, 'Strings should be sorted properly' + self.assertEqual(['Aushang', '\u00C4u\u00DFerung', 'Auszug'], sorted_list, + 'Strings should be sorted properly') def get_locale_key_linux_test(self): - """ Test the get_locale_key(string) function """ @@ -134,9 +181,11 @@ class TestUtils(TestCase): mocked_os.name = 'linux' unsorted_list = ['Auszug', 'Aushang', '\u00C4u\u00DFerung'] # WHEN: We sort the list and use get_locale_key() to generate the sorting keys + sorted_list = sorted(unsorted_list, key=get_locale_key) + # THEN: We get a properly sorted list - test_passes = sorted(unsorted_list, key=get_locale_key) == ['Aushang', '\u00C4u\u00DFerung', 'Auszug'] - assert test_passes, 'Strings should be sorted properly' + self.assertEqual(['Aushang', '\u00C4u\u00DFerung', 'Auszug'], sorted_list, + 'Strings should be sorted properly') def get_natural_key_test(self): """ @@ -146,7 +195,9 @@ class TestUtils(TestCase): # GIVEN: The language is English (a language, which sorts digits before letters) mocked_get_language.return_value = 'en' unsorted_list = ['item 10a', 'item 3b', '1st item'] + # WHEN: We sort the list and use get_natural_key() to generate the sorting keys + sorted_list = sorted(unsorted_list, key=get_natural_key) + # THEN: We get a properly sorted list - test_passes = sorted(unsorted_list, key=get_natural_key) == ['1st item', 'item 3b', 'item 10a'] - assert test_passes, 'Numbers should be sorted naturally' + self.assertEqual(['1st item', 'item 3b', 'item 10a'], sorted_list, 'Numbers should be sorted naturally') diff --git a/tests/functional/openlp_plugins/bibles/test_lib.py b/tests/functional/openlp_plugins/bibles/test_lib.py index 942b4deee..11044475b 100644 --- a/tests/functional/openlp_plugins/bibles/test_lib.py +++ b/tests/functional/openlp_plugins/bibles/test_lib.py @@ -1,3 +1,31 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ This module contains tests for the lib submodule of the Bibles plugin. """ diff --git a/tests/functional/openlp_plugins/bibles/test_versereferencelist.py b/tests/functional/openlp_plugins/bibles/test_versereferencelist.py index 372264a76..0c7ccd01e 100644 --- a/tests/functional/openlp_plugins/bibles/test_versereferencelist.py +++ b/tests/functional/openlp_plugins/bibles/test_versereferencelist.py @@ -1,15 +1,43 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ This module contains tests for the versereferencelist submodule of the Bibles plugin. """ from unittest import TestCase + from openlp.plugins.bibles.lib.versereferencelist import VerseReferenceList + class TestVerseReferenceList(TestCase): - def setUp(self): - """ - Initializes all we need - """ - + """ + Test the VerseReferenceList class + """ def add_first_verse_test(self): """ Test the addition of a verse to the empty list @@ -20,12 +48,12 @@ class TestVerseReferenceList(TestCase): chapter = 1 verse = 1 version = 'testVersion' - copyright = 'testCopyright' + copyright_ = 'testCopyright' permission = 'testPermision' - + # WHEN: We add it to the verse list - reference_list.add(book, chapter, verse, version, copyright, permission) - + reference_list.add(book, chapter, verse, version, copyright_, permission) + # THEN: The entries should be in the first entry of the list self.assertEqual(reference_list.current_index, 0, 'The current index should be 0') self.assertEqual(reference_list.verse_list[0]['book'], book, 'The book in first entry should be %s' % book) @@ -33,7 +61,7 @@ class TestVerseReferenceList(TestCase): self.assertEqual(reference_list.verse_list[0]['start'], verse, 'The start in first entry should be %u' % verse) self.assertEqual(reference_list.verse_list[0]['version'], version, 'The version in first entry should be %s' % version) self.assertEqual(reference_list.verse_list[0]['end'], verse, 'The end in first entry should be %u' % verse) - + def add_next_verse_test(self): """ Test the addition of the following verse @@ -44,14 +72,14 @@ class TestVerseReferenceList(TestCase): verse = 1 next_verse = 2 version = 'testVersion' - copyright = 'testCopyright' + copyright_ = 'testCopyright' permission = 'testPermision' reference_list = VerseReferenceList() - reference_list.add(book, chapter, verse, version, copyright, permission) + reference_list.add(book, chapter, verse, version, copyright_, permission) # WHEN: We add the following verse to the verse list - reference_list.add(book, chapter, next_verse, version, copyright, permission) - + reference_list.add(book, chapter, next_verse, version, copyright_, permission) + # THEN: The current index should be 0 and the end pointer of the entry should be '2' self.assertEqual(reference_list.current_index, 0, 'The current index should be 0') self.assertEqual(reference_list.verse_list[0]['end'], next_verse, 'The end in first entry should be %u' % next_verse) @@ -64,19 +92,18 @@ class TestVerseReferenceList(TestCase): book = 'testBook' chapter = 1 verse = 1 - next_verse = 2 another_book = 'testBook2' another_chapter = 2 another_verse = 5 version = 'testVersion' - copyright = 'testCopyright' + copyright_ = 'testCopyright' permission = 'testPermision' reference_list = VerseReferenceList() - reference_list.add(book, chapter, verse, version, copyright, permission) + reference_list.add(book, chapter, verse, version, copyright_, permission) # WHEN: We add a verse of another book to the verse list - reference_list.add(another_book, another_chapter, another_verse, version, copyright, permission) - + reference_list.add(another_book, another_chapter, another_verse, version, copyright_, permission) + # THEN: the current index should be 1 self.assertEqual(reference_list.current_index, 1, 'The current index should be 1') @@ -87,17 +114,18 @@ class TestVerseReferenceList(TestCase): # GIVEN: version, copyright and permission reference_list = VerseReferenceList() version = 'testVersion' - copyright = 'testCopyright' + copyright_ = 'testCopyright' permission = 'testPermision' # WHEN: a not existing version will be added - reference_list.add_version(version, copyright, permission) - + reference_list.add_version(version, copyright_, permission) + # THEN: the data will be appended to the list self.assertEqual(len(reference_list.version_list), 1, 'The version data should be appended') - self.assertEqual(reference_list.version_list[0], {'version': version, 'copyright': copyright, 'permission': permission}, - 'The version data should be appended') - + self.assertEqual(reference_list.version_list[0], + {'version': version, 'copyright': copyright_, 'permission': permission}, + 'The version data should be appended') + def add_existing_version_test(self): """ Test the addition of an existing version to the list @@ -105,12 +133,12 @@ class TestVerseReferenceList(TestCase): # GIVEN: version, copyright and permission, added to the version list reference_list = VerseReferenceList() version = 'testVersion' - copyright = 'testCopyright' + copyright_ = 'testCopyright' permission = 'testPermision' - reference_list.add_version(version, copyright, permission) - + reference_list.add_version(version, copyright_, permission) + # WHEN: an existing version will be added - reference_list.add_version(version, copyright, permission) - + reference_list.add_version(version, copyright_, permission) + # THEN: the data will not be appended to the list self.assertEqual(len(reference_list.version_list), 1, 'The version data should not be appended') diff --git a/tests/functional/openlp_plugins/images/test_lib.py b/tests/functional/openlp_plugins/images/test_lib.py index 4090d4e9e..50fd258d2 100644 --- a/tests/functional/openlp_plugins/images/test_lib.py +++ b/tests/functional/openlp_plugins/images/test_lib.py @@ -1,16 +1,40 @@ # -*- coding: utf-8 -*- # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ This module contains tests for the lib submodule of the Images plugin. """ - from unittest import TestCase -from mock import MagicMock, patch - from openlp.core.lib import Registry from openlp.plugins.images.lib.db import ImageFilenames, ImageGroups from openlp.plugins.images.lib.mediaitem import ImageMediaItem +from tests.functional import MagicMock, patch class TestImageMediaItem(TestCase): @@ -36,7 +60,7 @@ class TestImageMediaItem(TestCase): """ # GIVEN: An empty image_list image_list = [] - with patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list') as mocked_load_full_list: + with patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list'): self.media_item.manager = MagicMock() # WHEN: We run save_new_images_list with the empty list @@ -50,8 +74,8 @@ class TestImageMediaItem(TestCase): """ Test that the save_new_images_list() calls load_full_list() when reload_list is set to True """ - # GIVEN: A list with 1 image - image_list = [ 'test_image.jpg' ] + # GIVEN: A list with 1 image and a mocked out manager + image_list = ['test_image.jpg'] with patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list') as mocked_load_full_list: ImageFilenames.filename = '' self.media_item.manager = MagicMock() @@ -69,8 +93,8 @@ class TestImageMediaItem(TestCase): """ Test that the save_new_images_list() doesn't call load_full_list() when reload_list is set to False """ - # GIVEN: A list with 1 image - image_list = [ 'test_image.jpg' ] + # GIVEN: A list with 1 image and a mocked out manager + image_list = ['test_image.jpg'] with patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.load_full_list') as mocked_load_full_list: self.media_item.manager = MagicMock() @@ -126,31 +150,6 @@ class TestImageMediaItem(TestCase): self.media_item.reset_action.setVisible.assert_called_with(False) self.media_item.live_controller.display.reset_image.assert_called_with() - def _recursively_delete_group_side_effect(*args, **kwargs): - """ - Side effect method that creates custom retun values for the recursively_delete_group method - """ - if args[1] == ImageFilenames and args[2]: - # Create some fake objects that should be removed - returned_object1 = ImageFilenames() - returned_object1.id = 1 - returned_object1.filename = '/tmp/test_file_1.jpg' - returned_object2 = ImageFilenames() - returned_object2.id = 2 - returned_object2.filename = '/tmp/test_file_2.jpg' - returned_object3 = ImageFilenames() - returned_object3.id = 3 - returned_object3.filename = '/tmp/test_file_3.jpg' - return [returned_object1, returned_object2, returned_object3] - if args[1] == ImageGroups and args[2]: - # Change the parent_id that is matched so we don't get into an endless loop - ImageGroups.parent_id = 0 - # Create a fake group that will be used in the next run - returned_object1 = ImageGroups() - returned_object1.id = 1 - return [returned_object1] - return [] - def recursively_delete_group_test(self): """ Test that recursively_delete_group() works @@ -176,3 +175,28 @@ class TestImageMediaItem(TestCase): # CLEANUP: Remove added attribute from ImageFilenames and ImageGroups delattr(ImageFilenames, 'group_id') delattr(ImageGroups, 'parent_id') + + def _recursively_delete_group_side_effect(*args, **kwargs): + """ + Side effect method that creates custom return values for the recursively_delete_group method + """ + if args[1] == ImageFilenames and args[2]: + # Create some fake objects that should be removed + returned_object1 = ImageFilenames() + returned_object1.id = 1 + returned_object1.filename = '/tmp/test_file_1.jpg' + returned_object2 = ImageFilenames() + returned_object2.id = 2 + returned_object2.filename = '/tmp/test_file_2.jpg' + returned_object3 = ImageFilenames() + returned_object3.id = 3 + returned_object3.filename = '/tmp/test_file_3.jpg' + return [returned_object1, returned_object2, returned_object3] + if args[1] == ImageGroups and args[2]: + # Change the parent_id that is matched so we don't get into an endless loop + ImageGroups.parent_id = 0 + # Create a fake group that will be used in the next run + returned_object1 = ImageGroups() + returned_object1.id = 1 + return [returned_object1] + return [] diff --git a/tests/functional/openlp_plugins/presentations/test_mediaitem.py b/tests/functional/openlp_plugins/presentations/test_mediaitem.py index 95fea2bdb..49b47252c 100644 --- a/tests/functional/openlp_plugins/presentations/test_mediaitem.py +++ b/tests/functional/openlp_plugins/presentations/test_mediaitem.py @@ -1,17 +1,41 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ This module contains tests for the lib submodule of the Presentations plugin. """ -import os -from tempfile import mkstemp from unittest import TestCase -from mock import patch, MagicMock - from PyQt4 import QtGui from openlp.core.lib import Registry - from openlp.plugins.presentations.lib.mediaitem import PresentationMediaItem +from tests.functional import patch, MagicMock class TestMediaItem(TestCase): @@ -68,4 +92,3 @@ class TestMediaItem(TestCase): # THEN: The file mask should be generated. assert self.media_item.on_new_file_masks == 'Presentations (*.odp *.ppt )', \ 'The file mask should contain the odp and ppt extensions' - diff --git a/tests/functional/openlp_plugins/remotes/test_remotetab.py b/tests/functional/openlp_plugins/remotes/test_remotetab.py index e683699cd..86652ffa4 100644 --- a/tests/functional/openlp_plugins/remotes/test_remotetab.py +++ b/tests/functional/openlp_plugins/remotes/test_remotetab.py @@ -1,17 +1,44 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ This module contains tests for the lib submodule of the Remotes plugin. """ import os import re - from unittest import TestCase from tempfile import mkstemp -from mock import patch + +from PyQt4 import QtGui from openlp.core.lib import Settings from openlp.plugins.remotes.lib.remotetab import RemoteTab - -from PyQt4 import QtGui +from tests.functional import patch __default_settings__ = { 'remotes/twelve hour': True, @@ -23,9 +50,7 @@ __default_settings__ = { 'remotes/authentication enabled': False, 'remotes/ip address': '0.0.0.0' } - ZERO_URL = '0.0.0.0' - TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources')) @@ -60,7 +85,8 @@ class TestRemoteTab(TestCase): # WHEN: the default ip address is given ip_address = self.form.get_ip_address(ZERO_URL) # THEN: the default ip address will be returned - self.assertTrue(re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), 'The return value should be a valid ip address') + self.assertTrue(re.match('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', ip_address), + 'The return value should be a valid ip address') def get_ip_address_with_ip_test(self): """ @@ -80,9 +106,9 @@ class TestRemoteTab(TestCase): """ # GIVEN: A mocked location with patch('openlp.core.utils.applocation.Settings') as mocked_class, \ - patch('openlp.core.utils.AppLocation.get_directory') as mocked_get_directory, \ - patch('openlp.core.utils.applocation.check_directory_exists') as mocked_check_directory_exists, \ - patch('openlp.core.utils.applocation.os') as mocked_os: + patch('openlp.core.utils.AppLocation.get_directory') as mocked_get_directory, \ + patch('openlp.core.utils.applocation.check_directory_exists') as mocked_check_directory_exists, \ + patch('openlp.core.utils.applocation.os') as mocked_os: # GIVEN: A mocked out Settings class and a mocked out AppLocation.get_directory() mocked_settings = mocked_class.return_value mocked_settings.contains.return_value = False @@ -96,7 +122,7 @@ class TestRemoteTab(TestCase): # THEN: the following screen values should be set self.assertEqual(self.form.address_edit.text(), ZERO_URL, 'The default URL should be set on the screen') self.assertEqual(self.form.https_settings_group_box.isEnabled(), False, - 'The Https box should not be enabled') + 'The Https box should not be enabled') self.assertEqual(self.form.https_settings_group_box.isChecked(), False, 'The Https checked box should note be Checked') self.assertEqual(self.form.user_login_group_box.isChecked(), False, @@ -108,9 +134,9 @@ class TestRemoteTab(TestCase): """ # GIVEN: A mocked location with patch('openlp.core.utils.applocation.Settings') as mocked_class, \ - patch('openlp.core.utils.AppLocation.get_directory') as mocked_get_directory, \ - patch('openlp.core.utils.applocation.check_directory_exists') as mocked_check_directory_exists, \ - patch('openlp.core.utils.applocation.os') as mocked_os: + patch('openlp.core.utils.AppLocation.get_directory') as mocked_get_directory, \ + patch('openlp.core.utils.applocation.check_directory_exists') as mocked_check_directory_exists, \ + patch('openlp.core.utils.applocation.os') as mocked_os: # GIVEN: A mocked out Settings class and a mocked out AppLocation.get_directory() mocked_settings = mocked_class.return_value mocked_settings.contains.return_value = False diff --git a/tests/functional/openlp_plugins/remotes/test_router.py b/tests/functional/openlp_plugins/remotes/test_router.py index af1849a65..8fed7d286 100644 --- a/tests/functional/openlp_plugins/remotes/test_router.py +++ b/tests/functional/openlp_plugins/remotes/test_router.py @@ -1,15 +1,43 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ This module contains tests for the lib submodule of the Remotes plugin. """ import os - from unittest import TestCase from tempfile import mkstemp -from mock import MagicMock + +from PyQt4 import QtGui from openlp.core.lib import Settings from openlp.plugins.remotes.lib.httpserver import HttpRouter, fetch_password, make_sha_hash -from PyQt4 import QtGui +from tests.functional import MagicMock __default_settings__ = { 'remotes/twelve hour': True, diff --git a/tests/functional/openlp_plugins/songs/test_ewimport.py b/tests/functional/openlp_plugins/songs/test_ewimport.py index 3f1735a3c..b0ab77850 100644 --- a/tests/functional/openlp_plugins/songs/test_ewimport.py +++ b/tests/functional/openlp_plugins/songs/test_ewimport.py @@ -1,13 +1,39 @@ # -*- coding: utf-8 -*- # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ This module contains tests for the EasyWorship song importer. """ import os from unittest import TestCase -from mock import patch, MagicMock + +from tests.functional import MagicMock, patch from openlp.plugins.songs.lib.ewimport import EasyWorshipSongImport, FieldDescEntry, FieldType @@ -43,6 +69,7 @@ SONG_TEST_DATA = [ 'Just to bow and receive a new blessing,\nIn the beautiful garden of prayer.', 'v3')], 'verse_order_list': []}] + class EasyWorshipSongImportLogger(EasyWorshipSongImport): """ This class logs changes in the title instance variable @@ -60,6 +87,7 @@ class EasyWorshipSongImportLogger(EasyWorshipSongImport): def title(self, title): self._title_assignment_list.append(title) + class TestFieldDesc: def __init__(self, name, field_type, size): self.name = name diff --git a/tests/functional/openlp_plugins/songs/test_foilpresenterimport.py b/tests/functional/openlp_plugins/songs/test_foilpresenterimport.py index 200f20aa6..05e07c4cf 100644 --- a/tests/functional/openlp_plugins/songs/test_foilpresenterimport.py +++ b/tests/functional/openlp_plugins/songs/test_foilpresenterimport.py @@ -32,9 +32,8 @@ This module contains tests for the SongShow Plus song importer. import os from unittest import TestCase -from mock import patch, MagicMock +from tests.functional import patch, MagicMock -from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib.foilpresenterimport import FoilPresenter TEST_PATH = os.path.abspath( @@ -192,4 +191,4 @@ class TestFoilPresenter(TestCase): # THEN: _process_lyrics should return None and the song_import logError method should have been called once self.assertIsNone(result) self.mocked_song_import.logError.assert_called_once_with('Element Text', 'Translated String') - self.process_lyrics_patcher.start() \ No newline at end of file + self.process_lyrics_patcher.start() diff --git a/tests/functional/openlp_plugins/songs/test_lib.py b/tests/functional/openlp_plugins/songs/test_lib.py index a9e64b5c9..327bf68e8 100644 --- a/tests/functional/openlp_plugins/songs/test_lib.py +++ b/tests/functional/openlp_plugins/songs/test_lib.py @@ -1,13 +1,39 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ This module contains tests for the lib submodule of the Songs plugin. """ - from unittest import TestCase -from mock import patch, MagicMock - from openlp.plugins.songs.lib import VerseType, clean_string, clean_title, strip_rtf from openlp.plugins.songs.lib.songcompare import songs_probably_equal, _remove_typos, _op_length +from tests.functional import patch, MagicMock class TestLib(TestCase): @@ -68,10 +94,10 @@ class TestLib(TestCase): # GIVEN: Two equal songs. self.song1.search_lyrics = self.full_lyrics self.song2.search_lyrics = self.full_lyrics - + # WHEN: We compare those songs for equality. result = songs_probably_equal(self.song1, self.song2) - + # THEN: The result should be True. assert result == True, 'The result should be True' @@ -82,10 +108,10 @@ class TestLib(TestCase): # GIVEN: A song and a short version of the same song. self.song1.search_lyrics = self.full_lyrics self.song2.search_lyrics = self.short_lyrics - + # WHEN: We compare those songs for equality. result = songs_probably_equal(self.song1, self.song2) - + # THEN: The result should be True. assert result == True, 'The result should be True' @@ -96,10 +122,10 @@ class TestLib(TestCase): # GIVEN: A song and the same song with lots of errors. self.song1.search_lyrics = self.full_lyrics self.song2.search_lyrics = self.error_lyrics - + # WHEN: We compare those songs for equality. result = songs_probably_equal(self.song1, self.song2) - + # THEN: The result should be True. assert result == True, 'The result should be True' @@ -110,10 +136,10 @@ class TestLib(TestCase): # GIVEN: Two different songs. self.song1.search_lyrics = self.full_lyrics self.song2.search_lyrics = self.different_lyrics - + # WHEN: We compare those songs for equality. result = songs_probably_equal(self.song1, self.song2) - + # THEN: The result should be False. assert result == False, 'The result should be False' diff --git a/tests/functional/openlp_plugins/songs/test_mediaitem.py b/tests/functional/openlp_plugins/songs/test_mediaitem.py index 47940911d..a519f7021 100644 --- a/tests/functional/openlp_plugins/songs/test_mediaitem.py +++ b/tests/functional/openlp_plugins/songs/test_mediaitem.py @@ -5,13 +5,11 @@ import os from tempfile import mkstemp from unittest import TestCase -from mock import patch, MagicMock - from PyQt4 import QtCore, QtGui from openlp.core.lib import Registry, ServiceItem, Settings - from openlp.plugins.songs.lib.mediaitem import SongMediaItem +from tests.functional import patch, MagicMock class TestMediaItem(TestCase): diff --git a/tests/functional/openlp_plugins/songs/test_songshowplusimport.py b/tests/functional/openlp_plugins/songs/test_songshowplusimport.py index 2276784e0..7fbb76917 100644 --- a/tests/functional/openlp_plugins/songs/test_songshowplusimport.py +++ b/tests/functional/openlp_plugins/songs/test_songshowplusimport.py @@ -1,13 +1,41 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ This module contains tests for the SongShow Plus song importer. """ import os from unittest import TestCase -from mock import patch, MagicMock from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib.songshowplusimport import SongShowPlusImport +from tests.functional import patch, MagicMock TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../resources/songshowplussongs')) SONG_TEST_DATA = {'Amazing Grace.sbsong': diff --git a/tests/functional/openlp_plugins/songs/test_worshipcenterproimport.py b/tests/functional/openlp_plugins/songs/test_worshipcenterproimport.py index 7c9b80056..836c5340b 100644 --- a/tests/functional/openlp_plugins/songs/test_worshipcenterproimport.py +++ b/tests/functional/openlp_plugins/songs/test_worshipcenterproimport.py @@ -1,15 +1,45 @@ # -*- coding: utf-8 -*- # vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### """ This module contains tests for the WorshipCenter Pro song importer. """ +import os +from unittest import TestCase, SkipTest + +if os.name != 'nt': + raise SkipTest('Not Windows, skipping test') -from unittest import TestCase -from mock import patch, MagicMock import pyodbc from openlp.plugins.songs.lib.worshipcenterproimport import WorshipCenterProImport +from tests.functional import patch, MagicMock + class TestRecord(object): """ @@ -23,6 +53,7 @@ class TestRecord(object): self.Field = field self.Value = value + class WorshipCenterProImportLogger(WorshipCenterProImport): """ This class logs changes in the title instance variable @@ -189,4 +220,4 @@ class TestWorshipCenterProSongImport(TestCase): for call in verse_calls: mocked_add_verse.assert_any_call(call) self.assertEqual(mocked_add_verse.call_count, add_verse_call_count, - 'Incorrect number of calls made to addVerse') \ No newline at end of file + 'Incorrect number of calls made to addVerse') From 188f3126693bd54a22c480557b18f66019cda584 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Fri, 20 Sep 2013 11:35:50 +0200 Subject: [PATCH 09/18] Ooops. --- tests/functional/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py index f311052f9..3f471cf61 100644 --- a/tests/functional/__init__.py +++ b/tests/functional/__init__.py @@ -13,7 +13,7 @@ from PyQt4 import QtGui if sys.version_info[1] >= 3: from unittest.mock import patch, MagicMock else: - from mock import patch, MagicMockv + from mock import patch, MagicMock # Only one QApplication can be created. Use QtGui.QApplication.instance() when you need to "create" a QApplication. application = QtGui.QApplication([]) From 1215494303c49b0cd760ca304412f80b8eaa23ed Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sun, 22 Sep 2013 23:11:03 +0200 Subject: [PATCH 10/18] Updated more tests --- .../openlp_core_lib/test_pluginmanager.py | 34 ++-- .../openlp_core_lib/test_registry.py | 6 +- .../functional/openlp_core_lib/test_screen.py | 5 +- .../openlp_core_lib/test_serviceitem.py | 148 +++++++++--------- .../openlp_core_lib/test_settings.py | 12 +- .../openlp_core_lib/test_uistrings.py | 2 +- tests/utils/__init__.py | 51 ++++++ 7 files changed, 156 insertions(+), 102 deletions(-) diff --git a/tests/functional/openlp_core_lib/test_pluginmanager.py b/tests/functional/openlp_core_lib/test_pluginmanager.py index a3114c9e5..afd06fc47 100644 --- a/tests/functional/openlp_core_lib/test_pluginmanager.py +++ b/tests/functional/openlp_core_lib/test_pluginmanager.py @@ -69,8 +69,8 @@ class TestPluginManager(TestCase): plugin_manager.hook_media_manager() # THEN: The create_media_manager_item() method should have been called - assert mocked_plugin.create_media_manager_item.call_count == 0, \ - 'The create_media_manager_item() method should not have been called.' + self.assertEqual(0, mocked_plugin.create_media_manager_item.call_count, + 'The create_media_manager_item() method should not have been called.') def hook_media_manager_with_active_plugin_test(self): """ @@ -102,8 +102,8 @@ class TestPluginManager(TestCase): plugin_manager.hook_settings_tabs() # THEN: The hook_settings_tabs() method should have been called - assert mocked_plugin.create_media_manager_item.call_count == 0, \ - 'The create_media_manager_item() method should not have been called.' + self.assertEqual(0, mocked_plugin.create_media_manager_item.call_count, + 'The create_media_manager_item() method should not have been called.') def hook_settings_tabs_with_disabled_plugin_and_mocked_form_test(self): """ @@ -122,10 +122,10 @@ class TestPluginManager(TestCase): plugin_manager.hook_settings_tabs() # THEN: The create_settings_tab() method should not have been called, but the plugins lists should be the same - assert mocked_plugin.create_settings_tab.call_count == 0, \ - 'The create_media_manager_item() method should not have been called.' + self.assertEqual(0, mocked_plugin.create_settings_tab.call_count, + 'The create_media_manager_item() method should not have been called.') self.assertEqual(mocked_settings_form.plugin_manager.plugins, plugin_manager.plugins, - 'The plugins on the settings form should be the same as the plugins in the plugin manager') + 'The plugins on the settings form should be the same as the plugins in the plugin manager') def hook_settings_tabs_with_active_plugin_and_mocked_form_test(self): """ @@ -144,10 +144,10 @@ class TestPluginManager(TestCase): plugin_manager.hook_settings_tabs() # THEN: The create_media_manager_item() method should have been called with the mocked settings form - assert mocked_plugin.create_settings_tab.call_count == 1, \ - 'The create_media_manager_item() method should have been called once.' - self.assertEqual(mocked_settings_form.plugin_manager.plugins, plugin_manager.plugins, - 'The plugins on the settings form should be the same as the plugins in the plugin manager') + self.assertEqual(1, mocked_plugin.create_settings_tab.call_count, + 'The create_media_manager_item() method should have been called once.') + self.assertEqual(plugin_manager.plugins, mocked_settings_form.plugin_manager.plugins, + 'The plugins on the settings form should be the same as the plugins in the plugin manager') def hook_settings_tabs_with_active_plugin_and_no_form_test(self): """ @@ -179,8 +179,8 @@ class TestPluginManager(TestCase): plugin_manager.hook_import_menu() # THEN: The create_media_manager_item() method should have been called - assert mocked_plugin.add_import_menu_item.call_count == 0, \ - 'The add_import_menu_item() method should not have been called.' + self.assertEqual(0, mocked_plugin.add_import_menu_item.call_count, + 'The add_import_menu_item() method should not have been called.') def hook_import_menu_with_active_plugin_test(self): """ @@ -212,8 +212,8 @@ class TestPluginManager(TestCase): plugin_manager.hook_export_menu() # THEN: The add_export_menu_Item() method should not have been called - assert mocked_plugin.add_export_menu_Item.call_count == 0, \ - 'The add_export_menu_Item() method should not have been called.' + self.assertEqual(0, mocked_plugin.add_export_menu_Item.call_count, + 'The add_export_menu_Item() method should not have been called.') def hook_export_menu_with_active_plugin_test(self): """ @@ -246,8 +246,8 @@ class TestPluginManager(TestCase): plugin_manager.hook_upgrade_plugin_settings(settings) # THEN: The upgrade_settings() method should not have been called - assert mocked_plugin.upgrade_settings.call_count == 0, \ - 'The upgrade_settings() method should not have been called.' + self.assertEqual(0, mocked_plugin.upgrade_settings.call_count, + 'The upgrade_settings() method should not have been called.') def hook_upgrade_plugin_settings_with_active_plugin_test(self): """ diff --git a/tests/functional/openlp_core_lib/test_registry.py b/tests/functional/openlp_core_lib/test_registry.py index 06307630b..e1e5e268b 100644 --- a/tests/functional/openlp_core_lib/test_registry.py +++ b/tests/functional/openlp_core_lib/test_registry.py @@ -59,14 +59,14 @@ class TestRegistry(TestCase): with self.assertRaises(KeyError) as context: Registry().register('test1', mock_1) self.assertEqual(context.exception.args[0], 'Duplicate service exception test1', - 'KeyError exception should have been thrown for duplicate service') + 'KeyError exception should have been thrown for duplicate service') # WHEN I try to get back a non existent component # THEN I will get an exception with self.assertRaises(KeyError) as context: temp = Registry().get('test2') self.assertEqual(context.exception.args[0], 'Service test2 not found in list', - 'KeyError exception should have been thrown for missing service') + 'KeyError exception should have been thrown for missing service') # WHEN I try to replace a component I should be allowed (testing only) Registry().remove('test1') @@ -74,7 +74,7 @@ class TestRegistry(TestCase): with self.assertRaises(KeyError) as context: temp = Registry().get('test1') self.assertEqual(context.exception.args[0], 'Service test1 not found in list', - 'KeyError exception should have been thrown for deleted service') + 'KeyError exception should have been thrown for deleted service') def registry_function_test(self): """ diff --git a/tests/functional/openlp_core_lib/test_screen.py b/tests/functional/openlp_core_lib/test_screen.py index b8b617d3c..c541fbb12 100644 --- a/tests/functional/openlp_core_lib/test_screen.py +++ b/tests/functional/openlp_core_lib/test_screen.py @@ -81,5 +81,6 @@ class TestScreenList(TestCase): # THEN: The screen should have been added and the screens should be identical new_screen_count = len(self.screens.screen_list) - assert old_screen_count + 1 == new_screen_count, 'The new_screens list should be bigger' - assert SCREEN == self.screens.screen_list.pop(), 'The 2nd screen should be identical to the first screen' + self.assertEqual(old_screen_count + 1, new_screen_count, 'The new_screens list should be bigger') + self.assertEqual(SCREEN, self.screens.screen_list.pop(), + 'The 2nd screen should be identical to the first screen') diff --git a/tests/functional/openlp_core_lib/test_serviceitem.py b/tests/functional/openlp_core_lib/test_serviceitem.py index ce176c82c..0e1b150ae 100644 --- a/tests/functional/openlp_core_lib/test_serviceitem.py +++ b/tests/functional/openlp_core_lib/test_serviceitem.py @@ -30,11 +30,11 @@ Package to test the openlp.core.lib package. """ import os -import json from unittest import TestCase from openlp.core.lib import ItemCapabilities, ServiceItem, Registry from tests.functional import MagicMock, patch +from tests.utils import assert_length, convert_file_service_item VERSE = 'The Lord said to {r}Noah{/r}: \n'\ 'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\n'\ @@ -59,7 +59,7 @@ class TestServiceItem(TestCase): Registry().register('renderer', mocked_renderer) Registry().register('image_manager', MagicMock()) - def serviceitem_basic_test(self): + def service_item_basic_test(self): """ Test the Service Item - basic test """ @@ -69,10 +69,10 @@ class TestServiceItem(TestCase): service_item = ServiceItem(None) # THEN: We should get back a valid service item - assert service_item.is_valid is True, 'The new service item should be valid' - assert service_item.missing_frames() is True, 'There should not be any frames in the service item' + self.assertTrue(service_item.is_valid, 'The new service item should be valid') + self.assertTrue(service_item.missing_frames(), 'There should not be any frames in the service item') - def serviceitem_load_custom_from_service_test(self): + def service_item_load_custom_from_service_test(self): """ Test the Service Item - adding a custom slide from a saved service """ @@ -80,24 +80,29 @@ class TestServiceItem(TestCase): service_item = ServiceItem(None) service_item.add_icon = MagicMock() - # WHEN: adding a custom from a saved Service - line = self.convert_file_service_item('serviceitem_custom_1.osj') + # WHEN: We add a custom from a saved service + line = convert_file_service_item('serviceitem_custom_1.osj') service_item.set_from_service(line) # THEN: We should get back a valid service item - assert service_item.is_valid is True, 'The new service item should be valid' - assert len(service_item._display_frames) == 0, 'The service item should have no display frames' - assert len(service_item.capabilities) == 5, 'There should be 5 default custom item capabilities' - service_item.render(True) - assert service_item.get_display_title() == 'Test Custom', 'The title should be "Test Custom"' - assert service_item.get_frames()[0]['text'] == VERSE[:-1], \ - 'The returned text matches the input, except the last line feed' - assert service_item.get_rendered_frame(1) == VERSE.split('\n', 1)[0], 'The first line has been returned' - assert service_item.get_frame_title(0) == 'Slide 1', '"Slide 1" has been returned as the title' - assert service_item.get_frame_title(1) == 'Slide 2', '"Slide 2" has been returned as the title' - assert service_item.get_frame_title(2) == '', 'Blank has been returned as the title of slide 3' + self.assertTrue(service_item.is_valid, 'The new service item should be valid') + assert_length(0, service_item._display_frames, 'The service item should have no display frames') + assert_length(5, service_item.capabilities, 'There should be 5 default custom item capabilities') - def serviceitem_load_image_from_service_test(self): + # WHEN: We render the frames of the service item + service_item.render(True) + + # THEN: The frames should also be valid + self.assertEqual('Test Custom', service_item.get_display_title(), 'The title should be "Test Custom"') + self.assertEqual(VERSE[:-1], service_item.get_frames()[0]['text'], + 'The returned text matches the input, except the last line feed') + self.assertEqual(VERSE.split('\n', 1)[0], service_item.get_rendered_frame(1), + 'The first line has been returned') + self.assertEqual('Slide 1', service_item.get_frame_title(0), '"Slide 1" has been returned as the title') + self.assertEqual('Slide 2', service_item.get_frame_title(1), '"Slide 2" has been returned as the title') + self.assertEqual('', service_item.get_frame_title(2), 'Blank has been returned as the title of slide 3') + + def service_item_load_image_from_service_test(self): """ Test the Service Item - adding an image from a saved service """ @@ -110,29 +115,34 @@ class TestServiceItem(TestCase): service_item.add_icon = MagicMock() # WHEN: adding an image from a saved Service and mocked exists - line = self.convert_file_service_item('serviceitem_image_1.osj') + line = convert_file_service_item(TEST_PATH, 'serviceitem_image_1.osj') with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists: mocked_exists.return_value = True service_item.set_from_service(line, TEST_PATH) # THEN: We should get back a valid service item - assert service_item.is_valid is True, 'The new service item should be valid' - assert service_item.get_rendered_frame(0) == test_file, 'The first frame should match the path to the image' - assert service_item.get_frames()[0] == frame_array, 'The return should match frame array1' - assert service_item.get_frame_path(0) == test_file, 'The frame path should match the full path to the image' - assert service_item.get_frame_title(0) == image_name, 'The frame title should match the image name' - assert service_item.get_display_title() == image_name, 'The display title should match the first image name' - assert service_item.is_image() is True, 'This service item should be of an "image" type' - assert service_item.is_capable(ItemCapabilities.CanMaintain) is True, \ - 'This service item should be able to be Maintained' - assert service_item.is_capable(ItemCapabilities.CanPreview) is True, \ - 'This service item should be able to be be Previewed' - assert service_item.is_capable(ItemCapabilities.CanLoop) is True, \ - 'This service item should be able to be run in a can be made to Loop' - assert service_item.is_capable(ItemCapabilities.CanAppend) is True, \ - 'This service item should be able to have new items added to it' + self.assertTrue(service_item.is_valid, 'The new service item should be valid') + self.assertEqual(test_file, service_item.get_rendered_frame(0), + 'The first frame should match the path to the image') + self.assertEqual(frame_array, service_item.get_frames()[0], + 'The return should match frame array1') + self.assertEqual(test_file, service_item.get_frame_path(0), + 'The frame path should match the full path to the image') + self.assertEqual(image_name, service_item.get_frame_title(0), + 'The frame title should match the image name') + self.assertEqual(image_name, service_item.get_display_title(), + 'The display title should match the first image name') + self.assertTrue(service_item.is_image(), 'This service item should be of an "image" type') + self.assertTrue(service_item.is_capable(ItemCapabilities.CanMaintain), + 'This service item should be able to be Maintained') + self.assertTrue(service_item.is_capable(ItemCapabilities.CanPreview), + 'This service item should be able to be be Previewed') + self.assertTrue(service_item.is_capable(ItemCapabilities.CanLoop), + 'This service item should be able to be run in a can be made to Loop') + self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend), + 'This service item should be able to have new items added to it') - def serviceitem_load_image_from_local_service_test(self): + def service_item_load_image_from_local_service_test(self): """ Test the Service Item - adding an image from a saved local service """ @@ -151,50 +161,42 @@ class TestServiceItem(TestCase): service_item2.add_icon = MagicMock() # WHEN: adding an image from a saved Service and mocked exists - line = self.convert_file_service_item('serviceitem_image_2.osj') - line2 = self.convert_file_service_item('serviceitem_image_2.osj', 1) + line = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj') + line2 = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj', 1) with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists: mocked_exists.return_value = True service_item2.set_from_service(line2) service_item.set_from_service(line) - # THEN: We should get back a valid service item # This test is copied from service_item.py, but is changed since to conform to # new layout of service item. The layout use in serviceitem_image_2.osd is actually invalid now. - assert service_item.is_valid is True, 'The first service item should be valid' - assert service_item2.is_valid is True, 'The second service item should be valid' - assert service_item.get_rendered_frame(0) == test_file1, 'The first frame should match the path to the image' - assert service_item2.get_rendered_frame(0) == test_file2, 'The Second frame should match the path to the image' - assert service_item.get_frames()[0] == frame_array1, 'The return should match the frame array1' - assert service_item2.get_frames()[0] == frame_array2, 'The return should match the frame array2' - assert service_item.get_frame_path(0) == test_file1, 'The frame path should match the full path to the image' - assert service_item2.get_frame_path(0) == test_file2, 'The frame path should match the full path to the image' - assert service_item.get_frame_title(0) == image_name1, 'The 1st frame title should match the image name' - assert service_item2.get_frame_title(0) == image_name2, 'The 2nd frame title should match the image name' - assert service_item.title.lower() == service_item.name, \ - 'The plugin name should match the display title, as there are > 1 Images' - assert service_item.is_image() is True, 'This service item should be of an "image" type' - assert service_item.is_capable(ItemCapabilities.CanMaintain) is True, \ - 'This service item should be able to be Maintained' - assert service_item.is_capable(ItemCapabilities.CanPreview) is True, \ - 'This service item should be able to be be Previewed' - assert service_item.is_capable(ItemCapabilities.CanLoop) is True, \ - 'This service item should be able to be run in a can be made to Loop' - assert service_item.is_capable(ItemCapabilities.CanAppend) is True, \ - 'This service item should be able to have new items added to it' - - def convert_file_service_item(self, name, row=0): - service_file = os.path.join(TEST_PATH, name) - try: - open_file = open(service_file, 'r') - items = json.load(open_file) - first_line = items[row] - except IOError: - first_line = '' - finally: - open_file.close() - return first_line - + self.assertTrue(service_item.is_valid, 'The first service item should be valid') + self.assertTrue(service_item2.is_valid, 'The second service item should be valid') + self.assertEqual(test_file1, service_item.get_rendered_frame(0), + 'The first frame should match the path to the image') + self.assertEqual(test_file2, service_item2.get_rendered_frame(0), + 'The Second frame should match the path to the image') + self.assertEqual(frame_array1, service_item.get_frames()[0], 'The return should match the frame array1') + self.assertEqual(frame_array2, service_item2.get_frames()[0], 'The return should match the frame array2') + self.assertEqual(test_file1, service_item.get_frame_path(0), + 'The frame path should match the full path to the image') + self.assertEqual(test_file2, service_item2.get_frame_path(0), + 'The frame path should match the full path to the image') + self.assertEqual(image_name1, service_item.get_frame_title(0), + 'The 1st frame title should match the image name') + self.assertEqual(image_name2, service_item2.get_frame_title(0), + 'The 2nd frame title should match the image name') + self.assertEqual(service_item.name, service_item.title.lower(), + 'The plugin name should match the display title, as there are > 1 Images') + self.assertTrue(service_item.is_image(), 'This service item should be of an "image" type') + self.assertTrue(service_item.is_capable(ItemCapabilities.CanMaintain), + 'This service item should be able to be Maintained') + self.assertTrue(service_item.is_capable(ItemCapabilities.CanPreview), + 'This service item should be able to be be Previewed') + self.assertTrue(service_item.is_capable(ItemCapabilities.CanLoop), + 'This service item should be able to be run in a can be made to Loop') + self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend), + 'This service item should be able to have new items added to it') diff --git a/tests/functional/openlp_core_lib/test_settings.py b/tests/functional/openlp_core_lib/test_settings.py index e9ee02443..25647a6e1 100644 --- a/tests/functional/openlp_core_lib/test_settings.py +++ b/tests/functional/openlp_core_lib/test_settings.py @@ -68,13 +68,13 @@ class TestSettings(TestCase): default_value = Settings().value('core/has run wizard') # THEN the default value is returned - assert default_value is False, 'The default value should be False' + self.assertFalse(default_value, 'The default value should be False') # WHEN a new value is saved into config Settings().setValue('core/has run wizard', True) # THEN the new value is returned when re-read - assert Settings().value('core/has run wizard') is True, 'The saved value should have been returned' + self.assertTrue(Settings().value('core/has run wizard'), 'The saved value should have been returned') def settings_override_test(self): """ @@ -90,13 +90,13 @@ class TestSettings(TestCase): extend = Settings().value('test/extend') # THEN the default value is returned - assert extend == 'very wide', 'The default value of "very wide" should be returned' + self.assertEqual('very wide', extend, 'The default value of "very wide" should be returned') # WHEN a new value is saved into config Settings().setValue('test/extend', 'very short') # THEN the new value is returned when re-read - assert Settings().value('test/extend') == 'very short', 'The saved value should be returned' + self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned') def settings_override_with_group_test(self): """ @@ -114,10 +114,10 @@ class TestSettings(TestCase): extend = settings.value('extend') # THEN the default value is returned - assert extend == 'very wide', 'The default value defined should be returned' + self.assertEqual('very wide', extend, 'The default value defined should be returned') # WHEN a new value is saved into config Settings().setValue('test/extend', 'very short') # THEN the new value is returned when re-read - assert Settings().value('test/extend') == 'very short', 'The saved value should be returned' + self.assertEqual('very short', Settings().value('test/extend'), 'The saved value should be returned') diff --git a/tests/functional/openlp_core_lib/test_uistrings.py b/tests/functional/openlp_core_lib/test_uistrings.py index a69905680..fbfe07c78 100644 --- a/tests/functional/openlp_core_lib/test_uistrings.py +++ b/tests/functional/openlp_core_lib/test_uistrings.py @@ -45,6 +45,6 @@ class TestUiStrings(TestCase): second_instance = UiStrings() # THEN: Check if the instances are the same. - assert first_instance is second_instance, "They should be the same instance!" + self.assertIs(first_instance, second_instance, 'Two UiStrings objects should be the same instance') diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py index e69de29bb..d983f1b6e 100644 --- a/tests/utils/__init__.py +++ b/tests/utils/__init__.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 + +############################################################################### +# OpenLP - Open Source Lyrics Projection # +# --------------------------------------------------------------------------- # +# Copyright (c) 2008-2013 Raoul Snyman # +# Portions copyright (c) 2008-2013 Tim Bentley, Gerald Britton, Jonathan # +# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub, # +# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer. # +# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru, # +# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith, # +# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock, # +# Frode Woldsund, Martin Zibricky, Patrick Zimmermann # +# --------------------------------------------------------------------------- # +# This program is free software; you can redistribute it and/or modify it # +# under the terms of the GNU General Public License as published by the Free # +# Software Foundation; version 2 of the License. # +# # +# This program is distributed in the hope that it will be useful, but WITHOUT # +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # +# more details. # +# # +# You should have received a copy of the GNU General Public License along # +# with this program; if not, write to the Free Software Foundation, Inc., 59 # +# Temple Place, Suite 330, Boston, MA 02111-1307 USA # +############################################################################### +import os +import json + + +def assert_length(expected, iterable, msg=None): + if len(iterable) != expected: + if not msg: + msg = 'Expected length %s, got %s' % (expected, len(iterable)) + raise AssertionError(msg) + + +def convert_file_service_item(test_path, name, row=0): + service_file = os.path.join(test_path, name) + open_file = open(service_file, 'r') + try: + items = json.load(open_file) + first_line = items[row] + except IOError: + first_line = '' + finally: + open_file.close() + return first_line + From c05928c3d308e9829f7e063dddae7b21cf7c8669 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Fri, 27 Sep 2013 21:37:42 +0100 Subject: [PATCH 11/18] Fix main ui routes --- openlp/plugins/remotes/lib/httprouter.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 1b29bc416..6a2a71ab4 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -152,8 +152,8 @@ class HttpRouter(object): ('^/(main)$', {'function': self.serve_file, 'secure': False}), (r'^/files/(.*)$', {'function': self.serve_file, 'secure': False}), (r'^/api/poll$', {'function': self.poll, 'secure': False}), - (r'^/main/poll$', {'function': self.poll, 'secure': False}), - (r'^/main/image$', {'function': self.main_poll, 'secure': False}), + (r'^/main/poll$', {'function': self.main_poll, 'secure': False}), + (r'^/main/image$', {'function': self.main_image, 'secure': False}), (r'^/api/controller/(live|preview)/text$', {'function': self.controller_text, 'secure': False}), (r'^/api/controller/(live|preview)/(.*)$', {'function': self.controller, 'secure': True}), (r'^/api/service/list$', {'function': self.service_list, 'secure': False}), @@ -175,14 +175,6 @@ class HttpRouter(object): """ if self.path == '/favicon.ico': return - ########### - print(self.headers['content-type']) - if self.headers['content-type'] == 'application/json': - length = int(self.headers['content-length']) - postvars = parse_qs(self.rfile.read(length), keep_blank_values=1) - for var in postvars: - print(var.decode("utf-8")) - ############## if not hasattr(self, 'auth'): self.initialise() function, args = self.process_http_request(self.path) @@ -399,6 +391,7 @@ class HttpRouter(object): 'isSecure': Settings().value(self.settings_section + '/authentication enabled'), 'isAuthorised': self.authorised } + self.send_header('Content-type', 'application/json') return json.dumps({'results': result}).encode() def main_poll(self): @@ -408,6 +401,7 @@ class HttpRouter(object): result = { 'slide_count': self.live_controller.slide_count } + self.send_header('Content-type', 'application/json') return json.dumps({'results': result}).encode() def main_image(self): @@ -417,6 +411,7 @@ class HttpRouter(object): result = { 'slide_image': 'data:image/png;base64,' + str(image_to_byte(self.live_controller.slide_image)) } + self.send_header('Content-type', 'application/json') return json.dumps({'results': result}).encode() def display(self, action): @@ -428,6 +423,7 @@ class HttpRouter(object): This is the action, either ``hide`` or ``show``. """ self.live_controller.emit(QtCore.SIGNAL('slidecontroller_toggle_display'), action) + self.send_header('Content-type', 'application/json') return json.dumps({'results': {'success': True}}).encode() def alert(self): @@ -445,6 +441,7 @@ class HttpRouter(object): success = True else: success = False + self.send_header('Content-type', 'application/json') return json.dumps({'results': {'success': success}}).encode() def controller_text(self, var): @@ -472,6 +469,7 @@ class HttpRouter(object): json_data = {'results': {'slides': data}} if current_item: json_data['results']['item'] = self.live_controller.service_item.unique_identifier + self.send_header('Content-type', 'application/json') return json.dumps(json_data).encode() def controller(self, display_type, action): @@ -496,6 +494,7 @@ class HttpRouter(object): else: self.live_controller.emit(QtCore.SIGNAL(event)) json_data = {'results': {'success': True}} + self.send_header('Content-type', 'application/json') return json.dumps(json_data).encode() def service_list(self): @@ -505,6 +504,7 @@ class HttpRouter(object): ``action`` The action to perform. """ + self.send_header('Content-type', 'application/json') return json.dumps({'results': {'items': self._get_service_items()}}).encode() def service(self, action): @@ -523,6 +523,7 @@ class HttpRouter(object): self.service_manager.emit(QtCore.SIGNAL(event), data) else: Registry().execute(event) + self.send_header('Content-type', 'application/json') return json.dumps({'results': {'success': True}}).encode() def plugin_info(self, action): @@ -538,6 +539,7 @@ class HttpRouter(object): for plugin in self.plugin_manager.plugins: if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search: searches.append([plugin.name, str(plugin.text_strings[StringContent.Name]['plural'])]) + self.send_header('Content-type', 'application/json') return json.dumps({'results': {'items': searches}}).encode() def search(self, plugin_name): @@ -557,6 +559,7 @@ class HttpRouter(object): results = plugin.media_item.search(text, False) else: results = [] + self.send_header('Content-type', 'application/json') return json.dumps({'results': {'items': results}}).encode() def go_live(self, plugin_name): From 6533fb5bb000ce5276d983bc477b99f886707849 Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 28 Sep 2013 06:10:44 +0100 Subject: [PATCH 12/18] Fix mime types for JSON --- openlp/plugins/remotes/lib/httprouter.py | 30 +++++++++++++++--------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/openlp/plugins/remotes/lib/httprouter.py b/openlp/plugins/remotes/lib/httprouter.py index 6a2a71ab4..f12fbb290 100644 --- a/openlp/plugins/remotes/lib/httprouter.py +++ b/openlp/plugins/remotes/lib/httprouter.py @@ -244,6 +244,14 @@ class HttpRouter(object): self.send_header('Content-type', 'text/html') self.end_headers() + def do_json_header(self): + """ + Create a header for JSON messages + """ + self.send_response(200) + self.send_header('Content-type', 'application/json') + self.end_headers() + def do_http_error(self): """ Create a error http header. @@ -391,7 +399,7 @@ class HttpRouter(object): 'isSecure': Settings().value(self.settings_section + '/authentication enabled'), 'isAuthorised': self.authorised } - self.send_header('Content-type', 'application/json') + self.do_json_header() return json.dumps({'results': result}).encode() def main_poll(self): @@ -401,7 +409,7 @@ class HttpRouter(object): result = { 'slide_count': self.live_controller.slide_count } - self.send_header('Content-type', 'application/json') + self.do_json_header() return json.dumps({'results': result}).encode() def main_image(self): @@ -411,7 +419,7 @@ class HttpRouter(object): result = { 'slide_image': 'data:image/png;base64,' + str(image_to_byte(self.live_controller.slide_image)) } - self.send_header('Content-type', 'application/json') + self.do_json_header() return json.dumps({'results': result}).encode() def display(self, action): @@ -423,7 +431,7 @@ class HttpRouter(object): This is the action, either ``hide`` or ``show``. """ self.live_controller.emit(QtCore.SIGNAL('slidecontroller_toggle_display'), action) - self.send_header('Content-type', 'application/json') + self.do_json_header() return json.dumps({'results': {'success': True}}).encode() def alert(self): @@ -441,7 +449,7 @@ class HttpRouter(object): success = True else: success = False - self.send_header('Content-type', 'application/json') + self.do_json_header() return json.dumps({'results': {'success': success}}).encode() def controller_text(self, var): @@ -469,7 +477,7 @@ class HttpRouter(object): json_data = {'results': {'slides': data}} if current_item: json_data['results']['item'] = self.live_controller.service_item.unique_identifier - self.send_header('Content-type', 'application/json') + self.do_json_header() return json.dumps(json_data).encode() def controller(self, display_type, action): @@ -494,7 +502,7 @@ class HttpRouter(object): else: self.live_controller.emit(QtCore.SIGNAL(event)) json_data = {'results': {'success': True}} - self.send_header('Content-type', 'application/json') + self.do_json_header() return json.dumps(json_data).encode() def service_list(self): @@ -504,7 +512,7 @@ class HttpRouter(object): ``action`` The action to perform. """ - self.send_header('Content-type', 'application/json') + self.do_json_header() return json.dumps({'results': {'items': self._get_service_items()}}).encode() def service(self, action): @@ -523,7 +531,7 @@ class HttpRouter(object): self.service_manager.emit(QtCore.SIGNAL(event), data) else: Registry().execute(event) - self.send_header('Content-type', 'application/json') + self.do_json_header() return json.dumps({'results': {'success': True}}).encode() def plugin_info(self, action): @@ -539,7 +547,7 @@ class HttpRouter(object): for plugin in self.plugin_manager.plugins: if plugin.status == PluginStatus.Active and plugin.media_item and plugin.media_item.has_search: searches.append([plugin.name, str(plugin.text_strings[StringContent.Name]['plural'])]) - self.send_header('Content-type', 'application/json') + self.do_json_header() return json.dumps({'results': {'items': searches}}).encode() def search(self, plugin_name): @@ -559,7 +567,7 @@ class HttpRouter(object): results = plugin.media_item.search(text, False) else: results = [] - self.send_header('Content-type', 'application/json') + self.do_json_header() return json.dumps({'results': {'items': results}}).encode() def go_live(self, plugin_name): From a1b47f08adc5606f9718f03f08417308479ad73c Mon Sep 17 00:00:00 2001 From: Tim Bentley Date: Sat, 28 Sep 2013 21:43:00 +0100 Subject: [PATCH 13/18] fix ref --- openlp/plugins/remotes/lib/remotetab.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openlp/plugins/remotes/lib/remotetab.py b/openlp/plugins/remotes/lib/remotetab.py index c06d71ee9..17d368bd2 100644 --- a/openlp/plugins/remotes/lib/remotetab.py +++ b/openlp/plugins/remotes/lib/remotetab.py @@ -207,8 +207,8 @@ class RemoteTab(SettingsTab): https_url_temp = https_url + 'stage' self.stage_url.setText('%s' % (http_url_temp, http_url_temp)) self.stage_https_url.setText('%s' % (https_url_temp, https_url_temp)) - http_url_temp = http_url + 'live' - https_url_temp = https_url + 'live' + http_url_temp = http_url + 'main' + https_url_temp = https_url + 'main' self.live_url.setText('%s' % (http_url_temp, http_url_temp)) self.live_https_url.setText('%s' % (https_url_temp, https_url_temp)) From c30eedda089f022175522f084d6f7b36fd7771d5 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sun, 29 Sep 2013 23:21:47 +0200 Subject: [PATCH 14/18] Some more updates to some of the tests. --- tests/functional/openlp_core_lib/test_lib.py | 92 +++++++++++-------- .../openlp_core_lib/test_pluginmanager.py | 12 +-- 2 files changed, 61 insertions(+), 43 deletions(-) diff --git a/tests/functional/openlp_core_lib/test_lib.py b/tests/functional/openlp_core_lib/test_lib.py index 4b67cee2b..5e2bd187b 100644 --- a/tests/functional/openlp_core_lib/test_lib.py +++ b/tests/functional/openlp_core_lib/test_lib.py @@ -45,9 +45,9 @@ TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', class TestLib(TestCase): - def str_to_bool_with_bool_test(self): + def str_to_bool_with_bool_true_test(self): """ - Test the str_to_bool function with boolean input + Test the str_to_bool function with boolean input of True """ # GIVEN: A boolean value set to true true_boolean = True @@ -56,9 +56,13 @@ class TestLib(TestCase): true_result = str_to_bool(true_boolean) # THEN: We should get back a True bool - assert isinstance(true_result, bool), 'The result should be a boolean' - assert true_result is True, 'The result should be True' + self.assertIsInstance(true_result, bool, 'The result should be a boolean') + self.assertTrue(true_result, 'The result should be True') + def str_to_bool_with_bool_false_test(self): + """ + Test the str_to_bool function with boolean input of False + """ # GIVEN: A boolean value set to false false_boolean = False @@ -66,12 +70,12 @@ class TestLib(TestCase): false_result = str_to_bool(false_boolean) # THEN: We should get back a True bool - assert isinstance(false_result, bool), 'The result should be a boolean' - assert false_result is False, 'The result should be True' + self.assertIsinstance(false_result, bool, 'The result should be a boolean') + self.assertFalse(false_result, 'The result should be True') - def str_to_bool_with_invalid_test(self): + def str_to_bool_with_integer_test(self): """ - Test the str_to_bool function with a set of invalid inputs + Test the str_to_bool function with an integer input """ # GIVEN: An integer value int_string = 1 @@ -80,8 +84,12 @@ class TestLib(TestCase): int_result = str_to_bool(int_string) # THEN: we should get back a false - assert int_result is False, 'The result should be False' + self.assertFalse(int_result, 'The result should be False') + def str_to_bool_with_invalid_string_test(self): + """ + Test the str_to_bool function with an invalid string + """ # GIVEN: An string value with completely invalid input invalid_string = 'my feet are wet' @@ -89,11 +97,11 @@ class TestLib(TestCase): str_result = str_to_bool(invalid_string) # THEN: we should get back a false - assert str_result is False, 'The result should be False' + self.assertFalse(str_result, 'The result should be False') - def str_to_bool_with_false_values_test(self): + def str_to_bool_with_string_false_test(self): """ - Test the str_to_bool function with a set of false inputs + Test the str_to_bool function with a string saying "false" """ # GIVEN: A string set to "false" false_string = 'false' @@ -102,8 +110,12 @@ class TestLib(TestCase): false_result = str_to_bool(false_string) # THEN: we should get back a false - assert false_result is False, 'The result should be False' + self.assertFalse(false_result, 'The result should be False') + def str_to_bool_with_string_no_test(self): + """ + Test the str_to_bool function with a string saying "NO" + """ # GIVEN: An string set to "NO" no_string = 'NO' @@ -111,11 +123,11 @@ class TestLib(TestCase): str_result = str_to_bool(no_string) # THEN: we should get back a false - assert str_result is False, 'The result should be False' + self.assertFalse(str_result, 'The result should be False') - def str_to_bool_with_true_values_test(self): + def str_to_bool_with_true_string_value_test(self): """ - Test the str_to_bool function with a set of true inputs + Test the str_to_bool function with a string set to "True" """ # GIVEN: A string set to "True" true_string = 'True' @@ -124,8 +136,12 @@ class TestLib(TestCase): true_result = str_to_bool(true_string) # THEN: we should get back a true - assert true_result is True, 'The result should be True' + self.assertTrue(true_result, 'The result should be True') + def str_to_bool_with_yes_string_value_test(self): + """ + Test the str_to_bool function with a string set to "yes" + """ # GIVEN: An string set to "yes" yes_string = 'yes' @@ -133,7 +149,7 @@ class TestLib(TestCase): str_result = str_to_bool(yes_string) # THEN: we should get back a true - assert str_result is True, 'The result should be True' + self.assertTrue(str_result, 'The result should be True') def translate_test(self): """ @@ -152,7 +168,7 @@ class TestLib(TestCase): # THEN: the translated string should be returned, and the mocked function should have been called mocked_translate.assert_called_with(context, text, comment, encoding, n) - assert result == 'Translated string', 'The translated string should have been returned' + self.assertEqual('Translated string', result, 'The translated string should have been returned') def check_directory_exists_test(self): """ @@ -169,7 +185,7 @@ class TestLib(TestCase): # THEN: Only os.path.exists should have been called mocked_exists.assert_called_with(directory_to_check) - assert not mocked_makedirs.called, 'os.makedirs should not have been called' + self.assertIsNot(mocked_makedirs.called, 'os.makedirs should not have been called') # WHEN: os.path.exists returns False and we check the directory exists mocked_exists.return_value = False @@ -207,13 +223,14 @@ class TestLib(TestCase): # THEN: The result should be False mocked_isfile.assert_called_with(filename) - assert result is False, 'False should be returned if no file exists' + self.assertFalse(result, 'False should be returned if no file exists') def get_text_file_string_read_error_test(self): """ Test the get_text_file_string() method when a read error happens """ - with patch('openlp.core.lib.os.path.isfile') as mocked_isfile, patch('openlp.core.lib.open', create=True) as mocked_open: + with patch('openlp.core.lib.os.path.isfile') as mocked_isfile, \ + patch('openlp.core.lib.open', create=True) as mocked_open: # GIVEN: A mocked-out open() which raises an exception and isfile returns True filename = 'testfile.txt' mocked_isfile.return_value = True @@ -225,13 +242,13 @@ class TestLib(TestCase): # THEN: None should be returned mocked_isfile.assert_called_with(filename) mocked_open.assert_called_with(filename, 'r') - assert result is None, 'None should be returned if the file cannot be opened' + self.assertIsNone(result, 'None should be returned if the file cannot be opened') def get_text_file_string_decode_error_test(self): """ Test the get_text_file_string() method when the contents cannot be decoded """ - assert True, 'Impossible to test due to conflicts when mocking out the "open" function' + self.skipTest('Impossible to test due to conflicts when mocking out the "open" function') def build_icon_with_qicon_test(self): """ @@ -246,7 +263,7 @@ class TestLib(TestCase): result = build_icon(mocked_icon) # THEN: The result should be our mocked QIcon - assert result is mocked_icon, 'The result should be the mocked QIcon' + self.assertIs(mocked_icon, result, 'The result should be the mocked QIcon') def build_icon_with_resource_test(self): """ @@ -268,7 +285,7 @@ class TestLib(TestCase): MockedQPixmap.assert_called_with(resource_uri) # There really should be more assert statements here but due to type checking and things they all break. The # best we can do is to assert that we get back a MagicMock object. - assert isinstance(result, MagicMock), 'The result should be a MagicMock, because we mocked it out' + self.assertIsInstance(result, MagicMock, 'The result should be a MagicMock, because we mocked it out') def image_to_byte_test(self): """ @@ -293,7 +310,8 @@ class TestLib(TestCase): mocked_buffer.open.assert_called_with('writeonly') mocked_image.save.assert_called_with(mocked_buffer, "PNG") mocked_byte_array.toBase64.assert_called_with() - assert result == 'base64mock', 'The result should be the return value of the mocked out base64 method' + self.assertEqual('base64mock', result, + 'The result should be the return value of the mocked out base64 method') def create_thumb_with_size_test(self): """ @@ -312,16 +330,16 @@ class TestLib(TestCase): pass # Only continue when the thumb does not exist. - assert not os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists.' + self.assertFalse(os.path.exists(thumb_path), 'Test was not run, because the thumb already exists.') # WHEN: Create the thumb. icon = create_thumb(image_path, thumb_path, size=thumb_size) # THEN: Check if the thumb was created. - assert os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists.' - assert isinstance(icon, QtGui.QIcon), 'The icon should be a QIcon.' - assert not icon.isNull(), 'The icon should not be null.' - assert QtGui.QImageReader(thumb_path).size() == thumb_size, 'The thumb should have the given size.' + self.assertTrue(os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists') + self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon') + self.assertFalse(icon.isNull(), 'The icon should not be null') + self.assertEqual(thumb_size, QtGui.QImageReader(thumb_path).size(), 'The thumb should have the given size') # Remove the thumb so that the test actually tests if the thumb will be created. try: @@ -344,7 +362,7 @@ class TestLib(TestCase): # THEN: The selectedIndexes function should have been called and the result should be true mocked_list_widget.selectedIndexes.assert_called_with() - assert result, 'The result should be True' + self.assertTrue(result, 'The result should be True') def check_item_selected_false_test(self): """ @@ -352,7 +370,7 @@ class TestLib(TestCase): """ # GIVEN: A mocked out QtGui module and a list widget with selected indexes with patch('openlp.core.lib.QtGui') as MockedQtGui, \ - patch('openlp.core.lib.translate') as mocked_translate: + patch('openlp.core.lib.translate') as mocked_translate: mocked_translate.return_value = 'mocked translate' mocked_list_widget = MagicMock() mocked_list_widget.selectedIndexes.return_value = False @@ -365,7 +383,7 @@ class TestLib(TestCase): # THEN: The selectedIndexes function should have been called and the result should be true mocked_list_widget.selectedIndexes.assert_called_with() MockedQtGui.QMessageBox.information.assert_called_with('parent', 'mocked translate', 'message') - assert not result, 'The result should be False' + self.assertFalse(result, 'The result should be False') def clean_tags_test(self): """ @@ -387,7 +405,7 @@ class TestLib(TestCase): result_string = clean_tags(string_to_pass) # THEN: The strings should be identical. - assert result_string == wanted_string, 'The strings should be identical.' + self.assertEqual(wanted_string, result_string, 'The strings should be identical') def expand_tags_test(self): """ @@ -426,7 +444,7 @@ class TestLib(TestCase): result_string = expand_tags(string_to_pass) # THEN: The strings should be identical. - assert result_string == wanted_string, 'The strings should be identical.' + self.assertEqual(wanted_string, result_string, 'The strings should be identical.') def validate_thumb_file_does_not_exist_test(self): """ diff --git a/tests/functional/openlp_core_lib/test_pluginmanager.py b/tests/functional/openlp_core_lib/test_pluginmanager.py index afd06fc47..485288344 100644 --- a/tests/functional/openlp_core_lib/test_pluginmanager.py +++ b/tests/functional/openlp_core_lib/test_pluginmanager.py @@ -280,8 +280,8 @@ class TestPluginManager(TestCase): plugin_manager.hook_tools_menu() # THEN: The add_tools_menu_item() method should have been called - assert mocked_plugin.add_tools_menu_item.call_count == 0, \ - 'The add_tools_menu_item() method should not have been called.' + self.assertEqual(0, mocked_plugin.add_tools_menu_item.call_count, + 'The add_tools_menu_item() method should not have been called.') def hook_tools_menu_with_active_plugin_test(self): """ @@ -315,7 +315,7 @@ class TestPluginManager(TestCase): # THEN: The is_active() method should have been called, and initialise() method should NOT have been called mocked_plugin.is_active.assert_called_with() - assert mocked_plugin.initialise.call_count == 0, 'The initialise() method should not have been called.' + self.assertEqual(0, mocked_plugin.initialise.call_count, 'The initialise() method should not have been called.') def initialise_plugins_with_active_plugin_test(self): """ @@ -351,7 +351,7 @@ class TestPluginManager(TestCase): # THEN: The is_active() method should have been called, and initialise() method should NOT have been called mocked_plugin.is_active.assert_called_with() - assert mocked_plugin.finalise.call_count == 0, 'The finalise() method should not have been called.' + self.assertEqual(0, mocked_plugin.finalise.call_count, 'The finalise() method should not have been called.') def finalise_plugins_with_active_plugin_test(self): """ @@ -419,8 +419,8 @@ class TestPluginManager(TestCase): # THEN: The isActive() method should have been called, and initialise() method should NOT have been called mocked_plugin.is_active.assert_called_with() - assert mocked_plugin.new_service_created.call_count == 0,\ - 'The new_service_created() method should not have been called.' + self.assertEqual(0, mocked_plugin.new_service_created.call_count, + 'The new_service_created() method should not have been called.') def new_service_created_with_active_plugin_test(self): """ From 3503c54ce44c33192fa375321c7b18d9e9a100d1 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Sun, 29 Sep 2013 23:28:53 +0200 Subject: [PATCH 15/18] Some bugs and minor tweaks. --- tests/functional/openlp_core_lib/test_lib.py | 2 +- tests/functional/openlp_core_lib/test_serviceitem.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/functional/openlp_core_lib/test_lib.py b/tests/functional/openlp_core_lib/test_lib.py index 5e2bd187b..80e0e35f3 100644 --- a/tests/functional/openlp_core_lib/test_lib.py +++ b/tests/functional/openlp_core_lib/test_lib.py @@ -70,7 +70,7 @@ class TestLib(TestCase): false_result = str_to_bool(false_boolean) # THEN: We should get back a True bool - self.assertIsinstance(false_result, bool, 'The result should be a boolean') + self.assertIsInstance(false_result, bool, 'The result should be a boolean') self.assertFalse(false_result, 'The result should be True') def str_to_bool_with_integer_test(self): diff --git a/tests/functional/openlp_core_lib/test_serviceitem.py b/tests/functional/openlp_core_lib/test_serviceitem.py index 0e1b150ae..fda471428 100644 --- a/tests/functional/openlp_core_lib/test_serviceitem.py +++ b/tests/functional/openlp_core_lib/test_serviceitem.py @@ -81,7 +81,7 @@ class TestServiceItem(TestCase): service_item.add_icon = MagicMock() # WHEN: We add a custom from a saved service - line = convert_file_service_item('serviceitem_custom_1.osj') + line = convert_file_service_item(TEST_PATH, 'serviceitem_custom_1.osj') service_item.set_from_service(line) # THEN: We should get back a valid service item From 1347ad61d1059d5e79ccc53afb91be47314ae2d1 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 2 Oct 2013 23:07:20 +0200 Subject: [PATCH 16/18] Changed the way we create and initialise MediaManagerItems so that we can test things better. --- openlp/core/lib/mediamanageritem.py | 17 ++++++++-- openlp/plugins/bibles/bibleplugin.py | 14 ++++++--- openlp/plugins/bibles/lib/mediaitem.py | 5 +++ openlp/plugins/custom/lib/mediaitem.py | 7 ++++- openlp/plugins/images/lib/mediaitem.py | 31 ++++++++++++++----- openlp/plugins/media/lib/mediaitem.py | 7 ++++- openlp/plugins/presentations/lib/mediaitem.py | 30 ++++++++++-------- .../presentations/presentationplugin.py | 3 +- openlp/plugins/songs/lib/mediaitem.py | 5 +++ .../openlp_core_lib/test_serviceitem.py | 4 +-- .../openlp_plugins/images/test_lib.py | 9 +++--- .../presentations/test_mediaitem.py | 16 +++++----- .../openlp_plugins/songs/test_mediaitem.py | 4 +-- 13 files changed, 104 insertions(+), 48 deletions(-) diff --git a/openlp/core/lib/mediamanageritem.py b/openlp/core/lib/mediamanageritem.py index 9e7eb8d73..3fddc18f2 100644 --- a/openlp/core/lib/mediamanageritem.py +++ b/openlp/core/lib/mediamanageritem.py @@ -82,10 +82,17 @@ class MediaManagerItem(QtGui.QWidget): """ Constructor to create the media manager item. """ - super(MediaManagerItem, self).__init__() + super(MediaManagerItem, self).__init__(parent) + self.plugin = plugin + self._setup() + self.setup_item() + + def _setup(self): + """ + Run some initial setup. This method is separate from __init__ in order to mock it out in tests. + """ self.hide() self.whitespace = re.compile(r'[\W_]+', re.UNICODE) - self.plugin = plugin visible_title = self.plugin.get_string(StringContent.VisibleName) self.title = str(visible_title['title']) Registry().register(self.plugin.name, self) @@ -106,6 +113,12 @@ class MediaManagerItem(QtGui.QWidget): QtCore.QObject.connect(self, QtCore.SIGNAL('%s_go_live' % self.plugin.name), self.go_live_remote) QtCore.QObject.connect(self, QtCore.SIGNAL('%s_add_to_service' % self.plugin.name), self.add_to_service_remote) + def setup_item(self): + """ + Override this for additional Plugin setup + """ + pass + def required_icons(self): """ This method is called to define the icons for the plugin. It provides a default set and the plugin is able to diff --git a/openlp/plugins/bibles/bibleplugin.py b/openlp/plugins/bibles/bibleplugin.py index 18aab2a0d..4121b474c 100644 --- a/openlp/plugins/bibles/bibleplugin.py +++ b/openlp/plugins/bibles/bibleplugin.py @@ -67,6 +67,9 @@ __default_settings__ = { class BiblePlugin(Plugin): + """ + The Bible plugin provides a plugin for managing and displaying Bibles. + """ log.info('Bible Plugin loaded') def __init__(self): @@ -74,13 +77,14 @@ class BiblePlugin(Plugin): self.weight = -9 self.icon_path = ':/plugins/plugin_bibles.png' self.icon = build_icon(self.icon_path) - self.manager = None + self.manager = BibleManager(self) def initialise(self): + """ + Initialise the Bible plugin. + """ log.info('bibles Initialising') - if self.manager is None: - self.manager = BibleManager(self) - Plugin.initialise(self) + super(BiblePlugin, self).initialise() self.import_bible_item.setVisible(True) action_list = ActionList.get_instance() action_list.add_action(self.import_bible_item, UiStrings().Import) @@ -107,7 +111,7 @@ class BiblePlugin(Plugin): """ Perform tasks on application startup """ - Plugin.app_startup(self) + super(BiblePlugin, self).app_startup() if self.manager.old_bible_databases: if QtGui.QMessageBox.information(self.main_window, translate('OpenLP', 'Information'), diff --git a/openlp/plugins/bibles/lib/mediaitem.py b/openlp/plugins/bibles/lib/mediaitem.py index 94f015fdb..4ccd37df1 100644 --- a/openlp/plugins/bibles/lib/mediaitem.py +++ b/openlp/plugins/bibles/lib/mediaitem.py @@ -64,6 +64,11 @@ class BibleMediaItem(MediaManagerItem): self.lock_icon = build_icon(':/bibles/bibles_search_lock.png') self.unlock_icon = build_icon(':/bibles/bibles_search_unlock.png') MediaManagerItem.__init__(self, parent, plugin) + + def setup_item(self): + """ + Do some additional setup. + """ # Place to store the search results for both bibles. self.settings = self.plugin.settings_tab self.quick_preview_allowed = True diff --git a/openlp/plugins/custom/lib/mediaitem.py b/openlp/plugins/custom/lib/mediaitem.py index a01ba427f..f5b518ce8 100644 --- a/openlp/plugins/custom/lib/mediaitem.py +++ b/openlp/plugins/custom/lib/mediaitem.py @@ -58,6 +58,11 @@ class CustomMediaItem(MediaManagerItem): def __init__(self, parent, plugin): self.icon_path = 'custom/custom' super(CustomMediaItem, self).__init__(parent, plugin) + + def setup_item(self): + """ + Do some additional setup. + """ self.edit_custom_form = EditCustomForm(self, self.main_window, self.plugin.manager) self.single_service_item = False self.quick_preview_allowed = True @@ -65,7 +70,7 @@ class CustomMediaItem(MediaManagerItem): # Holds information about whether the edit is remotely triggered and # which Custom is required. self.remote_custom = -1 - self.manager = plugin.manager + self.manager = self.plugin.manager def add_end_header_bar(self): self.toolbar.addSeparator() diff --git a/openlp/plugins/images/lib/mediaitem.py b/openlp/plugins/images/lib/mediaitem.py index 9e396c3cf..70d4630a0 100644 --- a/openlp/plugins/images/lib/mediaitem.py +++ b/openlp/plugins/images/lib/mediaitem.py @@ -52,10 +52,18 @@ class ImageMediaItem(MediaManagerItem): def __init__(self, parent, plugin): self.icon_path = 'images/image' + self.manager = None + self.choose_group_form = None + self.add_group_form = None super(ImageMediaItem, self).__init__(parent, plugin) + + def setup_item(self): + """ + Do some additional setup. + """ self.quick_preview_allowed = True self.has_search = True - self.manager = plugin.manager + self.manager = self.plugin.manager self.choose_group_form = ChooseGroupForm(self) self.add_group_form = AddGroupForm(self) self.fill_groups_combobox(self.choose_group_form.group_combobox) @@ -91,8 +99,8 @@ class ImageMediaItem(MediaManagerItem): self.list_view.setIconSize(QtCore.QSize(88, 50)) self.list_view.setIndentation(self.list_view.default_indentation) self.list_view.allow_internal_dnd = True - self.servicePath = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails') - check_directory_exists(self.servicePath) + self.service_path = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails') + check_directory_exists(self.service_path) # Load images from the database self.load_full_list( self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename), initial_load=True) @@ -193,7 +201,7 @@ class ImageMediaItem(MediaManagerItem): """ images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id) for image in images: - delete_file(os.path.join(self.servicePath, os.path.split(image.filename)[1])) + delete_file(os.path.join(self.service_path, os.path.split(image.filename)[1])) self.manager.delete_object(ImageFilenames, image.id) image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == image_group.id) for group in image_groups: @@ -215,7 +223,7 @@ class ImageMediaItem(MediaManagerItem): if row_item: item_data = row_item.data(0, QtCore.Qt.UserRole) if isinstance(item_data, ImageFilenames): - delete_file(os.path.join(self.servicePath, row_item.text(0))) + delete_file(os.path.join(self.service_path, row_item.text(0))) if item_data.group_id == 0: self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item)) else: @@ -339,7 +347,7 @@ class ImageMediaItem(MediaManagerItem): for imageFile in images: log.debug('Loading image: %s', imageFile.filename) filename = os.path.split(imageFile.filename)[1] - thumb = os.path.join(self.servicePath, filename) + thumb = os.path.join(self.service_path, filename) if not os.path.exists(imageFile.filename): icon = build_icon(':/general/general_delete.png') else: @@ -672,7 +680,16 @@ class ImageMediaItem(MediaManagerItem): translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, ' 'the image file "%s" no longer exists.') % filename) - def search(self, string, showError): + def search(self, string, show_error=True): + """ + Perform a search on the image file names. + + ``string`` + The glob to search for + + ``show_error`` + Unused. + """ files = self.manager.get_all_objects(ImageFilenames, filter_clause=ImageFilenames.filename.contains(string), order_by_ref=ImageFilenames.filename) results = [] diff --git a/openlp/plugins/media/lib/mediaitem.py b/openlp/plugins/media/lib/mediaitem.py index a5b0397c5..4f173db84 100644 --- a/openlp/plugins/media/lib/mediaitem.py +++ b/openlp/plugins/media/lib/mediaitem.py @@ -61,10 +61,15 @@ class MediaMediaItem(MediaManagerItem): self.background = False self.automatic = '' super(MediaMediaItem, self).__init__(parent, plugin) + + def setup_item(self): + """ + Do some additional setup. + """ self.single_service_item = False self.has_search = True self.media_object = None - self.display_controller = DisplayController(parent) + self.display_controller = DisplayController(self.parent()) self.display_controller.controller_layout = QtGui.QVBoxLayout() self.media_controller.register_controller(self.display_controller) self.media_controller.set_controls_visible(self.display_controller, False) diff --git a/openlp/plugins/presentations/lib/mediaitem.py b/openlp/plugins/presentations/lib/mediaitem.py index f1d0f1110..695baddc5 100644 --- a/openlp/plugins/presentations/lib/mediaitem.py +++ b/openlp/plugins/presentations/lib/mediaitem.py @@ -52,14 +52,26 @@ class PresentationMediaItem(MediaManagerItem): """ log.info('Presentations Media Item loaded') - def __init__(self, parent, plugin, icon, controllers): + def __init__(self, parent, plugin, controllers): """ Constructor. Setup defaults """ - self.controllers = controllers self.icon_path = 'presentations/presentation' - self.Automatic = '' + self.controllers = controllers super(PresentationMediaItem, self).__init__(parent, plugin) + + def retranslateUi(self): + """ + The name of the plugin media displayed in UI + """ + self.on_new_prompt = translate('PresentationPlugin.MediaItem', 'Select Presentation(s)') + self.automatic = translate('PresentationPlugin.MediaItem', 'Automatic') + self.display_type_label.setText(translate('PresentationPlugin.MediaItem', 'Present using:')) + + def setup_item(self): + """ + Do some additional setup. + """ self.message_listener = MessageListener(self) self.has_search = True self.single_service_item = False @@ -68,14 +80,6 @@ class PresentationMediaItem(MediaManagerItem): # Allow DnD from the desktop self.list_view.activateDnD() - def retranslateUi(self): - """ - The name of the plugin media displayed in UI - """ - self.on_new_prompt = translate('PresentationPlugin.MediaItem', 'Select Presentation(s)') - self.Automatic = translate('PresentationPlugin.MediaItem', 'Automatic') - self.display_type_label.setText(translate('PresentationPlugin.MediaItem', 'Present using:')) - def build_file_mask_string(self): """ Build the list of file extensions to be used in the Open file dialog. @@ -137,7 +141,7 @@ class PresentationMediaItem(MediaManagerItem): if self.controllers[item].enabled(): self.display_type_combo_box.addItem(item) if self.display_type_combo_box.count() > 1: - self.display_type_combo_box.insertItem(0, self.Automatic) + self.display_type_combo_box.insertItem(0, self.automatic) self.display_type_combo_box.setCurrentIndex(0) if Settings().value(self.settings_section + '/override app') == QtCore.Qt.Checked: self.presentation_widget.show() @@ -253,7 +257,7 @@ class PresentationMediaItem(MediaManagerItem): (path, name) = os.path.split(filename) service_item.title = name if os.path.exists(filename): - if service_item.processor == self.Automatic: + if service_item.processor == self.automatic: service_item.processor = self.findControllerByType(filename) if not service_item.processor: return False diff --git a/openlp/plugins/presentations/presentationplugin.py b/openlp/plugins/presentations/presentationplugin.py index 1efd5ac0a..be5d3f52d 100644 --- a/openlp/plugins/presentations/presentationplugin.py +++ b/openlp/plugins/presentations/presentationplugin.py @@ -109,8 +109,7 @@ class PresentationPlugin(Plugin): """ Create the Media Manager List. """ - self.media_item = PresentationMediaItem( - self.main_window.media_dock_manager.media_dock, self, self.icon, self.controllers) + self.media_item = PresentationMediaItem(self.main_window.media_dock_manager.media_dock, self, self.controllers) def register_controllers(self, controller): """ diff --git a/openlp/plugins/songs/lib/mediaitem.py b/openlp/plugins/songs/lib/mediaitem.py index 68cd706a3..957edbca0 100644 --- a/openlp/plugins/songs/lib/mediaitem.py +++ b/openlp/plugins/songs/lib/mediaitem.py @@ -72,6 +72,11 @@ class SongMediaItem(MediaManagerItem): def __init__(self, parent, plugin): self.icon_path = 'songs/song' super(SongMediaItem, self).__init__(parent, plugin) + + def setup_item(self): + """ + Do some additional setup. + """ self.single_service_item = False # Holds information about whether the edit is remotely triggered and which Song is required. self.remote_song = -1 diff --git a/tests/functional/openlp_core_lib/test_serviceitem.py b/tests/functional/openlp_core_lib/test_serviceitem.py index fda471428..f23c92b16 100644 --- a/tests/functional/openlp_core_lib/test_serviceitem.py +++ b/tests/functional/openlp_core_lib/test_serviceitem.py @@ -95,9 +95,9 @@ class TestServiceItem(TestCase): # THEN: The frames should also be valid self.assertEqual('Test Custom', service_item.get_display_title(), 'The title should be "Test Custom"') self.assertEqual(VERSE[:-1], service_item.get_frames()[0]['text'], - 'The returned text matches the input, except the last line feed') + 'The returned text matches the input, except the last line feed') self.assertEqual(VERSE.split('\n', 1)[0], service_item.get_rendered_frame(1), - 'The first line has been returned') + 'The first line has been returned') self.assertEqual('Slide 1', service_item.get_frame_title(0), '"Slide 1" has been returned as the title') self.assertEqual('Slide 2', service_item.get_frame_title(1), '"Slide 2" has been returned as the title') self.assertEqual('', service_item.get_frame_title(2), 'Blank has been returned as the title of slide 3') diff --git a/tests/functional/openlp_plugins/images/test_lib.py b/tests/functional/openlp_plugins/images/test_lib.py index 50fd258d2..f4d0dc30d 100644 --- a/tests/functional/openlp_plugins/images/test_lib.py +++ b/tests/functional/openlp_plugins/images/test_lib.py @@ -48,11 +48,10 @@ class TestImageMediaItem(TestCase): Registry().register('service_list', MagicMock()) Registry().register('main_window', self.mocked_main_window) Registry().register('live_controller', MagicMock()) - mocked_parent = MagicMock() mocked_plugin = MagicMock() - with patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.__init__') as mocked_init: - mocked_init.return_value = None - self.media_item = ImageMediaItem(mocked_parent, mocked_plugin) + with patch('openlp.plugins.images.lib.mediaitem.MediaManagerItem._setup'), \ + patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.setup_item'): + self.media_item = ImageMediaItem(None, mocked_plugin) def save_new_images_list_empty_list_test(self): """ @@ -160,7 +159,7 @@ class TestImageMediaItem(TestCase): ImageGroups.parent_id = 1 self.media_item.manager = MagicMock() self.media_item.manager.get_all_objects.side_effect = self._recursively_delete_group_side_effect - self.media_item.servicePath = "" + self.media_item.service_path = "" test_group = ImageGroups() test_group.id = 1 diff --git a/tests/functional/openlp_plugins/presentations/test_mediaitem.py b/tests/functional/openlp_plugins/presentations/test_mediaitem.py index 49b47252c..5b5c99e78 100644 --- a/tests/functional/openlp_plugins/presentations/test_mediaitem.py +++ b/tests/functional/openlp_plugins/presentations/test_mediaitem.py @@ -49,11 +49,9 @@ class TestMediaItem(TestCase): Registry.create() Registry().register('service_manager', MagicMock()) Registry().register('main_window', MagicMock()) - - with patch('openlp.plugins.presentations.lib.mediaitem.PresentationMediaItem.__init__') as mocked_init: - mocked_init.return_value = None - self.media_item = PresentationMediaItem(MagicMock(), MagicMock, MagicMock(), MagicMock()) - + with patch('openlp.plugins.presentations.lib.mediaitem.MediaManagerItem._setup'), \ + patch('openlp.plugins.presentations.lib.mediaitem.PresentationMediaItem.setup_item'): + self.media_item = PresentationMediaItem(None, MagicMock, MagicMock()) self.application = QtGui.QApplication.instance() def tearDown(self): @@ -89,6 +87,8 @@ class TestMediaItem(TestCase): mocked_translate.side_effect = lambda module, string_to_translate: string_to_translate self.media_item.build_file_mask_string() - # THEN: The file mask should be generated. - assert self.media_item.on_new_file_masks == 'Presentations (*.odp *.ppt )', \ - 'The file mask should contain the odp and ppt extensions' + # THEN: The file mask should be generated correctly + self.assertIn('*.odp', self.media_item.on_new_file_masks, + 'The file mask should contain the odp extension') + self.assertIn('*.ppt', self.media_item.on_new_file_masks, + 'The file mask should contain the ppt extension') diff --git a/tests/functional/openlp_plugins/songs/test_mediaitem.py b/tests/functional/openlp_plugins/songs/test_mediaitem.py index a519f7021..39f3146de 100644 --- a/tests/functional/openlp_plugins/songs/test_mediaitem.py +++ b/tests/functional/openlp_plugins/songs/test_mediaitem.py @@ -23,9 +23,9 @@ class TestMediaItem(TestCase): Registry.create() Registry().register('service_list', MagicMock()) Registry().register('main_window', MagicMock()) - with patch('openlp.core.lib.mediamanageritem.MediaManagerItem.__init__'), \ + with patch('openlp.core.lib.mediamanageritem.MediaManagerItem._setup'), \ patch('openlp.plugins.songs.forms.editsongform.EditSongForm.__init__'): - self.media_item = SongMediaItem(MagicMock(), MagicMock()) + self.media_item = SongMediaItem(None, MagicMock()) fd, self.ini_file = mkstemp('.ini') Settings().set_filename(self.ini_file) From 316f33c0588a97f371c4e949d3b4ef054a86823d Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Wed, 2 Oct 2013 23:37:00 +0200 Subject: [PATCH 17/18] Change the indentation back to 4 --- tests/functional/openlp_core_lib/test_lib.py | 2 +- .../openlp_core_lib/test_pluginmanager.py | 22 +++++----- .../openlp_core_lib/test_registry.py | 6 +-- .../functional/openlp_core_lib/test_screen.py | 2 +- .../openlp_core_lib/test_serviceitem.py | 40 +++++++++---------- .../openlp_core_utils/test_utils.py | 7 ++-- .../bibles/test_versereferencelist.py | 7 ++-- 7 files changed, 44 insertions(+), 42 deletions(-) diff --git a/tests/functional/openlp_core_lib/test_lib.py b/tests/functional/openlp_core_lib/test_lib.py index 80e0e35f3..9bb74e0d3 100644 --- a/tests/functional/openlp_core_lib/test_lib.py +++ b/tests/functional/openlp_core_lib/test_lib.py @@ -311,7 +311,7 @@ class TestLib(TestCase): mocked_image.save.assert_called_with(mocked_buffer, "PNG") mocked_byte_array.toBase64.assert_called_with() self.assertEqual('base64mock', result, - 'The result should be the return value of the mocked out base64 method') + 'The result should be the return value of the mocked out base64 method') def create_thumb_with_size_test(self): """ diff --git a/tests/functional/openlp_core_lib/test_pluginmanager.py b/tests/functional/openlp_core_lib/test_pluginmanager.py index 485288344..2ab3b2dd7 100644 --- a/tests/functional/openlp_core_lib/test_pluginmanager.py +++ b/tests/functional/openlp_core_lib/test_pluginmanager.py @@ -70,7 +70,7 @@ class TestPluginManager(TestCase): # THEN: The create_media_manager_item() method should have been called self.assertEqual(0, mocked_plugin.create_media_manager_item.call_count, - 'The create_media_manager_item() method should not have been called.') + 'The create_media_manager_item() method should not have been called.') def hook_media_manager_with_active_plugin_test(self): """ @@ -103,7 +103,7 @@ class TestPluginManager(TestCase): # THEN: The hook_settings_tabs() method should have been called self.assertEqual(0, mocked_plugin.create_media_manager_item.call_count, - 'The create_media_manager_item() method should not have been called.') + 'The create_media_manager_item() method should not have been called.') def hook_settings_tabs_with_disabled_plugin_and_mocked_form_test(self): """ @@ -123,9 +123,9 @@ class TestPluginManager(TestCase): # THEN: The create_settings_tab() method should not have been called, but the plugins lists should be the same self.assertEqual(0, mocked_plugin.create_settings_tab.call_count, - 'The create_media_manager_item() method should not have been called.') + 'The create_media_manager_item() method should not have been called.') self.assertEqual(mocked_settings_form.plugin_manager.plugins, plugin_manager.plugins, - 'The plugins on the settings form should be the same as the plugins in the plugin manager') + 'The plugins on the settings form should be the same as the plugins in the plugin manager') def hook_settings_tabs_with_active_plugin_and_mocked_form_test(self): """ @@ -145,9 +145,9 @@ class TestPluginManager(TestCase): # THEN: The create_media_manager_item() method should have been called with the mocked settings form self.assertEqual(1, mocked_plugin.create_settings_tab.call_count, - 'The create_media_manager_item() method should have been called once.') + 'The create_media_manager_item() method should have been called once.') self.assertEqual(plugin_manager.plugins, mocked_settings_form.plugin_manager.plugins, - 'The plugins on the settings form should be the same as the plugins in the plugin manager') + 'The plugins on the settings form should be the same as the plugins in the plugin manager') def hook_settings_tabs_with_active_plugin_and_no_form_test(self): """ @@ -180,7 +180,7 @@ class TestPluginManager(TestCase): # THEN: The create_media_manager_item() method should have been called self.assertEqual(0, mocked_plugin.add_import_menu_item.call_count, - 'The add_import_menu_item() method should not have been called.') + 'The add_import_menu_item() method should not have been called.') def hook_import_menu_with_active_plugin_test(self): """ @@ -213,7 +213,7 @@ class TestPluginManager(TestCase): # THEN: The add_export_menu_Item() method should not have been called self.assertEqual(0, mocked_plugin.add_export_menu_Item.call_count, - 'The add_export_menu_Item() method should not have been called.') + 'The add_export_menu_Item() method should not have been called.') def hook_export_menu_with_active_plugin_test(self): """ @@ -247,7 +247,7 @@ class TestPluginManager(TestCase): # THEN: The upgrade_settings() method should not have been called self.assertEqual(0, mocked_plugin.upgrade_settings.call_count, - 'The upgrade_settings() method should not have been called.') + 'The upgrade_settings() method should not have been called.') def hook_upgrade_plugin_settings_with_active_plugin_test(self): """ @@ -281,7 +281,7 @@ class TestPluginManager(TestCase): # THEN: The add_tools_menu_item() method should have been called self.assertEqual(0, mocked_plugin.add_tools_menu_item.call_count, - 'The add_tools_menu_item() method should not have been called.') + 'The add_tools_menu_item() method should not have been called.') def hook_tools_menu_with_active_plugin_test(self): """ @@ -420,7 +420,7 @@ class TestPluginManager(TestCase): # THEN: The isActive() method should have been called, and initialise() method should NOT have been called mocked_plugin.is_active.assert_called_with() self.assertEqual(0, mocked_plugin.new_service_created.call_count, - 'The new_service_created() method should not have been called.') + 'The new_service_created() method should not have been called.') def new_service_created_with_active_plugin_test(self): """ diff --git a/tests/functional/openlp_core_lib/test_registry.py b/tests/functional/openlp_core_lib/test_registry.py index e1e5e268b..06307630b 100644 --- a/tests/functional/openlp_core_lib/test_registry.py +++ b/tests/functional/openlp_core_lib/test_registry.py @@ -59,14 +59,14 @@ class TestRegistry(TestCase): with self.assertRaises(KeyError) as context: Registry().register('test1', mock_1) self.assertEqual(context.exception.args[0], 'Duplicate service exception test1', - 'KeyError exception should have been thrown for duplicate service') + 'KeyError exception should have been thrown for duplicate service') # WHEN I try to get back a non existent component # THEN I will get an exception with self.assertRaises(KeyError) as context: temp = Registry().get('test2') self.assertEqual(context.exception.args[0], 'Service test2 not found in list', - 'KeyError exception should have been thrown for missing service') + 'KeyError exception should have been thrown for missing service') # WHEN I try to replace a component I should be allowed (testing only) Registry().remove('test1') @@ -74,7 +74,7 @@ class TestRegistry(TestCase): with self.assertRaises(KeyError) as context: temp = Registry().get('test1') self.assertEqual(context.exception.args[0], 'Service test1 not found in list', - 'KeyError exception should have been thrown for deleted service') + 'KeyError exception should have been thrown for deleted service') def registry_function_test(self): """ diff --git a/tests/functional/openlp_core_lib/test_screen.py b/tests/functional/openlp_core_lib/test_screen.py index c541fbb12..ba635ce83 100644 --- a/tests/functional/openlp_core_lib/test_screen.py +++ b/tests/functional/openlp_core_lib/test_screen.py @@ -83,4 +83,4 @@ class TestScreenList(TestCase): new_screen_count = len(self.screens.screen_list) self.assertEqual(old_screen_count + 1, new_screen_count, 'The new_screens list should be bigger') self.assertEqual(SCREEN, self.screens.screen_list.pop(), - 'The 2nd screen should be identical to the first screen') + 'The 2nd screen should be identical to the first screen') diff --git a/tests/functional/openlp_core_lib/test_serviceitem.py b/tests/functional/openlp_core_lib/test_serviceitem.py index f23c92b16..bcf9410ca 100644 --- a/tests/functional/openlp_core_lib/test_serviceitem.py +++ b/tests/functional/openlp_core_lib/test_serviceitem.py @@ -123,24 +123,24 @@ class TestServiceItem(TestCase): # THEN: We should get back a valid service item self.assertTrue(service_item.is_valid, 'The new service item should be valid') self.assertEqual(test_file, service_item.get_rendered_frame(0), - 'The first frame should match the path to the image') + 'The first frame should match the path to the image') self.assertEqual(frame_array, service_item.get_frames()[0], - 'The return should match frame array1') + 'The return should match frame array1') self.assertEqual(test_file, service_item.get_frame_path(0), - 'The frame path should match the full path to the image') + 'The frame path should match the full path to the image') self.assertEqual(image_name, service_item.get_frame_title(0), - 'The frame title should match the image name') + 'The frame title should match the image name') self.assertEqual(image_name, service_item.get_display_title(), - 'The display title should match the first image name') + 'The display title should match the first image name') self.assertTrue(service_item.is_image(), 'This service item should be of an "image" type') self.assertTrue(service_item.is_capable(ItemCapabilities.CanMaintain), - 'This service item should be able to be Maintained') + 'This service item should be able to be Maintained') self.assertTrue(service_item.is_capable(ItemCapabilities.CanPreview), - 'This service item should be able to be be Previewed') + 'This service item should be able to be be Previewed') self.assertTrue(service_item.is_capable(ItemCapabilities.CanLoop), - 'This service item should be able to be run in a can be made to Loop') + 'This service item should be able to be run in a can be made to Loop') self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend), - 'This service item should be able to have new items added to it') + 'This service item should be able to have new items added to it') def service_item_load_image_from_local_service_test(self): """ @@ -176,27 +176,27 @@ class TestServiceItem(TestCase): self.assertTrue(service_item.is_valid, 'The first service item should be valid') self.assertTrue(service_item2.is_valid, 'The second service item should be valid') self.assertEqual(test_file1, service_item.get_rendered_frame(0), - 'The first frame should match the path to the image') + 'The first frame should match the path to the image') self.assertEqual(test_file2, service_item2.get_rendered_frame(0), - 'The Second frame should match the path to the image') + 'The Second frame should match the path to the image') self.assertEqual(frame_array1, service_item.get_frames()[0], 'The return should match the frame array1') self.assertEqual(frame_array2, service_item2.get_frames()[0], 'The return should match the frame array2') self.assertEqual(test_file1, service_item.get_frame_path(0), - 'The frame path should match the full path to the image') + 'The frame path should match the full path to the image') self.assertEqual(test_file2, service_item2.get_frame_path(0), - 'The frame path should match the full path to the image') + 'The frame path should match the full path to the image') self.assertEqual(image_name1, service_item.get_frame_title(0), - 'The 1st frame title should match the image name') + 'The 1st frame title should match the image name') self.assertEqual(image_name2, service_item2.get_frame_title(0), - 'The 2nd frame title should match the image name') + 'The 2nd frame title should match the image name') self.assertEqual(service_item.name, service_item.title.lower(), - 'The plugin name should match the display title, as there are > 1 Images') + 'The plugin name should match the display title, as there are > 1 Images') self.assertTrue(service_item.is_image(), 'This service item should be of an "image" type') self.assertTrue(service_item.is_capable(ItemCapabilities.CanMaintain), - 'This service item should be able to be Maintained') + 'This service item should be able to be Maintained') self.assertTrue(service_item.is_capable(ItemCapabilities.CanPreview), - 'This service item should be able to be be Previewed') + 'This service item should be able to be be Previewed') self.assertTrue(service_item.is_capable(ItemCapabilities.CanLoop), - 'This service item should be able to be run in a can be made to Loop') + 'This service item should be able to be run in a can be made to Loop') self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend), - 'This service item should be able to have new items added to it') + 'This service item should be able to have new items added to it') diff --git a/tests/functional/openlp_core_utils/test_utils.py b/tests/functional/openlp_core_utils/test_utils.py index 03f0f3654..8ecc15018 100644 --- a/tests/functional/openlp_core_utils/test_utils.py +++ b/tests/functional/openlp_core_utils/test_utils.py @@ -134,7 +134,7 @@ class TestUtils(TestCase): # THEN: A tuple should be returned. self.assertEqual(wanted_result, result, - 'A two-entry tuple with the directory and file name (empty) should have been returned.') + 'A two-entry tuple with the directory and file name (empty) should have been returned.') def clean_filename_test(self): """ @@ -167,7 +167,7 @@ class TestUtils(TestCase): # THEN: We get a properly sorted list self.assertEqual(['Aushang', '\u00C4u\u00DFerung', 'Auszug'], sorted_list, - 'Strings should be sorted properly') + 'Strings should be sorted properly') def get_locale_key_linux_test(self): """ @@ -180,12 +180,13 @@ class TestUtils(TestCase): mocked_get_language.return_value = 'de' mocked_os.name = 'linux' unsorted_list = ['Auszug', 'Aushang', '\u00C4u\u00DFerung'] + # WHEN: We sort the list and use get_locale_key() to generate the sorting keys sorted_list = sorted(unsorted_list, key=get_locale_key) # THEN: We get a properly sorted list self.assertEqual(['Aushang', '\u00C4u\u00DFerung', 'Auszug'], sorted_list, - 'Strings should be sorted properly') + 'Strings should be sorted properly') def get_natural_key_test(self): """ diff --git a/tests/functional/openlp_plugins/bibles/test_versereferencelist.py b/tests/functional/openlp_plugins/bibles/test_versereferencelist.py index 0c7ccd01e..bb3179dda 100644 --- a/tests/functional/openlp_plugins/bibles/test_versereferencelist.py +++ b/tests/functional/openlp_plugins/bibles/test_versereferencelist.py @@ -82,7 +82,8 @@ class TestVerseReferenceList(TestCase): # THEN: The current index should be 0 and the end pointer of the entry should be '2' self.assertEqual(reference_list.current_index, 0, 'The current index should be 0') - self.assertEqual(reference_list.verse_list[0]['end'], next_verse, 'The end in first entry should be %u' % next_verse) + self.assertEqual(reference_list.verse_list[0]['end'], next_verse, + 'The end in first entry should be %u' % next_verse) def add_another_verse_test(self): """ @@ -123,8 +124,8 @@ class TestVerseReferenceList(TestCase): # THEN: the data will be appended to the list self.assertEqual(len(reference_list.version_list), 1, 'The version data should be appended') self.assertEqual(reference_list.version_list[0], - {'version': version, 'copyright': copyright_, 'permission': permission}, - 'The version data should be appended') + {'version': version, 'copyright': copyright_, 'permission': permission}, + 'The version data should be appended') def add_existing_version_test(self): """ From 3994669c1b44d76292cf4dc950382f2428959353 Mon Sep 17 00:00:00 2001 From: Raoul Snyman Date: Thu, 3 Oct 2013 21:56:12 +0200 Subject: [PATCH 18/18] Modified the check_dependencies.py script to only require mock if we're using Python 3.2 and earlier. --- scripts/check_dependencies.py | 86 +++++++++++++------ .../openlp_core_lib/test_pluginmanager.py | 2 +- 2 files changed, 61 insertions(+), 27 deletions(-) diff --git a/scripts/check_dependencies.py b/scripts/check_dependencies.py index 607525bf2..0758b30e3 100755 --- a/scripts/check_dependencies.py +++ b/scripts/check_dependencies.py @@ -44,7 +44,7 @@ from distutils.version import LooseVersion try: import nose except ImportError: - pass + nose = None IS_WIN = sys.platform.startswith('win') @@ -90,21 +90,34 @@ MODULES = [ OPTIONAL_MODULES = [ - ('MySQLdb', ' (MySQL support)'), - ('psycopg2', ' (PostgreSQL support)'), - ('nose', ' (testing framework)'), - ('mock', ' (testing module)'), + ('MySQLdb', '(MySQL support)', True), + ('psycopg2', '(PostgreSQL support)', True), + ('nose', '(testing framework)', True), + ('mock', '(testing module)', sys.version_info[1] < 3), ] w = sys.stdout.write def check_vers(version, required, text): + """ + Check the version of a dependency. Returns ``True`` if the version is greater than or equal, or False if less than. + + ``version`` + The actual version of the dependency + + ``required`` + The required version of the dependency + + ``text`` + The dependency's name + """ + space = (27 - len(required) - len(text)) * ' ' if not isinstance(version, str): version = '.'.join(map(str, version)) if not isinstance(required, str): required = '.'.join(map(str, required)) - w(' %s >= %s ... ' % (text, required)) + w(' %s >= %s ... ' % (text, required) + space) if LooseVersion(version) >= LooseVersion(required): w(version + os.linesep) return True @@ -113,6 +126,29 @@ def check_vers(version, required, text): return False +def check_module(mod, text='', indent=' '): + """ + Check that a module is installed. + + ``mod`` + The module to check for. + + ``text`` + The text to display. + + ``indent`` + How much to indent the text by. + """ + space = (31 - len(mod) - len(text)) * ' ' + w(indent + '%s %s... ' % (mod, text) + space) + try: + __import__(mod) + w('OK') + except ImportError: + w('FAIL') + w(os.linesep) + + def print_vers_fail(required, text): print(' %s >= %s ... FAIL' % (text, required)) @@ -143,18 +179,10 @@ def verify_versions(): print_vers_fail(VERS['enchant'], 'enchant') -def check_module(mod, text='', indent=' '): - space = (30 - len(mod) - len(text)) * ' ' - w(indent + '%s%s... ' % (mod, text) + space) - try: - __import__(mod) - w('OK') - except ImportError: - w('FAIL') - w(os.linesep) - - -def verify_pyenchant(): +def print_enchant_backends_and_languages(): + """ + Check if PyEnchant is installed. + """ w('Enchant (spell checker)... ') try: import enchant @@ -167,14 +195,15 @@ def verify_pyenchant(): w('FAIL' + os.linesep) -def verify_pyqt(): +def print_qt_image_formats(): + """ + Print out the image formats that Qt4 supports. + """ w('Qt4 image formats... ') try: from PyQt4 import QtGui - read_f = ', '.join([str(format).lower() - for format in QtGui.QImageReader.supportedImageFormats()]) - write_f = ', '.join([str(format).lower() - for format in QtGui.QImageWriter.supportedImageFormats()]) + read_f = ', '.join([bytes(fmt).decode().lower() for fmt in QtGui.QImageReader.supportedImageFormats()]) + write_f = ', '.join([bytes(fmt).decode().lower() for fmt in QtGui.QImageWriter.supportedImageFormats()]) w(os.linesep) print(' read: %s' % read_f) print(' write: %s' % write_f) @@ -183,20 +212,25 @@ def verify_pyqt(): def main(): + """ + Run the dependency checker. + """ + print('Checking Python version...') verify_python() print('Checking for modules...') for m in MODULES: check_module(m) print('Checking for optional modules...') for m in OPTIONAL_MODULES: - check_module(m[0], text=m[1]) + if m[2]: + check_module(m[0], text=m[1]) if IS_WIN: print('Checking for Windows specific modules...') for m in WIN32_MODULES: check_module(m) verify_versions() - verify_pyqt() - verify_pyenchant() + print_qt_image_formats() + print_enchant_backends_and_languages() if __name__ == '__main__': main() diff --git a/tests/functional/openlp_core_lib/test_pluginmanager.py b/tests/functional/openlp_core_lib/test_pluginmanager.py index 2ab3b2dd7..eb6d80f8c 100644 --- a/tests/functional/openlp_core_lib/test_pluginmanager.py +++ b/tests/functional/openlp_core_lib/test_pluginmanager.py @@ -420,7 +420,7 @@ class TestPluginManager(TestCase): # THEN: The isActive() method should have been called, and initialise() method should NOT have been called mocked_plugin.is_active.assert_called_with() self.assertEqual(0, mocked_plugin.new_service_created.call_count, - 'The new_service_created() method should not have been called.') + 'The new_service_created() method should not have been called.') def new_service_created_with_active_plugin_test(self): """