Merge branch 'develop' into feature/mopidy.js
This commit is contained in:
commit
e51fbd19fd
@ -19,7 +19,20 @@ class AudioListener(object):
|
||||
"""Helper to allow calling of audio listener events"""
|
||||
listeners = pykka.ActorRegistry.get_by_class(AudioListener)
|
||||
for listener in listeners:
|
||||
getattr(listener.proxy(), event)(**kwargs)
|
||||
listener.proxy().on_event(event, **kwargs)
|
||||
|
||||
def on_event(self, event, **kwargs):
|
||||
"""
|
||||
Called on all events.
|
||||
|
||||
*MAY* be implemented by actor. By default, this method forwards the
|
||||
event to the specific event methods.
|
||||
|
||||
:param event: the event name
|
||||
:type event: string
|
||||
:param kwargs: any other arguments to the specific event handlers
|
||||
"""
|
||||
getattr(self, event)(**kwargs)
|
||||
|
||||
def reached_end_of_stream(self):
|
||||
"""
|
||||
|
||||
@ -21,7 +21,20 @@ class BackendListener(object):
|
||||
"""Helper to allow calling of backend listener events"""
|
||||
listeners = pykka.ActorRegistry.get_by_class(BackendListener)
|
||||
for listener in listeners:
|
||||
getattr(listener.proxy(), event)(**kwargs)
|
||||
listener.proxy().on_event(event, **kwargs)
|
||||
|
||||
def on_event(self, event, **kwargs):
|
||||
"""
|
||||
Called on all events.
|
||||
|
||||
*MAY* be implemented by actor. By default, this method forwards the
|
||||
event to the specific event methods.
|
||||
|
||||
:param event: the event name
|
||||
:type event: string
|
||||
:param kwargs: any other arguments to the specific event handlers
|
||||
"""
|
||||
getattr(self, event)(**kwargs)
|
||||
|
||||
def playlists_loaded(self):
|
||||
"""
|
||||
|
||||
@ -57,7 +57,7 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider):
|
||||
# from other backends
|
||||
tracks += self.backend.library.lookup(track_uri)
|
||||
except LookupError as ex:
|
||||
logger.error('Playlist item could not be added: %s', ex)
|
||||
logger.warning('Playlist item could not be added: %s', ex)
|
||||
|
||||
playlist = Playlist(uri=uri, name=name, tracks=tracks)
|
||||
playlists.append(playlist)
|
||||
|
||||
@ -35,7 +35,7 @@ def parse_m3u(file_path, music_folder):
|
||||
with open(file_path) as m3u:
|
||||
contents = m3u.readlines()
|
||||
except IOError as error:
|
||||
logger.error('Couldn\'t open m3u: %s', locale_decode(error))
|
||||
logger.warning('Couldn\'t open m3u: %s', locale_decode(error))
|
||||
return uris
|
||||
|
||||
for line in contents:
|
||||
@ -64,7 +64,7 @@ def parse_mpd_tag_cache(tag_cache, music_dir=''):
|
||||
with open(tag_cache) as library:
|
||||
contents = library.read()
|
||||
except IOError as error:
|
||||
logger.error('Could not open tag cache: %s', locale_decode(error))
|
||||
logger.warning('Could not open tag cache: %s', locale_decode(error))
|
||||
return tracks
|
||||
|
||||
current = {}
|
||||
|
||||
@ -142,8 +142,9 @@ class SpotifySessionManager(process.BaseThread, PyspotifySessionManager):
|
||||
# startup until the Spotify backend is ready from 35s to 12s in one
|
||||
# test with clean Spotify cache. In cases with an outdated cache
|
||||
# the time improvements should be a lot greater.
|
||||
self._initial_data_receive_completed = True
|
||||
self.refresh_playlists()
|
||||
if not self._initial_data_receive_completed:
|
||||
self._initial_data_receive_completed = True
|
||||
self.refresh_playlists()
|
||||
|
||||
def end_of_track(self, session):
|
||||
"""Callback used by pyspotify"""
|
||||
|
||||
@ -19,7 +19,20 @@ class CoreListener(object):
|
||||
"""Helper to allow calling of core listener events"""
|
||||
listeners = pykka.ActorRegistry.get_by_class(CoreListener)
|
||||
for listener in listeners:
|
||||
getattr(listener.proxy(), event)(**kwargs)
|
||||
listener.proxy().on_event(event, **kwargs)
|
||||
|
||||
def on_event(self, event, **kwargs):
|
||||
"""
|
||||
Called on all events.
|
||||
|
||||
*MAY* be implemented by actor. By default, this method forwards the
|
||||
event to the specific event methods.
|
||||
|
||||
:param event: the event name
|
||||
:type event: string
|
||||
:param kwargs: any other arguments to the specific event handlers
|
||||
"""
|
||||
getattr(self, event)(**kwargs)
|
||||
|
||||
def track_playback_paused(self, track, time_position):
|
||||
"""
|
||||
|
||||
@ -53,6 +53,9 @@ class PlaybackController(object):
|
||||
Tracks are not removed from the playlist.
|
||||
"""
|
||||
|
||||
def get_current_tl_track(self):
|
||||
return self.current_tl_track
|
||||
|
||||
current_tl_track = None
|
||||
"""
|
||||
The currently playing or selected :class:`mopidy.models.TlTrack`, or
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
"""
|
||||
Frontend which lets you control Mopidy through HTTP and WebSockets.
|
||||
The HTTP frontends lets you control Mopidy through HTTP and WebSockets, e.g.
|
||||
from a web based client.
|
||||
|
||||
**Dependencies**
|
||||
|
||||
@ -15,36 +16,72 @@ Frontend which lets you control Mopidy through HTTP and WebSockets.
|
||||
|
||||
- :attr:`mopidy.settings.HTTP_SERVER_STATIC_DIR`
|
||||
|
||||
**Usage**
|
||||
|
||||
Setup
|
||||
=====
|
||||
|
||||
When this frontend is included in :attr:`mopidy.settings.FRONTENDS`, it starts
|
||||
a web server at the port specified by :attr:`mopidy.settings.HTTP_SERVER_PORT`.
|
||||
|
||||
As a simple security measure, the web server is by default only available from
|
||||
localhost. To make it available from other computers, change
|
||||
:attr:`mopidy.settings.HTTP_SERVER_HOSTNAME`. Before you do so, note that the
|
||||
HTTP frontend does not feature any form of user authentication or
|
||||
authorization. Anyone able to access the web server can use the full core API
|
||||
of Mopidy. Thus, you probably only want to make the web server available from
|
||||
your local network or place it behind a web proxy which takes care or user
|
||||
authentication. You have been warned.
|
||||
.. warning:: Security
|
||||
|
||||
This web server exposes a WebSocket at ``/mopidy/ws/``. The WebSocket gives you
|
||||
access to Mopidy's full API and enables Mopidy to instantly push events to the
|
||||
client, as they happen.
|
||||
As a simple security measure, the web server is by default only available
|
||||
from localhost. To make it available from other computers, change
|
||||
:attr:`mopidy.settings.HTTP_SERVER_HOSTNAME`. Before you do so, note that
|
||||
the HTTP frontend does not feature any form of user authentication or
|
||||
authorization. Anyone able to access the web server can use the full core
|
||||
API of Mopidy. Thus, you probably only want to make the web server
|
||||
available from your local network or place it behind a web proxy which
|
||||
takes care or user authentication. You have been warned.
|
||||
|
||||
|
||||
Using a web based Mopidy client
|
||||
===============================
|
||||
|
||||
The web server can also host any static files, for example the HTML, CSS,
|
||||
JavaScript and images needed by a web based Mopidy client. To host static
|
||||
JavaScript, and images needed for a web based Mopidy client. To host static
|
||||
files, change :attr:`mopidy.settings.HTTP_SERVER_STATIC_DIR` to point to the
|
||||
directory you want to serve.
|
||||
root directory of your web client, e.g.::
|
||||
|
||||
**WebSocket API**
|
||||
HTTP_SERVER_STATIC_DIR = u'/home/alice/dev/the-client'
|
||||
|
||||
If the directory includes a file named ``index.html``, it will be served on the
|
||||
root of Mopidy's web server.
|
||||
|
||||
If you're making a web based client and wants to do server side development as
|
||||
well, you are of course free to run your own web server and just use Mopidy's
|
||||
web server for the APIs. But, for clients implemented purely in JavaScript,
|
||||
letting Mopidy host the files is a simpler solution.
|
||||
|
||||
|
||||
WebSocket API
|
||||
=============
|
||||
|
||||
.. warning:: API stability
|
||||
|
||||
Since this frontend exposes our internal core API directly it is to be
|
||||
regarded as **experimental**. We cannot promise to keep any form of
|
||||
backwards compatibility between releases as we will need to change the core
|
||||
API while working out how to support new use cases. Thus, if you use this
|
||||
API, you must expect to do small adjustments to your client for every
|
||||
release of Mopidy.
|
||||
|
||||
From Mopidy 1.0 and onwards, we intend to keep the core API far more
|
||||
stable.
|
||||
|
||||
The web server exposes a WebSocket at ``/mopidy/ws/``. The WebSocket gives you
|
||||
access to Mopidy's full API and enables Mopidy to instantly push events to the
|
||||
client, as they happen.
|
||||
|
||||
On the WebSocket we send two different kind of messages: The client can send
|
||||
JSON-RPC 2.0 requests, and the server will respond with JSON-RPC 2.0 responses.
|
||||
In addition, the server will send event messages when something happens on the
|
||||
server. Both message types are encoded as JSON objects.
|
||||
|
||||
|
||||
Event messages
|
||||
--------------
|
||||
|
||||
Event objects will always have a key named ``event`` whose value is the event
|
||||
type. Depending on the event type, the event may include additional fields for
|
||||
related data. The events maps directly to the :class:`mopidy.core.CoreListener`
|
||||
@ -54,6 +91,10 @@ fields on the event objects. Example event message::
|
||||
|
||||
{"event": "track_playback_started", "track": {...}}
|
||||
|
||||
|
||||
JSON-RPC 2.0 messaging
|
||||
----------------------
|
||||
|
||||
JSON-RPC 2.0 messages can be recognized by checking for the key named
|
||||
``jsonrpc`` with the string value ``2.0``. For details on the messaging format,
|
||||
please refer to the `JSON-RPC 2.0 spec
|
||||
@ -66,7 +107,7 @@ JSON-RPC calls over the WebSocket. For example,
|
||||
|
||||
The core API's attributes is made available through setters and getters. For
|
||||
example, the attribute :attr:`mopidy.core.PlaybackController.current_track` is
|
||||
availableas the JSON-RPC method ``core.playback.get_current_track`.
|
||||
available as the JSON-RPC method ``core.playback.get_current_track``.
|
||||
|
||||
Example JSON-RPC request::
|
||||
|
||||
@ -80,20 +121,11 @@ The JSON-RPC method ``core.describe`` returns a data structure describing all
|
||||
available methods. If you're unsure how the core API maps to JSON-RPC, having a
|
||||
look at the ``core.describe`` response can be helpful.
|
||||
|
||||
**JavaScript wrapper**
|
||||
JavaScript wrapper
|
||||
==================
|
||||
|
||||
A JavaScript library wrapping the JSON-RPC over WebSocket API is under
|
||||
development. Details on it will appear here when it's released.
|
||||
|
||||
**API stability**
|
||||
|
||||
Since this frontend exposes our internal core API directly it is to be regarded
|
||||
as **experimental**. We cannot promise to keep any form of backwards
|
||||
compatibility between releases as we will need to change the core API while
|
||||
working out how to support new use cases. Thus, if you use this API, you must
|
||||
expect to do small adjustments to your client for every release of Mopidy.
|
||||
|
||||
From Mopidy 1.0 and onwards, we intend to keep the core API far more stable.
|
||||
"""
|
||||
|
||||
# flake8: noqa
|
||||
|
||||
@ -55,6 +55,7 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener):
|
||||
logger.debug('HTTP server will serve "%s" at /', static_dir)
|
||||
|
||||
mopidy_dir = os.path.join(os.path.dirname(__file__), 'data')
|
||||
favicon = os.path.join(mopidy_dir, 'favicon.png')
|
||||
|
||||
config = {
|
||||
b'/': {
|
||||
@ -62,6 +63,10 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener):
|
||||
'tools.staticdir.index': 'index.html',
|
||||
'tools.staticdir.dir': static_dir,
|
||||
},
|
||||
b'/favicon.ico': {
|
||||
'tools.staticfile.on': True,
|
||||
'tools.staticfile.filename': favicon,
|
||||
},
|
||||
b'/mopidy': {
|
||||
'tools.staticdir.on': True,
|
||||
'tools.staticdir.index': 'mopidy.html',
|
||||
@ -93,42 +98,8 @@ class HttpFrontend(pykka.ThreadingActor, CoreListener):
|
||||
cherrypy.engine.exit()
|
||||
logger.info('Stopped HTTP server')
|
||||
|
||||
def track_playback_paused(self, **data):
|
||||
self._broadcast_event('track_playback_paused', data)
|
||||
|
||||
def track_playback_resumed(self, **data):
|
||||
self._broadcast_event('track_playback_resumed', data)
|
||||
|
||||
def track_playback_started(self, **data):
|
||||
self._broadcast_event('track_playback_started', data)
|
||||
|
||||
def track_playback_ended(self, **data):
|
||||
self._broadcast_event('track_playback_ended', data)
|
||||
|
||||
def playback_state_changed(self, **data):
|
||||
self._broadcast_event('playback_state_changed', data)
|
||||
|
||||
def tracklist_changed(self, **data):
|
||||
self._broadcast_event('tracklist_changed', data)
|
||||
|
||||
def playlists_loaded(self, **data):
|
||||
self._broadcast_event('playlists_loaded', data)
|
||||
|
||||
def playlist_changed(self, **data):
|
||||
self._broadcast_event('playlist_changed', data)
|
||||
|
||||
def options_changed(self, **data):
|
||||
self._broadcast_event('options_changed', data)
|
||||
|
||||
def volume_changed(self, **data):
|
||||
self._broadcast_event('volume_changed', data)
|
||||
|
||||
def seeked(self, **data):
|
||||
self._broadcast_event('seeked', data)
|
||||
|
||||
def _broadcast_event(self, name, data):
|
||||
event = {}
|
||||
event.update(data)
|
||||
def on_event(self, name, **data):
|
||||
event = data
|
||||
event['event'] = name
|
||||
message = json.dumps(event, cls=models.ModelJSONEncoder)
|
||||
cherrypy.engine.publish('websocket-broadcast', TextMessage(message))
|
||||
|
||||
BIN
mopidy/frontends/http/data/favicon.png
Normal file
BIN
mopidy/frontends/http/data/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
@ -35,6 +35,8 @@ def format_dependency_list(adapters=None):
|
||||
pylast_info,
|
||||
dbus_info,
|
||||
serial_info,
|
||||
cherrypy_info,
|
||||
ws4py_info,
|
||||
]
|
||||
|
||||
lines = []
|
||||
@ -189,3 +191,25 @@ def serial_info():
|
||||
except ImportError:
|
||||
pass
|
||||
return dep_info
|
||||
|
||||
|
||||
def cherrypy_info():
|
||||
dep_info = {'name': 'cherrypy'}
|
||||
try:
|
||||
import cherrypy
|
||||
dep_info['version'] = cherrypy.__version__
|
||||
dep_info['path'] = cherrypy.__file__
|
||||
except ImportError:
|
||||
pass
|
||||
return dep_info
|
||||
|
||||
|
||||
def ws4py_info():
|
||||
dep_info = {'name': 'ws4py'}
|
||||
try:
|
||||
import ws4py
|
||||
dep_info['version'] = ws4py.__version__
|
||||
dep_info['path'] = ws4py.__file__
|
||||
except ImportError:
|
||||
pass
|
||||
return dep_info
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import mock
|
||||
|
||||
from mopidy import audio
|
||||
|
||||
from tests import unittest
|
||||
@ -9,6 +11,15 @@ class AudioListenerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.listener = audio.AudioListener()
|
||||
|
||||
def test_on_event_forwards_to_specific_handler(self):
|
||||
self.listener.state_changed = mock.Mock()
|
||||
|
||||
self.listener.on_event(
|
||||
'state_changed', old_state='stopped', new_state='playing')
|
||||
|
||||
self.listener.state_changed.assert_called_with(
|
||||
old_state='stopped', new_state='playing')
|
||||
|
||||
def test_listener_has_default_impl_for_reached_end_of_stream(self):
|
||||
self.listener.reached_end_of_stream()
|
||||
|
||||
|
||||
@ -1,13 +1,22 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import mock
|
||||
|
||||
from mopidy.backends.listener import BackendListener
|
||||
|
||||
from tests import unittest
|
||||
|
||||
|
||||
class CoreListenerTest(unittest.TestCase):
|
||||
class BackendListenerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.listener = BackendListener()
|
||||
|
||||
def test_on_event_forwards_to_specific_handler(self):
|
||||
self.listener.playlists_loaded = mock.Mock()
|
||||
|
||||
self.listener.on_event('playlists_loaded')
|
||||
|
||||
self.listener.playlists_loaded.assert_called_with()
|
||||
|
||||
def test_listener_has_default_impl_for_playlists_loaded(self):
|
||||
self.listener.playlists_loaded()
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import mock
|
||||
|
||||
from mopidy.core import CoreListener, PlaybackState
|
||||
from mopidy.models import Playlist, Track
|
||||
|
||||
@ -10,6 +12,15 @@ class CoreListenerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.listener = CoreListener()
|
||||
|
||||
def test_on_event_forwards_to_specific_handler(self):
|
||||
self.listener.track_playback_paused = mock.Mock()
|
||||
|
||||
self.listener.on_event(
|
||||
'track_playback_paused', track=Track(), position=0)
|
||||
|
||||
self.listener.track_playback_paused.assert_called_with(
|
||||
track=Track(), position=0)
|
||||
|
||||
def test_listener_has_default_impl_for_track_playback_paused(self):
|
||||
self.listener.track_playback_paused(Track(), 0)
|
||||
|
||||
|
||||
@ -1,21 +1,29 @@
|
||||
import json
|
||||
|
||||
import cherrypy
|
||||
try:
|
||||
import cherrypy
|
||||
except ImportError:
|
||||
cherrypy = False
|
||||
import mock
|
||||
|
||||
from mopidy.frontends.http import HttpFrontend
|
||||
from mopidy.exceptions import OptionalDependencyError
|
||||
try:
|
||||
from mopidy.frontends.http import HttpFrontend
|
||||
except OptionalDependencyError:
|
||||
pass
|
||||
|
||||
from tests import unittest
|
||||
|
||||
|
||||
@mock.patch.object(cherrypy.engine, 'publish')
|
||||
@unittest.skipUnless(cherrypy, 'cherrypy not found')
|
||||
@mock.patch('cherrypy.engine.publish')
|
||||
class HttpEventsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.http = HttpFrontend(core=mock.Mock())
|
||||
|
||||
def test_track_playback_paused_is_broadcasted(self, publish):
|
||||
publish.reset_mock()
|
||||
self.http.track_playback_paused(foo='bar')
|
||||
self.http.on_event('track_playback_paused', foo='bar')
|
||||
self.assertEqual(publish.call_args[0][0], 'websocket-broadcast')
|
||||
self.assertDictEqual(
|
||||
json.loads(str(publish.call_args[0][1])), {
|
||||
@ -25,100 +33,10 @@ class HttpEventsTest(unittest.TestCase):
|
||||
|
||||
def test_track_playback_resumed_is_broadcasted(self, publish):
|
||||
publish.reset_mock()
|
||||
self.http.track_playback_resumed(foo='bar')
|
||||
self.http.on_event('track_playback_resumed', foo='bar')
|
||||
self.assertEqual(publish.call_args[0][0], 'websocket-broadcast')
|
||||
self.assertDictEqual(
|
||||
json.loads(str(publish.call_args[0][1])), {
|
||||
'event': 'track_playback_resumed',
|
||||
'foo': 'bar',
|
||||
})
|
||||
|
||||
def test_track_playback_started_is_broadcasted(self, publish):
|
||||
publish.reset_mock()
|
||||
self.http.track_playback_started(foo='bar')
|
||||
self.assertEqual(publish.call_args[0][0], 'websocket-broadcast')
|
||||
self.assertDictEqual(
|
||||
json.loads(str(publish.call_args[0][1])), {
|
||||
'event': 'track_playback_started',
|
||||
'foo': 'bar',
|
||||
})
|
||||
|
||||
def test_track_playback_ended_is_broadcasted(self, publish):
|
||||
publish.reset_mock()
|
||||
self.http.track_playback_ended(foo='bar')
|
||||
self.assertEqual(publish.call_args[0][0], 'websocket-broadcast')
|
||||
self.assertDictEqual(
|
||||
json.loads(str(publish.call_args[0][1])), {
|
||||
'event': 'track_playback_ended',
|
||||
'foo': 'bar',
|
||||
})
|
||||
|
||||
def test_playback_state_changed_is_broadcasted(self, publish):
|
||||
publish.reset_mock()
|
||||
self.http.playback_state_changed(foo='bar')
|
||||
self.assertEqual(publish.call_args[0][0], 'websocket-broadcast')
|
||||
self.assertDictEqual(
|
||||
json.loads(str(publish.call_args[0][1])), {
|
||||
'event': 'playback_state_changed',
|
||||
'foo': 'bar',
|
||||
})
|
||||
|
||||
def test_tracklist_changed_is_broadcasted(self, publish):
|
||||
publish.reset_mock()
|
||||
self.http.tracklist_changed(foo='bar')
|
||||
self.assertEqual(publish.call_args[0][0], 'websocket-broadcast')
|
||||
self.assertDictEqual(
|
||||
json.loads(str(publish.call_args[0][1])), {
|
||||
'event': 'tracklist_changed',
|
||||
'foo': 'bar',
|
||||
})
|
||||
|
||||
def test_playlists_loaded_is_broadcasted(self, publish):
|
||||
publish.reset_mock()
|
||||
self.http.playlists_loaded(foo='bar')
|
||||
self.assertEqual(publish.call_args[0][0], 'websocket-broadcast')
|
||||
self.assertDictEqual(
|
||||
json.loads(str(publish.call_args[0][1])), {
|
||||
'event': 'playlists_loaded',
|
||||
'foo': 'bar',
|
||||
})
|
||||
|
||||
def test_playlist_changed_is_broadcasted(self, publish):
|
||||
publish.reset_mock()
|
||||
self.http.playlist_changed(foo='bar')
|
||||
self.assertEqual(publish.call_args[0][0], 'websocket-broadcast')
|
||||
self.assertDictEqual(
|
||||
json.loads(str(publish.call_args[0][1])), {
|
||||
'event': 'playlist_changed',
|
||||
'foo': 'bar',
|
||||
})
|
||||
|
||||
def test_options_changed_is_broadcasted(self, publish):
|
||||
publish.reset_mock()
|
||||
self.http.options_changed(foo='bar')
|
||||
self.assertEqual(publish.call_args[0][0], 'websocket-broadcast')
|
||||
self.assertDictEqual(
|
||||
json.loads(str(publish.call_args[0][1])), {
|
||||
'event': 'options_changed',
|
||||
'foo': 'bar',
|
||||
})
|
||||
|
||||
def test_volume_changed_is_broadcasted(self, publish):
|
||||
publish.reset_mock()
|
||||
self.http.volume_changed(foo='bar')
|
||||
self.assertEqual(publish.call_args[0][0], 'websocket-broadcast')
|
||||
self.assertDictEqual(
|
||||
json.loads(str(publish.call_args[0][1])), {
|
||||
'event': 'volume_changed',
|
||||
'foo': 'bar',
|
||||
})
|
||||
|
||||
def test_seeked_is_broadcasted(self, publish):
|
||||
publish.reset_mock()
|
||||
self.http.seeked(foo='bar')
|
||||
self.assertEqual(publish.call_args[0][0], 'websocket-broadcast')
|
||||
self.assertDictEqual(
|
||||
json.loads(str(publish.call_args[0][1])), {
|
||||
'event': 'seeked',
|
||||
'foo': 'bar',
|
||||
})
|
||||
|
||||
@ -27,6 +27,16 @@ try:
|
||||
except ImportError:
|
||||
spotify = False
|
||||
|
||||
try:
|
||||
import cherrypy
|
||||
except ImportError:
|
||||
cherrypy = False
|
||||
|
||||
try:
|
||||
import ws4py
|
||||
except ImportError:
|
||||
ws4py = False
|
||||
|
||||
from mopidy.utils import deps
|
||||
|
||||
from tests import unittest
|
||||
@ -115,3 +125,19 @@ class DepsTest(unittest.TestCase):
|
||||
self.assertEquals('pyserial', result['name'])
|
||||
self.assertEquals(serial.VERSION, result['version'])
|
||||
self.assertIn('serial', result['path'])
|
||||
|
||||
@unittest.skipUnless(cherrypy, 'cherrypy not found')
|
||||
def test_cherrypy_info(self):
|
||||
result = deps.cherrypy_info()
|
||||
|
||||
self.assertEquals('cherrypy', result['name'])
|
||||
self.assertEquals(cherrypy.__version__, result['version'])
|
||||
self.assertIn('cherrypy', result['path'])
|
||||
|
||||
@unittest.skipUnless(ws4py, 'ws4py not found')
|
||||
def test_ws4py_info(self):
|
||||
result = deps.ws4py_info()
|
||||
|
||||
self.assertEquals('ws4py', result['name'])
|
||||
self.assertEquals(ws4py.__version__, result['version'])
|
||||
self.assertIn('ws4py', result['path'])
|
||||
|
||||
@ -260,7 +260,7 @@ class JsonRpcBatchTest(JsonRpcTestBase):
|
||||
|
||||
self.assertEqual(len(response), 3)
|
||||
|
||||
response = {row['id']: row for row in response}
|
||||
response = dict((row['id'], row) for row in response)
|
||||
self.assertEqual(response[1]['result'], False)
|
||||
self.assertEqual(response[2]['result'], True)
|
||||
self.assertEqual(response[3]['result'], False)
|
||||
@ -277,7 +277,7 @@ class JsonRpcBatchTest(JsonRpcTestBase):
|
||||
|
||||
self.assertEqual(len(response), 2)
|
||||
|
||||
response = {row['id']: row for row in response}
|
||||
response = dict((row['id'], row) for row in response)
|
||||
self.assertNotIn(1, response)
|
||||
self.assertEqual(response[2]['result'], True)
|
||||
self.assertEqual(response[3]['result'], False)
|
||||
@ -313,7 +313,7 @@ class JsonRpcSingleCommandErrorTest(JsonRpcTestBase):
|
||||
|
||||
data = error['data']
|
||||
self.assertEqual(data['type'], 'ValueError')
|
||||
self.assertEqual(data['message'], "u'bogus' is not in list")
|
||||
self.assertIn('not in list', data['message'])
|
||||
self.assertIn('traceback', data)
|
||||
self.assertIn('Traceback (most recent call last):', data['traceback'])
|
||||
|
||||
@ -522,7 +522,7 @@ class JsonRpcBatchErrorTest(JsonRpcTestBase):
|
||||
response = self.jrw.handle_data(request)
|
||||
|
||||
self.assertEqual(len(response), 5)
|
||||
response = {row['id']: row for row in response}
|
||||
response = dict((row['id'], row) for row in response)
|
||||
self.assertEqual(response['1']['result'], None)
|
||||
self.assertEqual(response['2']['result'], None)
|
||||
self.assertEqual(response[None]['error']['code'], -32600)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user