diff --git a/api/funkwhale_api/common/tests/test_jwt_querystring.py b/api/funkwhale_api/common/tests/test_jwt_querystring.py
deleted file mode 100644
index 90e63775d..000000000
--- a/api/funkwhale_api/common/tests/test_jwt_querystring.py
+++ /dev/null
@@ -1,32 +0,0 @@
-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)
diff --git a/api/funkwhale_api/downloader/tests/test_downloader.py b/api/funkwhale_api/downloader/tests/test_downloader.py
deleted file mode 100644
index 7cfaa63c8..000000000
--- a/api/funkwhale_api/downloader/tests/test_downloader.py
+++ /dev/null
@@ -1,14 +0,0 @@
-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']))
diff --git a/api/funkwhale_api/factories.py b/api/funkwhale_api/factories.py
new file mode 100644
index 000000000..6fed66edb
--- /dev/null
+++ b/api/funkwhale_api/factories.py
@@ -0,0 +1,30 @@
+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
diff --git a/api/funkwhale_api/favorites/factories.py b/api/funkwhale_api/favorites/factories.py
new file mode 100644
index 000000000..233dd049c
--- /dev/null
+++ b/api/funkwhale_api/favorites/factories.py
@@ -0,0 +1,15 @@
+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'
diff --git a/api/funkwhale_api/favorites/tests/test_favorites.py b/api/funkwhale_api/favorites/tests/test_favorites.py
deleted file mode 100644
index 78c64a413..000000000
--- a/api/funkwhale_api/favorites/tests/test_favorites.py
+++ /dev/null
@@ -1,113 +0,0 @@
-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)
diff --git a/api/funkwhale_api/history/tests/factories.py b/api/funkwhale_api/history/factories.py
similarity index 58%
rename from api/funkwhale_api/history/tests/factories.py
rename to api/funkwhale_api/history/factories.py
index 0a411adf0..86fea64d2 100644
--- a/api/funkwhale_api/history/tests/factories.py
+++ b/api/funkwhale_api/history/factories.py
@@ -1,9 +1,11 @@
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):
user = factory.SubFactory(UserFactory)
track = factory.SubFactory(factories.TrackFactory)
diff --git a/api/funkwhale_api/history/tests/test_history.py b/api/funkwhale_api/history/tests/test_history.py
deleted file mode 100644
index 5cb45c946..000000000
--- a/api/funkwhale_api/history/tests/test_history.py
+++ /dev/null
@@ -1,50 +0,0 @@
-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)
diff --git a/api/funkwhale_api/music/tests/factories.py b/api/funkwhale_api/music/factories.py
similarity index 81%
rename from api/funkwhale_api/music/tests/factories.py
rename to api/funkwhale_api/music/factories.py
index 567e2a765..d776cd945 100644
--- a/api/funkwhale_api/music/tests/factories.py
+++ b/api/funkwhale_api/music/factories.py
@@ -1,11 +1,16 @@
import factory
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):
name = factory.Faker('name')
mbid = factory.Faker('uuid4')
@@ -14,6 +19,7 @@ class ArtistFactory(factory.django.DjangoModelFactory):
model = 'music.Artist'
+@registry.register
class AlbumFactory(factory.django.DjangoModelFactory):
title = factory.Faker('sentence', nb_words=3)
mbid = factory.Faker('uuid4')
@@ -26,17 +32,19 @@ class AlbumFactory(factory.django.DjangoModelFactory):
model = 'music.Album'
+@registry.register
class TrackFactory(factory.django.DjangoModelFactory):
title = factory.Faker('sentence', nb_words=3)
mbid = factory.Faker('uuid4')
album = factory.SubFactory(AlbumFactory)
artist = factory.SelfAttribute('album.artist')
position = 1
-
+ tags = ManyToManyFromList('tags')
class Meta:
model = 'music.Track'
+@registry.register
class TrackFileFactory(factory.django.DjangoModelFactory):
track = factory.SubFactory(TrackFactory)
audio_file = factory.django.FileField(
@@ -46,6 +54,7 @@ class TrackFileFactory(factory.django.DjangoModelFactory):
model = 'music.TrackFile'
+@registry.register
class ImportBatchFactory(factory.django.DjangoModelFactory):
submitted_by = factory.SubFactory(UserFactory)
@@ -53,14 +62,17 @@ class ImportBatchFactory(factory.django.DjangoModelFactory):
model = 'music.ImportBatch'
+@registry.register
class ImportJobFactory(factory.django.DjangoModelFactory):
batch = factory.SubFactory(ImportBatchFactory)
source = factory.Faker('url')
+ mbid = factory.Faker('uuid4')
class Meta:
model = 'music.ImportJob'
+@registry.register
class WorkFactory(factory.django.DjangoModelFactory):
mbid = factory.Faker('uuid4')
language = 'eng'
@@ -71,6 +83,7 @@ class WorkFactory(factory.django.DjangoModelFactory):
model = 'music.Work'
+@registry.register
class LyricsFactory(factory.django.DjangoModelFactory):
work = factory.SubFactory(WorkFactory)
url = factory.Faker('url')
@@ -80,6 +93,7 @@ class LyricsFactory(factory.django.DjangoModelFactory):
model = 'music.Lyrics'
+@registry.register
class TagFactory(factory.django.DjangoModelFactory):
name = factory.SelfAttribute('slug')
slug = factory.Faker('slug')
diff --git a/api/funkwhale_api/music/tests/test_api.py b/api/funkwhale_api/music/tests/test_api.py
deleted file mode 100644
index 2460fa97d..000000000
--- a/api/funkwhale_api/music/tests/test_api.py
+++ /dev/null
@@ -1,256 +0,0 @@
-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)
- )
diff --git a/api/funkwhale_api/music/tests/test_lyrics.py b/api/funkwhale_api/music/tests/test_lyrics.py
deleted file mode 100644
index 9a05e5eb8..000000000
--- a/api/funkwhale_api/music/tests/test_lyrics.py
+++ /dev/null
@@ -1,75 +0,0 @@
-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 = """
Hello
Is it me you're looking for?
"""
- 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 = "Hello
Is it me you're looking for?
"
- 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)
diff --git a/api/funkwhale_api/music/tests/test_metadata.py b/api/funkwhale_api/music/tests/test_metadata.py
deleted file mode 100644
index 9b8c76653..000000000
--- a/api/funkwhale_api/music/tests/test_metadata.py
+++ /dev/null
@@ -1,80 +0,0 @@
-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')
diff --git a/api/funkwhale_api/music/tests/test_music.py b/api/funkwhale_api/music/tests/test_music.py
deleted file mode 100644
index 5cf9d0cf9..000000000
--- a/api/funkwhale_api/music/tests/test_music.py
+++ /dev/null
@@ -1,113 +0,0 @@
-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)
diff --git a/api/funkwhale_api/music/tests/test_works.py b/api/funkwhale_api/music/tests/test_works.py
deleted file mode 100644
index 55714bce2..000000000
--- a/api/funkwhale_api/music/tests/test_works.py
+++ /dev/null
@@ -1,66 +0,0 @@
-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!')
diff --git a/api/funkwhale_api/musicbrainz/tests/__init__.py b/api/funkwhale_api/musicbrainz/tests/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/api/funkwhale_api/musicbrainz/tests/test_api.py b/api/funkwhale_api/musicbrainz/tests/test_api.py
deleted file mode 100644
index b0911f1c5..000000000
--- a/api/funkwhale_api/musicbrainz/tests/test_api.py
+++ /dev/null
@@ -1,87 +0,0 @@
-import json
-import unittest
-from test_plus.test import TestCase
-from django.urls import reverse
-
-from funkwhale_api.musicbrainz import api
-from . import data as api_data
-
-
-class TestAPI(TestCase):
- @unittest.mock.patch(
- 'funkwhale_api.musicbrainz.api.recordings.search',
- return_value=api_data.recordings['search']['brontide matador'])
- def test_can_search_recording_in_musicbrainz_api(self, *mocks):
- query = 'brontide matador'
- url = reverse('api:v1:providers:musicbrainz:search-recordings')
- expected = api_data.recordings['search']['brontide matador']
- response = self.client.get(url, data={'query': query})
-
- self.assertEqual(expected, json.loads(response.content.decode('utf-8')))
-
- @unittest.mock.patch(
- 'funkwhale_api.musicbrainz.api.releases.search',
- return_value=api_data.releases['search']['brontide matador'])
- def test_can_search_release_in_musicbrainz_api(self, *mocks):
- query = 'brontide matador'
- url = reverse('api:v1:providers:musicbrainz:search-releases')
- expected = api_data.releases['search']['brontide matador']
- response = self.client.get(url, data={'query': query})
-
- self.assertEqual(expected, json.loads(response.content.decode('utf-8')))
-
- @unittest.mock.patch(
- 'funkwhale_api.musicbrainz.api.artists.search',
- return_value=api_data.artists['search']['lost fingers'])
- def test_can_search_artists_in_musicbrainz_api(self, *mocks):
- query = 'lost fingers'
- url = reverse('api:v1:providers:musicbrainz:search-artists')
- expected = api_data.artists['search']['lost fingers']
- response = self.client.get(url, data={'query': query})
-
- self.assertEqual(expected, json.loads(response.content.decode('utf-8')))
-
- @unittest.mock.patch(
- 'funkwhale_api.musicbrainz.api.artists.get',
- return_value=api_data.artists['get']['lost fingers'])
- def test_can_get_artist_in_musicbrainz_api(self, *mocks):
- uuid = 'ac16bbc0-aded-4477-a3c3-1d81693d58c9'
- url = reverse('api:v1:providers:musicbrainz:artist-detail', kwargs={
- 'uuid': uuid,
- })
- response = self.client.get(url)
- expected = api_data.artists['get']['lost fingers']
-
- self.assertEqual(expected, json.loads(response.content.decode('utf-8')))
-
- @unittest.mock.patch(
- 'funkwhale_api.musicbrainz.api.release_groups.browse',
- return_value=api_data.release_groups['browse']['lost fingers'])
- def test_can_broswe_release_group_using_musicbrainz_api(self, *mocks):
- uuid = 'ac16bbc0-aded-4477-a3c3-1d81693d58c9'
- url = reverse(
- 'api:v1:providers:musicbrainz:release-group-browse',
- kwargs={
- 'artist_uuid': uuid,
- }
- )
- response = self.client.get(url)
- expected = api_data.release_groups['browse']['lost fingers']
-
- self.assertEqual(expected, json.loads(response.content.decode('utf-8')))
-
- @unittest.mock.patch(
- 'funkwhale_api.musicbrainz.api.releases.browse',
- return_value=api_data.releases['browse']['Lost in the 80s'])
- def test_can_broswe_releases_using_musicbrainz_api(self, *mocks):
- uuid = 'f04ed607-11b7-3843-957e-503ecdd485d1'
- url = reverse(
- 'api:v1:providers:musicbrainz:release-browse',
- kwargs={
- 'release_group_uuid': uuid,
- }
- )
- response = self.client.get(url)
- expected = api_data.releases['browse']['Lost in the 80s']
-
- self.assertEqual(expected, json.loads(response.content.decode('utf-8')))
diff --git a/api/funkwhale_api/musicbrainz/tests/test_cache.py b/api/funkwhale_api/musicbrainz/tests/test_cache.py
deleted file mode 100644
index d2d1260ec..000000000
--- a/api/funkwhale_api/musicbrainz/tests/test_cache.py
+++ /dev/null
@@ -1,17 +0,0 @@
-import unittest
-from test_plus.test import TestCase
-
-from funkwhale_api.musicbrainz import client
-
-
-class TestAPI(TestCase):
- def test_can_search_recording_in_musicbrainz_api(self, *mocks):
- r = {'hello': 'world'}
- mocked = 'funkwhale_api.musicbrainz.client._api.search_artists'
- with unittest.mock.patch(mocked, return_value=r) as m:
- self.assertEqual(client.api.artists.search('test'), r)
- # now call from cache
- self.assertEqual(client.api.artists.search('test'), r)
- self.assertEqual(client.api.artists.search('test'), r)
-
- self.assertEqual(m.call_count, 1)
diff --git a/api/funkwhale_api/playlists/factories.py b/api/funkwhale_api/playlists/factories.py
new file mode 100644
index 000000000..19e4770cf
--- /dev/null
+++ b/api/funkwhale_api/playlists/factories.py
@@ -0,0 +1,13 @@
+import factory
+
+from funkwhale_api.factories import registry
+from funkwhale_api.users.factories import UserFactory
+
+
+@registry.register
+class PlaylistFactory(factory.django.DjangoModelFactory):
+ name = factory.Faker('name')
+ user = factory.SubFactory(UserFactory)
+
+ class Meta:
+ model = 'playlists.Playlist'
diff --git a/api/funkwhale_api/playlists/tests/__init__.py b/api/funkwhale_api/playlists/tests/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/api/funkwhale_api/playlists/tests/test_playlists.py b/api/funkwhale_api/playlists/tests/test_playlists.py
deleted file mode 100644
index 2f61889ee..000000000
--- a/api/funkwhale_api/playlists/tests/test_playlists.py
+++ /dev/null
@@ -1,64 +0,0 @@
-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 import factories
-from funkwhale_api.users.models import User
-from funkwhale_api.playlists import models
-from funkwhale_api.playlists.serializers import PlaylistSerializer
-
-
-class TestPlayLists(TestCase):
-
- def setUp(self):
- super().setUp()
- self.user = User.objects.create_user(username='test', email='test@test.com', password='test')
-
- def test_can_create_playlist(self):
- tracks = factories.TrackFactory.create_batch(size=5)
- playlist = models.Playlist.objects.create(user=self.user, name="test")
-
- previous = None
- for i in range(len(tracks)):
- previous = playlist.add_track(tracks[i], previous=previous)
-
- playlist_tracks = list(playlist.playlist_tracks.all())
-
- previous = None
- for idx, track in enumerate(tracks):
- plt = playlist_tracks[idx]
- self.assertEqual(plt.position, idx)
- self.assertEqual(plt.track, track)
- if previous:
- self.assertEqual(playlist_tracks[idx + 1], previous)
- self.assertEqual(plt.playlist, playlist)
-
- def test_can_create_playlist_via_api(self):
- self.client.login(username=self.user.username, password='test')
- url = reverse('api:v1:playlists-list')
- data = {
- 'name': 'test',
- }
-
- response = self.client.post(url, data)
-
- playlist = self.user.playlists.latest('id')
- self.assertEqual(playlist.name, 'test')
-
- def test_can_add_playlist_track_via_api(self):
- tracks = factories.TrackFactory.create_batch(size=5)
- playlist = models.Playlist.objects.create(user=self.user, name="test")
-
- self.client.login(username=self.user.username, password='test')
-
- url = reverse('api:v1:playlist-tracks-list')
- data = {
- 'playlist': playlist.pk,
- 'track': tracks[0].pk
- }
-
- response = self.client.post(url, data)
- plts = self.user.playlists.latest('id').playlist_tracks.all()
- self.assertEqual(plts.first().track, tracks[0])
diff --git a/api/funkwhale_api/providers/audiofile/tests/__init__.py b/api/funkwhale_api/providers/audiofile/tests/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/api/funkwhale_api/providers/audiofile/tests/test_disk_import.py b/api/funkwhale_api/providers/audiofile/tests/test_disk_import.py
deleted file mode 100644
index f8d36986a..000000000
--- a/api/funkwhale_api/providers/audiofile/tests/test_disk_import.py
+++ /dev/null
@@ -1,50 +0,0 @@
-import os
-import datetime
-import unittest
-from test_plus.test import TestCase
-
-from funkwhale_api.providers.audiofile import tasks
-
-DATA_DIR = os.path.dirname(os.path.abspath(__file__))
-
-
-class TestAudioFile(TestCase):
- def test_can_import_single_audio_file(self, *mocks):
- metadata = {
- 'artist': ['Test artist'],
- 'album': ['Test album'],
- 'title': ['Test track'],
- 'TRACKNUMBER': ['4'],
- 'date': ['2012-08-15'],
- 'musicbrainz_albumid': ['a766da8b-8336-47aa-a3ee-371cc41ccc75'],
- 'musicbrainz_trackid': ['bd21ac48-46d8-4e78-925f-d9cc2a294656'],
- 'musicbrainz_artistid': ['013c8e5b-d72a-4cd3-8dee-6c64d6125823'],
- }
-
- m1 = unittest.mock.patch('mutagen.File', return_value=metadata)
- m2 = unittest.mock.patch(
- 'funkwhale_api.music.metadata.Metadata.get_file_type',
- return_value='OggVorbis',
- )
- with m1, m2:
- track_file = tasks.from_path(
- os.path.join(DATA_DIR, 'dummy_file.ogg'))
-
- self.assertEqual(
- track_file.track.title, metadata['title'][0])
- self.assertEqual(
- track_file.track.mbid, metadata['musicbrainz_trackid'][0])
- self.assertEqual(
- track_file.track.position, 4)
- self.assertEqual(
- track_file.track.album.title, metadata['album'][0])
- self.assertEqual(
- track_file.track.album.mbid,
- metadata['musicbrainz_albumid'][0])
- self.assertEqual(
- track_file.track.album.release_date, datetime.date(2012, 8, 15))
- self.assertEqual(
- track_file.track.artist.name, metadata['artist'][0])
- self.assertEqual(
- track_file.track.artist.mbid,
- metadata['musicbrainz_artistid'][0])
diff --git a/api/funkwhale_api/providers/youtube/tests/__init__.py b/api/funkwhale_api/providers/youtube/tests/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/api/funkwhale_api/providers/youtube/tests/test_youtube.py b/api/funkwhale_api/providers/youtube/tests/test_youtube.py
deleted file mode 100644
index 8a1dd1eb7..000000000
--- a/api/funkwhale_api/providers/youtube/tests/test_youtube.py
+++ /dev/null
@@ -1,99 +0,0 @@
-import json
-from collections import OrderedDict
-import unittest
-from test_plus.test import TestCase
-from django.urls import reverse
-from funkwhale_api.providers.youtube.client import client
-
-from . import data as api_data
-
-class TestAPI(TestCase):
- maxDiff = None
- @unittest.mock.patch(
- 'funkwhale_api.providers.youtube.client._do_search',
- return_value=api_data.search['8 bit adventure'])
- def test_can_get_search_results_from_youtube(self, *mocks):
- query = '8 bit adventure'
-
- results = client.search(query)
- self.assertEqual(results[0]['id']['videoId'], '0HxZn6CzOIo')
- self.assertEqual(results[0]['snippet']['title'], 'AdhesiveWombat - 8 Bit Adventure')
- self.assertEqual(results[0]['full_url'], 'https://www.youtube.com/watch?v=0HxZn6CzOIo')
-
- @unittest.mock.patch(
- 'funkwhale_api.providers.youtube.client._do_search',
- return_value=api_data.search['8 bit adventure'])
- def test_can_get_search_results_from_funkwhale(self, *mocks):
- query = '8 bit adventure'
- url = self.reverse('api:v1:providers:youtube:search')
- response = self.client.get(url + '?query={0}'.format(query))
- # we should cast the youtube result to something more generic
- expected = {
- "id": "0HxZn6CzOIo",
- "url": "https://www.youtube.com/watch?v=0HxZn6CzOIo",
- "type": "youtube#video",
- "description": "Make sure to apply adhesive evenly before use. GET IT HERE: http://adhesivewombat.bandcamp.com/album/marsupial-madness Facebook: ...",
- "channelId": "UCps63j3krzAG4OyXeEyuhFw",
- "title": "AdhesiveWombat - 8 Bit Adventure",
- "channelTitle": "AdhesiveWombat",
- "publishedAt": "2012-08-22T18:41:03.000Z",
- "cover": "https://i.ytimg.com/vi/0HxZn6CzOIo/hqdefault.jpg"
- }
-
- self.assertEqual(
- json.loads(response.content.decode('utf-8'))[0], expected)
-
- @unittest.mock.patch(
- 'funkwhale_api.providers.youtube.client._do_search',
- side_effect=[
- api_data.search['8 bit adventure'],
- api_data.search['system of a down toxicity'],
- ]
- )
- def test_can_send_multiple_queries_at_once(self, *mocks):
- queries = OrderedDict()
- queries['1'] = {
- 'q': '8 bit adventure',
- }
- queries['2'] = {
- 'q': 'system of a down toxicity',
- }
-
- results = client.search_multiple(queries)
-
- self.assertEqual(results['1'][0]['id']['videoId'], '0HxZn6CzOIo')
- self.assertEqual(results['1'][0]['snippet']['title'], 'AdhesiveWombat - 8 Bit Adventure')
- self.assertEqual(results['1'][0]['full_url'], 'https://www.youtube.com/watch?v=0HxZn6CzOIo')
- self.assertEqual(results['2'][0]['id']['videoId'], 'BorYwGi2SJc')
- self.assertEqual(results['2'][0]['snippet']['title'], 'System of a Down: Toxicity')
- self.assertEqual(results['2'][0]['full_url'], 'https://www.youtube.com/watch?v=BorYwGi2SJc')
-
- @unittest.mock.patch(
- 'funkwhale_api.providers.youtube.client._do_search',
- return_value=api_data.search['8 bit adventure'],
- )
- def test_can_send_multiple_queries_at_once_from_funwkhale(self, *mocks):
- queries = OrderedDict()
- queries['1'] = {
- 'q': '8 bit adventure',
- }
-
- expected = {
- "id": "0HxZn6CzOIo",
- "url": "https://www.youtube.com/watch?v=0HxZn6CzOIo",
- "type": "youtube#video",
- "description": "Make sure to apply adhesive evenly before use. GET IT HERE: http://adhesivewombat.bandcamp.com/album/marsupial-madness Facebook: ...",
- "channelId": "UCps63j3krzAG4OyXeEyuhFw",
- "title": "AdhesiveWombat - 8 Bit Adventure",
- "channelTitle": "AdhesiveWombat",
- "publishedAt": "2012-08-22T18:41:03.000Z",
- "cover": "https://i.ytimg.com/vi/0HxZn6CzOIo/hqdefault.jpg"
- }
-
- url = self.reverse('api:v1:providers:youtube:searchs')
- response = self.client.post(
- url, json.dumps(queries), content_type='application/json')
-
- self.assertEqual(
- expected,
- json.loads(response.content.decode('utf-8'))['1'][0])
diff --git a/api/funkwhale_api/radios/tests/__init__.py b/api/funkwhale_api/radios/tests/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/api/funkwhale_api/radios/tests/test_radios.py b/api/funkwhale_api/radios/tests/test_radios.py
deleted file mode 100644
index ab27d4516..000000000
--- a/api/funkwhale_api/radios/tests/test_radios.py
+++ /dev/null
@@ -1,196 +0,0 @@
-import random
-import json
-from test_plus.test import TestCase
-from django.urls import reverse
-from django.core.exceptions import ValidationError
-
-
-from funkwhale_api.radios import radios
-from funkwhale_api.radios import models
-from funkwhale_api.favorites.models import TrackFavorite
-from funkwhale_api.users.models import User
-from funkwhale_api.music.models import Artist
-from funkwhale_api.music.tests import factories
-from funkwhale_api.history.tests.factories import ListeningFactory
-
-
-class TestRadios(TestCase):
-
- def setUp(self):
- super().setUp()
- self.user = User.objects.create_user(username='test', email='test@test.com', password='test')
-
- def test_can_pick_track_from_choices(self):
- choices = [1, 2, 3, 4, 5]
-
- radio = radios.SimpleRadio()
-
- first_pick = radio.pick(choices=choices)
-
- self.assertIn(first_pick, choices)
-
- previous_choices = [first_pick]
- for remaining_choice in choices:
- pick = radio.pick(choices=choices, previous_choices=previous_choices)
- self.assertIn(pick, set(choices).difference(previous_choices))
-
- def test_can_pick_by_weight(self):
- choices_with_weight = [
- # choice, weight
- (1, 1),
- (2, 2),
- (3, 3),
- (4, 4),
- (5, 5),
- ]
-
- picks = {choice: 0 for choice, weight in choices_with_weight}
-
- for i in range(1000):
- radio = radios.SimpleRadio()
- pick = radio.weighted_pick(choices=choices_with_weight)
- picks[pick] = picks[pick] + 1
-
- self.assertTrue(picks[5] > picks[4])
- self.assertTrue(picks[4] > picks[3])
- self.assertTrue(picks[3] > picks[2])
- self.assertTrue(picks[2] > picks[1])
-
- def test_can_get_choices_for_favorites_radio(self):
- tracks = factories.TrackFactory.create_batch(size=100)
-
- for i in range(20):
- TrackFavorite.add(track=random.choice(tracks), user=self.user)
-
- radio = radios.FavoritesRadio()
- choices = radio.get_choices(user=self.user)
-
- self.assertEqual(choices.count(), self.user.track_favorites.all().count())
-
- for favorite in self.user.track_favorites.all():
- self.assertIn(favorite.track, choices)
-
- for i in range(20):
- pick = radio.pick(user=self.user)
- self.assertIn(pick, choices)
-
- def test_can_use_radio_session_to_filter_choices(self):
- tracks = factories.TrackFactory.create_batch(size=30)
- radio = radios.RandomRadio()
- session = radio.start_session(self.user)
-
- for i in range(30):
- p = radio.pick()
-
- # ensure 30 differents tracks have been suggested
- tracks_id = [session_track.track.pk for session_track in session.session_tracks.all()]
- self.assertEqual(len(set(tracks_id)), 30)
-
- def test_can_restore_radio_from_previous_session(self):
- tracks = factories.TrackFactory.create_batch(size=30)
-
- radio = radios.RandomRadio()
- session = radio.start_session(self.user)
-
- restarted_radio = radios.RandomRadio(session)
- self.assertEqual(radio.session, restarted_radio.session)
-
- def test_can_get_start_radio_from_api(self):
- url = reverse('api:v1:radios:sessions-list')
- response = self.client.post(url, {'radio_type': 'random'})
- session = models.RadioSession.objects.latest('id')
- self.assertEqual(session.radio_type, 'random')
- self.assertEqual(session.user, None)
-
- self.client.login(username=self.user.username, password='test')
- response = self.client.post(url, {'radio_type': 'random'})
- session = models.RadioSession.objects.latest('id')
- self.assertEqual(session.radio_type, 'random')
- self.assertEqual(session.user, self.user)
-
- def test_can_start_radio_for_anonymous_user(self):
- url = reverse('api:v1:radios:sessions-list')
- response = self.client.post(url, {'radio_type': 'random'})
- session = models.RadioSession.objects.latest('id')
-
- self.assertIsNone(session.user)
- self.assertIsNotNone(session.session_key)
-
- def test_can_get_track_for_session_from_api(self):
- tracks = factories.TrackFactory.create_batch(size=1)
-
- self.client.login(username=self.user.username, password='test')
- url = reverse('api:v1:radios:sessions-list')
- response = self.client.post(url, {'radio_type': 'random'})
- session = models.RadioSession.objects.latest('id')
-
- url = reverse('api:v1:radios:tracks-list')
- response = self.client.post(url, {'session': session.pk})
- data = json.loads(response.content.decode('utf-8'))
-
- self.assertEqual(data['track']['id'], tracks[0].id)
- self.assertEqual(data['position'], 1)
-
- next_track = factories.TrackFactory()
- response = self.client.post(url, {'session': session.pk})
- data = json.loads(response.content.decode('utf-8'))
-
- self.assertEqual(data['track']['id'], next_track.id)
- self.assertEqual(data['position'], 2)
-
- def test_related_object_radio_validate_related_object(self):
- # cannot start without related object
- radio = radios.ArtistRadio()
- with self.assertRaises(ValidationError):
- radio.start_session(self.user)
-
- # cannot start with bad related object type
- radio = radios.ArtistRadio()
- with self.assertRaises(ValidationError):
- radio.start_session(self.user, related_object=self.user)
-
- def test_can_start_artist_radio(self):
- artist = factories.ArtistFactory()
- wrong_tracks = factories.TrackFactory.create_batch(size=30)
- good_tracks = factories.TrackFactory.create_batch(
- artist=artist, size=5)
-
- radio = radios.ArtistRadio()
- session = radio.start_session(self.user, related_object=artist)
- self.assertEqual(session.radio_type, 'artist')
- for i in range(5):
- self.assertIn(radio.pick(), good_tracks)
-
- def test_can_start_tag_radio(self):
- tag = factories.TagFactory()
- wrong_tracks = factories.TrackFactory.create_batch(size=30)
- good_tracks = factories.TrackFactory.create_batch(size=5)
- for track in good_tracks:
- track.tags.add(tag)
-
- radio = radios.TagRadio()
- session = radio.start_session(self.user, related_object=tag)
- self.assertEqual(session.radio_type, 'tag')
- for i in range(5):
- self.assertIn(radio.pick(), good_tracks)
-
- def test_can_start_artist_radio_from_api(self):
- artist = factories.ArtistFactory()
- url = reverse('api:v1:radios:sessions-list')
-
- response = self.client.post(url, {'radio_type': 'artist', 'related_object_id': artist.id})
- session = models.RadioSession.objects.latest('id')
- self.assertEqual(session.radio_type, 'artist')
- self.assertEqual(session.related_object, artist)
-
- def test_can_start_less_listened_radio(self):
- history = ListeningFactory.create_batch(size=5, user=self.user)
- wrong_tracks = [h.track for h in history]
-
- good_tracks = factories.TrackFactory.create_batch(size=30)
-
- radio = radios.LessListenedRadio()
- session = radio.start_session(self.user)
- self.assertEqual(session.related_object, self.user)
- for i in range(5):
- self.assertIn(radio.pick(), good_tracks)
diff --git a/api/funkwhale_api/users/tests/factories.py b/api/funkwhale_api/users/factories.py
similarity index 82%
rename from api/funkwhale_api/users/tests/factories.py
rename to api/funkwhale_api/users/factories.py
index 351884ff4..0af155e77 100644
--- a/api/funkwhale_api/users/tests/factories.py
+++ b/api/funkwhale_api/users/factories.py
@@ -1,12 +1,14 @@
import factory
+from funkwhale_api.factories import registry
from django.contrib.auth.models import Permission
+@registry.register
class UserFactory(factory.django.DjangoModelFactory):
username = factory.Sequence(lambda n: 'user-{0}'.format(n))
email = factory.Sequence(lambda n: 'user-{0}@example.com'.format(n))
- password = factory.PostGenerationMethodCall('set_password', 'password')
+ password = factory.PostGenerationMethodCall('set_password', 'test')
class Meta:
model = 'users.User'
@@ -28,3 +30,9 @@ class UserFactory(factory.django.DjangoModelFactory):
]
# A list of permissions were passed in, use them
self.user_permissions.add(*perms)
+
+
+@registry.register(name='users.SuperUser')
+class SuperUserFactory(UserFactory):
+ is_staff = True
+ is_superuser = True
diff --git a/api/funkwhale_api/users/tests/__init__.py b/api/funkwhale_api/users/tests/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/api/funkwhale_api/users/tests/test_admin.py b/api/funkwhale_api/users/tests/test_admin.py
deleted file mode 100644
index 10b07b749..000000000
--- a/api/funkwhale_api/users/tests/test_admin.py
+++ /dev/null
@@ -1,40 +0,0 @@
-from test_plus.test import TestCase
-
-from ..admin import MyUserCreationForm
-
-
-class TestMyUserCreationForm(TestCase):
-
- def setUp(self):
- self.user = self.make_user()
-
- def test_clean_username_success(self):
- # Instantiate the form with a new username
- form = MyUserCreationForm({
- 'username': 'alamode',
- 'password1': '123456',
- 'password2': '123456',
- })
- # Run is_valid() to trigger the validation
- valid = form.is_valid()
- self.assertTrue(valid)
-
- # Run the actual clean_username method
- username = form.clean_username()
- self.assertEqual('alamode', username)
-
- def test_clean_username_false(self):
- # Instantiate the form with the same username as self.user
- form = MyUserCreationForm({
- 'username': self.user.username,
- 'password1': '123456',
- 'password2': '123456',
- })
- # Run is_valid() to trigger the validation, which is going to fail
- # because the username is already taken
- valid = form.is_valid()
- self.assertFalse(valid)
-
- # The form.errors dict should contain a single error called 'username'
- self.assertTrue(len(form.errors) == 1)
- self.assertTrue('username' in form.errors)
diff --git a/api/funkwhale_api/users/tests/test_models.py b/api/funkwhale_api/users/tests/test_models.py
deleted file mode 100644
index fbc7eb5f9..000000000
--- a/api/funkwhale_api/users/tests/test_models.py
+++ /dev/null
@@ -1,13 +0,0 @@
-from test_plus.test import TestCase
-
-
-class TestUser(TestCase):
-
- def setUp(self):
- self.user = self.make_user()
-
- def test__str__(self):
- self.assertEqual(
- self.user.__str__(),
- "testuser" # This is the default username for self.make_user()
- )
diff --git a/api/funkwhale_api/users/tests/test_views.py b/api/funkwhale_api/users/tests/test_views.py
deleted file mode 100644
index 52826cfa4..000000000
--- a/api/funkwhale_api/users/tests/test_views.py
+++ /dev/null
@@ -1,73 +0,0 @@
-import json
-
-from django.test import RequestFactory
-
-from test_plus.test import TestCase
-from funkwhale_api.users.models import User
-
-from . factories import UserFactory
-
-
-class UserTestCase(TestCase):
-
- def setUp(self):
- self.user = self.make_user()
- self.factory = RequestFactory()
-
- def test_can_create_user_via_api(self):
- url = self.reverse('rest_register')
- data = {
- 'username': 'test1',
- 'email': 'test1@test.com',
- 'password1': 'testtest',
- 'password2': 'testtest',
- }
- with self.settings(REGISTRATION_MODE="public"):
- response = self.client.post(url, data)
- self.assertEqual(response.status_code, 201)
-
- u = User.objects.get(email='test1@test.com')
- self.assertEqual(u.username, 'test1')
-
- def test_can_disable_registration_view(self):
- url = self.reverse('rest_register')
- data = {
- 'username': 'test1',
- 'email': 'test1@test.com',
- 'password1': 'testtest',
- 'password2': 'testtest',
- }
- with self.settings(REGISTRATION_MODE="disabled"):
- response = self.client.post(url, data)
- self.assertEqual(response.status_code, 403)
-
- def test_can_fetch_data_from_api(self):
- url = self.reverse('api:v1:users:users-me')
- response = self.client.get(url)
- # login required
- self.assertEqual(response.status_code, 401)
-
- user = UserFactory(
- is_staff=True,
- perms=[
- 'music.add_importbatch',
- 'dynamic_preferences.change_globalpreferencemodel',
- ]
- )
- self.assertTrue(user.has_perm('music.add_importbatch'))
- self.login(user)
-
- response = self.client.get(url)
- self.assertEqual(response.status_code, 200)
-
- payload = json.loads(response.content.decode('utf-8'))
-
- self.assertEqual(payload['username'], user.username)
- self.assertEqual(payload['is_staff'], user.is_staff)
- self.assertEqual(payload['is_superuser'], user.is_superuser)
- self.assertEqual(payload['email'], user.email)
- self.assertEqual(payload['name'], user.name)
- self.assertEqual(
- payload['permissions']['import.launch']['status'], True)
- self.assertEqual(
- payload['permissions']['settings.change']['status'], True)
diff --git a/api/funkwhale_api/utils/tests.py b/api/funkwhale_api/utils/tests.py
deleted file mode 100644
index 2605d3b4c..000000000
--- a/api/funkwhale_api/utils/tests.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import tempfile
-import shutil
-
-
-class TMPDirTestCaseMixin(object):
- def setUp(self):
- super().tearDown()
- self.download_dir = tempfile.mkdtemp()
-
- def tearDown(self):
- super().tearDown()
- shutil.rmtree(self.download_dir)
diff --git a/api/requirements/local.txt b/api/requirements/local.txt
index d8a1561e0..b466b20fd 100644
--- a/api/requirements/local.txt
+++ b/api/requirements/local.txt
@@ -5,7 +5,6 @@ django_coverage_plugin>=1.5,<1.6
Sphinx>=1.6,<1.7
django-extensions>=1.9,<1.10
Werkzeug>=0.13,<0.14
-django-test-plus>=1.0.20
factory_boy>=2.8.1
# django-debug-toolbar that works with Django 1.5+
diff --git a/api/requirements/test.txt b/api/requirements/test.txt
index bde5a2df9..c12b44827 100644
--- a/api/requirements/test.txt
+++ b/api/requirements/test.txt
@@ -2,7 +2,10 @@
flake8
pytest
-pytest-django
+# pytest-django until a new release containing django_assert_num_queries
+# is deployed to pypi
+git+https://github.com/pytest-dev/pytest-django.git@d3d9bb3ef6f0377cb5356eb368992a0834692378
+
pytest-mock
pytest-sugar
pytest-xdist
diff --git a/api/funkwhale_api/downloader/tests/__init__.py b/api/tests/__init__.py
similarity index 100%
rename from api/funkwhale_api/downloader/tests/__init__.py
rename to api/tests/__init__.py
diff --git a/api/tests/conftest.py b/api/tests/conftest.py
new file mode 100644
index 000000000..37f3dae3a
--- /dev/null
+++ b/api/tests/conftest.py
@@ -0,0 +1,42 @@
+import tempfile
+import shutil
+import pytest
+
+
+@pytest.fixture(scope="session", autouse=True)
+def factories_autodiscover():
+ from django.apps import apps
+ from funkwhale_api import factories
+ app_names = [app.name for app in apps.app_configs.values()]
+ factories.registry.autodiscover(app_names)
+
+
+@pytest.fixture
+def factories(db):
+ from funkwhale_api import factories
+ yield factories.registry
+
+
+@pytest.fixture
+def tmpdir():
+ d = tempfile.mkdtemp()
+ yield d
+ shutil.rmtree(d)
+
+
+@pytest.fixture
+def logged_in_client(db, factories, client):
+ user = factories['users.User']()
+ assert client.login(username=user.username, password='test')
+ setattr(client, 'user', user)
+ yield client
+ delattr(client, 'user')
+
+
+@pytest.fixture
+def superuser_client(db, factories, client):
+ user = factories['users.SuperUser']()
+ assert client.login(username=user.username, password='test')
+ setattr(client, 'user', user)
+ yield client
+ delattr(client, 'user')
diff --git a/api/funkwhale_api/providers/youtube/tests/data.py b/api/tests/data/youtube.py
similarity index 100%
rename from api/funkwhale_api/providers/youtube/tests/data.py
rename to api/tests/data/youtube.py
diff --git a/api/funkwhale_api/providers/audiofile/tests/dummy_file.ogg b/api/tests/files/dummy_file.ogg
similarity index 100%
rename from api/funkwhale_api/providers/audiofile/tests/dummy_file.ogg
rename to api/tests/files/dummy_file.ogg
diff --git a/api/funkwhale_api/favorites/tests/__init__.py b/api/tests/music/__init__.py
similarity index 100%
rename from api/funkwhale_api/favorites/tests/__init__.py
rename to api/tests/music/__init__.py
diff --git a/api/funkwhale_api/music/tests/cover.py b/api/tests/music/cover.py
similarity index 100%
rename from api/funkwhale_api/music/tests/cover.py
rename to api/tests/music/cover.py
diff --git a/api/funkwhale_api/music/tests/data.py b/api/tests/music/data.py
similarity index 100%
rename from api/funkwhale_api/music/tests/data.py
rename to api/tests/music/data.py
diff --git a/api/funkwhale_api/music/tests/mocking/lyricswiki.py b/api/tests/music/mocking/lyricswiki.py
similarity index 100%
rename from api/funkwhale_api/music/tests/mocking/lyricswiki.py
rename to api/tests/music/mocking/lyricswiki.py
diff --git a/api/funkwhale_api/music/tests/test.mp3 b/api/tests/music/test.mp3
similarity index 100%
rename from api/funkwhale_api/music/tests/test.mp3
rename to api/tests/music/test.mp3
diff --git a/api/funkwhale_api/music/tests/test.ogg b/api/tests/music/test.ogg
similarity index 100%
rename from api/funkwhale_api/music/tests/test.ogg
rename to api/tests/music/test.ogg
diff --git a/api/tests/music/test_api.py b/api/tests/music/test_api.py
new file mode 100644
index 000000000..e29aaf107
--- /dev/null
+++ b/api/tests/music/test_api.py
@@ -0,0 +1,253 @@
+import json
+import pytest
+from django.urls import reverse
+
+from funkwhale_api.music import models
+from funkwhale_api.musicbrainz import api
+from funkwhale_api.music import serializers
+
+from . import data as api_data
+
+
+def test_can_submit_youtube_url_for_track_import(mocker, superuser_client):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.artists.get',
+ return_value=api_data.artists['get']['adhesive_wombat'])
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.releases.get',
+ return_value=api_data.albums['get']['marsupial'])
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.recordings.get',
+ return_value=api_data.tracks['get']['8bitadventures'])
+ mocker.patch(
+ 'funkwhale_api.music.models.TrackFile.download_file',
+ return_value=None)
+ mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
+ video_id = 'tPEE9ZwTmy0'
+ url = reverse('api:v1:submit-single')
+ response = superuser_client.post(
+ url,
+ {'import_url': 'https://www.youtube.com/watch?v={0}'.format(video_id),
+ 'mbid': mbid})
+ track = models.Track.objects.get(mbid=mbid)
+ assert track.artist.name == 'Adhesive Wombat'
+ assert track.album.title == 'Marsupial Madness'
+
+
+def test_import_creates_an_import_with_correct_data(superuser_client, settings):
+ mbid = '9968a9d6-8d92-4051-8f76-674e157b6eed'
+ video_id = 'tPEE9ZwTmy0'
+ url = reverse('api:v1:submit-single')
+ settings.CELERY_ALWAYS_EAGER = False
+ response = superuser_client.post(
+ url,
+ {'import_url': 'https://www.youtube.com/watch?v={0}'.format(video_id),
+ 'mbid': mbid})
+
+ batch = models.ImportBatch.objects.latest('id')
+ assert batch.jobs.count() == 1
+ assert batch.submitted_by == superuser_client.user
+ assert batch.status == 'pending'
+ job = batch.jobs.first()
+ assert str(job.mbid) == mbid
+ assert job.status == 'pending'
+ assert job.source == 'https://www.youtube.com/watch?v={0}'.format(video_id)
+
+
+def test_can_import_whole_album(mocker, superuser_client, settings):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.artists.get',
+ return_value=api_data.artists['get']['soad'])
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.images.get_front',
+ return_value=b'')
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.releases.get',
+ return_value=api_data.albums['get_with_includes']['hypnotize'])
+ 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')
+ settings.CELERY_ALWAYS_EAGER = False
+ response = superuser_client.post(
+ url, json.dumps(payload), content_type="application/json")
+
+ batch = models.ImportBatch.objects.latest('id')
+ assert batch.jobs.count() == 3
+ assert batch.submitted_by == superuser_client.user
+ assert batch.status == 'pending'
+
+ album = models.Album.objects.latest('id')
+ assert str(album.mbid) == '47ae093f-1607-49a3-be11-a15d335ccc94'
+ medium_data = api_data.albums['get_with_includes']['hypnotize']['release']['medium-list'][0]
+ assert 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'])
+ assert instance.title == track['recording']['title']
+ assert instance.position == int(track['position'])
+ assert instance.title == track['recording']['title']
+
+ for row in payload['tracks']:
+ job = models.ImportJob.objects.get(mbid=row['mbid'])
+ assert str(job.mbid) == row['mbid']
+ assert job.status == 'pending'
+ assert job.source == row['source']
+
+
+def test_can_import_whole_artist(mocker, superuser_client, settings):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.artists.get',
+ return_value=api_data.artists['get']['soad'])
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.images.get_front',
+ return_value=b'')
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.releases.get',
+ return_value=api_data.albums['get_with_includes']['hypnotize'])
+ 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')
+ settings.CELERY_ALWAYS_EAGER = False
+ response = superuser_client.post(
+ url, json.dumps(payload), content_type="application/json")
+
+ batch = models.ImportBatch.objects.latest('id')
+ assert batch.jobs.count() == 3
+ assert batch.submitted_by == superuser_client.user
+ assert batch.status == 'pending'
+
+ album = models.Album.objects.latest('id')
+ assert str(album.mbid) == '47ae093f-1607-49a3-be11-a15d335ccc94'
+ medium_data = api_data.albums['get_with_includes']['hypnotize']['release']['medium-list'][0]
+ assert 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'])
+ assert instance.title == track['recording']['title']
+ assert instance.position == int(track['position'])
+ assert instance.title == track['recording']['title']
+
+ for row in payload['albums'][0]['tracks']:
+ job = models.ImportJob.objects.get(mbid=row['mbid'])
+ assert str(job.mbid) == row['mbid']
+ assert job.status == 'pending'
+ assert job.source == row['source']
+
+
+def test_user_can_query_api_for_his_own_batches(client, factories):
+ user1 = factories['users.SuperUser']()
+ user2 = factories['users.SuperUser']()
+
+ job = factories['music.ImportJob'](batch__submitted_by=user1)
+ url = reverse('api:v1:import-batches-list')
+
+ client.login(username=user2.username, password='test')
+ response2 = client.get(url)
+ results = json.loads(response2.content.decode('utf-8'))
+ assert results['count'] == 0
+ client.logout()
+
+ client.login(username=user1.username, password='test')
+ response1 = client.get(url)
+ results = json.loads(response1.content.decode('utf-8'))
+ assert results['count'] == 1
+ assert results['results'][0]['jobs'][0]['mbid'] == job.mbid
+
+
+def test_can_search_artist(factories, client):
+ artist1 = factories['music.Artist']()
+ artist2 = factories['music.Artist']()
+ expected = [serializers.ArtistSerializerNested(artist1).data]
+ url = reverse('api:v1:artists-search')
+ response = client.get(url, {'query': artist1.name})
+ assert json.loads(response.content.decode('utf-8')) == expected
+
+
+def test_can_search_artist_by_name_start(factories, client):
+ artist1 = factories['music.Artist'](name='alpha')
+ artist2 = factories['music.Artist'](name='beta')
+ expected = {
+ 'next': None,
+ 'previous': None,
+ 'count': 1,
+ 'results': [serializers.ArtistSerializerNested(artist1).data]
+ }
+ url = reverse('api:v1:artists-list')
+ response = client.get(url, {'name__startswith': 'a'})
+
+ assert expected == json.loads(response.content.decode('utf-8'))
+
+
+def test_can_search_tracks(factories, client):
+ track1 = factories['music.Track'](title="test track 1")
+ track2 = factories['music.Track']()
+ query = 'test track 1'
+ expected = [serializers.TrackSerializerNested(track1).data]
+ url = reverse('api:v1:tracks-search')
+ response = client.get(url, {'query': query})
+
+ assert expected == json.loads(response.content.decode('utf-8'))
+
+
+@pytest.mark.parametrize('route,method', [
+ ('api:v1:tags-list', 'get'),
+ ('api:v1:tracks-list', 'get'),
+ ('api:v1:artists-list', 'get'),
+ ('api:v1:albums-list', 'get'),
+])
+def test_can_restrict_api_views_to_authenticated_users(db, route, method, settings, client):
+ url = reverse(route)
+ settings.API_AUTHENTICATION_REQUIRED = True
+ response = getattr(client, method)(url)
+ assert response.status_code == 401
+
+
+def test_track_file_url_is_restricted_to_authenticated_users(client, factories, settings):
+ settings.API_AUTHENTICATION_REQUIRED = True
+ f = factories['music.TrackFile']()
+ assert f.audio_file is not None
+ url = f.path
+ response = client.get(url)
+ assert response.status_code == 401
+
+ user = factories['users.SuperUser']()
+ client.login(username=user.username, password='test')
+ response = client.get(url)
+
+ assert response.status_code == 200
+ assert response['X-Accel-Redirect'] == '/_protected{}'.format(f.audio_file.url)
diff --git a/api/tests/music/test_lyrics.py b/api/tests/music/test_lyrics.py
new file mode 100644
index 000000000..3670a2e5c
--- /dev/null
+++ b/api/tests/music/test_lyrics.py
@@ -0,0 +1,73 @@
+import json
+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 import lyrics as lyrics_utils
+
+from .mocking import lyricswiki
+from . import data as api_data
+
+
+
+def test_works_import_lyrics_if_any(mocker, factories):
+ mocker.patch(
+ 'funkwhale_api.music.lyrics._get_html',
+ return_value=lyricswiki.content)
+ lyrics = factories['music.Lyrics'](
+ 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():
+ c = """Hello
Is it me you're looking for?
"""
+ d = lyrics_utils.extract_content(c)
+ d = lyrics_utils.clean_content(d)
+
+ expected = """Hello
+Is it me you're looking for?
+"""
+ assert d == expected
+
+
+def test_markdown_rendering(factories):
+ content = """Hello
+Is it me you're looking for?"""
+
+ l = factories['music.Lyrics'](content=content)
+
+ expected = "Hello
\nIs it me you're looking for?
"
+ assert expected == l.content_rendered
+
+
+def test_works_import_lyrics_if_any(mocker, factories, logged_in_client):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.works.get',
+ return_value=api_data.works['get']['chop_suey'])
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.recordings.get',
+ return_value=api_data.tracks['get']['chop_suey'])
+ mocker.patch(
+ 'funkwhale_api.music.lyrics._get_html',
+ return_value=lyricswiki.content)
+ track = factories['music.Track'](
+ work=None,
+ mbid='07ca77cf-f513-4e9c-b190-d7e24bbad448')
+
+ url = reverse('api:v1:tracks-lyrics', kwargs={'pk': track.pk})
+ response = logged_in_client.get(url)
+
+ assert response.status_code == 200
+
+ track.refresh_from_db()
+ lyrics = models.Lyrics.objects.latest('id')
+ work = models.Work.objects.latest('id')
+
+ assert track.work == work
+ assert lyrics.work == work
diff --git a/api/tests/music/test_metadata.py b/api/tests/music/test_metadata.py
new file mode 100644
index 000000000..5df2dbcf1
--- /dev/null
+++ b/api/tests/music/test_metadata.py
@@ -0,0 +1,41 @@
+import datetime
+import os
+import pytest
+
+from funkwhale_api.music import metadata
+
+DATA_DIR = os.path.dirname(os.path.abspath(__file__))
+
+
+@pytest.mark.parametrize('field,value', [
+ ('title', 'Peer Gynt Suite no. 1, op. 46: I. Morning'),
+ ('artist', 'Edvard Grieg'),
+ ('album', 'Peer Gynt Suite no. 1, op. 46'),
+ ('date', datetime.date(2012, 8, 15)),
+ ('track_number', 1),
+ ('musicbrainz_albumid', 'a766da8b-8336-47aa-a3ee-371cc41ccc75'),
+ ('musicbrainz_recordingid', 'bd21ac48-46d8-4e78-925f-d9cc2a294656'),
+ ('musicbrainz_artistid', '013c8e5b-d72a-4cd3-8dee-6c64d6125823'),
+])
+def test_can_get_metadata_from_ogg_file(field, value):
+ path = os.path.join(DATA_DIR, 'test.ogg')
+ data = metadata.Metadata(path)
+
+ assert data.get(field) == value
+
+
+@pytest.mark.parametrize('field,value', [
+ ('title', 'Bend'),
+ ('artist', 'Binärpilot'),
+ ('album', 'You Can\'t Stop Da Funk'),
+ ('date', datetime.date(2006, 2, 7)),
+ ('track_number', 1),
+ ('musicbrainz_albumid', 'ce40cdb1-a562-4fd8-a269-9269f98d4124'),
+ ('musicbrainz_recordingid', 'f269d497-1cc0-4ae4-a0c4-157ec7d73fcb'),
+ ('musicbrainz_artistid', '9c6bddde-6228-4d9f-ad0d-03f6fcb19e13'),
+])
+def test_can_get_metadata_from_id3_mp3_file(field, value):
+ path = os.path.join(DATA_DIR, 'test.mp3')
+ data = metadata.Metadata(path)
+
+ assert data.get(field) == value
diff --git a/api/funkwhale_api/music/tests/test_models.py b/api/tests/music/test_models.py
similarity index 78%
rename from api/funkwhale_api/music/tests/test_models.py
rename to api/tests/music/test_models.py
index 4b43e4638..2ec192517 100644
--- a/api/funkwhale_api/music/tests/test_models.py
+++ b/api/tests/music/test_models.py
@@ -2,16 +2,14 @@ import pytest
from funkwhale_api.music import models
from funkwhale_api.music import importers
-from . import factories
-def test_can_store_release_group_id_on_album(db):
- album = factories.AlbumFactory()
+def test_can_store_release_group_id_on_album(factories):
+ album = factories['music.Album']()
assert album.release_group_id is not None
-def test_import_album_stores_release_group(db):
-
+def test_import_album_stores_release_group(factories):
album_data = {
"artist-credit": [
{
@@ -31,7 +29,7 @@ def test_import_album_stores_release_group(db):
"title": "Marsupial Madness",
'release-group': {'id': '447b4979-2178-405c-bfe6-46bf0b09e6c7'}
}
- artist = factories.ArtistFactory(
+ artist = factories['music.Artist'](
mbid=album_data['artist-credit'][0]['artist']['id']
)
cleaned_data = models.Album.clean_musicbrainz_data(album_data)
@@ -41,9 +39,9 @@ def test_import_album_stores_release_group(db):
assert album.artist == artist
-def test_import_job_is_bound_to_track_file(db, mocker):
- track = factories.TrackFactory()
- job = factories.ImportJobFactory(mbid=track.mbid)
+def test_import_job_is_bound_to_track_file(factories, mocker):
+ track = factories['music.Track']()
+ job = factories['music.ImportJob'](mbid=track.mbid)
mocker.patch('funkwhale_api.music.models.TrackFile.download_file')
job.run()
diff --git a/api/tests/music/test_music.py b/api/tests/music/test_music.py
new file mode 100644
index 000000000..076ad2bd0
--- /dev/null
+++ b/api/tests/music/test_music.py
@@ -0,0 +1,138 @@
+import pytest
+from funkwhale_api.music import models
+import datetime
+
+from . import data as api_data
+from .cover import binary_data
+
+
+def test_can_create_artist_from_api(mocker, db):
+ mocker.patch(
+ 'musicbrainzngs.search_artists',
+ return_value=api_data.artists['search']['adhesive_wombat'])
+ artist = models.Artist.create_from_api(query="Adhesive wombat")
+ data = models.Artist.api.search(query='Adhesive wombat')['artist-list'][0]
+
+ assert int(data['ext:score']), 100
+ assert data['id'], '62c3befb-6366-4585-b256-809472333801'
+ assert artist.mbid, data['id']
+ assert artist.name, 'Adhesive Wombat'
+
+
+def test_can_create_album_from_api(mocker, db):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.releases.search',
+ return_value=api_data.albums['search']['hypnotize'])
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.artists.get',
+ return_value=api_data.artists['get']['soad'])
+ 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]
+
+ assert album.mbid, data['id']
+ assert album.title, 'Hypnotize'
+ with pytest.raises(ValueError):
+ assert album.cover.path is not None
+ assert album.release_date, datetime.date(2005, 1, 1)
+ assert album.artist.name, 'System of a Down'
+ assert album.artist.mbid, data['artist-credit'][0]['artist']['id']
+
+
+def test_can_create_track_from_api(mocker, db):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.artists.get',
+ return_value=api_data.artists['get']['adhesive_wombat'])
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.releases.get',
+ return_value=api_data.albums['get']['marsupial'])
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.recordings.search',
+ return_value=api_data.tracks['search']['8bitadventures'])
+ track = models.Track.create_from_api(query="8-bit adventure")
+ data = models.Track.api.search(query='8-bit adventure')['recording-list'][0]
+ assert int(data['ext:score']) == 100
+ assert data['id'] == '9968a9d6-8d92-4051-8f76-674e157b6eed'
+ assert track.mbid == data['id']
+ assert track.artist.pk is not None
+ assert str(track.artist.mbid) == '62c3befb-6366-4585-b256-809472333801'
+ assert track.artist.name == 'Adhesive Wombat'
+ assert str(track.album.mbid) == 'a50d2a81-2a50-484d-9cb4-b9f6833f583e'
+ assert track.album.title == 'Marsupial Madness'
+
+
+def test_can_create_track_from_api_with_corresponding_tags(mocker, db):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.artists.get',
+ return_value=api_data.artists['get']['adhesive_wombat'])
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.releases.get',
+ return_value=api_data.albums['get']['marsupial'])
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.recordings.get',
+ return_value=api_data.tracks['get']['8bitadventures'])
+ 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:
+ assert tag in track_tags
+
+
+def test_can_get_or_create_track_from_api(mocker, db):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.artists.get',
+ return_value=api_data.artists['get']['adhesive_wombat'])
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.releases.get',
+ return_value=api_data.albums['get']['marsupial'])
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.recordings.search',
+ return_value=api_data.tracks['search']['8bitadventures'])
+ track = models.Track.create_from_api(query="8-bit adventure")
+ data = models.Track.api.search(query='8-bit adventure')['recording-list'][0]
+ assert int(data['ext:score']) == 100
+ assert data['id'] == '9968a9d6-8d92-4051-8f76-674e157b6eed'
+ assert track.mbid == data['id']
+ assert track.artist.pk is not None
+ assert str(track.artist.mbid) == '62c3befb-6366-4585-b256-809472333801'
+ assert track.artist.name == 'Adhesive Wombat'
+
+ track2, created = models.Track.get_or_create_from_api(mbid=data['id'])
+ assert not created
+ assert track == track2
+
+
+def test_album_tags_deduced_from_tracks_tags(factories, django_assert_num_queries):
+ tag = factories['taggit.Tag']()
+ album = factories['music.Album']()
+ tracks = factories['music.Track'].create_batch(
+ 5, album=album, tags=[tag])
+
+ album = models.Album.objects.prefetch_related('tracks__tags').get(pk=album.pk)
+
+ with django_assert_num_queries(0):
+ assert tag in album.tags
+
+
+def test_artist_tags_deduced_from_album_tags(factories, django_assert_num_queries):
+ tag = factories['taggit.Tag']()
+ album = factories['music.Album']()
+ artist = album.artist
+ tracks = factories['music.Track'].create_batch(
+ 5, album=album, tags=[tag])
+
+ artist = models.Artist.objects.prefetch_related('albums__tracks__tags').get(pk=artist.pk)
+
+ with django_assert_num_queries(0):
+ assert tag in artist.tags
+
+
+def test_can_download_image_file_for_album(mocker, factories):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.images.get_front',
+ return_value=binary_data)
+ # client._api.get_image_front('55ea4f82-b42b-423e-a0e5-290ccdf443ed')
+ album = factories['music.Album'](mbid='55ea4f82-b42b-423e-a0e5-290ccdf443ed')
+ album.get_image()
+ album.save()
+
+ assert album.cover.file.read() == binary_data
diff --git a/api/tests/music/test_works.py b/api/tests/music/test_works.py
new file mode 100644
index 000000000..9b72768ad
--- /dev/null
+++ b/api/tests/music/test_works.py
@@ -0,0 +1,65 @@
+import json
+from django.urls import reverse
+
+from funkwhale_api.music import models
+from funkwhale_api.musicbrainz import api
+from funkwhale_api.music import serializers
+
+from . import data as api_data
+
+
+def test_can_import_work(factories, mocker):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.works.get',
+ return_value=api_data.works['get']['chop_suey'])
+ recording = factories['music.Track'](
+ mbid='07ca77cf-f513-4e9c-b190-d7e24bbad448')
+ mbid = 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5'
+ work = models.Work.create_from_api(id=mbid)
+
+ assert work.title == 'Chop Suey!'
+ assert work.nature == 'song'
+ assert work.language == 'eng'
+ assert work.mbid == mbid
+
+ # a imported work should also be linked to corresponding recordings
+
+ recording.refresh_from_db()
+ assert recording.work == work
+
+
+def test_can_get_work_from_recording(factories, mocker):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.works.get',
+ return_value=api_data.works['get']['chop_suey'])
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.recordings.get',
+ return_value=api_data.tracks['get']['chop_suey'])
+ recording = factories['music.Track'](
+ work=None,
+ mbid='07ca77cf-f513-4e9c-b190-d7e24bbad448')
+ mbid = 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5'
+
+ assert recording.work == None
+
+ work = recording.get_work()
+
+ assert work.title == 'Chop Suey!'
+ assert work.nature == 'song'
+ assert work.language == 'eng'
+ assert work.mbid == mbid
+
+ recording.refresh_from_db()
+ assert recording.work == work
+
+
+def test_works_import_lyrics_if_any(db, mocker):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.works.get',
+ return_value=api_data.works['get']['chop_suey'])
+ mbid = 'e2ecabc4-1b9d-30b2-8f30-3596ec423dc5'
+ work = models.Work.create_from_api(id=mbid)
+
+ lyrics = models.Lyrics.objects.latest('id')
+ assert lyrics.work == work
+ assert lyrics.url == 'http://lyrics.wikia.com/System_Of_A_Down:Chop_Suey!'
diff --git a/api/funkwhale_api/history/tests/__init__.py b/api/tests/musicbrainz/__init__.py
similarity index 100%
rename from api/funkwhale_api/history/tests/__init__.py
rename to api/tests/musicbrainz/__init__.py
diff --git a/api/funkwhale_api/musicbrainz/tests/data.py b/api/tests/musicbrainz/data.py
similarity index 100%
rename from api/funkwhale_api/musicbrainz/tests/data.py
rename to api/tests/musicbrainz/data.py
diff --git a/api/tests/musicbrainz/test_api.py b/api/tests/musicbrainz/test_api.py
new file mode 100644
index 000000000..bbade3400
--- /dev/null
+++ b/api/tests/musicbrainz/test_api.py
@@ -0,0 +1,90 @@
+import json
+from django.urls import reverse
+
+from funkwhale_api.musicbrainz import api
+from . import data as api_data
+
+
+
+def test_can_search_recording_in_musicbrainz_api(db, mocker, client):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.recordings.search',
+ return_value=api_data.recordings['search']['brontide matador'])
+ query = 'brontide matador'
+ url = reverse('api:v1:providers:musicbrainz:search-recordings')
+ expected = api_data.recordings['search']['brontide matador']
+ response = client.get(url, data={'query': query})
+
+ assert expected == json.loads(response.content.decode('utf-8'))
+
+
+def test_can_search_release_in_musicbrainz_api(db, mocker, client):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.releases.search',
+ return_value=api_data.releases['search']['brontide matador'])
+ query = 'brontide matador'
+ url = reverse('api:v1:providers:musicbrainz:search-releases')
+ expected = api_data.releases['search']['brontide matador']
+ response = client.get(url, data={'query': query})
+
+ assert expected == json.loads(response.content.decode('utf-8'))
+
+
+def test_can_search_artists_in_musicbrainz_api(db, mocker, client):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.artists.search',
+ return_value=api_data.artists['search']['lost fingers'])
+ query = 'lost fingers'
+ url = reverse('api:v1:providers:musicbrainz:search-artists')
+ expected = api_data.artists['search']['lost fingers']
+ response = client.get(url, data={'query': query})
+
+ assert expected == json.loads(response.content.decode('utf-8'))
+
+
+def test_can_get_artist_in_musicbrainz_api(db, mocker, client):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.artists.get',
+ return_value=api_data.artists['get']['lost fingers'])
+ uuid = 'ac16bbc0-aded-4477-a3c3-1d81693d58c9'
+ url = reverse('api:v1:providers:musicbrainz:artist-detail', kwargs={
+ 'uuid': uuid,
+ })
+ response = client.get(url)
+ expected = api_data.artists['get']['lost fingers']
+
+ assert expected == json.loads(response.content.decode('utf-8'))
+
+
+def test_can_broswe_release_group_using_musicbrainz_api(db, mocker, client):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.release_groups.browse',
+ return_value=api_data.release_groups['browse']['lost fingers'])
+ uuid = 'ac16bbc0-aded-4477-a3c3-1d81693d58c9'
+ url = reverse(
+ 'api:v1:providers:musicbrainz:release-group-browse',
+ kwargs={
+ 'artist_uuid': uuid,
+ }
+ )
+ response = client.get(url)
+ expected = api_data.release_groups['browse']['lost fingers']
+
+ assert expected == json.loads(response.content.decode('utf-8'))
+
+
+def test_can_broswe_releases_using_musicbrainz_api(db, mocker, client):
+ mocker.patch(
+ 'funkwhale_api.musicbrainz.api.releases.browse',
+ return_value=api_data.releases['browse']['Lost in the 80s'])
+ uuid = 'f04ed607-11b7-3843-957e-503ecdd485d1'
+ url = reverse(
+ 'api:v1:providers:musicbrainz:release-browse',
+ kwargs={
+ 'release_group_uuid': uuid,
+ }
+ )
+ response = client.get(url)
+ expected = api_data.releases['browse']['Lost in the 80s']
+
+ assert expected == json.loads(response.content.decode('utf-8'))
diff --git a/api/tests/musicbrainz/test_cache.py b/api/tests/musicbrainz/test_cache.py
new file mode 100644
index 000000000..fe0d56773
--- /dev/null
+++ b/api/tests/musicbrainz/test_cache.py
@@ -0,0 +1,13 @@
+from funkwhale_api.musicbrainz import client
+
+
+def test_can_search_recording_in_musicbrainz_api(mocker):
+ r = {'hello': 'world'}
+ m = mocker.patch(
+ 'funkwhale_api.musicbrainz.client._api.search_artists',
+ return_value=r)
+ assert client.api.artists.search('test') == r
+ # now call from cache
+ assert client.api.artists.search('test') == r
+ assert client.api.artists.search('test') == r
+ assert m.call_count == 1
diff --git a/api/tests/test_disk_import.py b/api/tests/test_disk_import.py
new file mode 100644
index 000000000..9aaf39975
--- /dev/null
+++ b/api/tests/test_disk_import.py
@@ -0,0 +1,39 @@
+import os
+import datetime
+
+from funkwhale_api.providers.audiofile import tasks
+
+DATA_DIR = os.path.join(
+ os.path.dirname(os.path.abspath(__file__)),
+ 'files'
+)
+
+
+def test_can_import_single_audio_file(db, mocker):
+ metadata = {
+ 'artist': ['Test artist'],
+ 'album': ['Test album'],
+ 'title': ['Test track'],
+ 'TRACKNUMBER': ['4'],
+ 'date': ['2012-08-15'],
+ 'musicbrainz_albumid': ['a766da8b-8336-47aa-a3ee-371cc41ccc75'],
+ 'musicbrainz_trackid': ['bd21ac48-46d8-4e78-925f-d9cc2a294656'],
+ 'musicbrainz_artistid': ['013c8e5b-d72a-4cd3-8dee-6c64d6125823'],
+ }
+
+ m1 = mocker.patch('mutagen.File', return_value=metadata)
+ m2 = mocker.patch(
+ 'funkwhale_api.music.metadata.Metadata.get_file_type',
+ return_value='OggVorbis',
+ )
+ track_file = tasks.from_path(os.path.join(DATA_DIR, 'dummy_file.ogg'))
+ track = track_file.track
+
+ assert track.title == metadata['title'][0]
+ assert track.mbid == metadata['musicbrainz_trackid'][0]
+ assert track.position == 4
+ assert track.album.title == metadata['album'][0]
+ assert track.album.mbid == metadata['musicbrainz_albumid'][0]
+ assert track.album.release_date == datetime.date(2012, 8, 15)
+ assert track.artist.name == metadata['artist'][0]
+ assert track.artist.mbid == metadata['musicbrainz_artistid'][0]
diff --git a/api/tests/test_downloader.py b/api/tests/test_downloader.py
new file mode 100644
index 000000000..ede7bb16c
--- /dev/null
+++ b/api/tests/test_downloader.py
@@ -0,0 +1,11 @@
+import os
+
+from funkwhale_api import downloader
+
+
+def test_can_download_audio_from_youtube_url_to_vorbis(tmpdir):
+ data = downloader.download(
+ 'https://www.youtube.com/watch?v=tPEE9ZwTmy0',
+ target_directory=tmpdir)
+ assert data['audio_file_path'] == os.path.join(tmpdir, 'tPEE9ZwTmy0.ogg')
+ assert os.path.exists(data['audio_file_path'])
diff --git a/api/tests/test_favorites.py b/api/tests/test_favorites.py
new file mode 100644
index 000000000..418166d8e
--- /dev/null
+++ b/api/tests/test_favorites.py
@@ -0,0 +1,92 @@
+import json
+import pytest
+from django.urls import reverse
+
+from funkwhale_api.music.models import Track, Artist
+from funkwhale_api.favorites.models import TrackFavorite
+
+
+
+def test_user_can_add_favorite(factories):
+ track = factories['music.Track']()
+ user = factories['users.User']()
+ f = TrackFavorite.add(track, user)
+
+ assert f.track == track
+ assert f.user == user
+
+
+def test_user_can_get_his_favorites(factories, logged_in_client, client):
+ favorite = factories['favorites.TrackFavorite'](user=logged_in_client.user)
+ url = reverse('api:v1:favorites:tracks-list')
+ response = logged_in_client.get(url)
+
+ expected = [
+ {
+ 'track': favorite.track.pk,
+ 'id': favorite.id,
+ 'creation_date': favorite.creation_date.isoformat().replace('+00:00', 'Z'),
+ }
+ ]
+ parsed_json = json.loads(response.content.decode('utf-8'))
+
+ assert expected == parsed_json['results']
+
+
+def test_user_can_add_favorite_via_api(factories, logged_in_client, client):
+ track = factories['music.Track']()
+ url = reverse('api:v1:favorites:tracks-list')
+ response = logged_in_client.post(url, {'track': track.pk})
+
+ favorite = TrackFavorite.objects.latest('id')
+ expected = {
+ 'track': track.pk,
+ 'id': favorite.id,
+ 'creation_date': favorite.creation_date.isoformat().replace('+00:00', 'Z'),
+ }
+ parsed_json = json.loads(response.content.decode('utf-8'))
+
+ assert expected == parsed_json
+ assert favorite.track == track
+ assert favorite.user == logged_in_client.user
+
+
+def test_user_can_remove_favorite_via_api(logged_in_client, factories, client):
+ favorite = factories['favorites.TrackFavorite'](user=logged_in_client.user)
+ url = reverse('api:v1:favorites:tracks-detail', kwargs={'pk': favorite.pk})
+ response = client.delete(url, {'track': favorite.track.pk})
+ assert response.status_code == 204
+ assert TrackFavorite.objects.count() == 0
+
+def test_user_can_remove_favorite_via_api_using_track_id(factories, logged_in_client):
+ favorite = factories['favorites.TrackFavorite'](user=logged_in_client.user)
+
+ url = reverse('api:v1:favorites:tracks-remove')
+ response = logged_in_client.delete(
+ url, json.dumps({'track': favorite.track.pk}),
+ content_type='application/json'
+ )
+
+ assert response.status_code == 204
+ assert TrackFavorite.objects.count() == 0
+
+
+@pytest.mark.parametrize('url,method', [
+ ('api:v1:favorites:tracks-list', 'get'),
+])
+def test_url_require_auth(url, method, db, settings, client):
+ settings.API_AUTHENTICATION_REQUIRED = True
+ url = reverse(url)
+ response = getattr(client, method)(url)
+ assert response.status_code == 401
+
+
+def test_can_filter_tracks_by_favorites(factories, logged_in_client):
+ favorite = factories['favorites.TrackFavorite'](user=logged_in_client.user)
+
+ url = reverse('api:v1:tracks-list')
+ response = logged_in_client.get(url, data={'favorites': True})
+
+ parsed_json = json.loads(response.content.decode('utf-8'))
+ assert parsed_json['count'] == 1
+ assert parsed_json['results'][0]['id'] == favorite.track.id
diff --git a/api/tests/test_history.py b/api/tests/test_history.py
new file mode 100644
index 000000000..113e5ff64
--- /dev/null
+++ b/api/tests/test_history.py
@@ -0,0 +1,42 @@
+import random
+import json
+from django.urls import reverse
+from django.core.exceptions import ValidationError
+from django.utils import timezone
+
+from funkwhale_api.history import models
+
+
+def test_can_create_listening(factories):
+ track = factories['music.Track']()
+ user = factories['users.User']()
+ now = timezone.now()
+ l = models.Listening.objects.create(user=user, track=track)
+
+
+def test_anonymous_user_can_create_listening_via_api(client, factories, settings):
+ settings.API_AUTHENTICATION_REQUIRED = False
+ track = factories['music.Track']()
+ url = reverse('api:v1:history:listenings-list')
+ response = client.post(url, {
+ 'track': track.pk,
+ })
+
+ listening = models.Listening.objects.latest('id')
+
+ assert listening.track == track
+ assert listening.session_key == client.session.session_key
+
+
+def test_logged_in_user_can_create_listening_via_api(logged_in_client, factories):
+ track = factories['music.Track']()
+
+ url = reverse('api:v1:history:listenings-list')
+ response = logged_in_client.post(url, {
+ 'track': track.pk,
+ })
+
+ listening = models.Listening.objects.latest('id')
+
+ assert listening.track == track
+ assert listening.user == logged_in_client.user
diff --git a/api/tests/test_jwt_querystring.py b/api/tests/test_jwt_querystring.py
new file mode 100644
index 000000000..bd07e1dc3
--- /dev/null
+++ b/api/tests/test_jwt_querystring.py
@@ -0,0 +1,21 @@
+from django.urls import reverse
+from rest_framework_jwt.settings import api_settings
+
+jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
+jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
+
+
+def test_can_authenticate_using_token_param_in_url(factories, settings, client):
+ user = factories['users.User']()
+ settings.API_AUTHENTICATION_REQUIRED = True
+ url = reverse('api:v1:tracks-list')
+ response = client.get(url)
+
+ assert response.status_code == 401
+
+ payload = jwt_payload_handler(user)
+ token = jwt_encode_handler(payload)
+ response = client.get(url, data={
+ 'jwt': token
+ })
+ assert response.status_code == 200
diff --git a/api/tests/test_playlists.py b/api/tests/test_playlists.py
new file mode 100644
index 000000000..f496a64cb
--- /dev/null
+++ b/api/tests/test_playlists.py
@@ -0,0 +1,54 @@
+import json
+from django.urls import reverse
+from django.core.exceptions import ValidationError
+from django.utils import timezone
+
+from funkwhale_api.playlists import models
+from funkwhale_api.playlists.serializers import PlaylistSerializer
+
+
+
+def test_can_create_playlist(factories):
+ tracks = factories['music.Track'].create_batch(5)
+ playlist = factories['playlists.Playlist']()
+
+ previous = None
+ for track in tracks:
+ previous = playlist.add_track(track, previous=previous)
+
+ playlist_tracks = list(playlist.playlist_tracks.all())
+
+ previous = None
+ for idx, track in enumerate(tracks):
+ plt = playlist_tracks[idx]
+ assert plt.position == idx
+ assert plt.track == track
+ if previous:
+ assert playlist_tracks[idx + 1] == previous
+ assert plt.playlist == playlist
+
+
+def test_can_create_playlist_via_api(logged_in_client):
+ url = reverse('api:v1:playlists-list')
+ data = {
+ 'name': 'test',
+ }
+
+ response = logged_in_client.post(url, data)
+
+ playlist = logged_in_client.user.playlists.latest('id')
+ assert playlist.name == 'test'
+
+
+def test_can_add_playlist_track_via_api(factories, logged_in_client):
+ tracks = factories['music.Track'].create_batch(5)
+ playlist = factories['playlists.Playlist'](user=logged_in_client.user)
+ url = reverse('api:v1:playlist-tracks-list')
+ data = {
+ 'playlist': playlist.pk,
+ 'track': tracks[0].pk
+ }
+
+ response = logged_in_client.post(url, data)
+ plts = logged_in_client.user.playlists.latest('id').playlist_tracks.all()
+ assert plts.first().track == tracks[0]
diff --git a/api/tests/test_radios.py b/api/tests/test_radios.py
new file mode 100644
index 000000000..d67611123
--- /dev/null
+++ b/api/tests/test_radios.py
@@ -0,0 +1,195 @@
+import json
+import random
+import pytest
+
+from django.urls import reverse
+from django.core.exceptions import ValidationError
+
+
+from funkwhale_api.radios import radios
+from funkwhale_api.radios import models
+from funkwhale_api.favorites.models import TrackFavorite
+
+
+def test_can_pick_track_from_choices():
+ choices = [1, 2, 3, 4, 5]
+
+ radio = radios.SimpleRadio()
+
+ first_pick = radio.pick(choices=choices)
+
+ assert first_pick in choices
+
+ previous_choices = [first_pick]
+ for remaining_choice in choices:
+ pick = radio.pick(choices=choices, previous_choices=previous_choices)
+ assert pick in set(choices).difference(previous_choices)
+
+
+def test_can_pick_by_weight():
+ choices_with_weight = [
+ # choice, weight
+ (1, 1),
+ (2, 2),
+ (3, 3),
+ (4, 4),
+ (5, 5),
+ ]
+
+ picks = {choice: 0 for choice, weight in choices_with_weight}
+
+ for i in range(1000):
+ radio = radios.SimpleRadio()
+ pick = radio.weighted_pick(choices=choices_with_weight)
+ picks[pick] = picks[pick] + 1
+
+ assert picks[5] > picks[4]
+ assert picks[4] > picks[3]
+ assert picks[3] > picks[2]
+ assert picks[2] > picks[1]
+
+
+def test_can_get_choices_for_favorites_radio(factories):
+ tracks = factories['music.Track'].create_batch(100)
+ user = factories['users.User']()
+ for i in range(20):
+ TrackFavorite.add(track=random.choice(tracks), user=user)
+
+ radio = radios.FavoritesRadio()
+ choices = radio.get_choices(user=user)
+
+ assert choices.count() == user.track_favorites.all().count()
+
+ for favorite in user.track_favorites.all():
+ assert favorite.track in choices
+
+ for i in range(20):
+ pick = radio.pick(user=user)
+ assert pick in choices
+
+
+def test_can_use_radio_session_to_filter_choices(factories):
+ tracks = factories['music.Track'].create_batch(30)
+ user = factories['users.User']()
+ radio = radios.RandomRadio()
+ session = radio.start_session(user)
+
+ for i in range(30):
+ p = radio.pick()
+
+ # ensure 30 differents tracks have been suggested
+ tracks_id = [
+ session_track.track.pk
+ for session_track in session.session_tracks.all()]
+ assert len(set(tracks_id)) == 30
+
+
+def test_can_restore_radio_from_previous_session(factories):
+ user = factories['users.User']()
+ radio = radios.RandomRadio()
+ session = radio.start_session(user)
+
+ restarted_radio = radios.RandomRadio(session)
+ assert radio.session == restarted_radio.session
+
+
+def test_can_start_radio_for_logged_in_user(logged_in_client):
+ url = reverse('api:v1:radios:sessions-list')
+ response = logged_in_client.post(url, {'radio_type': 'random'})
+ session = models.RadioSession.objects.latest('id')
+ assert session.radio_type == 'random'
+ assert session.user == logged_in_client.user
+
+
+def test_can_start_radio_for_anonymous_user(client, db):
+ url = reverse('api:v1:radios:sessions-list')
+ response = client.post(url, {'radio_type': 'random'})
+ session = models.RadioSession.objects.latest('id')
+
+ assert session.radio_type == 'random'
+ assert session.user is None
+ assert session.session_key == client.session.session_key
+
+
+def test_can_get_track_for_session_from_api(factories, logged_in_client):
+ tracks = factories['music.Track'].create_batch(size=1)
+
+ url = reverse('api:v1:radios:sessions-list')
+ response = logged_in_client.post(url, {'radio_type': 'random'})
+ session = models.RadioSession.objects.latest('id')
+
+ url = reverse('api:v1:radios:tracks-list')
+ response = logged_in_client.post(url, {'session': session.pk})
+ data = json.loads(response.content.decode('utf-8'))
+
+ assert data['track']['id'] == tracks[0].id
+ assert data['position'] == 1
+
+ next_track = factories['music.Track']()
+ response = logged_in_client.post(url, {'session': session.pk})
+ data = json.loads(response.content.decode('utf-8'))
+
+ assert data['track']['id'] == next_track.id
+ assert data['position'] == 2
+
+
+def test_related_object_radio_validate_related_object(factories):
+ user = factories['users.User']()
+ # cannot start without related object
+ radio = radios.ArtistRadio()
+ with pytest.raises(ValidationError):
+ radio.start_session(user)
+
+ # cannot start with bad related object type
+ radio = radios.ArtistRadio()
+ with pytest.raises(ValidationError):
+ radio.start_session(user, related_object=user)
+
+
+def test_can_start_artist_radio(factories):
+ user = factories['users.User']()
+ artist = factories['music.Artist']()
+ wrong_tracks = factories['music.Track'].create_batch(5)
+ good_tracks = factories['music.Track'].create_batch(5, artist=artist)
+
+ radio = radios.ArtistRadio()
+ session = radio.start_session(user, related_object=artist)
+ assert session.radio_type == 'artist'
+ for i in range(5):
+ assert radio.pick() in good_tracks
+
+
+def test_can_start_tag_radio(factories):
+ user = factories['users.User']()
+ tag = factories['taggit.Tag']()
+ wrong_tracks = factories['music.Track'].create_batch(5)
+ good_tracks = factories['music.Track'].create_batch(5, tags=[tag])
+
+ radio = radios.TagRadio()
+ session = radio.start_session(user, related_object=tag)
+ assert session.radio_type =='tag'
+ for i in range(5):
+ assert radio.pick() in good_tracks
+
+
+def test_can_start_artist_radio_from_api(client, factories):
+ artist = factories['music.Artist']()
+ url = reverse('api:v1:radios:sessions-list')
+
+ response = client.post(
+ url, {'radio_type': 'artist', 'related_object_id': artist.id})
+ session = models.RadioSession.objects.latest('id')
+ assert session.radio_type, 'artist'
+ assert session.related_object, artist
+
+
+def test_can_start_less_listened_radio(factories):
+ user = factories['users.User']()
+ history = factories['history.Listening'].create_batch(5, user=user)
+ wrong_tracks = [h.track for h in history]
+ good_tracks = factories['music.Track'].create_batch(size=5)
+ radio = radios.LessListenedRadio()
+ session = radio.start_session(user)
+ assert session.related_object == user
+ for i in range(5):
+ assert radio.pick() in good_tracks
diff --git a/api/tests/test_youtube.py b/api/tests/test_youtube.py
new file mode 100644
index 000000000..017d742ef
--- /dev/null
+++ b/api/tests/test_youtube.py
@@ -0,0 +1,95 @@
+import json
+from collections import OrderedDict
+from django.urls import reverse
+from funkwhale_api.providers.youtube.client import client
+
+from .data import youtube as api_data
+
+
+def test_can_get_search_results_from_youtube(mocker):
+ mocker.patch(
+ 'funkwhale_api.providers.youtube.client._do_search',
+ return_value=api_data.search['8 bit adventure'])
+ query = '8 bit adventure'
+ results = client.search(query)
+ assert results[0]['id']['videoId'] == '0HxZn6CzOIo'
+ assert results[0]['snippet']['title'] == 'AdhesiveWombat - 8 Bit Adventure'
+ assert results[0]['full_url'] == 'https://www.youtube.com/watch?v=0HxZn6CzOIo'
+
+
+def test_can_get_search_results_from_funkwhale(mocker, client, db):
+ mocker.patch(
+ 'funkwhale_api.providers.youtube.client._do_search',
+ return_value=api_data.search['8 bit adventure'])
+ query = '8 bit adventure'
+ url = reverse('api:v1:providers:youtube:search')
+ response = client.get(url, {'query': query})
+ # we should cast the youtube result to something more generic
+ expected = {
+ "id": "0HxZn6CzOIo",
+ "url": "https://www.youtube.com/watch?v=0HxZn6CzOIo",
+ "type": "youtube#video",
+ "description": "Make sure to apply adhesive evenly before use. GET IT HERE: http://adhesivewombat.bandcamp.com/album/marsupial-madness Facebook: ...",
+ "channelId": "UCps63j3krzAG4OyXeEyuhFw",
+ "title": "AdhesiveWombat - 8 Bit Adventure",
+ "channelTitle": "AdhesiveWombat",
+ "publishedAt": "2012-08-22T18:41:03.000Z",
+ "cover": "https://i.ytimg.com/vi/0HxZn6CzOIo/hqdefault.jpg"
+ }
+
+ assert json.loads(response.content.decode('utf-8'))[0] == expected
+
+
+def test_can_send_multiple_queries_at_once(mocker):
+ mocker.patch(
+ 'funkwhale_api.providers.youtube.client._do_search',
+ side_effect=[
+ api_data.search['8 bit adventure'],
+ api_data.search['system of a down toxicity'],
+ ]
+ )
+
+ queries = OrderedDict()
+ queries['1'] = {
+ 'q': '8 bit adventure',
+ }
+ queries['2'] = {
+ 'q': 'system of a down toxicity',
+ }
+
+ results = client.search_multiple(queries)
+
+ assert results['1'][0]['id']['videoId'] == '0HxZn6CzOIo'
+ assert results['1'][0]['snippet']['title'] == 'AdhesiveWombat - 8 Bit Adventure'
+ assert results['1'][0]['full_url'] == 'https://www.youtube.com/watch?v=0HxZn6CzOIo'
+ assert results['2'][0]['id']['videoId'] == 'BorYwGi2SJc'
+ assert results['2'][0]['snippet']['title'] == 'System of a Down: Toxicity'
+ assert results['2'][0]['full_url'] == 'https://www.youtube.com/watch?v=BorYwGi2SJc'
+
+
+def test_can_send_multiple_queries_at_once_from_funwkhale(mocker, db, client):
+ mocker.patch(
+ 'funkwhale_api.providers.youtube.client._do_search',
+ return_value=api_data.search['8 bit adventure'])
+ queries = OrderedDict()
+ queries['1'] = {
+ 'q': '8 bit adventure',
+ }
+
+ expected = {
+ "id": "0HxZn6CzOIo",
+ "url": "https://www.youtube.com/watch?v=0HxZn6CzOIo",
+ "type": "youtube#video",
+ "description": "Make sure to apply adhesive evenly before use. GET IT HERE: http://adhesivewombat.bandcamp.com/album/marsupial-madness Facebook: ...",
+ "channelId": "UCps63j3krzAG4OyXeEyuhFw",
+ "title": "AdhesiveWombat - 8 Bit Adventure",
+ "channelTitle": "AdhesiveWombat",
+ "publishedAt": "2012-08-22T18:41:03.000Z",
+ "cover": "https://i.ytimg.com/vi/0HxZn6CzOIo/hqdefault.jpg"
+ }
+
+ url = reverse('api:v1:providers:youtube:searchs')
+ response = client.post(
+ url, json.dumps(queries), content_type='application/json')
+
+ assert expected == json.loads(response.content.decode('utf-8'))['1'][0]
diff --git a/api/funkwhale_api/music/tests/__init__.py b/api/tests/users/__init__.py
similarity index 100%
rename from api/funkwhale_api/music/tests/__init__.py
rename to api/tests/users/__init__.py
diff --git a/api/tests/users/test_admin.py b/api/tests/users/test_admin.py
new file mode 100644
index 000000000..7645a0295
--- /dev/null
+++ b/api/tests/users/test_admin.py
@@ -0,0 +1,35 @@
+from funkwhale_api.users.admin import MyUserCreationForm
+
+
+def test_clean_username_success(db):
+ # Instantiate the form with a new username
+ form = MyUserCreationForm({
+ 'username': 'alamode',
+ 'password1': '123456',
+ 'password2': '123456',
+ })
+ # Run is_valid() to trigger the validation
+ valid = form.is_valid()
+ assert valid
+
+ # Run the actual clean_username method
+ username = form.clean_username()
+ assert 'alamode' == username
+
+
+def test_clean_username_false(factories):
+ user = factories['users.User']()
+ # Instantiate the form with the same username as self.user
+ form = MyUserCreationForm({
+ 'username': user.username,
+ 'password1': '123456',
+ 'password2': '123456',
+ })
+ # Run is_valid() to trigger the validation, which is going to fail
+ # because the username is already taken
+ valid = form.is_valid()
+ assert not valid
+
+ # The form.errors dict should contain a single error called 'username'
+ assert len(form.errors) == 1
+ assert 'username' in form.errors
diff --git a/api/tests/users/test_models.py b/api/tests/users/test_models.py
new file mode 100644
index 000000000..57793f494
--- /dev/null
+++ b/api/tests/users/test_models.py
@@ -0,0 +1,4 @@
+
+def test__str__(factories):
+ user = factories['users.User'](username='hello')
+ assert user.__str__() == 'hello'
diff --git a/api/tests/users/test_views.py b/api/tests/users/test_views.py
new file mode 100644
index 000000000..42be77b7c
--- /dev/null
+++ b/api/tests/users/test_views.py
@@ -0,0 +1,64 @@
+import json
+
+from django.test import RequestFactory
+from django.urls import reverse
+
+from funkwhale_api.users.models import User
+
+
+def test_can_create_user_via_api(settings, client, db):
+ url = reverse('rest_register')
+ data = {
+ 'username': 'test1',
+ 'email': 'test1@test.com',
+ 'password1': 'testtest',
+ 'password2': 'testtest',
+ }
+ settings.REGISTRATION_MODE = "public"
+ response = client.post(url, data)
+ assert response.status_code == 201
+
+ u = User.objects.get(email='test1@test.com')
+ assert u.username == 'test1'
+
+
+def test_can_disable_registration_view(settings, client, db):
+ url = reverse('rest_register')
+ data = {
+ 'username': 'test1',
+ 'email': 'test1@test.com',
+ 'password1': 'testtest',
+ 'password2': 'testtest',
+ }
+ settings.REGISTRATION_MODE = "disabled"
+ response = client.post(url, data)
+ assert response.status_code == 403
+
+
+def test_can_fetch_data_from_api(client, factories):
+ url = reverse('api:v1:users:users-me')
+ response = client.get(url)
+ # login required
+ assert response.status_code == 401
+
+ user = factories['users.User'](
+ is_staff=True,
+ perms=[
+ 'music.add_importbatch',
+ 'dynamic_preferences.change_globalpreferencemodel',
+ ]
+ )
+ assert user.has_perm('music.add_importbatch')
+ client.login(username=user.username, password='test')
+ response = client.get(url)
+ assert response.status_code == 200
+
+ payload = json.loads(response.content.decode('utf-8'))
+
+ assert payload['username'] == user.username
+ assert payload['is_staff'] == user.is_staff
+ assert payload['is_superuser'] == user.is_superuser
+ assert payload['email'] == user.email
+ assert payload['name'] == user.name
+ assert payload['permissions']['import.launch']['status']
+ assert payload['permissions']['settings.change']['status']