This commit is contained in:
Tim Bentley 2013-10-04 20:28:09 +01:00
commit bd7d412cc9
46 changed files with 1957 additions and 1199 deletions

View File

@ -82,10 +82,17 @@ class MediaManagerItem(QtGui.QWidget):
""" """
Constructor to create the media manager item. 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.hide()
self.whitespace = re.compile(r'[\W_]+', re.UNICODE) self.whitespace = re.compile(r'[\W_]+', re.UNICODE)
self.plugin = plugin
visible_title = self.plugin.get_string(StringContent.VisibleName) visible_title = self.plugin.get_string(StringContent.VisibleName)
self.title = str(visible_title['title']) self.title = str(visible_title['title'])
Registry().register(self.plugin.name, self) 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_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) 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): 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 This method is called to define the icons for the plugin. It provides a default set and the plugin is able to

View File

@ -75,12 +75,6 @@ try:
ICU_VERSION = 'OK' ICU_VERSION = 'OK'
except ImportError: except ImportError:
ICU_VERSION = '-' ICU_VERSION = '-'
try:
import cherrypy
CHERRYPY_VERSION = cherrypy.__version__
except ImportError:
CHERRYPY_VERSION = '-'
try: try:
WEBKIT_VERSION = QtWebKit.qWebKitVersion() WEBKIT_VERSION = QtWebKit.qWebKitVersion()
except AttributeError: except AttributeError:
@ -140,7 +134,6 @@ class ExceptionForm(QtGui.QDialog, Ui_ExceptionDialog):
'Chardet: %s\n' % CHARDET_VERSION + \ 'Chardet: %s\n' % CHARDET_VERSION + \
'PyEnchant: %s\n' % ENCHANT_VERSION + \ 'PyEnchant: %s\n' % ENCHANT_VERSION + \
'Mako: %s\n' % MAKO_VERSION + \ 'Mako: %s\n' % MAKO_VERSION + \
'CherryPy: %s\n' % CHERRYPY_VERSION + \
'pyICU: %s\n' % ICU_VERSION + \ 'pyICU: %s\n' % ICU_VERSION + \
'pyUNO bridge: %s\n' % self._pyuno_import() + \ 'pyUNO bridge: %s\n' % self._pyuno_import() + \
'VLC: %s\n' % VLC_VERSION 'VLC: %s\n' % VLC_VERSION

View File

@ -67,6 +67,9 @@ __default_settings__ = {
class BiblePlugin(Plugin): class BiblePlugin(Plugin):
"""
The Bible plugin provides a plugin for managing and displaying Bibles.
"""
log.info('Bible Plugin loaded') log.info('Bible Plugin loaded')
def __init__(self): def __init__(self):
@ -74,13 +77,14 @@ class BiblePlugin(Plugin):
self.weight = -9 self.weight = -9
self.icon_path = ':/plugins/plugin_bibles.png' self.icon_path = ':/plugins/plugin_bibles.png'
self.icon = build_icon(self.icon_path) self.icon = build_icon(self.icon_path)
self.manager = None self.manager = BibleManager(self)
def initialise(self): def initialise(self):
"""
Initialise the Bible plugin.
"""
log.info('bibles Initialising') log.info('bibles Initialising')
if self.manager is None: super(BiblePlugin, self).initialise()
self.manager = BibleManager(self)
Plugin.initialise(self)
self.import_bible_item.setVisible(True) self.import_bible_item.setVisible(True)
action_list = ActionList.get_instance() action_list = ActionList.get_instance()
action_list.add_action(self.import_bible_item, UiStrings().Import) action_list.add_action(self.import_bible_item, UiStrings().Import)
@ -107,7 +111,7 @@ class BiblePlugin(Plugin):
""" """
Perform tasks on application startup Perform tasks on application startup
""" """
Plugin.app_startup(self) super(BiblePlugin, self).app_startup()
if self.manager.old_bible_databases: if self.manager.old_bible_databases:
if QtGui.QMessageBox.information(self.main_window, if QtGui.QMessageBox.information(self.main_window,
translate('OpenLP', 'Information'), translate('OpenLP', 'Information'),

View File

@ -64,6 +64,11 @@ class BibleMediaItem(MediaManagerItem):
self.lock_icon = build_icon(':/bibles/bibles_search_lock.png') self.lock_icon = build_icon(':/bibles/bibles_search_lock.png')
self.unlock_icon = build_icon(':/bibles/bibles_search_unlock.png') self.unlock_icon = build_icon(':/bibles/bibles_search_unlock.png')
MediaManagerItem.__init__(self, parent, plugin) MediaManagerItem.__init__(self, parent, plugin)
def setup_item(self):
"""
Do some additional setup.
"""
# Place to store the search results for both bibles. # Place to store the search results for both bibles.
self.settings = self.plugin.settings_tab self.settings = self.plugin.settings_tab
self.quick_preview_allowed = True self.quick_preview_allowed = True

View File

@ -58,6 +58,11 @@ class CustomMediaItem(MediaManagerItem):
def __init__(self, parent, plugin): def __init__(self, parent, plugin):
self.icon_path = 'custom/custom' self.icon_path = 'custom/custom'
super(CustomMediaItem, self).__init__(parent, plugin) 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.edit_custom_form = EditCustomForm(self, self.main_window, self.plugin.manager)
self.single_service_item = False self.single_service_item = False
self.quick_preview_allowed = True self.quick_preview_allowed = True
@ -65,7 +70,7 @@ class CustomMediaItem(MediaManagerItem):
# Holds information about whether the edit is remotely triggered and # Holds information about whether the edit is remotely triggered and
# which Custom is required. # which Custom is required.
self.remote_custom = -1 self.remote_custom = -1
self.manager = plugin.manager self.manager = self.plugin.manager
def add_end_header_bar(self): def add_end_header_bar(self):
self.toolbar.addSeparator() self.toolbar.addSeparator()

View File

@ -52,10 +52,18 @@ class ImageMediaItem(MediaManagerItem):
def __init__(self, parent, plugin): def __init__(self, parent, plugin):
self.icon_path = 'images/image' self.icon_path = 'images/image'
self.manager = None
self.choose_group_form = None
self.add_group_form = None
super(ImageMediaItem, self).__init__(parent, plugin) super(ImageMediaItem, self).__init__(parent, plugin)
def setup_item(self):
"""
Do some additional setup.
"""
self.quick_preview_allowed = True self.quick_preview_allowed = True
self.has_search = True self.has_search = True
self.manager = plugin.manager self.manager = self.plugin.manager
self.choose_group_form = ChooseGroupForm(self) self.choose_group_form = ChooseGroupForm(self)
self.add_group_form = AddGroupForm(self) self.add_group_form = AddGroupForm(self)
self.fill_groups_combobox(self.choose_group_form.group_combobox) 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.setIconSize(QtCore.QSize(88, 50))
self.list_view.setIndentation(self.list_view.default_indentation) self.list_view.setIndentation(self.list_view.default_indentation)
self.list_view.allow_internal_dnd = True self.list_view.allow_internal_dnd = True
self.servicePath = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails') self.service_path = os.path.join(AppLocation.get_section_data_path(self.settings_section), 'thumbnails')
check_directory_exists(self.servicePath) check_directory_exists(self.service_path)
# Load images from the database # Load images from the database
self.load_full_list( self.load_full_list(
self.manager.get_all_objects(ImageFilenames, order_by_ref=ImageFilenames.filename), initial_load=True) 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) images = self.manager.get_all_objects(ImageFilenames, ImageFilenames.group_id == image_group.id)
for image in images: 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) self.manager.delete_object(ImageFilenames, image.id)
image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == image_group.id) image_groups = self.manager.get_all_objects(ImageGroups, ImageGroups.parent_id == image_group.id)
for group in image_groups: for group in image_groups:
@ -215,7 +223,7 @@ class ImageMediaItem(MediaManagerItem):
if row_item: if row_item:
item_data = row_item.data(0, QtCore.Qt.UserRole) item_data = row_item.data(0, QtCore.Qt.UserRole)
if isinstance(item_data, ImageFilenames): 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: if item_data.group_id == 0:
self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item)) self.list_view.takeTopLevelItem(self.list_view.indexOfTopLevelItem(row_item))
else: else:
@ -339,7 +347,7 @@ class ImageMediaItem(MediaManagerItem):
for imageFile in images: for imageFile in images:
log.debug('Loading image: %s', imageFile.filename) log.debug('Loading image: %s', imageFile.filename)
filename = os.path.split(imageFile.filename)[1] 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): if not os.path.exists(imageFile.filename):
icon = build_icon(':/general/general_delete.png') icon = build_icon(':/general/general_delete.png')
else: else:
@ -672,7 +680,16 @@ class ImageMediaItem(MediaManagerItem):
translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, ' translate('ImagePlugin.MediaItem', 'There was a problem replacing your background, '
'the image file "%s" no longer exists.') % filename) '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), files = self.manager.get_all_objects(ImageFilenames, filter_clause=ImageFilenames.filename.contains(string),
order_by_ref=ImageFilenames.filename) order_by_ref=ImageFilenames.filename)
results = [] results = []

View File

@ -61,10 +61,15 @@ class MediaMediaItem(MediaManagerItem):
self.background = False self.background = False
self.automatic = '' self.automatic = ''
super(MediaMediaItem, self).__init__(parent, plugin) super(MediaMediaItem, self).__init__(parent, plugin)
def setup_item(self):
"""
Do some additional setup.
"""
self.single_service_item = False self.single_service_item = False
self.has_search = True self.has_search = True
self.media_object = None self.media_object = None
self.display_controller = DisplayController(parent) self.display_controller = DisplayController(self.parent())
self.display_controller.controller_layout = QtGui.QVBoxLayout() self.display_controller.controller_layout = QtGui.QVBoxLayout()
self.media_controller.register_controller(self.display_controller) self.media_controller.register_controller(self.display_controller)
self.media_controller.set_controls_visible(self.display_controller, False) self.media_controller.set_controls_visible(self.display_controller, False)

View File

@ -52,14 +52,26 @@ class PresentationMediaItem(MediaManagerItem):
""" """
log.info('Presentations Media Item loaded') log.info('Presentations Media Item loaded')
def __init__(self, parent, plugin, icon, controllers): def __init__(self, parent, plugin, controllers):
""" """
Constructor. Setup defaults Constructor. Setup defaults
""" """
self.controllers = controllers
self.icon_path = 'presentations/presentation' self.icon_path = 'presentations/presentation'
self.Automatic = '' self.controllers = controllers
super(PresentationMediaItem, self).__init__(parent, plugin) 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.message_listener = MessageListener(self)
self.has_search = True self.has_search = True
self.single_service_item = False self.single_service_item = False
@ -68,14 +80,6 @@ class PresentationMediaItem(MediaManagerItem):
# Allow DnD from the desktop # Allow DnD from the desktop
self.list_view.activateDnD() 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): def build_file_mask_string(self):
""" """
Build the list of file extensions to be used in the Open file dialog. 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(): if self.controllers[item].enabled():
self.display_type_combo_box.addItem(item) self.display_type_combo_box.addItem(item)
if self.display_type_combo_box.count() > 1: 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) self.display_type_combo_box.setCurrentIndex(0)
if Settings().value(self.settings_section + '/override app') == QtCore.Qt.Checked: if Settings().value(self.settings_section + '/override app') == QtCore.Qt.Checked:
self.presentation_widget.show() self.presentation_widget.show()
@ -253,7 +257,7 @@ class PresentationMediaItem(MediaManagerItem):
(path, name) = os.path.split(filename) (path, name) = os.path.split(filename)
service_item.title = name service_item.title = name
if os.path.exists(filename): if os.path.exists(filename):
if service_item.processor == self.Automatic: if service_item.processor == self.automatic:
service_item.processor = self.findControllerByType(filename) service_item.processor = self.findControllerByType(filename)
if not service_item.processor: if not service_item.processor:
return False return False

