Skip to content
Snippets Groups Projects
Verified Commit c8a2ae42 authored by Eliot Berriot's avatar Eliot Berriot
Browse files

Merge branch 'release/0.3'

parents 86c607de 7bbb7387
No related branches found
No related tags found
No related merge requests found
Showing
with 100 additions and 812 deletions
...@@ -5,6 +5,26 @@ Changelog ...@@ -5,6 +5,26 @@ Changelog
0.2.7 (Unreleased) 0.2.7 (Unreleased)
------------------ ------------------
- Shortcuts: can now use the ``f`` shortcut to toggle the currently playing track
as a favorite (#53)
- Shortcuts: avoid collisions between shortcuts by using the exact modifier (#53)
- Player: Added looping controls and shortcuts (#52)
- Player: Added shuffling controls and shortcuts (#52)
- Favorites: can now modify the ordering of track list (#50)
- Library: can now search/reorder results on artist browsing view (#50)
- Upgraded celery to 4.1, added endpoint logic for fingerprinting audio files
- Fixed #56: invalidate tokens on password change, also added change password form
- Fixed #57: now refresh jwt token on page refresh
- removed ugly dividers in batch import list
- Fixed a few padding issues
- Now persist/restore queue/radio/player state automatically
- Removed old broken imports
- Now force tests paths
- Fixed #54: Now use pytest everywhere \o/
- Now use vuex to manage state for favorites
- Now use vuex to manage state for authentication
- Now use vuex to manage state for player/queue/radios
0.2.6 (2017-12-15) 0.2.6 (2017-12-15)
------------------ ------------------
......
...@@ -46,9 +46,8 @@ v1_patterns += [ ...@@ -46,9 +46,8 @@ v1_patterns += [
include( include(
('funkwhale_api.users.api_urls', 'users'), ('funkwhale_api.users.api_urls', 'users'),
namespace='users')), namespace='users')),
url(r'^token/', url(r'^token/$', jwt_views.obtain_jwt_token, name='token'),
jwt_views.obtain_jwt_token), url(r'^token/refresh/$', jwt_views.refresh_jwt_token, name='token_refresh'),
url(r'^token/refresh/', jwt_views.refresh_jwt_token),
] ]
urlpatterns = [ urlpatterns = [
......
...@@ -280,8 +280,9 @@ JWT_AUTH = { ...@@ -280,8 +280,9 @@ JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7), 'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=30), 'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=30),
'JWT_AUTH_HEADER_PREFIX': 'JWT', 'JWT_AUTH_HEADER_PREFIX': 'JWT',
'JWT_GET_USER_SECRET_KEY': lambda user: user.secret_key
} }
OLD_PASSWORD_FIELD_ENABLED = True
ACCOUNT_ADAPTER = 'funkwhale_api.users.adapters.FunkwhaleAccountAdapter' ACCOUNT_ADAPTER = 'funkwhale_api.users.adapters.FunkwhaleAccountAdapter'
CORS_ORIGIN_ALLOW_ALL = True CORS_ORIGIN_ALLOW_ALL = True
# CORS_ORIGIN_WHITELIST = ( # CORS_ORIGIN_WHITELIST = (
......
...@@ -31,3 +31,9 @@ if settings.DEBUG: ...@@ -31,3 +31,9 @@ if settings.DEBUG:
url(r'^404/$', default_views.page_not_found), url(r'^404/$', default_views.page_not_found),
url(r'^500/$', default_views.server_error), url(r'^500/$', default_views.server_error),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
if 'debug_toolbar' in settings.INSTALLED_APPS:
import debug_toolbar
urlpatterns += [
url(r'^__debug__/', include(debug_toolbar.urls)),
]
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
__version__ = '0.2.6' __version__ = '0.3'
__version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace('-', '.', 1).split('.')]) __version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace('-', '.', 1).split('.')])
from test_plus.test import TestCase
from rest_framework_jwt.settings import api_settings
from funkwhale_api.users.models import User
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
class TestJWTQueryString(TestCase):
www_authenticate_realm = 'api'
def test_can_authenticate_using_token_param_in_url(self):
user = User.objects.create_superuser(
username='test', email='test@test.com', password='test')
url = self.reverse('api:v1:tracks-list')
with self.settings(API_AUTHENTICATION_REQUIRED=True):
response = self.client.get(url)
self.assertEqual(response.status_code, 401)
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
print(payload, token)
with self.settings(API_AUTHENTICATION_REQUIRED=True):
response = self.client.get(url, data={
'jwt': token
})
self.assertEqual(response.status_code, 200)
import os
from test_plus.test import TestCase
from .. import downloader
from funkwhale_api.utils.tests import TMPDirTestCaseMixin
class TestDownloader(TMPDirTestCaseMixin, TestCase):
def test_can_download_audio_from_youtube_url_to_vorbis(self):
data = downloader.download('https://www.youtube.com/watch?v=tPEE9ZwTmy0', target_directory=self.download_dir)
self.assertEqual(
data['audio_file_path'],
os.path.join(self.download_dir, 'tPEE9ZwTmy0.ogg'))
self.assertTrue(os.path.exists(data['audio_file_path']))
import factory
import persisting_theory
class FactoriesRegistry(persisting_theory.Registry):
look_into = 'factories'
def prepare_name(self, data, name=None):
return name or data._meta.model._meta.label
registry = FactoriesRegistry()
def ManyToManyFromList(field_name):
"""
To automate the pattern described in
http://factoryboy.readthedocs.io/en/latest/recipes.html#simple-many-to-many-relationship
"""
@factory.post_generation
def inner(self, create, extracted, **kwargs):
if not create:
return
if extracted:
field = getattr(self, field_name)
field.add(*extracted)
return inner
import factory
from funkwhale_api.factories import registry
from funkwhale_api.music.factories import TrackFactory
from funkwhale_api.users.factories import UserFactory
@registry.register
class TrackFavorite(factory.django.DjangoModelFactory):
track = factory.SubFactory(TrackFactory)
user = factory.SubFactory(UserFactory)
class Meta:
model = 'favorites.TrackFavorite'
import json
from test_plus.test import TestCase
from django.urls import reverse
from funkwhale_api.music.models import Track, Artist
from funkwhale_api.favorites.models import TrackFavorite
from funkwhale_api.users.models import User
class TestFavorites(TestCase):
def setUp(self):
super().setUp()
self.artist = Artist.objects.create(name='test')
self.track = Track.objects.create(title='test', artist=self.artist)
self.user = User.objects.create_user(username='test', email='test@test.com', password='test')
def test_user_can_add_favorite(self):
TrackFavorite.add(self.track, self.user)
favorite = TrackFavorite.objects.latest('id')
self.assertEqual(favorite.track, self.track)
self.assertEqual(favorite.user, self.user)
def test_user_can_get_his_favorites(self):
favorite = TrackFavorite.add(self.track, self.user)
url = reverse('api:v1:favorites:tracks-list')
self.client.login(username=self.user.username, password='test')
response = self.client.get(url)
expected = [
{
'track': self.track.pk,
'id': favorite.id,
'creation_date': favorite.creation_date.isoformat().replace('+00:00', 'Z'),
}
]
parsed_json = json.loads(response.content.decode('utf-8'))
self.assertEqual(expected, parsed_json['results'])
def test_user_can_add_favorite_via_api(self):
url = reverse('api:v1:favorites:tracks-list')
self.client.login(username=self.user.username, password='test')
response = self.client.post(url, {'track': self.track.pk})
favorite = TrackFavorite.objects.latest('id')
expected = {
'track': self.track.pk,
'id': favorite.id,
'creation_date': favorite.creation_date.isoformat().replace('+00:00', 'Z'),
}
parsed_json = json.loads(response.content.decode('utf-8'))
self.assertEqual(expected, parsed_json)
self.assertEqual(favorite.track, self.track)
self.assertEqual(favorite.user, self.user)
def test_user_can_remove_favorite_via_api(self):
favorite = TrackFavorite.add(self.track, self.user)
url = reverse('api:v1:favorites:tracks-detail', kwargs={'pk': favorite.pk})
self.client.login(username=self.user.username, password='test')
response = self.client.delete(url, {'track': self.track.pk})
self.assertEqual(response.status_code, 204)
self.assertEqual(TrackFavorite.objects.count(), 0)
def test_user_can_remove_favorite_via_api_using_track_id(self):
favorite = TrackFavorite.add(self.track, self.user)
url = reverse('api:v1:favorites:tracks-remove')
self.client.login(username=self.user.username, password='test')
response = self.client.delete(
url, json.dumps({'track': self.track.pk}),
content_type='application/json'
)
self.assertEqual(response.status_code, 204)
self.assertEqual(TrackFavorite.objects.count(), 0)
from funkwhale_api.users.models import User
def test_can_restrict_api_views_to_authenticated_users(self):
urls = [
('api:v1:favorites:tracks-list', 'get'),
]
for route_name, method in urls:
url = self.reverse(route_name)
with self.settings(API_AUTHENTICATION_REQUIRED=True):
response = getattr(self.client, method)(url)
self.assertEqual(response.status_code, 401)
self.client.login(username=self.user.username, password='test')
for route_name, method in urls:
url = self.reverse(route_name)
with self.settings(API_AUTHENTICATION_REQUIRED=False):
response = getattr(self.client, method)(url)
self.assertEqual(response.status_code, 200)
def test_can_filter_tracks_by_favorites(self):
favorite = TrackFavorite.add(self.track, self.user)
url = reverse('api:v1:tracks-list')
self.client.login(username=self.user.username, password='test')
response = self.client.get(url, data={'favorites': True})
parsed_json = json.loads(response.content.decode('utf-8'))
self.assertEqual(parsed_json['count'], 1)
self.assertEqual(parsed_json['results'][0]['id'], self.track.id)
import factory import factory
from funkwhale_api.music.tests import factories
from funkwhale_api.users.tests.factories import UserFactory from funkwhale_api.factories import registry
from funkwhale_api.music import factories
from funkwhale_api.users.factories import UserFactory
@registry.register
class ListeningFactory(factory.django.DjangoModelFactory): class ListeningFactory(factory.django.DjangoModelFactory):
user = factory.SubFactory(UserFactory) user = factory.SubFactory(UserFactory)
track = factory.SubFactory(factories.TrackFactory) track = factory.SubFactory(factories.TrackFactory)
......
import random
import json
from test_plus.test import TestCase
from django.urls import reverse
from django.core.exceptions import ValidationError
from django.utils import timezone
from funkwhale_api.music.tests.factories import TrackFactory
from funkwhale_api.users.models import User
from funkwhale_api.history import models
class TestHistory(TestCase):
def setUp(self):
super().setUp()
self.user = User.objects.create_user(username='test', email='test@test.com', password='test')
def test_can_create_listening(self):
track = TrackFactory()
now = timezone.now()
l = models.Listening.objects.create(user=self.user, track=track)
def test_anonymous_user_can_create_listening_via_api(self):
track = TrackFactory()
url = self.reverse('api:v1:history:listenings-list')
response = self.client.post(url, {
'track': track.pk,
})
listening = models.Listening.objects.latest('id')
self.assertEqual(listening.track, track)
self.assertIsNotNone(listening.session_key)
def test_logged_in_user_can_create_listening_via_api(self):
track = TrackFactory()
self.client.login(username=self.user.username, password='test')
url = self.reverse('api:v1:history:listenings-list')
response = self.client.post(url, {
'track': track.pk,
})
listening = models.Listening.objects.latest('id')
self.assertEqual(listening.track, track)
self.assertEqual(listening.user, self.user)
import factory import factory
import os import os
from funkwhale_api.users.tests.factories import UserFactory from funkwhale_api.factories import registry, ManyToManyFromList
from funkwhale_api.users.factories import UserFactory
SAMPLES_PATH = os.path.dirname(os.path.abspath(__file__)) SAMPLES_PATH = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
'tests', 'music'
)
@registry.register
class ArtistFactory(factory.django.DjangoModelFactory): class ArtistFactory(factory.django.DjangoModelFactory):
name = factory.Faker('name') name = factory.Faker('name')
mbid = factory.Faker('uuid4') mbid = factory.Faker('uuid4')
...@@ -14,6 +19,7 @@ class ArtistFactory(factory.django.DjangoModelFactory): ...@@ -14,6 +19,7 @@ class ArtistFactory(factory.django.DjangoModelFactory):
model = 'music.Artist' model = 'music.Artist'
@registry.register
class AlbumFactory(factory.django.DjangoModelFactory): class AlbumFactory(factory.django.DjangoModelFactory):
title = factory.Faker('sentence', nb_words=3) title = factory.Faker('sentence', nb_words=3)
mbid = factory.Faker('uuid4') mbid = factory.Faker('uuid4')
...@@ -26,17 +32,19 @@ class AlbumFactory(factory.django.DjangoModelFactory): ...@@ -26,17 +32,19 @@ class AlbumFactory(factory.django.DjangoModelFactory):
model = 'music.Album' model = 'music.Album'
@registry.register
class TrackFactory(factory.django.DjangoModelFactory): class TrackFactory(factory.django.DjangoModelFactory):
title = factory.Faker('sentence', nb_words=3) title = factory.Faker('sentence', nb_words=3)
mbid = factory.Faker('uuid4') mbid = factory.Faker('uuid4')
album = factory.SubFactory(AlbumFactory) album = factory.SubFactory(AlbumFactory)
artist = factory.SelfAttribute('album.artist') artist = factory.SelfAttribute('album.artist')
position = 1 position = 1
tags = ManyToManyFromList('tags')
class Meta: class Meta:
model = 'music.Track' model = 'music.Track'
@registry.register
class TrackFileFactory(factory.django.DjangoModelFactory): class TrackFileFactory(factory.django.DjangoModelFactory):
track = factory.SubFactory(TrackFactory) track = factory.SubFactory(TrackFactory)
audio_file = factory.django.FileField( audio_file = factory.django.FileField(
...@@ -46,6 +54,7 @@ class TrackFileFactory(factory.django.DjangoModelFactory): ...@@ -46,6 +54,7 @@ class TrackFileFactory(factory.django.DjangoModelFactory):
model = 'music.TrackFile' model = 'music.TrackFile'
@registry.register
class ImportBatchFactory(factory.django.DjangoModelFactory): class ImportBatchFactory(factory.django.DjangoModelFactory):
submitted_by = factory.SubFactory(UserFactory) submitted_by = factory.SubFactory(UserFactory)
...@@ -53,14 +62,17 @@ class ImportBatchFactory(factory.django.DjangoModelFactory): ...@@ -53,14 +62,17 @@ class ImportBatchFactory(factory.django.DjangoModelFactory):
model = 'music.ImportBatch' model = 'music.ImportBatch'
@registry.register
class ImportJobFactory(factory.django.DjangoModelFactory): class ImportJobFactory(factory.django.DjangoModelFactory):
batch = factory.SubFactory(ImportBatchFactory) batch = factory.SubFactory(ImportBatchFactory)
source = factory.Faker('url') source = factory.Faker('url')
mbid = factory.Faker('uuid4')
class Meta: class Meta:
model = 'music.ImportJob' model = 'music.ImportJob'
@registry.register
class WorkFactory(factory.django.DjangoModelFactory): class WorkFactory(factory.django.DjangoModelFactory):
mbid = factory.Faker('uuid4') mbid = factory.Faker('uuid4')
language = 'eng' language = 'eng'
...@@ -71,6 +83,7 @@ class WorkFactory(factory.django.DjangoModelFactory): ...@@ -71,6 +83,7 @@ class WorkFactory(factory.django.DjangoModelFactory):
model = 'music.Work' model = 'music.Work'
@registry.register
class LyricsFactory(factory.django.DjangoModelFactory): class LyricsFactory(factory.django.DjangoModelFactory):
work = factory.SubFactory(WorkFactory) work = factory.SubFactory(WorkFactory)
url = factory.Faker('url') url = factory.Faker('url')
...@@ -80,6 +93,7 @@ class LyricsFactory(factory.django.DjangoModelFactory): ...@@ -80,6 +93,7 @@ class LyricsFactory(factory.django.DjangoModelFactory):
model = 'music.Lyrics' model = 'music.Lyrics'
@registry.register
class TagFactory(factory.django.DjangoModelFactory): class TagFactory(factory.django.DjangoModelFactory):
name = factory.SelfAttribute('slug') name = factory.SelfAttribute('slug')
slug = factory.Faker('slug') slug = factory.Faker('slug')
......
...@@ -8,5 +8,5 @@ class ArtistFilter(django_filters.FilterSet): ...@@ -8,5 +8,5 @@ class ArtistFilter(django_filters.FilterSet):
class Meta: class Meta:
model = models.Artist model = models.Artist
fields = { fields = {
'name': ['exact', 'iexact', 'startswith'] 'name': ['exact', 'iexact', 'startswith', 'icontains']
} }
...@@ -13,14 +13,14 @@ class TagSerializer(serializers.ModelSerializer): ...@@ -13,14 +13,14 @@ class TagSerializer(serializers.ModelSerializer):
class SimpleArtistSerializer(serializers.ModelSerializer): class SimpleArtistSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = models.Artist model = models.Artist
fields = ('id', 'mbid', 'name') fields = ('id', 'mbid', 'name', 'creation_date')
class ArtistSerializer(serializers.ModelSerializer): class ArtistSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True, read_only=True) tags = TagSerializer(many=True, read_only=True)
class Meta: class Meta:
model = models.Artist model = models.Artist
fields = ('id', 'mbid', 'name', 'tags') fields = ('id', 'mbid', 'name', 'tags', 'creation_date')
class TrackFileSerializer(serializers.ModelSerializer): class TrackFileSerializer(serializers.ModelSerializer):
......
import json
import unittest
from test_plus.test import TestCase
from django.urls import reverse
from funkwhale_api.music import models
from funkwhale_api.utils.tests import TMPDirTestCaseMixin
from funkwhale_api.musicbrainz import api
from funkwhale_api.music import serializers
from funkwhale_api.users.models import User
from . import data as api_data
from . import factories
class TestAPI(TMPDirTestCaseMixin, TestCase):
@unittest.mock.patch('funkwhale_api.musicbrainz.api.artists.get', return_value=api_data.artists['get']['adhesive_wombat'])
@unittest.mock.patch('funkwhale_api.musicbrainz.api.releases.get', return_value=api_data.albums['get']['marsupial'])
@unittest.mock.patch('funkwhale_api.musicbrainz.api.recordings.get', return_value=api_data.tracks['get']['8bitadventures'])
@unittest.mock.patch('funkwhale_api.music.models.TrackFile.download_file', return_value=None)
def test_can_submit_youtube_url_for_track_import(self, *mocks):
mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
video_id = 'tPEE9ZwTmy0'
url = reverse('api:v1:submit-single')
user = User.objects.create_superuser(username='test', email='test@test.com', password='test')
self.client.login(username=user.username, password='test')
response = self.client.post(url, {'import_url': 'https://www.youtube.com/watch?v={0}'.format(video_id), 'mbid': mbid})
track = models.Track.objects.get(mbid=mbid)
self.assertEqual(track.artist.name, 'Adhesive Wombat')
self.assertEqual(track.album.title, 'Marsupial Madness')
# self.assertIn(video_id, track.files.first().audio_file.name)
def test_import_creates_an_import_with_correct_data(self):
user = User.objects.create_superuser(username='test', email='test@test.com', password='test')
mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
video_id = 'tPEE9ZwTmy0'
url = reverse('api:v1:submit-single')
self.client.login(username=user.username, password='test')
with self.settings(CELERY_ALWAYS_EAGER=False):
response = self.client.post(url, {'import_url': 'https://www.youtube.com/watch?v={0}'.format(video_id), 'mbid': mbid})
batch = models.ImportBatch.objects.latest('id')
self.assertEqual(batch.jobs.count(), 1)
self.assertEqual(batch.submitted_by, user)
self.assertEqual(batch.status, 'pending')
job = batch.jobs.first()
self.assertEqual(str(job.mbid), mbid)
self.assertEqual(job.status, 'pending')
self.assertEqual(job.source, 'https://www.youtube.com/watch?v={0}'.format(video_id))
@unittest.mock.patch('funkwhale_api.musicbrainz.api.artists.get', return_value=api_data.artists['get']['soad'])
@unittest.mock.patch('funkwhale_api.musicbrainz.api.images.get_front', return_value=b'')
@unittest.mock.patch('funkwhale_api.musicbrainz.api.releases.get', return_value=api_data.albums['get_with_includes']['hypnotize'])
def test_can_import_whole_album(self, *mocks):
user = User.objects.create_superuser(username='test', email='test@test.com', password='test')
payload = {
'releaseId': '47ae093f-1607-49a3-be11-a15d335ccc94',
'tracks': [
{
'mbid': '1968a9d6-8d92-4051-8f76-674e157b6eed',
'source': 'https://www.youtube.com/watch?v=1111111111',
},
{
'mbid': '2968a9d6-8d92-4051-8f76-674e157b6eed',
'source': 'https://www.youtube.com/watch?v=2222222222',
},
{
'mbid': '3968a9d6-8d92-4051-8f76-674e157b6eed',
'source': 'https://www.youtube.com/watch?v=3333333333',
},
]
}
url = reverse('api:v1:submit-album')
self.client.login(username=user.username, password='test')
with self.settings(CELERY_ALWAYS_EAGER=False):
response = self.client.post(url, json.dumps(payload), content_type="application/json")
batch = models.ImportBatch.objects.latest('id')
self.assertEqual(batch.jobs.count(), 3)
self.assertEqual(batch.submitted_by, user)
self.assertEqual(batch.status, 'pending')
album = models.Album.objects.latest('id')
self.assertEqual(str(album.mbid), '47ae093f-1607-49a3-be11-a15d335ccc94')
medium_data = api_data.albums['get_with_includes']['hypnotize']['release']['medium-list'][0]
self.assertEqual(int(medium_data['track-count']), album.tracks.all().count())
for track in medium_data['track-list']:
instance = models.Track.objects.get(mbid=track['recording']['id'])
self.assertEqual(instance.title, track['recording']['title'])
self.assertEqual(instance.position, int(track['position']))
self.assertEqual(instance.title, track['recording']['title'])
for row in payload['tracks']:
job = models.ImportJob.objects.get(mbid=row['mbid'])
self.assertEqual(str(job.mbid), row['mbid'])
self.assertEqual(job.status, 'pending')
self.assertEqual(job.source, row['source'])
@unittest.mock.patch('funkwhale_api.musicbrainz.api.artists.get', return_value=api_data.artists['get']['soad'])
@unittest.mock.patch('funkwhale_api.musicbrainz.api.images.get_front', return_value=b'')
@unittest.mock.patch('funkwhale_api.musicbrainz.api.releases.get', return_value=api_data.albums['get_with_includes']['hypnotize'])
def test_can_import_whole_artist(self, *mocks):
user = User.objects.create_superuser(username='test', email='test@test.com', password='test')
payload = {
'artistId': 'mbid',
'albums': [
{
'releaseId': '47ae093f-1607-49a3-be11-a15d335ccc94',
'tracks': [
{
'mbid': '1968a9d6-8d92-4051-8f76-674e157b6eed',
'source': 'https://www.youtube.com/watch?v=1111111111',
},
{
'mbid': '2968a9d6-8d92-4051-8f76-674e157b6eed',
'source': 'https://www.youtube.com/watch?v=2222222222',
},
{
'mbid': '3968a9d6-8d92-4051-8f76-674e157b6eed',
'source': 'https://www.youtube.com/watch?v=3333333333',
},
]
}
]
}
url = reverse('api:v1:submit-artist')
self.client.login(username=user.username, password='test')
with self.settings(CELERY_ALWAYS_EAGER=False):
response = self.client.post(url, json.dumps(payload), content_type="application/json")
batch = models.ImportBatch.objects.latest('id')
self.assertEqual(batch.jobs.count(), 3)
self.assertEqual(batch.submitted_by, user)
self.assertEqual(batch.status, 'pending')
album = models.Album.objects.latest('id')
self.assertEqual(str(album.mbid), '47ae093f-1607-49a3-be11-a15d335ccc94')
medium_data = api_data.albums['get_with_includes']['hypnotize']['release']['medium-list'][0]
self.assertEqual(int(medium_data['track-count']), album.tracks.all().count())
for track in medium_data['track-list']:
instance = models.Track.objects.get(mbid=track['recording']['id'])
self.assertEqual(instance.title, track['recording']['title'])
self.assertEqual(instance.position, int(track['position']))
self.assertEqual(instance.title, track['recording']['title'])
for row in payload['albums'][0]['tracks']:
job = models.ImportJob.objects.get(mbid=row['mbid'])
self.assertEqual(str(job.mbid), row['mbid'])
self.assertEqual(job.status, 'pending')
self.assertEqual(job.source, row['source'])
def test_user_can_query_api_for_his_own_batches(self):
user1 = User.objects.create_superuser(username='test1', email='test1@test.com', password='test')
user2 = User.objects.create_superuser(username='test2', email='test2@test.com', password='test')
mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
source = 'https://www.youtube.com/watch?v=tPEE9ZwTmy0'
batch = models.ImportBatch.objects.create(submitted_by=user1)
job = models.ImportJob.objects.create(batch=batch, mbid=mbid, source=source)
url = reverse('api:v1:import-batches-list')
self.client.login(username=user2.username, password='test')
response2 = self.client.get(url)
self.assertJSONEqual(response2.content.decode('utf-8'), '{"count":0,"next":null,"previous":null,"results":[]}')
self.client.logout()
self.client.login(username=user1.username, password='test')
response1 = self.client.get(url)
self.assertIn(mbid, response1.content.decode('utf-8'))
def test_can_search_artist(self):
artist1 = models.Artist.objects.create(name='Test1')
artist2 = models.Artist.objects.create(name='Test2')
query = 'test1'
expected = '[{0}]'.format(json.dumps(serializers.ArtistSerializerNested(artist1).data))
url = self.reverse('api:v1:artists-search')
response = self.client.get(url + '?query={0}'.format(query))
self.assertJSONEqual(expected, json.loads(response.content.decode('utf-8')))
def test_can_search_artist_by_name_start(self):
artist1 = factories.ArtistFactory(name='alpha')
artist2 = factories.ArtistFactory(name='beta')
results = {
'next': None,
'previous': None,
'count': 1,
'results': [serializers.ArtistSerializerNested(artist1).data]
}
expected = json.dumps(results)
url = self.reverse('api:v1:artists-list')
response = self.client.get(url, {'name__startswith': 'a'})
self.assertJSONEqual(expected, json.loads(response.content.decode('utf-8')))
def test_can_search_tracks(self):
artist1 = models.Artist.objects.create(name='Test1')
artist2 = models.Artist.objects.create(name='Test2')
track1 = models.Track.objects.create(artist=artist1, title="test_track1")
track2 = models.Track.objects.create(artist=artist2, title="test_track2")
query = 'test track 1'
expected = '[{0}]'.format(json.dumps(serializers.TrackSerializerNested(track1).data))
url = self.reverse('api:v1:tracks-search')
response = self.client.get(url + '?query={0}'.format(query))
self.assertJSONEqual(expected, json.loads(response.content.decode('utf-8')))
def test_can_restrict_api_views_to_authenticated_users(self):
urls = [
('api:v1:tags-list', 'get'),
('api:v1:tracks-list', 'get'),
('api:v1:artists-list', 'get'),
('api:v1:albums-list', 'get'),
]
for route_name, method in urls:
url = self.reverse(route_name)
with self.settings(API_AUTHENTICATION_REQUIRED=True):
response = getattr(self.client, method)(url)
self.assertEqual(response.status_code, 401)
user = User.objects.create_superuser(username='test', email='test@test.com', password='test')
self.client.login(username=user.username, password='test')
for route_name, method in urls:
url = self.reverse(route_name)
with self.settings(API_AUTHENTICATION_REQUIRED=False):
response = getattr(self.client, method)(url)
self.assertEqual(response.status_code, 200)
def test_track_file_url_is_restricted_to_authenticated_users(self):
f = factories.TrackFileFactory()
self.assertNotEqual(f.audio_file, None)
url = f.path
with self.settings(API_AUTHENTICATION_REQUIRED=True):
response = self.client.get(url)
self.assertEqual(response.status_code, 401)
user = User.objects.create_superuser(
username='test', email='test@test.com', password='test')
self.client.login(username=user.username, password='test')
with self.settings(API_AUTHENTICATION_REQUIRED=True):
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(
response['X-Accel-Redirect'],
'/_protected{}'.format(f.audio_file.url)
)
import json
import unittest
from test_plus.test import TestCase
from django.urls import reverse
from funkwhale_api.music import models
from funkwhale_api.musicbrainz import api
from funkwhale_api.music import serializers
from funkwhale_api.users.models import User
from funkwhale_api.music import lyrics as lyrics_utils
from .mocking import lyricswiki
from . import factories
from . import data as api_data
class TestLyrics(TestCase):
@unittest.mock.patch('funkwhale_api.music.lyrics._get_html',
return_value=lyricswiki.content)
def test_works_import_lyrics_if_any(self, *mocks):
lyrics = factories.LyricsFactory(
url='http://lyrics.wikia.com/System_Of_A_Down:Chop_Suey!')
lyrics.fetch_content()
self.assertIn(
'Grab a brush and put on a little makeup',
lyrics.content,
)
def test_clean_content(self):
c = """<div class="lyricbox">Hello<br /><script>alert('hello');</script>Is it me you're looking for?<br /></div>"""
d = lyrics_utils.extract_content(c)
d = lyrics_utils.clean_content(d)
expected = """Hello
Is it me you're looking for?
"""
self.assertEqual(d, expected)
def test_markdown_rendering(self):
content = """Hello
Is it me you're looking for?"""
l = factories.LyricsFactory(content=content)
expected = "<p>Hello<br />Is it me you're looking for?</p>"
self.assertHTMLEqual(expected, l.content_rendered)
@unittest.mock.patch('funkwhale_api.musicbrainz.api.works.get',
return_value=api_data.works['get']['chop_suey'])
@unittest.mock.patch('funkwhale_api.musicbrainz.api.recordings.get',
return_value=api_data.tracks['get']['chop_suey'])
@unittest.mock.patch('funkwhale_api.music.lyrics._get_html',
return_value=lyricswiki.content)
def test_works_import_lyrics_if_any(self, *mocks):
track = factories.TrackFactory(
work=None,
mbid='07ca77cf-f513-4e9c-b190-d7e24bbad448')
url = reverse('api:v1:tracks-lyrics', kwargs={'pk': track.pk})
user = User.objects.create_user(
username='test', email='test@test.com', password='test')
self.client.login(username=user.username, password='test')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
track.refresh_from_db()
lyrics = models.Lyrics.objects.latest('id')
work = models.Work.objects.latest('id')
self.assertEqual(track.work, work)
self.assertEqual(lyrics.work, work)
import unittest
import os
import datetime
from test_plus.test import TestCase
from funkwhale_api.music import metadata
DATA_DIR = os.path.dirname(os.path.abspath(__file__))
class TestMetadata(TestCase):
def test_can_get_metadata_from_ogg_file(self, *mocks):
path = os.path.join(DATA_DIR, 'test.ogg')
data = metadata.Metadata(path)
self.assertEqual(
data.get('title'),
'Peer Gynt Suite no. 1, op. 46: I. Morning'
)
self.assertEqual(
data.get('artist'),
'Edvard Grieg'
)
self.assertEqual(
data.get('album'),
'Peer Gynt Suite no. 1, op. 46'
)
self.assertEqual(
data.get('date'),
datetime.date(2012, 8, 15),
)
self.assertEqual(
data.get('track_number'),
1
)
self.assertEqual(
data.get('musicbrainz_albumid'),
'a766da8b-8336-47aa-a3ee-371cc41ccc75')
self.assertEqual(
data.get('musicbrainz_recordingid'),
'bd21ac48-46d8-4e78-925f-d9cc2a294656')
self.assertEqual(
data.get('musicbrainz_artistid'),
'013c8e5b-d72a-4cd3-8dee-6c64d6125823')
def test_can_get_metadata_from_id3_mp3_file(self, *mocks):
path = os.path.join(DATA_DIR, 'test.mp3')
data = metadata.Metadata(path)
self.assertEqual(
data.get('title'),
'Bend'
)
self.assertEqual(
data.get('artist'),
'Binärpilot'
)
self.assertEqual(
data.get('album'),
'You Can\'t Stop Da Funk'
)
self.assertEqual(
data.get('date'),
datetime.date(2006, 2, 7),
)
self.assertEqual(
data.get('track_number'),
1
)
self.assertEqual(
data.get('musicbrainz_albumid'),
'ce40cdb1-a562-4fd8-a269-9269f98d4124')
self.assertEqual(
data.get('musicbrainz_recordingid'),
'f269d497-1cc0-4ae4-a0c4-157ec7d73fcb')
self.assertEqual(
data.get('musicbrainz_artistid'),
'9c6bddde-6228-4d9f-ad0d-03f6fcb19e13')
from test_plus.test import TestCase
import unittest.mock
from funkwhale_api.music import models
import datetime
from . import factories
from . import data as api_data
from .cover import binary_data
class TestMusic(TestCase):
@unittest.mock.patch('musicbrainzngs.search_artists', return_value=api_data.artists['search']['adhesive_wombat'])
def test_can_create_artist_from_api(self, *mocks):
artist = models.Artist.create_from_api(query="Adhesive wombat")
data = models.Artist.api.search(query='Adhesive wombat')['artist-list'][0]
self.assertEqual(int(data['ext:score']), 100)
self.assertEqual(data['id'], '62c3befb-6366-4585-b256-809472333801')
self.assertEqual(artist.mbid, data['id'])
self.assertEqual(artist.name, 'Adhesive Wombat')
@unittest.mock.patch('funkwhale_api.musicbrainz.api.releases.search', return_value=api_data.albums['search']['hypnotize'])
@unittest.mock.patch('funkwhale_api.musicbrainz.api.artists.get', return_value=api_data.artists['get']['soad'])
def test_can_create_album_from_api(self, *mocks):
album = models.Album.create_from_api(query="Hypnotize", artist='system of a down', type='album')
data = models.Album.api.search(query='Hypnotize', artist='system of a down', type='album')['release-list'][0]
self.assertEqual(album.mbid, data['id'])
self.assertEqual(album.title, 'Hypnotize')
with self.assertRaises(ValueError):
self.assertFalse(album.cover.path is None)
self.assertEqual(album.release_date, datetime.date(2005, 1, 1))
self.assertEqual(album.artist.name, 'System of a Down')
self.assertEqual(album.artist.mbid, data['artist-credit'][0]['artist']['id'])
@unittest.mock.patch('funkwhale_api.musicbrainz.api.artists.get', return_value=api_data.artists['get']['adhesive_wombat'])
@unittest.mock.patch('funkwhale_api.musicbrainz.api.releases.get', return_value=api_data.albums['get']['marsupial'])
@unittest.mock.patch('funkwhale_api.musicbrainz.api.recordings.search', return_value=api_data.tracks['search']['8bitadventures'])
def test_can_create_track_from_api(self, *mocks):
track = models.Track.create_from_api(query="8-bit adventure")
data = models.Track.api.search(query='8-bit adventure')['recording-list'][0]
self.assertEqual(int(data['ext:score']), 100)
self.assertEqual(data['id'], '9968a9d6-8d92-4051-8f76-674e157b6eed')
self.assertEqual(track.mbid, data['id'])
self.assertTrue(track.artist.pk is not None)
self.assertEqual(str(track.artist.mbid), '62c3befb-6366-4585-b256-809472333801')
self.assertEqual(track.artist.name, 'Adhesive Wombat')
self.assertEqual(str(track.album.mbid), 'a50d2a81-2a50-484d-9cb4-b9f6833f583e')
self.assertEqual(track.album.title, 'Marsupial Madness')
@unittest.mock.patch('funkwhale_api.musicbrainz.api.artists.get', return_value=api_data.artists['get']['adhesive_wombat'])
@unittest.mock.patch('funkwhale_api.musicbrainz.api.releases.get', return_value=api_data.albums['get']['marsupial'])
@unittest.mock.patch('funkwhale_api.musicbrainz.api.recordings.get', return_value=api_data.tracks['get']['8bitadventures'])
def test_can_create_track_from_api_with_corresponding_tags(self, *mocks):
track = models.Track.create_from_api(id='9968a9d6-8d92-4051-8f76-674e157b6eed')
expected_tags = ['techno', 'good-music']
track_tags = [tag.slug for tag in track.tags.all()]
for tag in expected_tags:
self.assertIn(tag, track_tags)
@unittest.mock.patch('funkwhale_api.musicbrainz.api.artists.get', return_value=api_data.artists['get']['adhesive_wombat'])
@unittest.mock.patch('funkwhale_api.musicbrainz.api.releases.get', return_value=api_data.albums['get']['marsupial'])
@unittest.mock.patch('funkwhale_api.musicbrainz.api.recordings.search', return_value=api_data.tracks['search']['8bitadventures'])
def test_can_get_or_create_track_from_api(self, *mocks):
track = models.Track.create_from_api(query="8-bit adventure")
data = models.Track.api.search(query='8-bit adventure')['recording-list'][0]
self.assertEqual(int(data['ext:score']), 100)
self.assertEqual(data['id'], '9968a9d6-8d92-4051-8f76-674e157b6eed')
self.assertEqual(track.mbid, data['id'])
self.assertTrue(track.artist.pk is not None)
self.assertEqual(str(track.artist.mbid), '62c3befb-6366-4585-b256-809472333801')
self.assertEqual(track.artist.name, 'Adhesive Wombat')
track2, created = models.Track.get_or_create_from_api(mbid=data['id'])
self.assertFalse(created)
self.assertEqual(track, track2)
def test_album_tags_deduced_from_tracks_tags(self):
tag = factories.TagFactory()
album = factories.AlbumFactory()
tracks = factories.TrackFactory.create_batch(album=album, size=5)
for track in tracks:
track.tags.add(tag)
album = models.Album.objects.prefetch_related('tracks__tags').get(pk=album.pk)
with self.assertNumQueries(0):
self.assertIn(tag, album.tags)
def test_artist_tags_deduced_from_album_tags(self):
tag = factories.TagFactory()
artist = factories.ArtistFactory()
album = factories.AlbumFactory(artist=artist)
tracks = factories.TrackFactory.create_batch(album=album, size=5)
for track in tracks:
track.tags.add(tag)
artist = models.Artist.objects.prefetch_related('albums__tracks__tags').get(pk=artist.pk)
with self.assertNumQueries(0):
self.assertIn(tag, artist.tags)
@unittest.mock.patch('funkwhale_api.musicbrainz.api.images.get_front', return_value=binary_data)
def test_can_download_image_file_for_album(self, *mocks):
# client._api.get_image_front('55ea4f82-b42b-423e-a0e5-290ccdf443ed')
album = factories.AlbumFactory(mbid='55ea4f82-b42b-423e-a0e5-290ccdf443ed')
album.get_image()
album.save()
self.assertEqual(album.cover.file.read(), binary_data)
import json
import unittest
from test_plus.test import TestCase
from django.urls import reverse
from funkwhale_api.music import models
from funkwhale_api.musicbrainz import api
from funkwhale_api.music import serializers
from funkwhale_api.music.tests import factories
from funkwhale_api.users.models import User
from . import data as api_data
class TestWorks(TestCase):
@unittest.mock.patch('funkwhale_api.musicbrainz.api.works.get',
return_value=api_data.works['get']['chop_suey'])
def test_can_import_work(self, *mocks):
recording = factories.TrackFactory(
mbid='07ca77cf-f513-4e9c-b190-d7e24bbad448')
mbid = 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5'
work = models.Work.create_from_api(id=mbid)
self.assertEqual(work.title, 'Chop Suey!')
self.assertEqual(work.nature, 'song')
self.assertEqual(work.language, 'eng')
self.assertEqual(work.mbid, mbid)
# a imported work should also be linked to corresponding recordings
recording.refresh_from_db()
self.assertEqual(recording.work, work)
@unittest.mock.patch('funkwhale_api.musicbrainz.api.works.get',
return_value=api_data.works['get']['chop_suey'])
@unittest.mock.patch('funkwhale_api.musicbrainz.api.recordings.get',
return_value=api_data.tracks['get']['chop_suey'])
def test_can_get_work_from_recording(self, *mocks):
recording = factories.TrackFactory(
work=None,
mbid='07ca77cf-f513-4e9c-b190-d7e24bbad448')
mbid = 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5'
self.assertEqual(recording.work, None)
work = recording.get_work()
self.assertEqual(work.title, 'Chop Suey!')
self.assertEqual(work.nature, 'song')
self.assertEqual(work.language, 'eng')
self.assertEqual(work.mbid, mbid)
recording.refresh_from_db()
self.assertEqual(recording.work, work)
@unittest.mock.patch('funkwhale_api.musicbrainz.api.works.get',
return_value=api_data.works['get']['chop_suey'])
def test_works_import_lyrics_if_any(self, *mocks):
mbid = 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5'
work = models.Work.create_from_api(id=mbid)
lyrics = models.Lyrics.objects.latest('id')
self.assertEqual(lyrics.work, work)
self.assertEqual(
lyrics.url, 'http://lyrics.wikia.com/System_Of_A_Down:Chop_Suey!')
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment