Merge pull request #1128 from adamcik/feature/validate-core-input
Validate core inputs
This commit is contained in:
commit
e265f5d673
@ -13,9 +13,11 @@ Core API
|
|||||||
- Calling :meth:`mopidy.core.library.LibraryController.search`` with ``kwargs``
|
- Calling :meth:`mopidy.core.library.LibraryController.search`` with ``kwargs``
|
||||||
as the query is no longer supported (PR: :issue:`1090`)
|
as the query is no longer supported (PR: :issue:`1090`)
|
||||||
|
|
||||||
- Update core controllers to handle backend exceptions in all calls that rely
|
- Updated core controllers to handle backend exceptions in all calls that rely
|
||||||
on multiple backends. (Issue: :issue:`667`)
|
on multiple backends. (Issue: :issue:`667`)
|
||||||
|
|
||||||
|
- Update core methods to do strict input checking. (Fixes: :issue:`#700`)
|
||||||
|
|
||||||
Models
|
Models
|
||||||
------
|
------
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,7 @@ import logging
|
|||||||
import operator
|
import operator
|
||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
|
from mopidy.utils import deprecation, validation
|
||||||
from mopidy.utils import deprecation
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -70,6 +69,9 @@ class LibraryController(object):
|
|||||||
"""
|
"""
|
||||||
if uri is None:
|
if uri is None:
|
||||||
return self._roots()
|
return self._roots()
|
||||||
|
elif not uri.strip():
|
||||||
|
return []
|
||||||
|
validation.check_uri(uri)
|
||||||
return self._browse(uri)
|
return self._browse(uri)
|
||||||
|
|
||||||
def _roots(self):
|
def _roots(self):
|
||||||
@ -111,6 +113,9 @@ class LibraryController(object):
|
|||||||
|
|
||||||
.. versionadded:: 1.0
|
.. versionadded:: 1.0
|
||||||
"""
|
"""
|
||||||
|
validation.check_choice(field, validation.DISTINCT_FIELDS)
|
||||||
|
query is None or validation.check_query(query) # TODO: normalize?
|
||||||
|
|
||||||
result = set()
|
result = set()
|
||||||
futures = {b: b.library.get_distinct(field, query)
|
futures = {b: b.library.get_distinct(field, query)
|
||||||
for b in self.backends.with_library.values()}
|
for b in self.backends.with_library.values()}
|
||||||
@ -137,6 +142,8 @@ class LibraryController(object):
|
|||||||
|
|
||||||
.. versionadded:: 1.0
|
.. versionadded:: 1.0
|
||||||
"""
|
"""
|
||||||
|
validation.check_uris(uris)
|
||||||
|
|
||||||
futures = {
|
futures = {
|
||||||
backend: backend.library.get_images(backend_uris)
|
backend: backend.library.get_images(backend_uris)
|
||||||
for (backend, backend_uris)
|
for (backend, backend_uris)
|
||||||
@ -187,6 +194,11 @@ class LibraryController(object):
|
|||||||
if none_set or both_set:
|
if none_set or both_set:
|
||||||
raise ValueError("One of 'uri' or 'uris' must be set")
|
raise ValueError("One of 'uri' or 'uris' must be set")
|
||||||
|
|
||||||
|
# TODO: validation.one_of(*args)?
|
||||||
|
|
||||||
|
uris is None or validation.check_uris(uris)
|
||||||
|
uri is None or validation.check_uri(uri)
|
||||||
|
|
||||||
if uri:
|
if uri:
|
||||||
deprecation.warn('core.library.lookup:uri_arg')
|
deprecation.warn('core.library.lookup:uri_arg')
|
||||||
|
|
||||||
@ -219,6 +231,8 @@ class LibraryController(object):
|
|||||||
:param uri: directory or track URI
|
:param uri: directory or track URI
|
||||||
:type uri: string
|
:type uri: string
|
||||||
"""
|
"""
|
||||||
|
uri is None or validation.check_uri(uri)
|
||||||
|
|
||||||
futures = {}
|
futures = {}
|
||||||
backends = {}
|
backends = {}
|
||||||
uri_scheme = urlparse.urlparse(uri).scheme if uri else None
|
uri_scheme = urlparse.urlparse(uri).scheme if uri else None
|
||||||
@ -285,6 +299,10 @@ class LibraryController(object):
|
|||||||
"""
|
"""
|
||||||
query = _normalize_query(query or kwargs)
|
query = _normalize_query(query or kwargs)
|
||||||
|
|
||||||
|
uris is None or validation.check_uris(uris)
|
||||||
|
query is None or validation.check_query(query)
|
||||||
|
validation.check_boolean(exact)
|
||||||
|
|
||||||
if kwargs:
|
if kwargs:
|
||||||
deprecation.warn('core.library.search:kwargs_query')
|
deprecation.warn('core.library.search:kwargs_query')
|
||||||
|
|
||||||
@ -319,6 +337,7 @@ class LibraryController(object):
|
|||||||
|
|
||||||
def _normalize_query(query):
|
def _normalize_query(query):
|
||||||
broken_client = False
|
broken_client = False
|
||||||
|
# TODO: this breaks if query is not a dictionary like object...
|
||||||
for (field, values) in query.items():
|
for (field, values) in query.items():
|
||||||
if isinstance(values, basestring):
|
if isinstance(values, basestring):
|
||||||
broken_client = True
|
broken_client = True
|
||||||
|
|||||||
@ -2,6 +2,8 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from mopidy.utils import validation
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -31,6 +33,8 @@ class MixerController(object):
|
|||||||
|
|
||||||
Returns :class:`True` if call is successful, otherwise :class:`False`.
|
Returns :class:`True` if call is successful, otherwise :class:`False`.
|
||||||
"""
|
"""
|
||||||
|
validation.check_integer(volume, min=0, max=100)
|
||||||
|
|
||||||
if self._mixer is None:
|
if self._mixer is None:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
@ -52,6 +56,7 @@ class MixerController(object):
|
|||||||
|
|
||||||
Returns :class:`True` if call is successful, otherwise :class:`False`.
|
Returns :class:`True` if call is successful, otherwise :class:`False`.
|
||||||
"""
|
"""
|
||||||
|
validation.check_boolean(mute)
|
||||||
if self._mixer is None:
|
if self._mixer is None:
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -3,9 +3,10 @@ from __future__ import absolute_import, unicode_literals
|
|||||||
import logging
|
import logging
|
||||||
import urlparse
|
import urlparse
|
||||||
|
|
||||||
|
from mopidy import models
|
||||||
from mopidy.audio import PlaybackState
|
from mopidy.audio import PlaybackState
|
||||||
from mopidy.core import listener
|
from mopidy.core import listener
|
||||||
from mopidy.utils import deprecation
|
from mopidy.utils import deprecation, validation
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -96,6 +97,8 @@ class PlaybackController(object):
|
|||||||
"PAUSED" -> "PLAYING" [ label="resume" ]
|
"PAUSED" -> "PLAYING" [ label="resume" ]
|
||||||
"PAUSED" -> "STOPPED" [ label="stop" ]
|
"PAUSED" -> "STOPPED" [ label="stop" ]
|
||||||
"""
|
"""
|
||||||
|
validation.check_choice(new_state, validation.PLAYBACK_STATES)
|
||||||
|
|
||||||
(old_state, self._state) = (self.get_state(), new_state)
|
(old_state, self._state) = (self.get_state(), new_state)
|
||||||
logger.debug('Changing state: %s -> %s', old_state, new_state)
|
logger.debug('Changing state: %s -> %s', old_state, new_state)
|
||||||
|
|
||||||
@ -270,6 +273,7 @@ class PlaybackController(object):
|
|||||||
:param tl_track: track to play
|
:param tl_track: track to play
|
||||||
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
|
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
|
||||||
"""
|
"""
|
||||||
|
tl_track is None or validation.check_instance(tl_track, models.TlTrack)
|
||||||
self._play(tl_track, on_error_step=1)
|
self._play(tl_track, on_error_step=1)
|
||||||
|
|
||||||
def _play(self, tl_track=None, on_error_step=1):
|
def _play(self, tl_track=None, on_error_step=1):
|
||||||
@ -360,6 +364,8 @@ class PlaybackController(object):
|
|||||||
:type time_position: int
|
:type time_position: int
|
||||||
:rtype: :class:`True` if successful, else :class:`False`
|
:rtype: :class:`True` if successful, else :class:`False`
|
||||||
"""
|
"""
|
||||||
|
validation.check_integer(time_position, min=0)
|
||||||
|
|
||||||
if not self.core.tracklist.tracks:
|
if not self.core.tracklist.tracks:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import urlparse
|
|||||||
|
|
||||||
from mopidy.core import listener
|
from mopidy.core import listener
|
||||||
from mopidy.models import Playlist
|
from mopidy.models import Playlist
|
||||||
from mopidy.utils import deprecation
|
from mopidy.utils import deprecation, validation
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -62,6 +62,8 @@ class PlaylistsController(object):
|
|||||||
|
|
||||||
.. versionadded:: 1.0
|
.. versionadded:: 1.0
|
||||||
"""
|
"""
|
||||||
|
validation.check_uri(uri)
|
||||||
|
|
||||||
uri_scheme = urlparse.urlparse(uri).scheme
|
uri_scheme = urlparse.urlparse(uri).scheme
|
||||||
backend = self.backends.with_playlists.get(uri_scheme, None)
|
backend = self.backends.with_playlists.get(uri_scheme, None)
|
||||||
if backend:
|
if backend:
|
||||||
@ -139,6 +141,8 @@ class PlaylistsController(object):
|
|||||||
:param uri: URI of the playlist to delete
|
:param uri: URI of the playlist to delete
|
||||||
:type uri: string
|
:type uri: string
|
||||||
"""
|
"""
|
||||||
|
validation.check_uri(uri)
|
||||||
|
|
||||||
uri_scheme = urlparse.urlparse(uri).scheme
|
uri_scheme = urlparse.urlparse(uri).scheme
|
||||||
backend = self.backends.with_playlists.get(uri_scheme, None)
|
backend = self.backends.with_playlists.get(uri_scheme, None)
|
||||||
if backend:
|
if backend:
|
||||||
@ -172,6 +176,10 @@ class PlaylistsController(object):
|
|||||||
deprecation.warn('core.playlists.filter')
|
deprecation.warn('core.playlists.filter')
|
||||||
|
|
||||||
criteria = criteria or kwargs
|
criteria = criteria or kwargs
|
||||||
|
validation.check_query(
|
||||||
|
criteria, validation.PLAYLIST_FIELDS, list_values=False)
|
||||||
|
|
||||||
|
# TODO: stop using self playlists
|
||||||
matches = self.playlists
|
matches = self.playlists
|
||||||
for (key, value) in criteria.iteritems():
|
for (key, value) in criteria.iteritems():
|
||||||
matches = filter(lambda p: getattr(p, key) == value, matches)
|
matches = filter(lambda p: getattr(p, key) == value, matches)
|
||||||
@ -207,6 +215,8 @@ class PlaylistsController(object):
|
|||||||
:param uri_scheme: limit to the backend matching the URI scheme
|
:param uri_scheme: limit to the backend matching the URI scheme
|
||||||
:type uri_scheme: string
|
:type uri_scheme: string
|
||||||
"""
|
"""
|
||||||
|
# TODO: check: uri_scheme is None or uri_scheme?
|
||||||
|
|
||||||
futures = {}
|
futures = {}
|
||||||
backends = {}
|
backends = {}
|
||||||
playlists_loaded = False
|
playlists_loaded = False
|
||||||
@ -251,8 +261,11 @@ class PlaylistsController(object):
|
|||||||
:type playlist: :class:`mopidy.models.Playlist`
|
:type playlist: :class:`mopidy.models.Playlist`
|
||||||
:rtype: :class:`mopidy.models.Playlist` or :class:`None`
|
:rtype: :class:`mopidy.models.Playlist` or :class:`None`
|
||||||
"""
|
"""
|
||||||
|
validation.check_instance(playlist, Playlist)
|
||||||
|
|
||||||
if playlist.uri is None:
|
if playlist.uri is None:
|
||||||
return
|
return # TODO: log this problem?
|
||||||
|
|
||||||
uri_scheme = urlparse.urlparse(playlist.uri).scheme
|
uri_scheme = urlparse.urlparse(playlist.uri).scheme
|
||||||
backend = self.backends.with_playlists.get(uri_scheme, None)
|
backend = self.backends.with_playlists.get(uri_scheme, None)
|
||||||
if backend:
|
if backend:
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
import collections
|
|
||||||
import logging
|
import logging
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from mopidy import compat
|
|
||||||
from mopidy.core import listener
|
from mopidy.core import listener
|
||||||
from mopidy.models import TlTrack
|
from mopidy.models import TlTrack, Track
|
||||||
from mopidy.utils import deprecation
|
from mopidy.utils import deprecation, validation
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -93,6 +91,7 @@ class TracklistController(object):
|
|||||||
:class:`False`
|
:class:`False`
|
||||||
Tracks are not removed from the tracklist.
|
Tracks are not removed from the tracklist.
|
||||||
"""
|
"""
|
||||||
|
validation.check_boolean(value)
|
||||||
if self.get_consume() != value:
|
if self.get_consume() != value:
|
||||||
self._trigger_options_changed()
|
self._trigger_options_changed()
|
||||||
return setattr(self, '_consume', value)
|
return setattr(self, '_consume', value)
|
||||||
@ -121,7 +120,7 @@ class TracklistController(object):
|
|||||||
:class:`False`
|
:class:`False`
|
||||||
Tracks are played in the order of the tracklist.
|
Tracks are played in the order of the tracklist.
|
||||||
"""
|
"""
|
||||||
|
validation.check_boolean(value)
|
||||||
if self.get_random() != value:
|
if self.get_random() != value:
|
||||||
self._trigger_options_changed()
|
self._trigger_options_changed()
|
||||||
if value:
|
if value:
|
||||||
@ -157,7 +156,7 @@ class TracklistController(object):
|
|||||||
:class:`False`
|
:class:`False`
|
||||||
The tracklist is played once.
|
The tracklist is played once.
|
||||||
"""
|
"""
|
||||||
|
validation.check_boolean(value)
|
||||||
if self.get_repeat() != value:
|
if self.get_repeat() != value:
|
||||||
self._trigger_options_changed()
|
self._trigger_options_changed()
|
||||||
return setattr(self, '_repeat', value)
|
return setattr(self, '_repeat', value)
|
||||||
@ -188,6 +187,7 @@ class TracklistController(object):
|
|||||||
:class:`False`
|
:class:`False`
|
||||||
Playback continues after current song.
|
Playback continues after current song.
|
||||||
"""
|
"""
|
||||||
|
validation.check_boolean(value)
|
||||||
if self.get_single() != value:
|
if self.get_single() != value:
|
||||||
self._trigger_options_changed()
|
self._trigger_options_changed()
|
||||||
return setattr(self, '_single', value)
|
return setattr(self, '_single', value)
|
||||||
@ -205,9 +205,10 @@ class TracklistController(object):
|
|||||||
The position of the given track in the tracklist.
|
The position of the given track in the tracklist.
|
||||||
|
|
||||||
:param tl_track: the track to find the index of
|
:param tl_track: the track to find the index of
|
||||||
:type tl_track: :class:`mopidy.models.TlTrack`
|
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
|
||||||
:rtype: :class:`int` or :class:`None`
|
:rtype: :class:`int` or :class:`None`
|
||||||
"""
|
"""
|
||||||
|
tl_track is None or validation.check_instance(tl_track, TlTrack)
|
||||||
try:
|
try:
|
||||||
return self._tl_tracks.index(tl_track)
|
return self._tl_tracks.index(tl_track)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@ -223,6 +224,7 @@ class TracklistController(object):
|
|||||||
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
|
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
|
||||||
:rtype: :class:`mopidy.models.TlTrack` or :class:`None`
|
:rtype: :class:`mopidy.models.TlTrack` or :class:`None`
|
||||||
"""
|
"""
|
||||||
|
tl_track is None or validation.check_instance(tl_track, TlTrack)
|
||||||
if self.get_single() and self.get_repeat():
|
if self.get_single() and self.get_repeat():
|
||||||
return tl_track
|
return tl_track
|
||||||
elif self.get_single():
|
elif self.get_single():
|
||||||
@ -247,6 +249,7 @@ class TracklistController(object):
|
|||||||
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
|
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
|
||||||
:rtype: :class:`mopidy.models.TlTrack` or :class:`None`
|
:rtype: :class:`mopidy.models.TlTrack` or :class:`None`
|
||||||
"""
|
"""
|
||||||
|
tl_track is None or validation.check_instance(tl_track, TlTrack)
|
||||||
|
|
||||||
if not self.get_tl_tracks():
|
if not self.get_tl_tracks():
|
||||||
return None
|
return None
|
||||||
@ -288,6 +291,8 @@ class TracklistController(object):
|
|||||||
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
|
:type tl_track: :class:`mopidy.models.TlTrack` or :class:`None`
|
||||||
:rtype: :class:`mopidy.models.TlTrack` or :class:`None`
|
:rtype: :class:`mopidy.models.TlTrack` or :class:`None`
|
||||||
"""
|
"""
|
||||||
|
tl_track is None or validation.check_instance(tl_track, TlTrack)
|
||||||
|
|
||||||
if self.get_repeat() or self.get_consume() or self.get_random():
|
if self.get_repeat() or self.get_consume() or self.get_random():
|
||||||
return tl_track
|
return tl_track
|
||||||
|
|
||||||
@ -330,8 +335,13 @@ class TracklistController(object):
|
|||||||
"""
|
"""
|
||||||
assert tracks is not None or uri is not None or uris is not None, \
|
assert tracks is not None or uri is not None or uris is not None, \
|
||||||
'tracks, uri or uris must be provided'
|
'tracks, uri or uris must be provided'
|
||||||
|
# TODO: check that only one of tracks uri and uris is set...
|
||||||
|
# TODO: can at_position be negative?
|
||||||
|
|
||||||
# TODO: assert that tracks are track instances
|
tracks is None or validation.check_instances(tracks, Track)
|
||||||
|
uri is None or validation.check_uri(uri)
|
||||||
|
uris is None or validation.check_uris(uris)
|
||||||
|
validation.check_integer(at_position or 0)
|
||||||
|
|
||||||
if tracks:
|
if tracks:
|
||||||
deprecation.warn('core.tracklist.add:tracks_arg')
|
deprecation.warn('core.tracklist.add:tracks_arg')
|
||||||
@ -412,17 +422,18 @@ class TracklistController(object):
|
|||||||
:rtype: list of :class:`mopidy.models.TlTrack`
|
:rtype: list of :class:`mopidy.models.TlTrack`
|
||||||
"""
|
"""
|
||||||
criteria = criteria or kwargs
|
criteria = criteria or kwargs
|
||||||
|
tlids = criteria.pop('tlid', [])
|
||||||
|
validation.check_query(criteria, validation.TRACKLIST_FIELDS)
|
||||||
|
validation.check_instances(tlids, int)
|
||||||
|
# TODO: deprecate kwargs
|
||||||
|
# TODO: id=[1, 2, 3] filtering can't possibly be working
|
||||||
|
|
||||||
matches = self._tl_tracks
|
matches = self._tl_tracks
|
||||||
for (key, values) in criteria.items():
|
for (key, values) in criteria.items():
|
||||||
if (not isinstance(values, collections.Iterable) or
|
matches = [
|
||||||
isinstance(values, compat.string_types)):
|
ct for ct in matches if getattr(ct.track, key) in values]
|
||||||
# Fail hard if anyone is using the <0.17 calling style
|
if tlids:
|
||||||
raise ValueError('Filter values must be iterable: %r' % values)
|
matches = [ct for ct in matches if ct.tlid in tlids]
|
||||||
if key == 'tlid':
|
|
||||||
matches = [ct for ct in matches if ct.tlid in values]
|
|
||||||
else:
|
|
||||||
matches = [
|
|
||||||
ct for ct in matches if getattr(ct.track, key) in values]
|
|
||||||
return matches
|
return matches
|
||||||
|
|
||||||
def move(self, start, end, to_position):
|
def move(self, start, end, to_position):
|
||||||
@ -443,6 +454,7 @@ class TracklistController(object):
|
|||||||
|
|
||||||
tl_tracks = self._tl_tracks
|
tl_tracks = self._tl_tracks
|
||||||
|
|
||||||
|
# TODO: use validation helpers?
|
||||||
assert start < end, 'start must be smaller than end'
|
assert start < end, 'start must be smaller than end'
|
||||||
assert start >= 0, 'start must be at least zero'
|
assert start >= 0, 'start must be at least zero'
|
||||||
assert end <= len(tl_tracks), \
|
assert end <= len(tl_tracks), \
|
||||||
@ -470,6 +482,7 @@ class TracklistController(object):
|
|||||||
:type criteria: dict
|
:type criteria: dict
|
||||||
:rtype: list of :class:`mopidy.models.TlTrack` that was removed
|
:rtype: list of :class:`mopidy.models.TlTrack` that was removed
|
||||||
"""
|
"""
|
||||||
|
# TODO: deprecate kwargs
|
||||||
tl_tracks = self.filter(criteria, **kwargs)
|
tl_tracks = self.filter(criteria, **kwargs)
|
||||||
for tl_track in tl_tracks:
|
for tl_track in tl_tracks:
|
||||||
position = self._tl_tracks.index(tl_track)
|
position = self._tl_tracks.index(tl_track)
|
||||||
@ -491,6 +504,7 @@ class TracklistController(object):
|
|||||||
"""
|
"""
|
||||||
tl_tracks = self._tl_tracks
|
tl_tracks = self._tl_tracks
|
||||||
|
|
||||||
|
# TOOD: use validation helpers?
|
||||||
if start is not None and end is not None:
|
if start is not None and end is not None:
|
||||||
assert start < end, 'start must be smaller than end'
|
assert start < end, 'start must be smaller than end'
|
||||||
|
|
||||||
@ -519,6 +533,7 @@ class TracklistController(object):
|
|||||||
:type end: int
|
:type end: int
|
||||||
:rtype: :class:`mopidy.models.TlTrack`
|
:rtype: :class:`mopidy.models.TlTrack`
|
||||||
"""
|
"""
|
||||||
|
# TODO: validate slice?
|
||||||
return self._tl_tracks[start:end]
|
return self._tl_tracks[start:end]
|
||||||
|
|
||||||
def _mark_playing(self, tl_track):
|
def _mark_playing(self, tl_track):
|
||||||
|
|||||||
@ -46,3 +46,7 @@ class ScannerError(MopidyException):
|
|||||||
|
|
||||||
class AudioException(MopidyException):
|
class AudioException(MopidyException):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ValidationError(ValueError):
|
||||||
|
pass
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
from __future__ import absolute_import, unicode_literals
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
import urlparse
|
||||||
|
|
||||||
from mopidy.mpd import exceptions, protocol, translator
|
from mopidy.mpd import exceptions, protocol, translator
|
||||||
from mopidy.utils import deprecation
|
from mopidy.utils import deprecation
|
||||||
|
|
||||||
@ -21,8 +23,11 @@ def add(context, uri):
|
|||||||
if not uri.strip('/'):
|
if not uri.strip('/'):
|
||||||
return
|
return
|
||||||
|
|
||||||
if context.core.tracklist.add(uris=[uri]).get():
|
# If we have an URI just try and add it directly without bothering with
|
||||||
return
|
# jumping through browse...
|
||||||
|
if urlparse.urlparse(uri).scheme != '':
|
||||||
|
if context.core.tracklist.add(uris=[uri]).get():
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
uris = []
|
uris = []
|
||||||
|
|||||||
@ -270,10 +270,12 @@ def list_(context, *args):
|
|||||||
if field not in _LIST_MAPPING:
|
if field not in _LIST_MAPPING:
|
||||||
raise exceptions.MpdArgError('incorrect arguments')
|
raise exceptions.MpdArgError('incorrect arguments')
|
||||||
|
|
||||||
|
query = None
|
||||||
if len(params) == 1:
|
if len(params) == 1:
|
||||||
if field != 'album':
|
if field != 'album':
|
||||||
raise exceptions.MpdArgError('should be "Album" for 3 arguments')
|
raise exceptions.MpdArgError('should be "Album" for 3 arguments')
|
||||||
query = {'artist': params}
|
if params[0].strip():
|
||||||
|
query = {'artist': params}
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
query = _query_from_mpd_search_parameters(params, _LIST_MAPPING)
|
query = _query_from_mpd_search_parameters(params, _LIST_MAPPING)
|
||||||
|
|||||||
102
mopidy/utils/validation.py
Normal file
102
mopidy/utils/validation.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import urlparse
|
||||||
|
|
||||||
|
from mopidy import compat, exceptions
|
||||||
|
|
||||||
|
PLAYBACK_STATES = {'paused', 'stopped', 'playing'}
|
||||||
|
|
||||||
|
SEARCH_FIELDS = {
|
||||||
|
'uri', 'track_name', 'album', 'artist', 'albumartist', 'composer',
|
||||||
|
'performer', 'track_no', 'genre', 'date', 'comment', 'any'}
|
||||||
|
|
||||||
|
PLAYLIST_FIELDS = {'uri', 'name'} # TODO: add length and last_modified?
|
||||||
|
|
||||||
|
TRACKLIST_FIELDS = { # TODO: add bitrate, length, disc_no, track_no, modified?
|
||||||
|
'uri', 'name', 'genre', 'date', 'comment', 'musicbrainz_id'}
|
||||||
|
|
||||||
|
DISTINCT_FIELDS = {
|
||||||
|
'artist', 'albumartist', 'album', 'composer', 'performer', 'date', 'genre'}
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: _check_iterable(check, msg, **kwargs) + [check(a) for a in arg]?
|
||||||
|
def _check_iterable(arg, msg, **kwargs):
|
||||||
|
"""Ensure we have an iterable which is not a string."""
|
||||||
|
if isinstance(arg, compat.string_types):
|
||||||
|
raise exceptions.ValidationError(msg.format(arg=arg, **kwargs))
|
||||||
|
elif not isinstance(arg, collections.Iterable):
|
||||||
|
raise exceptions.ValidationError(msg.format(arg=arg, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
def check_choice(arg, choices, msg='Expected one of {choices}, not {arg!r}'):
|
||||||
|
if arg not in choices:
|
||||||
|
raise exceptions.ValidationError(msg.format(
|
||||||
|
arg=arg, choices=tuple(choices)))
|
||||||
|
|
||||||
|
|
||||||
|
def check_boolean(arg, msg='Expected a boolean, not {arg!r}'):
|
||||||
|
check_instance(arg, bool, msg=msg)
|
||||||
|
|
||||||
|
|
||||||
|
def check_instance(arg, cls, msg='Expected a {name} instance, not {arg!r}'):
|
||||||
|
if not isinstance(arg, cls):
|
||||||
|
raise exceptions.ValidationError(
|
||||||
|
msg.format(arg=arg, name=cls.__name__))
|
||||||
|
|
||||||
|
|
||||||
|
def check_instances(arg, cls, msg='Expected a list of {name}, not {arg!r}'):
|
||||||
|
_check_iterable(arg, msg, name=cls.__name__)
|
||||||
|
if not all(isinstance(instance, cls) for instance in arg):
|
||||||
|
raise exceptions.ValidationError(
|
||||||
|
msg.format(arg=arg, name=cls.__name__))
|
||||||
|
|
||||||
|
|
||||||
|
def check_integer(arg, min=None, max=None):
|
||||||
|
if not isinstance(arg, (int, long)):
|
||||||
|
raise exceptions.ValidationError('Expected an integer, not %r' % arg)
|
||||||
|
elif min is not None and arg < min:
|
||||||
|
raise exceptions.ValidationError(
|
||||||
|
'Expected number larger or equal to %d, not %r' % (min, arg))
|
||||||
|
elif max is not None and arg > max:
|
||||||
|
raise exceptions.ValidationError(
|
||||||
|
'Expected number smaller or equal to %d, not %r' % (max, arg))
|
||||||
|
|
||||||
|
|
||||||
|
def check_query(arg, fields=SEARCH_FIELDS, list_values=True):
|
||||||
|
# TODO: normalize name -> track_name
|
||||||
|
# TODO: normalize value -> [value]
|
||||||
|
# TODO: normalize blank -> [] or just remove field?
|
||||||
|
# TODO: remove list_values?
|
||||||
|
|
||||||
|
if not isinstance(arg, collections.Mapping):
|
||||||
|
raise exceptions.ValidationError(
|
||||||
|
'Expected a query dictionary, not {arg!r}'.format(arg=arg))
|
||||||
|
|
||||||
|
for key, value in arg.items():
|
||||||
|
check_choice(key, fields, msg='Expected query field to be one of '
|
||||||
|
'{choices}, not {arg!r}')
|
||||||
|
if list_values:
|
||||||
|
msg = 'Expected "{key}" to be list of strings, not {arg!r}'
|
||||||
|
_check_iterable(value, msg, key=key)
|
||||||
|
[_check_query_value(key, v, msg) for v in value]
|
||||||
|
else:
|
||||||
|
_check_query_value(
|
||||||
|
key, value, 'Expected "{key}" to be a string, not {arg!r}')
|
||||||
|
|
||||||
|
|
||||||
|
def _check_query_value(key, arg, msg):
|
||||||
|
if not isinstance(arg, compat.string_types) or not arg.strip():
|
||||||
|
raise exceptions.ValidationError(msg.format(arg=arg, key=key))
|
||||||
|
|
||||||
|
|
||||||
|
def check_uri(arg, msg='Expected a valid URI, not {arg!r}'):
|
||||||
|
if not isinstance(arg, compat.string_types):
|
||||||
|
raise exceptions.ValidationError(msg.format(arg=arg))
|
||||||
|
elif urlparse.urlparse(arg).scheme == '':
|
||||||
|
raise exceptions.ValidationError(msg.format(arg=arg))
|
||||||
|
|
||||||
|
|
||||||
|
def check_uris(arg, msg='Expected a list of URIs, not {arg!r}'):
|
||||||
|
_check_iterable(arg, msg)
|
||||||
|
[check_uri(a, msg) for a in arg]
|
||||||
@ -9,7 +9,7 @@ import mock
|
|||||||
|
|
||||||
import pykka
|
import pykka
|
||||||
|
|
||||||
from mopidy import core
|
from mopidy import core, exceptions
|
||||||
from mopidy.local import actor, json
|
from mopidy.local import actor, json
|
||||||
from mopidy.models import Album, Artist, Image, Track
|
from mopidy.models import Album, Artist, Image, Track
|
||||||
|
|
||||||
@ -137,8 +137,8 @@ class LocalLibraryProviderTest(unittest.TestCase):
|
|||||||
self.assertEqual(result[uri], self.tracks[0:1])
|
self.assertEqual(result[uri], self.tracks[0:1])
|
||||||
|
|
||||||
def test_lookup_unknown_track(self):
|
def test_lookup_unknown_track(self):
|
||||||
tracks = self.library.lookup(uris=['fake uri'])
|
tracks = self.library.lookup(uris=['fake:/uri'])
|
||||||
self.assertEqual(tracks, {'fake uri': []})
|
self.assertEqual(tracks, {'fake:/uri': []})
|
||||||
|
|
||||||
# test backward compatibility with local libraries returning a
|
# test backward compatibility with local libraries returning a
|
||||||
# single Track
|
# single Track
|
||||||
@ -343,42 +343,44 @@ class LocalLibraryProviderTest(unittest.TestCase):
|
|||||||
result = self.find_exact(any=['local:track:path1'])
|
result = self.find_exact(any=['local:track:path1'])
|
||||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||||
|
|
||||||
|
# TODO: This is really just a test of the query validation code now,
|
||||||
|
# as this code path never even makes it to the local backend.
|
||||||
def test_find_exact_wrong_type(self):
|
def test_find_exact_wrong_type(self):
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.find_exact(wrong=['test'])
|
self.find_exact(wrong=['test'])
|
||||||
|
|
||||||
def test_find_exact_with_empty_query(self):
|
def test_find_exact_with_empty_query(self):
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.find_exact(artist=[''])
|
self.find_exact(artist=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.find_exact(albumartist=[''])
|
self.find_exact(albumartist=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.find_exact(track_name=[''])
|
self.find_exact(track_name=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.find_exact(composer=[''])
|
self.find_exact(composer=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.find_exact(performer=[''])
|
self.find_exact(performer=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.find_exact(album=[''])
|
self.find_exact(album=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.find_exact(track_no=[''])
|
self.find_exact(track_no=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.find_exact(genre=[''])
|
self.find_exact(genre=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.find_exact(date=[''])
|
self.find_exact(date=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.find_exact(comment=[''])
|
self.find_exact(comment=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.find_exact(any=[''])
|
self.find_exact(any=[''])
|
||||||
|
|
||||||
def test_search_no_hits(self):
|
def test_search_no_hits(self):
|
||||||
@ -553,41 +555,41 @@ class LocalLibraryProviderTest(unittest.TestCase):
|
|||||||
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
self.assertEqual(list(result[0].tracks), self.tracks[:1])
|
||||||
|
|
||||||
def test_search_wrong_type(self):
|
def test_search_wrong_type(self):
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.search(wrong=['test'])
|
self.search(wrong=['test'])
|
||||||
|
|
||||||
def test_search_with_empty_query(self):
|
def test_search_with_empty_query(self):
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.search(artist=[''])
|
self.search(artist=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.search(albumartist=[''])
|
self.search(albumartist=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.search(composer=[''])
|
self.search(composer=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.search(performer=[''])
|
self.search(performer=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.search(track_name=[''])
|
self.search(track_name=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.search(album=[''])
|
self.search(album=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.search(genre=[''])
|
self.search(genre=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.search(date=[''])
|
self.search(date=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.search(comment=[''])
|
self.search(comment=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.search(uri=[''])
|
self.search(uri=[''])
|
||||||
|
|
||||||
with self.assertRaises(LookupError):
|
with self.assertRaises(exceptions.ValidationError):
|
||||||
self.search(any=[''])
|
self.search(any=[''])
|
||||||
|
|
||||||
def test_default_get_images_impl_no_images(self):
|
def test_default_get_images_impl_no_images(self):
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import pykka
|
|||||||
from mopidy import core
|
from mopidy import core
|
||||||
from mopidy.core import PlaybackState
|
from mopidy.core import PlaybackState
|
||||||
from mopidy.local import actor
|
from mopidy.local import actor
|
||||||
from mopidy.models import Track
|
from mopidy.models import TlTrack, Track
|
||||||
from mopidy.utils import deprecation
|
from mopidy.utils import deprecation
|
||||||
|
|
||||||
from tests import dummy_audio, path_to_data_dir
|
from tests import dummy_audio, path_to_data_dir
|
||||||
@ -841,22 +841,6 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
|||||||
self.playback.seek(self.tracklist.tracks[-1].length * 100)
|
self.playback.seek(self.tracklist.tracks[-1].length * 100)
|
||||||
self.assertEqual(self.playback.state, PlaybackState.STOPPED)
|
self.assertEqual(self.playback.state, PlaybackState.STOPPED)
|
||||||
|
|
||||||
@unittest.SkipTest
|
|
||||||
@populate_tracklist
|
|
||||||
def test_seek_beyond_start_of_song(self):
|
|
||||||
# FIXME need to decide return value
|
|
||||||
self.playback.play()
|
|
||||||
result = self.playback.seek(-1000)
|
|
||||||
self.assert_(not result, 'Seek return value was %s' % result)
|
|
||||||
|
|
||||||
@populate_tracklist
|
|
||||||
def test_seek_beyond_start_of_song_update_postion(self):
|
|
||||||
self.playback.play()
|
|
||||||
self.playback.seek(-1000)
|
|
||||||
position = self.playback.time_position
|
|
||||||
self.assertGreaterEqual(position, 0)
|
|
||||||
self.assertEqual(self.playback.state, PlaybackState.PLAYING)
|
|
||||||
|
|
||||||
@populate_tracklist
|
@populate_tracklist
|
||||||
def test_stop_when_stopped(self):
|
def test_stop_when_stopped(self):
|
||||||
self.playback.stop()
|
self.playback.stop()
|
||||||
@ -1088,4 +1072,4 @@ class LocalPlaybackProviderTest(unittest.TestCase):
|
|||||||
@populate_tracklist
|
@populate_tracklist
|
||||||
def test_playing_track_that_isnt_in_playlist(self):
|
def test_playing_track_that_isnt_in_playlist(self):
|
||||||
with self.assertRaises(AssertionError):
|
with self.assertRaises(AssertionError):
|
||||||
self.playback.play((17, Track()))
|
self.playback.play(TlTrack(17, Track()))
|
||||||
|
|||||||
@ -76,7 +76,7 @@ class StatusHandlerTest(unittest.TestCase):
|
|||||||
self.assertEqual(int(result['repeat']), 0)
|
self.assertEqual(int(result['repeat']), 0)
|
||||||
|
|
||||||
def test_status_method_contains_repeat_is_1(self):
|
def test_status_method_contains_repeat_is_1(self):
|
||||||
self.core.tracklist.repeat = 1
|
self.core.tracklist.set_repeat(True)
|
||||||
result = dict(status.status(self.context))
|
result = dict(status.status(self.context))
|
||||||
self.assertIn('repeat', result)
|
self.assertIn('repeat', result)
|
||||||
self.assertEqual(int(result['repeat']), 1)
|
self.assertEqual(int(result['repeat']), 1)
|
||||||
@ -87,7 +87,7 @@ class StatusHandlerTest(unittest.TestCase):
|
|||||||
self.assertEqual(int(result['random']), 0)
|
self.assertEqual(int(result['random']), 0)
|
||||||
|
|
||||||
def test_status_method_contains_random_is_1(self):
|
def test_status_method_contains_random_is_1(self):
|
||||||
self.core.tracklist.random = 1
|
self.core.tracklist.set_random(True)
|
||||||
result = dict(status.status(self.context))
|
result = dict(status.status(self.context))
|
||||||
self.assertIn('random', result)
|
self.assertIn('random', result)
|
||||||
self.assertEqual(int(result['random']), 1)
|
self.assertEqual(int(result['random']), 1)
|
||||||
@ -103,7 +103,7 @@ class StatusHandlerTest(unittest.TestCase):
|
|||||||
self.assertEqual(int(result['consume']), 0)
|
self.assertEqual(int(result['consume']), 0)
|
||||||
|
|
||||||
def test_status_method_contains_consume_is_1(self):
|
def test_status_method_contains_consume_is_1(self):
|
||||||
self.core.tracklist.consume = 1
|
self.core.tracklist.set_consume(True)
|
||||||
result = dict(status.status(self.context))
|
result = dict(status.status(self.context))
|
||||||
self.assertIn('consume', result)
|
self.assertIn('consume', result)
|
||||||
self.assertEqual(int(result['consume']), 1)
|
self.assertEqual(int(result['consume']), 1)
|
||||||
|
|||||||
163
tests/utils/test_validation.py
Normal file
163
tests/utils/test_validation.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
from pytest import raises
|
||||||
|
|
||||||
|
from mopidy import compat, exceptions
|
||||||
|
from mopidy.utils import validation
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_boolean_with_valid_values():
|
||||||
|
for value in (True, False):
|
||||||
|
validation.check_boolean(value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_boolean_with_other_values():
|
||||||
|
for value in 1, 0, None, '', list(), tuple():
|
||||||
|
with raises(exceptions.ValidationError):
|
||||||
|
validation.check_boolean(value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_boolean_error_message():
|
||||||
|
with raises(exceptions.ValidationError) as excinfo:
|
||||||
|
validation.check_boolean(1234)
|
||||||
|
assert 'Expected a boolean, not 1234' == str(excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_choice_with_valid_values():
|
||||||
|
for value, choices in (2, (1, 2, 3)), ('abc', ('abc', 'def')):
|
||||||
|
validation.check_choice(value, choices)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_choice_with_invalid_values():
|
||||||
|
for value, choices in (5, (1, 2, 3)), ('xyz', ('abc', 'def')):
|
||||||
|
with raises(exceptions.ValidationError):
|
||||||
|
validation.check_choice(value, choices)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_choice_error_message():
|
||||||
|
with raises(exceptions.ValidationError) as excinfo:
|
||||||
|
validation.check_choice(5, (1, 2, 3))
|
||||||
|
assert 'Expected one of (1, 2, 3), not 5' == str(excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_instance_with_valid_choices():
|
||||||
|
for value, cls in ((True, bool), ('a', compat.text_type), (123, int)):
|
||||||
|
validation.check_instance(value, cls)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_instance_with_invalid_values():
|
||||||
|
for value, cls in (1, str), ('abc', int):
|
||||||
|
with raises(exceptions.ValidationError):
|
||||||
|
validation.check_instance(value, cls)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_instance_error_message():
|
||||||
|
with raises(exceptions.ValidationError) as excinfo:
|
||||||
|
validation.check_instance(1, dict)
|
||||||
|
assert 'Expected a dict instance, not 1' == str(excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_instances_with_valid_values():
|
||||||
|
validation.check_instances([], int)
|
||||||
|
validation.check_instances([1, 2], int)
|
||||||
|
validation.check_instances((1, 2), int)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_instances_with_invalid_values():
|
||||||
|
with raises(exceptions.ValidationError):
|
||||||
|
validation.check_instances('abc', compat.string_types)
|
||||||
|
with raises(exceptions.ValidationError):
|
||||||
|
validation.check_instances(['abc', 123], compat.string_types)
|
||||||
|
with raises(exceptions.ValidationError):
|
||||||
|
validation.check_instances(None, compat.string_types)
|
||||||
|
with raises(exceptions.ValidationError):
|
||||||
|
validation.check_instances([None], compat.string_types)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_instances_error_message():
|
||||||
|
with raises(exceptions.ValidationError) as excinfo:
|
||||||
|
validation.check_instances([1], compat.string_types)
|
||||||
|
assert 'Expected a list of basestring, not [1]' == str(excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_query_valid_values():
|
||||||
|
for value in {}, {'any': []}, {'any': ['abc']}:
|
||||||
|
validation.check_query(value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_query_random_iterables():
|
||||||
|
for value in None, tuple(), list(), 'abc':
|
||||||
|
with raises(exceptions.ValidationError):
|
||||||
|
validation.check_query(value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_mapping_error_message():
|
||||||
|
with raises(exceptions.ValidationError) as excinfo:
|
||||||
|
validation.check_query([])
|
||||||
|
assert 'Expected a query dictionary, not []' == str(excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_query_invalid_fields():
|
||||||
|
for value in 'wrong', 'bar', 'foo', 'tlid':
|
||||||
|
with raises(exceptions.ValidationError):
|
||||||
|
validation.check_query({value: []})
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_field_error_message():
|
||||||
|
with raises(exceptions.ValidationError) as excinfo:
|
||||||
|
validation.check_query({'wrong': ['abc']})
|
||||||
|
assert 'Expected query field to be one of ' in str(excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_query_invalid_values():
|
||||||
|
for value in '', None, 'foo', 123, [''], [None]:
|
||||||
|
with raises(exceptions.ValidationError):
|
||||||
|
validation.check_query({'any': value})
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_values_error_message():
|
||||||
|
with raises(exceptions.ValidationError) as excinfo:
|
||||||
|
validation.check_query({'any': 'abc'})
|
||||||
|
assert 'Expected "any" to be list of strings, not' in str(excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_uri_with_valid_values():
|
||||||
|
for value in 'foobar:', 'http://example.com', 'git+http://example.com':
|
||||||
|
validation.check_uri(value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_uri_with_invalid_values():
|
||||||
|
# Note that tuple catches a potential bug with using "'foo' % arg" for
|
||||||
|
# formatting.
|
||||||
|
for value in ('foobar', 'htt p://example.com', None, 1234, tuple()):
|
||||||
|
with raises(exceptions.ValidationError):
|
||||||
|
validation.check_uri(value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_uri_error_message():
|
||||||
|
with raises(exceptions.ValidationError) as excinfo:
|
||||||
|
validation.check_uri('testing')
|
||||||
|
assert "Expected a valid URI, not u'testing'" == str(excinfo.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_uris_with_valid_values():
|
||||||
|
validation.check_uris([])
|
||||||
|
validation.check_uris(['foobar:'])
|
||||||
|
validation.check_uris(('foobar:',))
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_uris_with_invalid_values():
|
||||||
|
with raises(exceptions.ValidationError):
|
||||||
|
validation.check_uris('foobar:')
|
||||||
|
with raises(exceptions.ValidationError):
|
||||||
|
validation.check_uris(None)
|
||||||
|
with raises(exceptions.ValidationError):
|
||||||
|
validation.check_uris([None])
|
||||||
|
with raises(exceptions.ValidationError):
|
||||||
|
validation.check_uris(['foobar:', 'foobar'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_uris_error_message():
|
||||||
|
with raises(exceptions.ValidationError) as excinfo:
|
||||||
|
validation.check_uris('testing')
|
||||||
|
assert "Expected a list of URIs, not u'testing'" == str(excinfo.value)
|
||||||
Loading…
Reference in New Issue
Block a user