2017-12-24 18:15:21 +00:00
|
|
|
import json
|
2017-12-27 22:32:02 +00:00
|
|
|
import os
|
2017-12-24 18:15:21 +00:00
|
|
|
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
|
2018-02-27 16:43:50 +00:00
|
|
|
from funkwhale_api.music import tasks
|
2017-12-24 18:15:21 +00:00
|
|
|
|
|
|
|
|
2017-12-27 22:32:02 +00:00
|
|
|
DATA_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
2017-12-24 18:15:21 +00:00
|
|
|
|
2018-03-25 20:27:38 +00:00
|
|
|
def test_can_submit_youtube_url_for_track_import(
|
|
|
|
artists, albums, tracks, mocker, superuser_client):
|
2017-12-24 18:15:21 +00:00
|
|
|
mocker.patch(
|
|
|
|
'funkwhale_api.musicbrainz.api.artists.get',
|
2018-03-25 20:27:38 +00:00
|
|
|
return_value=artists['get']['adhesive_wombat'])
|
2017-12-24 18:15:21 +00:00
|
|
|
mocker.patch(
|
|
|
|
'funkwhale_api.musicbrainz.api.releases.get',
|
2018-03-25 20:27:38 +00:00
|
|
|
return_value=albums['get']['marsupial'])
|
2017-12-24 18:15:21 +00:00
|
|
|
mocker.patch(
|
|
|
|
'funkwhale_api.musicbrainz.api.recordings.get',
|
2018-03-25 20:27:38 +00:00
|
|
|
return_value=tracks['get']['8bitadventures'])
|
2017-12-24 18:15:21 +00:00
|
|
|
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'
|
|
|
|
|
|
|
|
|
2017-12-26 20:12:37 +00:00
|
|
|
def test_import_creates_an_import_with_correct_data(mocker, superuser_client):
|
|
|
|
mocker.patch('funkwhale_api.music.tasks.import_job_run')
|
2017-12-24 18:15:21 +00:00
|
|
|
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})
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
2018-03-25 20:27:38 +00:00
|
|
|
def test_can_import_whole_album(
|
|
|
|
artists, albums, mocker, superuser_client):
|
2017-12-26 20:12:37 +00:00
|
|
|
mocker.patch('funkwhale_api.music.tasks.import_job_run')
|
2017-12-24 18:15:21 +00:00
|
|
|
mocker.patch(
|
|
|
|
'funkwhale_api.musicbrainz.api.artists.get',
|
2018-03-25 20:27:38 +00:00
|
|
|
return_value=artists['get']['soad'])
|
2017-12-24 18:15:21 +00:00
|
|
|
mocker.patch(
|
|
|
|
'funkwhale_api.musicbrainz.api.images.get_front',
|
|
|
|
return_value=b'')
|
|
|
|
mocker.patch(
|
|
|
|
'funkwhale_api.musicbrainz.api.releases.get',
|
2018-03-25 20:27:38 +00:00
|
|
|
return_value=albums['get_with_includes']['hypnotize'])
|
2017-12-24 18:15:21 +00:00
|
|
|
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')
|
|
|
|
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'
|
2018-03-25 20:27:38 +00:00
|
|
|
medium_data = albums['get_with_includes']['hypnotize']['release']['medium-list'][0]
|
2017-12-24 18:15:21 +00:00
|
|
|
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']
|
|
|
|
|
|
|
|
|
2018-03-25 20:27:38 +00:00
|
|
|
def test_can_import_whole_artist(
|
|
|
|
artists, albums, mocker, superuser_client):
|
2017-12-26 20:12:37 +00:00
|
|
|
mocker.patch('funkwhale_api.music.tasks.import_job_run')
|
2017-12-24 18:15:21 +00:00
|
|
|
mocker.patch(
|
|
|
|
'funkwhale_api.musicbrainz.api.artists.get',
|
2018-03-25 20:27:38 +00:00
|
|
|
return_value=artists['get']['soad'])
|
2017-12-24 18:15:21 +00:00
|
|
|
mocker.patch(
|
|
|
|
'funkwhale_api.musicbrainz.api.images.get_front',
|
|
|
|
return_value=b'')
|
|
|
|
mocker.patch(
|
|
|
|
'funkwhale_api.musicbrainz.api.releases.get',
|
2018-03-25 20:27:38 +00:00
|
|
|
return_value=albums['get_with_includes']['hypnotize'])
|
2017-12-24 18:15:21 +00:00
|
|
|
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')
|
|
|
|
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'
|
2018-03-25 20:27:38 +00:00
|
|
|
medium_data = albums['get_with_includes']['hypnotize']['release']['medium-list'][0]
|
2017-12-24 18:15:21 +00:00
|
|
|
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']
|
|
|
|
|
|
|
|
|
2018-03-25 20:27:38 +00:00
|
|
|
def test_user_can_query_api_for_his_own_batches(
|
|
|
|
superuser_api_client, factories):
|
|
|
|
factories['music.ImportJob']()
|
|
|
|
job = factories['music.ImportJob'](
|
|
|
|
batch__submitted_by=superuser_api_client.user)
|
2017-12-24 18:15:21 +00:00
|
|
|
url = reverse('api:v1:import-batches-list')
|
|
|
|
|
2018-03-25 20:27:38 +00:00
|
|
|
response = superuser_api_client.get(url)
|
|
|
|
results = response.data
|
2017-12-24 18:15:21 +00:00
|
|
|
assert results['count'] == 1
|
|
|
|
assert results['results'][0]['jobs'][0]['mbid'] == job.mbid
|
|
|
|
|
|
|
|
|
2018-03-25 20:27:38 +00:00
|
|
|
def test_user_cannnot_access_other_batches(
|
|
|
|
superuser_api_client, factories):
|
|
|
|
factories['music.ImportJob']()
|
|
|
|
job = factories['music.ImportJob']()
|
2017-12-27 22:32:02 +00:00
|
|
|
url = reverse('api:v1:import-batches-list')
|
2018-03-25 20:27:38 +00:00
|
|
|
|
|
|
|
response = superuser_api_client.get(url)
|
|
|
|
results = response.data
|
|
|
|
assert results['count'] == 0
|
|
|
|
|
|
|
|
|
|
|
|
def test_user_can_create_an_empty_batch(superuser_api_client, factories):
|
|
|
|
url = reverse('api:v1:import-batches-list')
|
|
|
|
response = superuser_api_client.post(url)
|
2017-12-27 22:32:02 +00:00
|
|
|
|
|
|
|
assert response.status_code == 201
|
|
|
|
|
2018-03-25 20:27:38 +00:00
|
|
|
batch = superuser_api_client.user.imports.latest('id')
|
2017-12-27 22:32:02 +00:00
|
|
|
|
2018-03-25 20:27:38 +00:00
|
|
|
assert batch.submitted_by == superuser_api_client.user
|
2017-12-27 22:32:02 +00:00
|
|
|
assert batch.source == 'api'
|
|
|
|
|
|
|
|
|
2018-03-25 20:27:38 +00:00
|
|
|
def test_user_can_create_import_job_with_file(
|
|
|
|
superuser_api_client, factories, mocker):
|
2017-12-27 22:32:02 +00:00
|
|
|
path = os.path.join(DATA_DIR, 'test.ogg')
|
2018-02-27 16:43:50 +00:00
|
|
|
m = mocker.patch('funkwhale_api.common.utils.on_commit')
|
2018-03-25 20:27:38 +00:00
|
|
|
batch = factories['music.ImportBatch'](
|
|
|
|
submitted_by=superuser_api_client.user)
|
2017-12-27 22:32:02 +00:00
|
|
|
url = reverse('api:v1:import-jobs-list')
|
|
|
|
with open(path, 'rb') as f:
|
|
|
|
content = f.read()
|
|
|
|
f.seek(0)
|
2018-03-25 20:27:38 +00:00
|
|
|
response = superuser_api_client.post(url, {
|
2017-12-27 22:32:02 +00:00
|
|
|
'batch': batch.pk,
|
|
|
|
'audio_file': f,
|
|
|
|
'source': 'file://'
|
2018-03-25 20:27:38 +00:00
|
|
|
})
|
2017-12-27 22:32:02 +00:00
|
|
|
|
|
|
|
assert response.status_code == 201
|
|
|
|
|
|
|
|
job = batch.jobs.latest('id')
|
|
|
|
|
|
|
|
assert job.status == 'pending'
|
|
|
|
assert job.source.startswith('file://')
|
|
|
|
assert 'test.ogg' in job.source
|
|
|
|
assert job.audio_file.read() == content
|
|
|
|
|
2018-02-27 16:43:50 +00:00
|
|
|
m.assert_called_once_with(
|
|
|
|
tasks.import_job_run.delay,
|
|
|
|
import_job_id=job.pk)
|
2017-12-27 22:32:02 +00:00
|
|
|
|
|
|
|
|
2018-03-25 20:27:38 +00:00
|
|
|
def test_can_search_artist(factories, logged_in_client):
|
2017-12-24 18:15:21 +00:00
|
|
|
artist1 = factories['music.Artist']()
|
|
|
|
artist2 = factories['music.Artist']()
|
|
|
|
expected = [serializers.ArtistSerializerNested(artist1).data]
|
|
|
|
url = reverse('api:v1:artists-search')
|
2018-03-25 20:27:38 +00:00
|
|
|
response = logged_in_client.get(url, {'query': artist1.name})
|
|
|
|
assert response.data == expected
|
2017-12-24 18:15:21 +00:00
|
|
|
|
|
|
|
|
2018-03-25 20:27:38 +00:00
|
|
|
def test_can_search_artist_by_name_start(factories, logged_in_client):
|
2017-12-24 18:15:21 +00:00
|
|
|
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')
|
2018-03-25 20:27:38 +00:00
|
|
|
response = logged_in_client.get(url, {'name__startswith': 'a'})
|
2017-12-24 18:15:21 +00:00
|
|
|
|
2018-03-25 20:27:38 +00:00
|
|
|
assert expected == response.data
|
2017-12-24 18:15:21 +00:00
|
|
|
|
|
|
|
|
2018-03-25 20:27:38 +00:00
|
|
|
def test_can_search_tracks(factories, logged_in_client):
|
2017-12-24 18:15:21 +00:00
|
|
|
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')
|
2018-03-25 20:27:38 +00:00
|
|
|
response = logged_in_client.get(url, {'query': query})
|
2017-12-24 18:15:21 +00:00
|
|
|
|
2018-03-25 20:27:38 +00:00
|
|
|
assert expected == response.data
|
2017-12-24 18:15:21 +00:00
|
|
|
|
|
|
|
|
|
|
|
@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'),
|
|
|
|
])
|
2018-03-25 20:27:38 +00:00
|
|
|
def test_can_restrict_api_views_to_authenticated_users(
|
|
|
|
db, route, method, settings, client):
|
2017-12-24 18:15:21 +00:00
|
|
|
url = reverse(route)
|
|
|
|
settings.API_AUTHENTICATION_REQUIRED = True
|
|
|
|
response = getattr(client, method)(url)
|
|
|
|
assert response.status_code == 401
|
|
|
|
|
|
|
|
|
2018-03-25 20:27:38 +00:00
|
|
|
def test_track_file_url_is_restricted_to_authenticated_users(
|
|
|
|
api_client, factories, settings):
|
2017-12-24 18:15:21 +00:00
|
|
|
settings.API_AUTHENTICATION_REQUIRED = True
|
|
|
|
f = factories['music.TrackFile']()
|
|
|
|
assert f.audio_file is not None
|
|
|
|
url = f.path
|
2018-03-25 20:27:38 +00:00
|
|
|
response = api_client.get(url)
|
2017-12-24 18:15:21 +00:00
|
|
|
assert response.status_code == 401
|
|
|
|
|
2018-03-25 20:27:38 +00:00
|
|
|
|
|
|
|
def test_track_file_url_is_accessible_to_authenticated_users(
|
|
|
|
logged_in_api_client, factories, settings):
|
|
|
|
settings.API_AUTHENTICATION_REQUIRED = True
|
|
|
|
f = factories['music.TrackFile']()
|
|
|
|
assert f.audio_file is not None
|
|
|
|
url = f.path
|
|
|
|
response = logged_in_api_client.get(url)
|
2017-12-24 18:15:21 +00:00
|
|
|
|
|
|
|
assert response.status_code == 200
|
|
|
|
assert response['X-Accel-Redirect'] == '/_protected{}'.format(f.audio_file.url)
|