View File

@ -109,8 +109,7 @@ class PresentationPlugin(Plugin):
""" """
Create the Media Manager List. Create the Media Manager List.
""" """
self.media_item = PresentationMediaItem( self.media_item = PresentationMediaItem(self.main_window.media_dock_manager.media_dock, self, self.controllers)
self.main_window.media_dock_manager.media_dock, self, self.icon, self.controllers)
def register_controllers(self, controller): def register_controllers(self, controller):
""" """

View File

@ -40,6 +40,8 @@ window.OpenLP = {
// defeat Safari bug // defeat Safari bug
targ = targ.parentNode; targ = targ.parentNode;
} }
var isSecure = false;
var isAuthorised = false;
return $(targ); return $(targ);
}, },
getSearchablePlugins: function () { getSearchablePlugins: function () {
@ -147,11 +149,13 @@ window.OpenLP = {
}, },
pollServer: function () { pollServer: function () {
$.getJSON( $.getJSON(
"/stage/poll", "/api/poll",
function (data, status) { function (data, status) {
var prevItem = OpenLP.currentItem; var prevItem = OpenLP.currentItem;
OpenLP.currentSlide = data.results.slide; OpenLP.currentSlide = data.results.slide;
OpenLP.currentItem = data.results.item; OpenLP.currentItem = data.results.item;
OpenLP.isSecure = data.results.isSecure;
OpenLP.isAuthorised = data.results.isAuthorised;
if ($("#service-manager").is(":visible")) { if ($("#service-manager").is(":visible")) {
if (OpenLP.currentService != data.results.service) { if (OpenLP.currentService != data.results.service) {
OpenLP.currentService = data.results.service; OpenLP.currentService = data.results.service;

View File

@ -26,7 +26,7 @@
window.OpenLP = { window.OpenLP = {
loadService: function (event) { loadService: function (event) {
$.getJSON( $.getJSON(
"/stage/service/list", "/api/service/list",
function (data, status) { function (data, status) {
OpenLP.nextSong = ""; OpenLP.nextSong = "";
$("#notes").html(""); $("#notes").html("");
@ -46,7 +46,7 @@ window.OpenLP = {
}, },
loadSlides: function (event) { loadSlides: function (event) {
$.getJSON( $.getJSON(
"/stage/controller/live/text", "/api/controller/live/text",
function (data, status) { function (data, status) {
OpenLP.currentSlides = data.results.slides; OpenLP.currentSlides = data.results.slides;
OpenLP.currentSlide = 0; OpenLP.currentSlide = 0;
@ -137,7 +137,7 @@ window.OpenLP = {
}, },
pollServer: function () { pollServer: function () {
$.getJSON( $.getJSON(
"/stage/poll", "/api/poll",
function (data, status) { function (data, status) {
OpenLP.updateClock(data); OpenLP.updateClock(data);
if (OpenLP.currentItem != data.results.item || if (OpenLP.currentItem != data.results.item ||

View File

@ -28,6 +28,7 @@
############################################################################### ###############################################################################
from .remotetab import RemoteTab from .remotetab import RemoteTab
from .httpserver import HttpServer from .httprouter import HttpRouter
from .httpserver import OpenLPServer
__all__ = ['RemoteTab', 'HttpServer'] __all__ = ['RemoteTab', 'OpenLPServer', 'HttpRouter']

View File

@ -0,0 +1,638 @@
# -*- 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": "<your alert 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 base64
import json
import logging
import os
import re
import urllib.request
import urllib.error
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
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 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 variables.
"""
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 = [
('^/$', {'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'^/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}),
(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}),
(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.settings_section = 'remotes'
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)
return
def process_http_request(self, url_path, *args):
"""
Common function to process HTTP requests
``url_path``
The requested URL.
``*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:
log.debug('Route "%s" matched "%s"', route, url_path)
args = []
for param in match.groups():
args.append(param)
return func, args
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_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.
"""
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()
self.wfile.write(bytes('<html><body>Sorry, an error occurred </body></html>', 'UTF-8'))
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 &amp; 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.do_not_found()
ext = os.path.splitext(file_name)[1]
html = None
if ext == '.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':
self.send_header('Content-type', 'text/css')
elif ext == '.js':
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/x-icon')
elif ext == '.png':
self.send_header('Content-type', 'image/png')
else:
self.send_header('Content-type', '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.do_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(),
'version': 2,
'isSecure': Settings().value(self.settings_section + '/authentication enabled'),
'isAuthorised': self.authorised
}
self.do_json_header()
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
}
self.do_json_header()
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))
}
self.do_json_header()
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)
self.do_json_header()
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.do_http_error()
text = urllib.parse.unquote(text)
self.alerts_manager.emit(QtCore.SIGNAL('alerts_text'), [text])
success = True
else:
success = False
self.do_json_header()
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
self.do_json_header()
return json.dumps(json_data).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 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:
self.live_controller.emit(QtCore.SIGNAL(event))
json_data = {'results': {'success': True}}
self.do_json_header()
return json.dumps(json_data).encode()
def service_list(self):
"""
Handles requests for service items in the service manager
``action``
The action to perform.
"""
self.do_json_header()
return json.dumps({'results': {'items': self._get_service_items()}}).encode()
def service(self, action):
"""
Handles requests for service items in the service manager
``action``
The action to perform.
"""
event = 'servicemanager_%s_item' % action
if self.request_data:
try:
data = json.loads(self.request_data)['request']['id']
except KeyError:
return self.do_http_error()
self.service_manager.emit(QtCore.SIGNAL(event), data)
else:
Registry().execute(event)
self.do_json_header()
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'])])
self.do_json_header()
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.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:
results = plugin.media_item.search(text, False)
else:
results = []
self.do_json_header()
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.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.do_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.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.do_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)

View File

@ -31,661 +31,122 @@
The :mod:`http` module contains the API web server. This is a lightweight web 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 server used by remotes to interact with OpenLP. It uses JSON to communicate with
the remotes. 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": "<your alert 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 ssl
import logging import socket
import os import os
import re import logging
import urllib.request, urllib.parse, urllib.error from urllib.parse import urlparse, parse_qs
import urllib.parse
import cherrypy
from mako.template import Template
from PyQt4 import QtCore from PyQt4 import QtCore
from openlp.core.lib import Registry, Settings, PluginStatus, StringContent, image_to_byte from openlp.core.lib import Settings
from openlp.core.utils import AppLocation, translate from openlp.core.utils import AppLocation
from hashlib import sha1 from openlp.plugins.remotes.lib import HttpRouter
from socketserver import BaseServer, ThreadingMixIn
from http.server import BaseHTTPRequestHandler, HTTPServer
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def make_sha_hash(password): class CustomHandler(BaseHTTPRequestHandler, HttpRouter):
""" """
Create an encrypted password for the given password. 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.
""" """
log.debug("make_sha_hash")
return sha1(password.encode()).hexdigest() def do_POST(self):
"""
Present pages / data and invoke URL level user authentication.
"""
self.do_post_processor()
def do_GET(self):
"""
Present pages / data and invoke URL level user authentication.
"""
self.do_post_processor()
def fetch_password(username): class ThreadingHTTPServer(ThreadingMixIn, HTTPServer):
""" pass
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 HttpServer(object): class HttpThread(QtCore.QThread):
""" """
Ability to control OpenLP via a web browser. A special Qt thread class to allow the HTTP server to run at the same time as the UI.
This class controls the Cherrypy server and configuration.
""" """
_cp_config = { def __init__(self, server):
'tools.sessions.on': True, """
'tools.auth.on': True 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): 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') log.debug('Initialise httpserver')
self.settings_section = 'remotes' self.settings_section = 'remotes'
self.router = HttpRouter() self.http_thread = HttpThread(self)
self.http_thread.start()
def start_server(self): def start_server(self):
""" """
Start the http server based on configuration. Start the correct server and save the handler
"""
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'): if Settings().value(self.settings_section + '/https enabled'):
port = Settings().value(self.settings_section + '/https port') port = Settings().value(self.settings_section + '/https port')
address = Settings().value(self.settings_section + '/ip address') self.httpd = HTTPSServer((address, port), CustomHandler)
local_data = AppLocation.get_directory(AppLocation.DataDir) log.debug('Started ssl httpd...')
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')})
else: else:
port = Settings().value(self.settings_section + '/port') port = Settings().value(self.settings_section + '/port')
address = Settings().value(self.settings_section + '/ip address') self.httpd = ThreadingHTTPServer((address, port), CustomHandler)
cherrypy.config.update({'server.socket_host': str(address)}) log.debug('Started non ssl httpd...')
cherrypy.config.update({'server.socket_port': port}) self.httpd.serve_forever()
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
class Public(object): def stop_server(self):
""" """
Main access class with may have security enabled on it. Stop the server
""" """
@cherrypy.expose self.http_thread.exit(0)
def default(self, *args, **kwargs): self.httpd = None
self.router.request_data = None log.debug('Stopped the server.')
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()
class HttpRouter(object): class HTTPSServer(HTTPServer):
""" def __init__(self, address, handler):
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 Initialise the secure handlers for the SSL server if required.s
""" """
self.routes = [ BaseServer.__init__(self, address, handler)
('^/$', self.serve_file), local_data = AppLocation.get_directory(AppLocation.DataDir)
('^/(stage)$', self.serve_file), self.socket = ssl.SSLSocket(
('^/(main)$', self.serve_file), sock=socket.socket(self.address_family, self.socket_type),
(r'^/files/(.*)$', self.serve_file), ssl_version=ssl.PROTOCOL_TLSv1,
(r'^/api/poll$', self.poll), certfile=os.path.join(local_data, 'remotes', 'openlp.crt'),
(r'^/stage/poll$', self.poll), keyfile=os.path.join(local_data, 'remotes', 'openlp.key'),
(r'^/main/poll$', self.main_poll), server_side=True)
(r'^/main/image$', self.main_image), self.server_bind()
(r'^/api/controller/(live|preview)/(.*)$', self.controller), self.server_activate()
(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 &amp; 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'<html><body>Sorry, an error occurred </body></html>']
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)

View File

@ -207,8 +207,8 @@ class RemoteTab(SettingsTab):
https_url_temp = https_url + 'stage' https_url_temp = https_url + 'stage'
self.stage_url.setText('<a href="%s">%s</a>' % (http_url_temp, http_url_temp)) self.stage_url.setText('<a href="%s">%s</a>' % (http_url_temp, http_url_temp))
self.stage_https_url.setText('<a href="%s">%s</a>' % (https_url_temp, https_url_temp)) self.stage_https_url.setText('<a href="%s">%s</a>' % (https_url_temp, https_url_temp))
http_url_temp = http_url + 'live' http_url_temp = http_url + 'main'
https_url_temp = https_url + 'live' https_url_temp = https_url + 'main'
self.live_url.setText('<a href="%s">%s</a>' % (http_url_temp, http_url_temp)) self.live_url.setText('<a href="%s">%s</a>' % (http_url_temp, http_url_temp))
self.live_https_url.setText('<a href="%s">%s</a>' % (https_url_temp, https_url_temp)) self.live_https_url.setText('<a href="%s">%s</a>' % (https_url_temp, https_url_temp))

View File

@ -28,11 +28,10 @@
############################################################################### ###############################################################################
import logging import logging
import time
from PyQt4 import QtGui
from openlp.core.lib import Plugin, StringContent, translate, build_icon 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__) log = logging.getLogger(__name__)
@ -67,8 +66,7 @@ class RemotesPlugin(Plugin):
""" """
log.debug('initialise') log.debug('initialise')
super(RemotesPlugin, self).initialise() super(RemotesPlugin, self).initialise()
self.server = HttpServer() self.server = OpenLPServer()
self.server.start_server()
def finalise(self): def finalise(self):
""" """
@ -77,7 +75,7 @@ class RemotesPlugin(Plugin):
log.debug('finalise') log.debug('finalise')
super(RemotesPlugin, self).finalise() super(RemotesPlugin, self).finalise()
if self.server: if self.server:
self.server.close() self.server.stop_server()
self.server = None self.server = None
def about(self): def about(self):
@ -109,5 +107,6 @@ class RemotesPlugin(Plugin):
Called when Config is changed to restart the server on new address or port Called when Config is changed to restart the server on new address or port
""" """
log.debug('remote config changed') log.debug('remote config changed')
self.main_window.information_message(translate('RemotePlugin', 'Configuration Change'), self.finalise()
translate('RemotePlugin', 'OpenLP will need to be restarted for the Remote changes to become active.')) time.sleep(0.5)
self.initialise()

View File

@ -72,6 +72,11 @@ class SongMediaItem(MediaManagerItem):
def __init__(self, parent, plugin): def __init__(self, parent, plugin):
self.icon_path = 'songs/song' self.icon_path = 'songs/song'
super(SongMediaItem, self).__init__(parent, plugin) super(SongMediaItem, self).__init__(parent, plugin)
def setup_item(self):
"""
Do some additional setup.
"""
self.single_service_item = False self.single_service_item = False
# Holds information about whether the edit is remotely triggered and which Song is required. # Holds information about whether the edit is remotely triggered and which Song is required.
self.remote_song = -1 self.remote_song = -1

View File

@ -44,10 +44,11 @@ from distutils.version import LooseVersion
try: try:
import nose import nose
except ImportError: except ImportError:
pass nose = None
IS_WIN = sys.platform.startswith('win') IS_WIN = sys.platform.startswith('win')
VERS = { VERS = {
'Python': '3.0', 'Python': '3.0',
'PyQt4': '4.6', 'PyQt4': '4.6',
@ -84,26 +85,39 @@ MODULES = [
'enchant', 'enchant',
'bs4', 'bs4',
'mako', 'mako',
'cherrypy',
'uno', 'uno',
] ]
OPTIONAL_MODULES = [ OPTIONAL_MODULES = [
('MySQLdb', ' (MySQL support)'), ('MySQLdb', '(MySQL support)', True),
('psycopg2', ' (PostgreSQL support)'), ('psycopg2', '(PostgreSQL support)', True),
('nose', ' (testing framework)'), ('nose', '(testing framework)', True),
('mock', ' (testing module)'), ('mock', '(testing module)', sys.version_info[1] < 3),
] ]
w = sys.stdout.write w = sys.stdout.write
def check_vers(version, required, text): 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): if not isinstance(version, str):
version = '.'.join(map(str, version)) version = '.'.join(map(str, version))
if not isinstance(required, str): if not isinstance(required, str):
required = '.'.join(map(str, required)) required = '.'.join(map(str, required))
w(' %s >= %s ... ' % (text, required)) w(' %s >= %s ... ' % (text, required) + space)
if LooseVersion(version) >= LooseVersion(required): if LooseVersion(version) >= LooseVersion(required):
w(version + os.linesep) w(version + os.linesep)
return True return True
@ -111,13 +125,39 @@ def check_vers(version, required, text):
w('FAIL' + os.linesep) w('FAIL' + os.linesep)
return False 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): def print_vers_fail(required, text):
print(' %s >= %s ... FAIL' % (text, required)) print(' %s >= %s ... FAIL' % (text, required))
def verify_python(): def verify_python():
if not check_vers(list(sys.version_info), VERS['Python'], text='Python'): if not check_vers(list(sys.version_info), VERS['Python'], text='Python'):
exit(1) exit(1)
def verify_versions(): def verify_versions():
print('Verifying version of modules...') print('Verifying version of modules...')
try: try:
@ -138,17 +178,11 @@ def verify_versions():
except ImportError: except ImportError:
print_vers_fail(VERS['enchant'], 'enchant') 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)... ') w('Enchant (spell checker)... ')
try: try:
import enchant import enchant
@ -160,39 +194,43 @@ def verify_pyenchant():
except ImportError: except ImportError:
w('FAIL' + os.linesep) w('FAIL' + os.linesep)
def verify_pyqt():
def print_qt_image_formats():
"""
Print out the image formats that Qt4 supports.
"""
w('Qt4 image formats... ') w('Qt4 image formats... ')
try: try:
from PyQt4 import QtGui from PyQt4 import QtGui
read_f = ', '.join([str(format).lower() read_f = ', '.join([bytes(fmt).decode().lower() for fmt in QtGui.QImageReader.supportedImageFormats()])
for format in QtGui.QImageReader.supportedImageFormats()]) write_f = ', '.join([bytes(fmt).decode().lower() for fmt in QtGui.QImageWriter.supportedImageFormats()])
write_f = ', '.join([str(format).lower()
for format in QtGui.QImageWriter.supportedImageFormats()])
w(os.linesep) w(os.linesep)
print(' read: %s' % read_f) print(' read: %s' % read_f)
print(' write: %s' % write_f) print(' write: %s' % write_f)
except ImportError: except ImportError:
w('FAIL' + os.linesep) w('FAIL' + os.linesep)
def main():
verify_python()
def main():
"""
Run the dependency checker.
"""
print('Checking Python version...')
verify_python()
print('Checking for modules...') print('Checking for modules...')
for m in MODULES: for m in MODULES:
check_module(m) check_module(m)
print('Checking for optional modules...') print('Checking for optional modules...')
for m in 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: if IS_WIN:
print('Checking for Windows specific modules...') print('Checking for Windows specific modules...')
for m in WIN32_MODULES: for m in WIN32_MODULES:
check_module(m) check_module(m)
verify_versions() verify_versions()
verify_pyqt() print_qt_image_formats()
verify_pyenchant() print_enchant_backends_and_languages()
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -7,7 +7,13 @@ sip.setapi('QTime', 2)
sip.setapi('QUrl', 2) sip.setapi('QUrl', 2)
sip.setapi('QVariant', 2) sip.setapi('QVariant', 2)
import sys
from PyQt4 import QtGui from PyQt4 import QtGui
if sys.version_info[1] >= 3:
from unittest.mock import patch, MagicMock
else:
from mock import patch, MagicMock
# Only one QApplication can be created. Use QtGui.QApplication.instance() when you need to "create" a QApplication. # Only one QApplication can be created. Use QtGui.QApplication.instance() when you need to "create" a QApplication.
application = QtGui.QApplication([]) application = QtGui.QApplication([])

View File

@ -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. Package to test the openlp.core.lib package.
""" """
from unittest import TestCase from unittest import TestCase
from mock import MagicMock, patch
from sqlalchemy.pool import NullPool from sqlalchemy.pool import NullPool
from sqlalchemy.orm.scoping import ScopedSession from sqlalchemy.orm.scoping import ScopedSession
from sqlalchemy import MetaData from sqlalchemy import MetaData
from openlp.core.lib.db import init_db, get_upgrade_op from openlp.core.lib.db import init_db, get_upgrade_op
from tests.functional import patch, MagicMock
class TestDB(TestCase): class TestDB(TestCase):

View File

@ -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. Package to test the openlp.core.lib.formattingtags package.
""" """
import copy import copy
from unittest import TestCase from unittest import TestCase
from mock import patch
from openlp.core.lib import FormattingTags from openlp.core.lib import FormattingTags
from tests.functional import patch
TAG = { TAG = {

View File

@ -1,15 +1,41 @@
""" # -*- coding: utf-8 -*-
Package to test the openlp.core.ui package. # 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 import os
from unittest import TestCase from unittest import TestCase
from PyQt4 import QtCore, QtGui from PyQt4 import QtGui
from openlp.core.lib import Registry, ImageManager, ScreenList from openlp.core.lib import Registry, ImageManager, ScreenList
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources')) TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))

View File

@ -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. Package to test the openlp.core.lib package.
""" """
@ -6,22 +34,20 @@ import os
from unittest import TestCase from unittest import TestCase
from datetime import datetime, timedelta from datetime import datetime, timedelta
from mock import MagicMock, patch
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.lib import str_to_bool, create_thumb, translate, check_directory_exists, get_text_file_string, \ 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 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')) TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
class TestLib(TestCase): 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 # GIVEN: A boolean value set to true
true_boolean = True true_boolean = True
@ -30,9 +56,13 @@ class TestLib(TestCase):
true_result = str_to_bool(true_boolean) true_result = str_to_bool(true_boolean)
# THEN: We should get back a True bool # THEN: We should get back a True bool
assert isinstance(true_result, bool), 'The result should be a boolean' self.assertIsInstance(true_result, bool, 'The result should be a boolean')
assert true_result is True, 'The result should be True' 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 # GIVEN: A boolean value set to false
false_boolean = False false_boolean = False
@ -40,12 +70,12 @@ class TestLib(TestCase):
false_result = str_to_bool(false_boolean) false_result = str_to_bool(false_boolean)
# THEN: We should get back a True bool # THEN: We should get back a True bool
assert isinstance(false_result, bool), 'The result should be a boolean' self.assertIsInstance(false_result, bool, 'The result should be a boolean')
assert false_result is False, 'The result should be True' 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 # GIVEN: An integer value
int_string = 1 int_string = 1
@ -54,8 +84,12 @@ class TestLib(TestCase):
int_result = str_to_bool(int_string) int_result = str_to_bool(int_string)
# THEN: we should get back a false # 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 # GIVEN: An string value with completely invalid input
invalid_string = 'my feet are wet' invalid_string = 'my feet are wet'
@ -63,11 +97,11 @@ class TestLib(TestCase):
str_result = str_to_bool(invalid_string) str_result = str_to_bool(invalid_string)
# THEN: we should get back a false # 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" # GIVEN: A string set to "false"
false_string = 'false' false_string = 'false'
@ -76,8 +110,12 @@ class TestLib(TestCase):
false_result = str_to_bool(false_string) false_result = str_to_bool(false_string)
# THEN: we should get back a false # 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" # GIVEN: An string set to "NO"
no_string = 'NO' no_string = 'NO'
@ -85,11 +123,11 @@ class TestLib(TestCase):
str_result = str_to_bool(no_string) str_result = str_to_bool(no_string)
# THEN: we should get back a false # 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" # GIVEN: A string set to "True"
true_string = 'True' true_string = 'True'
@ -98,8 +136,12 @@ class TestLib(TestCase):
true_result = str_to_bool(true_string) true_result = str_to_bool(true_string)
# THEN: we should get back a true # 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" # GIVEN: An string set to "yes"
yes_string = 'yes' yes_string = 'yes'
@ -107,7 +149,7 @@ class TestLib(TestCase):
str_result = str_to_bool(yes_string) str_result = str_to_bool(yes_string)
# THEN: we should get back a true # 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): def translate_test(self):
""" """
@ -126,7 +168,7 @@ class TestLib(TestCase):
# THEN: the translated string should be returned, and the mocked function should have been called # 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) 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): def check_directory_exists_test(self):
""" """
@ -143,7 +185,7 @@ class TestLib(TestCase):
# THEN: Only os.path.exists should have been called # THEN: Only os.path.exists should have been called
mocked_exists.assert_called_with(directory_to_check) 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 # WHEN: os.path.exists returns False and we check the directory exists
mocked_exists.return_value = False mocked_exists.return_value = False
@ -181,13 +223,14 @@ class TestLib(TestCase):
# THEN: The result should be False # THEN: The result should be False
mocked_isfile.assert_called_with(filename) 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): def get_text_file_string_read_error_test(self):
""" """
Test the get_text_file_string() method when a read error happens 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 # GIVEN: A mocked-out open() which raises an exception and isfile returns True
filename = 'testfile.txt' filename = 'testfile.txt'
mocked_isfile.return_value = True mocked_isfile.return_value = True
@ -199,13 +242,13 @@ class TestLib(TestCase):
# THEN: None should be returned # THEN: None should be returned
mocked_isfile.assert_called_with(filename) mocked_isfile.assert_called_with(filename)
mocked_open.assert_called_with(filename, 'r') 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): def get_text_file_string_decode_error_test(self):
""" """
Test the get_text_file_string() method when the contents cannot be decoded 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): def build_icon_with_qicon_test(self):
""" """
@ -220,7 +263,7 @@ class TestLib(TestCase):
result = build_icon(mocked_icon) result = build_icon(mocked_icon)
# THEN: The result should be our mocked QIcon # 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): def build_icon_with_resource_test(self):
""" """
@ -242,7 +285,7 @@ class TestLib(TestCase):
MockedQPixmap.assert_called_with(resource_uri) 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 # 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. # 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): def image_to_byte_test(self):
""" """
@ -267,7 +310,8 @@ class TestLib(TestCase):
mocked_buffer.open.assert_called_with('writeonly') mocked_buffer.open.assert_called_with('writeonly')
mocked_image.save.assert_called_with(mocked_buffer, "PNG") mocked_image.save.assert_called_with(mocked_buffer, "PNG")
mocked_byte_array.toBase64.assert_called_with() 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): def create_thumb_with_size_test(self):
""" """
@ -286,16 +330,16 @@ class TestLib(TestCase):
pass pass
# Only continue when the thumb does not exist. # 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. # WHEN: Create the thumb.
icon = create_thumb(image_path, thumb_path, size=thumb_size) icon = create_thumb(image_path, thumb_path, size=thumb_size)
# THEN: Check if the thumb was created. # THEN: Check if the thumb was created.
assert os.path.exists(thumb_path), 'Test was not ran, because the thumb already exists.' self.assertTrue(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.' self.assertIsInstance(icon, QtGui.QIcon, 'The icon should be a QIcon')
assert not icon.isNull(), 'The icon should not be null.' self.assertFalse(icon.isNull(), 'The icon should not be null')
assert QtGui.QImageReader(thumb_path).size() == thumb_size, 'The thumb should have the given size.' 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. # Remove the thumb so that the test actually tests if the thumb will be created.
try: try:
@ -318,7 +362,7 @@ class TestLib(TestCase):
# THEN: The selectedIndexes function should have been called and the result should be true # THEN: The selectedIndexes function should have been called and the result should be true
mocked_list_widget.selectedIndexes.assert_called_with() 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): def check_item_selected_false_test(self):
""" """
@ -326,7 +370,7 @@ class TestLib(TestCase):
""" """
# GIVEN: A mocked out QtGui module and a list widget with selected indexes # GIVEN: A mocked out QtGui module and a list widget with selected indexes
with patch('openlp.core.lib.QtGui') as MockedQtGui, \ 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_translate.return_value = 'mocked translate'
mocked_list_widget = MagicMock() mocked_list_widget = MagicMock()
mocked_list_widget.selectedIndexes.return_value = False mocked_list_widget.selectedIndexes.return_value = False
@ -339,7 +383,7 @@ class TestLib(TestCase):
# THEN: The selectedIndexes function should have been called and the result should be true # THEN: The selectedIndexes function should have been called and the result should be true
mocked_list_widget.selectedIndexes.assert_called_with() mocked_list_widget.selectedIndexes.assert_called_with()
MockedQtGui.QMessageBox.information.assert_called_with('parent', 'mocked translate', 'message') 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): def clean_tags_test(self):
""" """
@ -361,7 +405,7 @@ class TestLib(TestCase):
result_string = clean_tags(string_to_pass) result_string = clean_tags(string_to_pass)
# THEN: The strings should be identical. # 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): def expand_tags_test(self):
""" """
@ -400,7 +444,7 @@ class TestLib(TestCase):
result_string = expand_tags(string_to_pass) result_string = expand_tags(string_to_pass)
# THEN: The strings should be identical. # 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): def validate_thumb_file_does_not_exist_test(self):
""" """

View File

@ -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. Package to test the openlp.core.lib.pluginmanager package.
""" """
from unittest import TestCase from unittest import TestCase
from mock import MagicMock
from openlp.core.lib.pluginmanager import PluginManager from openlp.core.lib.pluginmanager import PluginManager
from openlp.core.lib import Settings, Registry, PluginStatus from openlp.core.lib import Settings, Registry, PluginStatus
from tests.functional import MagicMock
class TestPluginManager(TestCase): class TestPluginManager(TestCase):
@ -42,8 +69,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_media_manager() plugin_manager.hook_media_manager()
# THEN: The create_media_manager_item() method should have been called # THEN: The create_media_manager_item() method should have been called
assert mocked_plugin.create_media_manager_item.call_count == 0, \ 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): def hook_media_manager_with_active_plugin_test(self):
""" """
@ -75,8 +102,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_settings_tabs() plugin_manager.hook_settings_tabs()
# THEN: The hook_settings_tabs() method should have been called # THEN: The hook_settings_tabs() method should have been called
assert mocked_plugin.create_media_manager_item.call_count == 0, \ 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): def hook_settings_tabs_with_disabled_plugin_and_mocked_form_test(self):
""" """
@ -95,8 +122,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_settings_tabs() plugin_manager.hook_settings_tabs()
# THEN: The create_settings_tab() method should not have been called, but the plugins lists should be the same # 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, \ 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, 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')
@ -117,10 +144,10 @@ class TestPluginManager(TestCase):
plugin_manager.hook_settings_tabs() plugin_manager.hook_settings_tabs()
# THEN: The create_media_manager_item() method should have been called with the mocked settings form # 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, \ 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(mocked_settings_form.plugin_manager.plugins, plugin_manager.plugins, 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): def hook_settings_tabs_with_active_plugin_and_no_form_test(self):
""" """
@ -152,8 +179,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_import_menu() plugin_manager.hook_import_menu()
# THEN: The create_media_manager_item() method should have been called # THEN: The create_media_manager_item() method should have been called
assert mocked_plugin.add_import_menu_item.call_count == 0, \ 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): def hook_import_menu_with_active_plugin_test(self):
""" """
@ -185,8 +212,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_export_menu() plugin_manager.hook_export_menu()
# THEN: The add_export_menu_Item() method should not have been called # THEN: The add_export_menu_Item() method should not have been called
assert mocked_plugin.add_export_menu_Item.call_count == 0, \ 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): def hook_export_menu_with_active_plugin_test(self):
""" """
@ -219,8 +246,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_upgrade_plugin_settings(settings) plugin_manager.hook_upgrade_plugin_settings(settings)
# THEN: The upgrade_settings() method should not have been called # THEN: The upgrade_settings() method should not have been called
assert mocked_plugin.upgrade_settings.call_count == 0, \ 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): def hook_upgrade_plugin_settings_with_active_plugin_test(self):
""" """
@ -253,8 +280,8 @@ class TestPluginManager(TestCase):
plugin_manager.hook_tools_menu() plugin_manager.hook_tools_menu()
# THEN: The add_tools_menu_item() method should have been called # THEN: The add_tools_menu_item() method should have been called
assert mocked_plugin.add_tools_menu_item.call_count == 0, \ 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): def hook_tools_menu_with_active_plugin_test(self):
""" """
@ -288,7 +315,7 @@ class TestPluginManager(TestCase):
# THEN: The is_active() method should have been called, and initialise() method should NOT have been called # THEN: The is_active() method should have been called, and initialise() method should NOT have been called
mocked_plugin.is_active.assert_called_with() 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): def initialise_plugins_with_active_plugin_test(self):
""" """
@ -324,7 +351,7 @@ class TestPluginManager(TestCase):
# THEN: The is_active() method should have been called, and initialise() method should NOT have been called # THEN: The is_active() method should have been called, and initialise() method should NOT have been called
mocked_plugin.is_active.assert_called_with() 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): def finalise_plugins_with_active_plugin_test(self):
""" """
@ -392,8 +419,8 @@ class TestPluginManager(TestCase):
# THEN: The isActive() method should have been called, and initialise() method should NOT have been called # THEN: The isActive() method should have been called, and initialise() method should NOT have been called
mocked_plugin.is_active.assert_called_with() mocked_plugin.is_active.assert_called_with()
assert mocked_plugin.new_service_created.call_count == 0,\ 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): def new_service_created_with_active_plugin_test(self):
""" """

View File

@ -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. Package to test the openlp.core.lib package.
""" """
import os import os
from unittest import TestCase from unittest import TestCase
from mock import MagicMock
from openlp.core.lib import Registry from openlp.core.lib import Registry
from tests.functional import MagicMock
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources')) TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))

View File

@ -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. Package to test the openlp.core.lib.screenlist package.
""" """
from unittest import TestCase from unittest import TestCase
from mock import MagicMock
from PyQt4 import QtGui, QtCore from PyQt4 import QtGui, QtCore
from openlp.core.lib import Registry, ScreenList from openlp.core.lib import Registry, ScreenList
from tests.functional import MagicMock
SCREEN = { SCREEN = {
'primary': False, 'primary': False,
@ -55,5 +81,6 @@ class TestScreenList(TestCase):
# THEN: The screen should have been added and the screens should be identical # THEN: The screen should have been added and the screens should be identical
new_screen_count = len(self.screens.screen_list) new_screen_count = len(self.screens.screen_list)
assert old_screen_count + 1 == new_screen_count, 'The new_screens list should be bigger' self.assertEqual(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(SCREEN, self.screens.screen_list.pop(),
'The 2nd screen should be identical to the first screen')

View File

@ -1,16 +1,40 @@
# -*- coding: utf-8 -*- # -*- 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 os
import json
import tempfile
from unittest import TestCase from unittest import TestCase
from mock import MagicMock, patch
from openlp.core.lib import ItemCapabilities, ServiceItem, Registry from openlp.core.lib import ItemCapabilities, ServiceItem, Registry
from lxml import objectify, etree 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'\ VERSE = 'The Lord said to {r}Noah{/r}: \n'\
'There\'s gonna be a {su}floody{/su}, {sb}floody{/sb}\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}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' 'r{/pk}{o}e{/o}{pp}n{/pp} of the Lord\n'
FOOTER = ['Arky Arky (Unknown)', 'Public Domain', 'CCLI 123456'] FOOTER = ['Arky Arky (Unknown)', 'Public Domain', 'CCLI 123456']
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources')) TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'resources'))
@ -36,7 +59,7 @@ class TestServiceItem(TestCase):
Registry().register('renderer', mocked_renderer) Registry().register('renderer', mocked_renderer)
Registry().register('image_manager', MagicMock()) Registry().register('image_manager', MagicMock())
def serviceitem_basic_test(self): def service_item_basic_test(self):
""" """
Test the Service Item - basic test Test the Service Item - basic test
""" """
@ -46,10 +69,10 @@ class TestServiceItem(TestCase):
service_item = ServiceItem(None) service_item = ServiceItem(None)
# THEN: We should get back a valid service item # THEN: We should get back a valid service item
assert service_item.is_valid is True, 'The new service item should be valid' self.assertTrue(service_item.is_valid, '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.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 Test the Service Item - adding a custom slide from a saved service
""" """
@ -57,24 +80,29 @@ class TestServiceItem(TestCase):
service_item = ServiceItem(None) service_item = ServiceItem(None)
service_item.add_icon = MagicMock() service_item.add_icon = MagicMock()
# WHEN: adding a custom from a saved Service # WHEN: We add a custom from a saved service
line = self.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) service_item.set_from_service(line)
# THEN: We should get back a valid service item # THEN: We should get back a valid service item
assert service_item.is_valid is True, 'The new service item should be valid' self.assertTrue(service_item.is_valid, 'The new service item should be valid')
assert len(service_item._display_frames) == 0, 'The service item should have no display frames' assert_length(0, service_item._display_frames, 'The service item should have no display frames')
assert len(service_item.capabilities) == 5, 'There should be 5 default custom item capabilities' assert_length(5, service_item.capabilities, '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'
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 Test the Service Item - adding an image from a saved service
""" """
@ -87,29 +115,34 @@ class TestServiceItem(TestCase):
service_item.add_icon = MagicMock() service_item.add_icon = MagicMock()
# WHEN: adding an image from a saved Service and mocked exists # 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: with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
mocked_exists.return_value = True mocked_exists.return_value = True
service_item.set_from_service(line, TEST_PATH) service_item.set_from_service(line, TEST_PATH)
# THEN: We should get back a valid service item # THEN: We should get back a valid service item
assert service_item.is_valid is True, 'The new service item should be valid' self.assertTrue(service_item.is_valid, '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' self.assertEqual(test_file, service_item.get_rendered_frame(0),
assert service_item.get_frames()[0] == frame_array, 'The return should match frame array1' 'The first frame should match the path to the image')
assert service_item.get_frame_path(0) == test_file, 'The frame path should match the full path to the image' self.assertEqual(frame_array, service_item.get_frames()[0],
assert service_item.get_frame_title(0) == image_name, 'The frame title should match the image name' 'The return should match frame array1')
assert service_item.get_display_title() == image_name, 'The display title should match the first image name' self.assertEqual(test_file, service_item.get_frame_path(0),
assert service_item.is_image() is True, 'This service item should be of an "image" type' 'The frame path should match the full path to the image')
assert service_item.is_capable(ItemCapabilities.CanMaintain) is True, \ self.assertEqual(image_name, service_item.get_frame_title(0),
'This service item should be able to be Maintained' 'The frame title should match the image name')
assert service_item.is_capable(ItemCapabilities.CanPreview) is True, \ self.assertEqual(image_name, service_item.get_display_title(),
'This service item should be able to be be Previewed' 'The display title should match the first image name')
assert service_item.is_capable(ItemCapabilities.CanLoop) is True, \ self.assertTrue(service_item.is_image(), 'This service item should be of an "image" type')
'This service item should be able to be run in a can be made to Loop' self.assertTrue(service_item.is_capable(ItemCapabilities.CanMaintain),
assert service_item.is_capable(ItemCapabilities.CanAppend) is True, \ 'This service item should be able to be Maintained')
'This service item should be able to have new items added to it' 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 Test the Service Item - adding an image from a saved local service
""" """
@ -128,50 +161,42 @@ class TestServiceItem(TestCase):
service_item2.add_icon = MagicMock() service_item2.add_icon = MagicMock()
# WHEN: adding an image from a saved Service and mocked exists # WHEN: adding an image from a saved Service and mocked exists
line = self.convert_file_service_item('serviceitem_image_2.osj') line = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj')
line2 = self.convert_file_service_item('serviceitem_image_2.osj', 1) line2 = convert_file_service_item(TEST_PATH, 'serviceitem_image_2.osj', 1)
with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists: with patch('openlp.core.ui.servicemanager.os.path.exists') as mocked_exists:
mocked_exists.return_value = True mocked_exists.return_value = True
service_item2.set_from_service(line2) service_item2.set_from_service(line2)
service_item.set_from_service(line) service_item.set_from_service(line)
# THEN: We should get back a valid service item # THEN: We should get back a valid service item
# This test is copied from service_item.py, but is changed since to conform to # 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. # 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' self.assertTrue(service_item.is_valid, 'The first service item should be valid')
assert service_item2.is_valid is True, 'The second service item should be valid' self.assertTrue(service_item2.is_valid, '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' self.assertEqual(test_file1, service_item.get_rendered_frame(0),
assert service_item2.get_rendered_frame(0) == test_file2, 'The Second frame should match the path to the image' 'The first frame should match the path to the image')
assert service_item.get_frames()[0] == frame_array1, 'The return should match the frame array1' self.assertEqual(test_file2, service_item2.get_rendered_frame(0),
assert service_item2.get_frames()[0] == frame_array2, 'The return should match the frame array2' 'The Second frame should match the path to the image')
assert service_item.get_frame_path(0) == test_file1, 'The frame path should match the full path to the image' self.assertEqual(frame_array1, service_item.get_frames()[0], 'The return should match the frame array1')
assert service_item2.get_frame_path(0) == test_file2, 'The frame path should match the full path to the image' self.assertEqual(frame_array2, service_item2.get_frames()[0], 'The return should match the frame array2')
assert service_item.get_frame_title(0) == image_name1, 'The 1st frame title should match the image name' self.assertEqual(test_file1, service_item.get_frame_path(0),
assert service_item2.get_frame_title(0) == image_name2, 'The 2nd frame title should match the image name' 'The frame path should match the full path to the image')
assert service_item.title.lower() == service_item.name, \ self.assertEqual(test_file2, service_item2.get_frame_path(0),
'The plugin name should match the display title, as there are > 1 Images' 'The frame path should match the full path to the image')
assert service_item.is_image() is True, 'This service item should be of an "image" type' self.assertEqual(image_name1, service_item.get_frame_title(0),
assert service_item.is_capable(ItemCapabilities.CanMaintain) is True, \ 'The 1st frame title should match the image name')
'This service item should be able to be Maintained' self.assertEqual(image_name2, service_item2.get_frame_title(0),
assert service_item.is_capable(ItemCapabilities.CanPreview) is True, \ 'The 2nd frame title should match the image name')
'This service item should be able to be be Previewed' self.assertEqual(service_item.name, service_item.title.lower(),
assert service_item.is_capable(ItemCapabilities.CanLoop) is True, \ 'The plugin name should match the display title, as there are > 1 Images')
'This service item should be able to be run in a can be made to Loop' self.assertTrue(service_item.is_image(), 'This service item should be of an "image" type')
assert service_item.is_capable(ItemCapabilities.CanAppend) is True, \ self.assertTrue(service_item.is_capable(ItemCapabilities.CanMaintain),
'This service item should be able to have new items added to it' 'This service item should be able to be Maintained')
self.assertTrue(service_item.is_capable(ItemCapabilities.CanPreview),
def convert_file_service_item(self, name, row=0): 'This service item should be able to be be Previewed')
service_file = os.path.join(TEST_PATH, name) self.assertTrue(service_item.is_capable(ItemCapabilities.CanLoop),
try: 'This service item should be able to be run in a can be made to Loop')
open_file = open(service_file, 'r') self.assertTrue(service_item.is_capable(ItemCapabilities.CanAppend),
items = json.load(open_file) 'This service item should be able to have new items added to it')
first_line = items[row]
except IOError:
first_line = ''
finally:
open_file.close()
return first_line

View File

@ -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 import os
from unittest import TestCase from unittest import TestCase
from tempfile import mkstemp from tempfile import mkstemp
from openlp.core.lib import Settings
from PyQt4 import QtGui from PyQt4 import QtGui
from openlp.core.lib import Settings
class TestSettings(TestCase): class TestSettings(TestCase):
""" """
@ -40,13 +68,13 @@ class TestSettings(TestCase):
default_value = Settings().value('core/has run wizard') default_value = Settings().value('core/has run wizard')
# THEN the default value is returned # 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 # WHEN a new value is saved into config
Settings().setValue('core/has run wizard', True) Settings().setValue('core/has run wizard', True)
# THEN the new value is returned when re-read # 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): def settings_override_test(self):
""" """
@ -62,13 +90,13 @@ class TestSettings(TestCase):
extend = Settings().value('test/extend') extend = Settings().value('test/extend')
# THEN the default value is returned # 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 # WHEN a new value is saved into config
Settings().setValue('test/extend', 'very short') Settings().setValue('test/extend', 'very short')
# THEN the new value is returned when re-read # 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): def settings_override_with_group_test(self):
""" """
@ -86,10 +114,10 @@ class TestSettings(TestCase):
extend = settings.value('extend') extend = settings.value('extend')
# THEN the default value is returned # 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 # WHEN a new value is saved into config
Settings().setValue('test/extend', 'very short') Settings().setValue('test/extend', 'very short')
# THEN the new value is returned when re-read # 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')

View File

@ -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. Package to test the openlp.core.lib.uistrings package.
""" """
from unittest import TestCase from unittest import TestCase
from openlp.core.lib import UiStrings from openlp.core.lib import UiStrings
@ -18,6 +45,6 @@ class TestUiStrings(TestCase):
second_instance = UiStrings() second_instance = UiStrings()
# THEN: Check if the instances are the same. # 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')

View File

@ -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. Package to test the openlp.core.utils.actions package.
""" """
@ -12,6 +40,9 @@ from openlp.core.utils import ActionList
class TestActionList(TestCase): class TestActionList(TestCase):
"""
Test the ActionList class
"""
def setUp(self): def setUp(self):
""" """

View File

@ -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. Functional tests to test the AppLocation class and related methods.
""" """
import copy import copy
from unittest import TestCase from unittest import TestCase
from mock import patch
from openlp.core.utils import AppLocation from openlp.core.utils import AppLocation
from tests.functional import patch
FILE_LIST = ['file1', 'file2', 'file3.txt', 'file4.txt', 'file5.mp3', 'file6.mp3'] 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_settings.contains.assert_called_with('advanced/data path')
mocked_get_directory.assert_called_with(AppLocation.DataDir) mocked_get_directory.assert_called_with(AppLocation.DataDir)
mocked_check_directory_exists.assert_called_with('test/dir') 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): 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 # 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.contains.assert_called_with('advanced/data path')
mocked_settings.value.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): def get_files_no_section_no_extension_test(self):
""" """
@ -74,7 +100,7 @@ class TestAppLocation(TestCase):
result = AppLocation.get_files() result = AppLocation.get_files()
# Then: check if the file lists are identical. # 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): def get_files_test(self):
""" """
@ -93,7 +119,7 @@ class TestAppLocation(TestCase):
mocked_listdir.assert_called_with('test/dir/section') mocked_listdir.assert_called_with('test/dir/section')
# Then: check if the file lists are identical. # 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): 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 # THEN: check that all the correct methods were called, and the result is correct
mocked_check_directory_exists.assert_called_with('test/dir/section') 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): def get_directory_for_app_dir_test(self):
""" """
Test the AppLocation.get_directory() method for AppLocation.AppDir 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: with patch('openlp.core.utils.applocation._get_frozen_path') as mocked_get_frozen_path:
mocked_get_frozen_path.return_value = 'app/dir' mocked_get_frozen_path.return_value = 'app/dir'
# WHEN: We call AppLocation.get_directory # WHEN: We call AppLocation.get_directory
directory = AppLocation.get_directory(AppLocation.AppDir) directory = AppLocation.get_directory(AppLocation.AppDir)
# THEN: # THEN: check that the correct directory is returned
assert directory == 'app/dir', 'Directory should be "app/dir"' self.assertEqual('app/dir', directory, 'Directory should be "app/dir"')
def get_directory_for_plugins_dir_test(self): def get_directory_for_plugins_dir_test(self):
""" """
Test the AppLocation.get_directory() method for AppLocation.PluginsDir 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, \ 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.abspath') as mocked_abspath, \
patch('openlp.core.utils.applocation.os.path.split') as mocked_split, \ patch('openlp.core.utils.applocation.os.path.split') as mocked_split, \
@ -142,6 +170,5 @@ class TestAppLocation(TestCase):
# WHEN: We call AppLocation.get_directory # WHEN: We call AppLocation.get_directory
directory = AppLocation.get_directory(AppLocation.PluginsDir) directory = AppLocation.get_directory(AppLocation.PluginsDir)
# THEN: # THEN: The correct directory should be returned
assert directory == 'plugins/dir', 'Directory should be "plugins/dir"' self.assertEqual('plugins/dir', directory, 'Directory should be "plugins/dir"')

View File

@ -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. Functional tests to test the AppLocation class and related methods.
""" """
from unittest import TestCase from unittest import TestCase
from mock import patch
from openlp.core.utils import clean_filename, get_filesystem_encoding, _get_frozen_path, get_locale_key, \ from openlp.core.utils import clean_filename, get_filesystem_encoding, _get_frozen_path, get_locale_key, \
get_natural_key, split_filename get_natural_key, split_filename
from tests.functional import patch
class TestUtils(TestCase): class TestUtils(TestCase):
""" """
A test suite to test out various methods around the AppLocation class. 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, \ with patch('openlp.core.utils.sys.getfilesystemencoding') as mocked_getfilesystemencoding, \
patch('openlp.core.utils.sys.getdefaultencoding') as mocked_getdefaultencoding: patch('openlp.core.utils.sys.getdefaultencoding') as mocked_getdefaultencoding:
# GIVEN: sys.getfilesystemencoding returns "cp1252"
mocked_getfilesystemencoding.return_value = 'cp1252' mocked_getfilesystemencoding.return_value = 'cp1252'
# WHEN: get_filesystem_encoding() is called # WHEN: get_filesystem_encoding() is called
@ -27,10 +54,16 @@ class TestUtils(TestCase):
# THEN: getdefaultencoding should have been called # THEN: getdefaultencoding should have been called
mocked_getfilesystemencoding.assert_called_with() mocked_getfilesystemencoding.assert_called_with()
assert not mocked_getdefaultencoding.called self.assertEqual(0, mocked_getdefaultencoding.called, 'getdefaultencoding should not have been called')
assert result == 'cp1252', 'The result should be "cp1252"' 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_getfilesystemencoding.return_value = None
mocked_getdefaultencoding.return_value = 'utf-8' mocked_getdefaultencoding.return_value = 'utf-8'
@ -40,23 +73,35 @@ class TestUtils(TestCase):
# THEN: getdefaultencoding should have been called # THEN: getdefaultencoding should have been called
mocked_getfilesystemencoding.assert_called_with() mocked_getfilesystemencoding.assert_called_with()
mocked_getdefaultencoding.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: with patch('openlp.core.utils.sys') as mocked_sys:
# GIVEN: The sys module "without" a "frozen" attribute # GIVEN: The sys module "without" a "frozen" attribute
mocked_sys.frozen = None mocked_sys.frozen = None
# WHEN: We call _get_frozen_path() with two parameters # WHEN: We call _get_frozen_path() with two parameters
frozen_path = _get_frozen_path('frozen', 'not frozen')
# THEN: The non-frozen parameter is returned # 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 # GIVEN: The sys module *with* a "frozen" attribute
mocked_sys.frozen = 1 mocked_sys.frozen = 1
# WHEN: We call _get_frozen_path() with two parameters # WHEN: We call _get_frozen_path() with two parameters
frozen_path = _get_frozen_path('frozen', 'not frozen')
# THEN: The frozen parameter is returned # 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): def split_filename_with_file_path_test(self):
""" """
@ -72,7 +117,7 @@ class TestUtils(TestCase):
result = split_filename(file_path) result = split_filename(file_path)
# THEN: A tuple should be returned. # 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): def split_filename_with_dir_path_test(self):
""" """
@ -88,8 +133,8 @@ class TestUtils(TestCase):
result = split_filename(file_path) result = split_filename(file_path)
# THEN: A tuple should be returned. # THEN: A tuple should be returned.
assert result == wanted_result, \ 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): def clean_filename_test(self):
""" """
@ -103,7 +148,7 @@ class TestUtils(TestCase):
result = clean_filename(invalid_name) result = clean_filename(invalid_name)
# THEN: The file name should be cleaned. # 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): def get_locale_key_windows_test(self):
""" """
@ -116,13 +161,15 @@ class TestUtils(TestCase):
mocked_get_language.return_value = 'de' mocked_get_language.return_value = 'de'
mocked_os.name = 'nt' mocked_os.name = 'nt'
unsorted_list = ['Auszug', 'Aushang', '\u00C4u\u00DFerung'] unsorted_list = ['Auszug', 'Aushang', '\u00C4u\u00DFerung']
# WHEN: We sort the list and use get_locale_key() to generate the sorting keys # 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 # THEN: We get a properly sorted list
test_passes = sorted(unsorted_list, key=get_locale_key) == ['Aushang', '\u00C4u\u00DFerung', 'Auszug'] self.assertEqual(['Aushang', '\u00C4u\u00DFerung', 'Auszug'], sorted_list,
assert test_passes, 'Strings should be sorted properly' 'Strings should be sorted properly')
def get_locale_key_linux_test(self): def get_locale_key_linux_test(self):
""" """
Test the get_locale_key(string) function Test the get_locale_key(string) function
""" """
@ -133,10 +180,13 @@ class TestUtils(TestCase):
mocked_get_language.return_value = 'de' mocked_get_language.return_value = 'de'
mocked_os.name = 'linux' mocked_os.name = 'linux'
unsorted_list = ['Auszug', 'Aushang', '\u00C4u\u00DFerung'] unsorted_list = ['Auszug', 'Aushang', '\u00C4u\u00DFerung']
# WHEN: We sort the list and use get_locale_key() to generate the sorting keys # 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 # THEN: We get a properly sorted list
test_passes = sorted(unsorted_list, key=get_locale_key) == ['Aushang', '\u00C4u\u00DFerung', 'Auszug'] self.assertEqual(['Aushang', '\u00C4u\u00DFerung', 'Auszug'], sorted_list,
assert test_passes, 'Strings should be sorted properly' 'Strings should be sorted properly')
def get_natural_key_test(self): def get_natural_key_test(self):
""" """
@ -146,7 +196,9 @@ class TestUtils(TestCase):
# GIVEN: The language is English (a language, which sorts digits before letters) # GIVEN: The language is English (a language, which sorts digits before letters)
mocked_get_language.return_value = 'en' mocked_get_language.return_value = 'en'
unsorted_list = ['item 10a', 'item 3b', '1st item'] unsorted_list = ['item 10a', 'item 3b', '1st item']
# WHEN: We sort the list and use get_natural_key() to generate the sorting keys # 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 # THEN: We get a properly sorted list
test_passes = sorted(unsorted_list, key=get_natural_key) == ['1st item', 'item 3b', 'item 10a'] self.assertEqual(['1st item', 'item 3b', 'item 10a'], sorted_list, 'Numbers should be sorted naturally')
assert test_passes, 'Numbers should be sorted naturally'

View File

@ -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. This module contains tests for the lib submodule of the Bibles plugin.
""" """

View File

@ -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. This module contains tests for the versereferencelist submodule of the Bibles plugin.
""" """
from unittest import TestCase from unittest import TestCase
from openlp.plugins.bibles.lib.versereferencelist import VerseReferenceList from openlp.plugins.bibles.lib.versereferencelist import VerseReferenceList
class TestVerseReferenceList(TestCase): class TestVerseReferenceList(TestCase):
def setUp(self): """
""" Test the VerseReferenceList class
Initializes all we need """
"""
def add_first_verse_test(self): def add_first_verse_test(self):
""" """
Test the addition of a verse to the empty list Test the addition of a verse to the empty list
@ -20,12 +48,12 @@ class TestVerseReferenceList(TestCase):
chapter = 1 chapter = 1
verse = 1 verse = 1
version = 'testVersion' version = 'testVersion'
copyright = 'testCopyright' copyright_ = 'testCopyright'
permission = 'testPermision' permission = 'testPermision'
# WHEN: We add it to the verse list # 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 # 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.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) 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]['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]['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) self.assertEqual(reference_list.verse_list[0]['end'], verse, 'The end in first entry should be %u' % verse)
def add_next_verse_test(self): def add_next_verse_test(self):
""" """
Test the addition of the following verse Test the addition of the following verse
@ -44,17 +72,18 @@ class TestVerseReferenceList(TestCase):
verse = 1 verse = 1
next_verse = 2 next_verse = 2
version = 'testVersion' version = 'testVersion'
copyright = 'testCopyright' copyright_ = 'testCopyright'
permission = 'testPermision' permission = 'testPermision'
reference_list = VerseReferenceList() 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 # 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' # 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.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): def add_another_verse_test(self):
""" """
@ -64,19 +93,18 @@ class TestVerseReferenceList(TestCase):
book = 'testBook' book = 'testBook'
chapter = 1 chapter = 1
verse = 1 verse = 1
next_verse = 2
another_book = 'testBook2' another_book = 'testBook2'
another_chapter = 2 another_chapter = 2
another_verse = 5 another_verse = 5
version = 'testVersion' version = 'testVersion'
copyright = 'testCopyright' copyright_ = 'testCopyright'
permission = 'testPermision' permission = 'testPermision'
reference_list = VerseReferenceList() 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 # 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 # THEN: the current index should be 1
self.assertEqual(reference_list.current_index, 1, 'The current index should be 1') self.assertEqual(reference_list.current_index, 1, 'The current index should be 1')
@ -87,17 +115,18 @@ class TestVerseReferenceList(TestCase):
# GIVEN: version, copyright and permission # GIVEN: version, copyright and permission
reference_list = VerseReferenceList() reference_list = VerseReferenceList()
version = 'testVersion' version = 'testVersion'
copyright = 'testCopyright' copyright_ = 'testCopyright'
permission = 'testPermision' permission = 'testPermision'
# WHEN: a not existing version will be added # 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 # 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(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}, self.assertEqual(reference_list.version_list[0],
{'version': version, 'copyright': copyright_, 'permission': permission},
'The version data should be appended') 'The version data should be appended')
def add_existing_version_test(self): def add_existing_version_test(self):
""" """
Test the addition of an existing version to the list Test the addition of an existing version to the list
@ -105,12 +134,12 @@ class TestVerseReferenceList(TestCase):
# GIVEN: version, copyright and permission, added to the version list # GIVEN: version, copyright and permission, added to the version list
reference_list = VerseReferenceList() reference_list = VerseReferenceList()
version = 'testVersion' version = 'testVersion'
copyright = 'testCopyright' copyright_ = 'testCopyright'
permission = 'testPermision' permission = 'testPermision'
reference_list.add_version(version, copyright, permission) reference_list.add_version(version, copyright_, permission)
# WHEN: an existing version will be added # 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 # 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') self.assertEqual(len(reference_list.version_list), 1, 'The version data should not be appended')

View File

@ -1,16 +1,40 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 # 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. This module contains tests for the lib submodule of the Images plugin.
""" """
from unittest import TestCase from unittest import TestCase
from mock import MagicMock, patch
from openlp.core.lib import Registry from openlp.core.lib import Registry
from openlp.plugins.images.lib.db import ImageFilenames, ImageGroups from openlp.plugins.images.lib.db import ImageFilenames, ImageGroups
from openlp.plugins.images.lib.mediaitem import ImageMediaItem from openlp.plugins.images.lib.mediaitem import ImageMediaItem
from tests.functional import MagicMock, patch
class TestImageMediaItem(TestCase): class TestImageMediaItem(TestCase):
@ -24,11 +48,10 @@ class TestImageMediaItem(TestCase):
Registry().register('service_list', MagicMock()) Registry().register('service_list', MagicMock())
Registry().register('main_window', self.mocked_main_window) Registry().register('main_window', self.mocked_main_window)
Registry().register('live_controller', MagicMock()) Registry().register('live_controller', MagicMock())
mocked_parent = MagicMock()
mocked_plugin = MagicMock() mocked_plugin = MagicMock()
with patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.__init__') as mocked_init: with patch('openlp.plugins.images.lib.mediaitem.MediaManagerItem._setup'), \
mocked_init.return_value = None patch('openlp.plugins.images.lib.mediaitem.ImageMediaItem.setup_item'):
self.media_item = ImageMediaItem(mocked_parent, mocked_plugin) self.media_item = ImageMediaItem(None, mocked_plugin)
def save_new_images_list_empty_list_test(self): def save_new_images_list_empty_list_test(self):
""" """
@ -36,7 +59,7 @@ class TestImageMediaItem(TestCase):
""" """
# GIVEN: An empty image_list # GIVEN: An empty image_list
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() self.media_item.manager = MagicMock()
# WHEN: We run save_new_images_list with the empty list # WHEN: We run save_new_images_list with the empty list
@ -50,8 +73,8 @@ class TestImageMediaItem(TestCase):
""" """
Test that the save_new_images_list() calls load_full_list() when reload_list is set to True Test that the save_new_images_list() calls load_full_list() when reload_list is set to True
""" """
# GIVEN: A list with 1 image # GIVEN: A list with 1 image and a mocked out manager
image_list = [ 'test_image.jpg' ] image_list = ['test_image.jpg']
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') as mocked_load_full_list:
ImageFilenames.filename = '' ImageFilenames.filename = ''
self.media_item.manager = MagicMock() self.media_item.manager = MagicMock()
@ -69,8 +92,8 @@ class TestImageMediaItem(TestCase):
""" """
Test that the save_new_images_list() doesn't call load_full_list() when reload_list is set to False 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 # GIVEN: A list with 1 image and a mocked out manager
image_list = [ 'test_image.jpg' ] image_list = ['test_image.jpg']
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') as mocked_load_full_list:
self.media_item.manager = MagicMock() self.media_item.manager = MagicMock()
@ -126,9 +149,35 @@ class TestImageMediaItem(TestCase):
self.media_item.reset_action.setVisible.assert_called_with(False) self.media_item.reset_action.setVisible.assert_called_with(False)
self.media_item.live_controller.display.reset_image.assert_called_with() self.media_item.live_controller.display.reset_image.assert_called_with()
def recursively_delete_group_test(self):
"""
Test that recursively_delete_group() works
"""
# GIVEN: An ImageGroups object and mocked functions
with patch('openlp.core.utils.delete_file') as mocked_delete_file:
ImageFilenames.group_id = 1
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.service_path = ""
test_group = ImageGroups()
test_group.id = 1
# WHEN: recursively_delete_group() is called
self.media_item.recursively_delete_group(test_group)
# THEN:
assert mocked_delete_file.call_count == 0, 'delete_file() should not be called'
assert self.media_item.manager.delete_object.call_count == 7, \
'manager.delete_object() should be called exactly 7 times'
# CLEANUP: Remove added attribute from ImageFilenames and ImageGroups
delattr(ImageFilenames, 'group_id')
delattr(ImageGroups, 'parent_id')
def _recursively_delete_group_side_effect(*args, **kwargs): def _recursively_delete_group_side_effect(*args, **kwargs):
""" """
Side effect method that creates custom retun values for the recursively_delete_group method Side effect method that creates custom return values for the recursively_delete_group method
""" """
if args[1] == ImageFilenames and args[2]: if args[1] == ImageFilenames and args[2]:
# Create some fake objects that should be removed # Create some fake objects that should be removed
@ -150,29 +199,3 @@ class TestImageMediaItem(TestCase):
returned_object1.id = 1 returned_object1.id = 1
return [returned_object1] return [returned_object1]
return [] return []
def recursively_delete_group_test(self):
"""
Test that recursively_delete_group() works
"""
# GIVEN: An ImageGroups object and mocked functions
with patch('openlp.core.utils.delete_file') as mocked_delete_file:
ImageFilenames.group_id = 1
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 = ""
test_group = ImageGroups()
test_group.id = 1
# WHEN: recursively_delete_group() is called
self.media_item.recursively_delete_group(test_group)
# THEN:
assert mocked_delete_file.call_count == 0, 'delete_file() should not be called'
assert self.media_item.manager.delete_object.call_count == 7, \
'manager.delete_object() should be called exactly 7 times'
# CLEANUP: Remove added attribute from ImageFilenames and ImageGroups
delattr(ImageFilenames, 'group_id')
delattr(ImageGroups, 'parent_id')

View File

@ -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. This module contains tests for the lib submodule of the Presentations plugin.
""" """
import os
from tempfile import mkstemp
from unittest import TestCase from unittest import TestCase
from mock import patch, MagicMock
from PyQt4 import QtGui from PyQt4 import QtGui
from openlp.core.lib import Registry from openlp.core.lib import Registry
from openlp.plugins.presentations.lib.mediaitem import PresentationMediaItem from openlp.plugins.presentations.lib.mediaitem import PresentationMediaItem
from tests.functional import patch, MagicMock
class TestMediaItem(TestCase): class TestMediaItem(TestCase):
@ -25,11 +49,9 @@ class TestMediaItem(TestCase):
Registry.create() Registry.create()
Registry().register('service_manager', MagicMock()) Registry().register('service_manager', MagicMock())
Registry().register('main_window', MagicMock()) Registry().register('main_window', MagicMock())
with patch('openlp.plugins.presentations.lib.mediaitem.MediaManagerItem._setup'), \
with patch('openlp.plugins.presentations.lib.mediaitem.PresentationMediaItem.__init__') as mocked_init: patch('openlp.plugins.presentations.lib.mediaitem.PresentationMediaItem.setup_item'):
mocked_init.return_value = None self.media_item = PresentationMediaItem(None, MagicMock, MagicMock())
self.media_item = PresentationMediaItem(MagicMock(), MagicMock, MagicMock(), MagicMock())
self.application = QtGui.QApplication.instance() self.application = QtGui.QApplication.instance()
def tearDown(self): def tearDown(self):
@ -65,7 +87,8 @@ class TestMediaItem(TestCase):
mocked_translate.side_effect = lambda module, string_to_translate: string_to_translate mocked_translate.side_effect = lambda module, string_to_translate: string_to_translate
self.media_item.build_file_mask_string() self.media_item.build_file_mask_string()
# THEN: The file mask should be generated. # THEN: The file mask should be generated correctly
assert self.media_item.on_new_file_masks == 'Presentations (*.odp *.ppt )', \ self.assertIn('*.odp', self.media_item.on_new_file_masks,
'The file mask should contain the odp and ppt extensions' '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')

View File

@ -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. This module contains tests for the lib submodule of the Remotes plugin.
""" """
import os import os
import re import re
from unittest import TestCase from unittest import TestCase
from tempfile import mkstemp from tempfile import mkstemp
from mock import patch
from PyQt4 import QtGui
from openlp.core.lib import Settings from openlp.core.lib import Settings
from openlp.plugins.remotes.lib.remotetab import RemoteTab from openlp.plugins.remotes.lib.remotetab import RemoteTab
from tests.functional import patch
from PyQt4 import QtGui
__default_settings__ = { __default_settings__ = {
'remotes/twelve hour': True, 'remotes/twelve hour': True,
@ -23,9 +50,7 @@ __default_settings__ = {
'remotes/authentication enabled': False, 'remotes/authentication enabled': False,
'remotes/ip address': '0.0.0.0' 'remotes/ip address': '0.0.0.0'
} }
ZERO_URL = '0.0.0.0' ZERO_URL = '0.0.0.0'
TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', '..', 'resources')) 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 # WHEN: the default ip address is given
ip_address = self.form.get_ip_address(ZERO_URL) ip_address = self.form.get_ip_address(ZERO_URL)
# THEN: the default ip address will be returned # 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): def get_ip_address_with_ip_test(self):
""" """
@ -80,9 +106,9 @@ class TestRemoteTab(TestCase):
""" """
# GIVEN: A mocked location # GIVEN: A mocked location
with patch('openlp.core.utils.applocation.Settings') as mocked_class, \ 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.get_directory') as mocked_get_directory, \
patch('openlp.core.utils.applocation.check_directory_exists') as mocked_check_directory_exists, \ 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.os') as mocked_os:
# GIVEN: A mocked out Settings class and a mocked out AppLocation.get_directory() # GIVEN: A mocked out Settings class and a mocked out AppLocation.get_directory()
mocked_settings = mocked_class.return_value mocked_settings = mocked_class.return_value
mocked_settings.contains.return_value = False mocked_settings.contains.return_value = False
@ -96,7 +122,7 @@ class TestRemoteTab(TestCase):
# THEN: the following screen values should be set # 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.address_edit.text(), ZERO_URL, 'The default URL should be set on the screen')
self.assertEqual(self.form.https_settings_group_box.isEnabled(), False, 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, self.assertEqual(self.form.https_settings_group_box.isChecked(), False,
'The Https checked box should note be Checked') 'The Https checked box should note be Checked')
self.assertEqual(self.form.user_login_group_box.isChecked(), False, self.assertEqual(self.form.user_login_group_box.isChecked(), False,
@ -108,9 +134,9 @@ class TestRemoteTab(TestCase):
""" """
# GIVEN: A mocked location # GIVEN: A mocked location
with patch('openlp.core.utils.applocation.Settings') as mocked_class, \ 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.get_directory') as mocked_get_directory, \
patch('openlp.core.utils.applocation.check_directory_exists') as mocked_check_directory_exists, \ 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.os') as mocked_os:
# GIVEN: A mocked out Settings class and a mocked out AppLocation.get_directory() # GIVEN: A mocked out Settings class and a mocked out AppLocation.get_directory()
mocked_settings = mocked_class.return_value mocked_settings = mocked_class.return_value
mocked_settings.contains.return_value = False mocked_settings.contains.return_value = False

View File

@ -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. This module contains tests for the lib submodule of the Remotes plugin.
""" """
import os import os
from unittest import TestCase from unittest import TestCase
from tempfile import mkstemp from tempfile import mkstemp
from mock import MagicMock
from PyQt4 import QtGui
from openlp.core.lib import Settings 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 from tests.functional import MagicMock
__default_settings__ = { __default_settings__ = {
'remotes/twelve hour': True, 'remotes/twelve hour': True,
@ -44,40 +72,22 @@ class TestRouter(TestCase):
del self.application del self.application
os.unlink(self.ini_file) 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 # GIVEN: A default configuration
# WHEN: called with the defined userid Settings().setValue('remotes/user id', 'openlp')
password = fetch_password('itwinkle') 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 # WHEN: called with the defined userid
password = fetch_password('openlp') router = HttpRouter()
required_password = make_sha_hash('password') router.initialise()
test_value = 'b3BlbmxwOnBhc3N3b3Jk'
print(router.auth)
# THEN: the function should return the correct password # THEN: the function should return the correct password
self.assertEqual(password, required_password, 'The result for fetch_password should be the defined password') self.assertEqual(router.auth, test_value,
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,
'The result for make_sha_hash should return the correct encrypted password') 'The result for make_sha_hash should return the correct encrypted password')
def process_http_request_test(self): def process_http_request_test(self):
@ -85,15 +95,18 @@ class TestRouter(TestCase):
Test the router control functionality Test the router control functionality
""" """
# GIVEN: A testing set of Routes # GIVEN: A testing set of Routes
router = HttpRouter()
mocked_function = MagicMock() mocked_function = MagicMock()
test_route = [ 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 # 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 # THEN: the function should have been called only once
assert mocked_function.call_count == 1, \ assert function['function'] == mocked_function, \
'The mocked function should have been matched and called once.' 'The mocked function should match defined value.'
assert function['secure'] == False, \
'The mocked function should not require any security.'

View File

@ -1,13 +1,39 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 # 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. This module contains tests for the EasyWorship song importer.
""" """
import os import os
from unittest import TestCase 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 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')], 'Just to bow and receive a new blessing,\nIn the beautiful garden of prayer.', 'v3')],
'verse_order_list': []}] 'verse_order_list': []}]
class EasyWorshipSongImportLogger(EasyWorshipSongImport): class EasyWorshipSongImportLogger(EasyWorshipSongImport):
""" """
This class logs changes in the title instance variable This class logs changes in the title instance variable
@ -60,6 +87,7 @@ class EasyWorshipSongImportLogger(EasyWorshipSongImport):
def title(self, title): def title(self, title):
self._title_assignment_list.append(title) self._title_assignment_list.append(title)
class TestFieldDesc: class TestFieldDesc:
def __init__(self, name, field_type, size): def __init__(self, name, field_type, size):
self.name = name self.name = name

View File

@ -32,9 +32,8 @@ This module contains tests for the SongShow Plus song importer.
import os import os
from unittest import TestCase 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 from openlp.plugins.songs.lib.foilpresenterimport import FoilPresenter
TEST_PATH = os.path.abspath( 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 # THEN: _process_lyrics should return None and the song_import logError method should have been called once
self.assertIsNone(result) self.assertIsNone(result)
self.mocked_song_import.logError.assert_called_once_with('Element Text', 'Translated String') self.mocked_song_import.logError.assert_called_once_with('Element Text', 'Translated String')
self.process_lyrics_patcher.start() self.process_lyrics_patcher.start()

View File

@ -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. This module contains tests for the lib submodule of the Songs plugin.
""" """
from unittest import TestCase 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 import VerseType, clean_string, clean_title, strip_rtf
from openlp.plugins.songs.lib.songcompare import songs_probably_equal, _remove_typos, _op_length from openlp.plugins.songs.lib.songcompare import songs_probably_equal, _remove_typos, _op_length
from tests.functional import patch, MagicMock
class TestLib(TestCase): class TestLib(TestCase):
@ -68,10 +94,10 @@ class TestLib(TestCase):
# GIVEN: Two equal songs. # GIVEN: Two equal songs.
self.song1.search_lyrics = self.full_lyrics self.song1.search_lyrics = self.full_lyrics
self.song2.search_lyrics = self.full_lyrics self.song2.search_lyrics = self.full_lyrics
# WHEN: We compare those songs for equality. # WHEN: We compare those songs for equality.
result = songs_probably_equal(self.song1, self.song2) result = songs_probably_equal(self.song1, self.song2)
# THEN: The result should be True. # THEN: The result should be True.
assert result == True, '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. # GIVEN: A song and a short version of the same song.
self.song1.search_lyrics = self.full_lyrics self.song1.search_lyrics = self.full_lyrics
self.song2.search_lyrics = self.short_lyrics self.song2.search_lyrics = self.short_lyrics
# WHEN: We compare those songs for equality. # WHEN: We compare those songs for equality.
result = songs_probably_equal(self.song1, self.song2) result = songs_probably_equal(self.song1, self.song2)
# THEN: The result should be True. # THEN: The result should be True.
assert result == True, '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. # GIVEN: A song and the same song with lots of errors.
self.song1.search_lyrics = self.full_lyrics self.song1.search_lyrics = self.full_lyrics
self.song2.search_lyrics = self.error_lyrics self.song2.search_lyrics = self.error_lyrics
# WHEN: We compare those songs for equality. # WHEN: We compare those songs for equality.
result = songs_probably_equal(self.song1, self.song2) result = songs_probably_equal(self.song1, self.song2)
# THEN: The result should be True. # THEN: The result should be True.
assert result == True, 'The result should be True' assert result == True, 'The result should be True'
@ -110,10 +136,10 @@ class TestLib(TestCase):
# GIVEN: Two different songs. # GIVEN: Two different songs.
self.song1.search_lyrics = self.full_lyrics self.song1.search_lyrics = self.full_lyrics
self.song2.search_lyrics = self.different_lyrics self.song2.search_lyrics = self.different_lyrics
# WHEN: We compare those songs for equality. # WHEN: We compare those songs for equality.
result = songs_probably_equal(self.song1, self.song2) result = songs_probably_equal(self.song1, self.song2)
# THEN: The result should be False. # THEN: The result should be False.
assert result == False, 'The result should be False' assert result == False, 'The result should be False'

View File

@ -5,13 +5,11 @@ import os
from tempfile import mkstemp from tempfile import mkstemp
from unittest import TestCase from unittest import TestCase
from mock import patch, MagicMock
from PyQt4 import QtCore, QtGui from PyQt4 import QtCore, QtGui
from openlp.core.lib import Registry, ServiceItem, Settings from openlp.core.lib import Registry, ServiceItem, Settings
from openlp.plugins.songs.lib.mediaitem import SongMediaItem from openlp.plugins.songs.lib.mediaitem import SongMediaItem
from tests.functional import patch, MagicMock
class TestMediaItem(TestCase): class TestMediaItem(TestCase):
@ -25,9 +23,9 @@ class TestMediaItem(TestCase):
Registry.create() Registry.create()
Registry().register('service_list', MagicMock()) Registry().register('service_list', MagicMock())
Registry().register('main_window', 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__'): 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') fd, self.ini_file = mkstemp('.ini')
Settings().set_filename(self.ini_file) Settings().set_filename(self.ini_file)

View File

@ -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. This module contains tests for the SongShow Plus song importer.
""" """
import os import os
from unittest import TestCase from unittest import TestCase
from mock import patch, MagicMock
from openlp.plugins.songs.lib import VerseType from openlp.plugins.songs.lib import VerseType
from openlp.plugins.songs.lib.songshowplusimport import SongShowPlusImport 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')) TEST_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../resources/songshowplussongs'))
SONG_TEST_DATA = {'Amazing Grace.sbsong': SONG_TEST_DATA = {'Amazing Grace.sbsong':

View File

@ -1,15 +1,45 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=120 tabstop=4 softtabstop=4 # 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. 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 import pyodbc
from openlp.plugins.songs.lib.worshipcenterproimport import WorshipCenterProImport from openlp.plugins.songs.lib.worshipcenterproimport import WorshipCenterProImport
from tests.functional import patch, MagicMock
class TestRecord(object): class TestRecord(object):
""" """
@ -23,6 +53,7 @@ class TestRecord(object):
self.Field = field self.Field = field
self.Value = value self.Value = value
class WorshipCenterProImportLogger(WorshipCenterProImport): class WorshipCenterProImportLogger(WorshipCenterProImport):
""" """
This class logs changes in the title instance variable This class logs changes in the title instance variable
@ -189,4 +220,4 @@ class TestWorshipCenterProSongImport(TestCase):
for call in verse_calls: for call in verse_calls:
mocked_add_verse.assert_any_call(call) mocked_add_verse.assert_any_call(call)
self.assertEqual(mocked_add_verse.call_count, add_verse_call_count, self.assertEqual(mocked_add_verse.call_count, add_verse_call_count,
'Incorrect number of calls made to addVerse') 'Incorrect number of calls made to addVerse')

View File

@ -1,138 +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
import cherrypy
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.
"""
cherrypy.response.status = 200
return None

View File

@ -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