Skip to content
Snippets Groups Projects
test_tasks.py 29 KiB
Newer Older
  • Learn to ignore specific revisions
  • Eliot Berriot's avatar
    Eliot Berriot committed
    import io
    
    import uuid
    
    from django.core.paginator import Paginator
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    from django.utils import timezone
    
    from funkwhale_api.federation import serializers as federation_serializers
    
    from funkwhale_api.federation import jsonld
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    from funkwhale_api.music import licenses, metadata, signals, tasks
    
    DATA_DIR = os.path.dirname(os.path.abspath(__file__))
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    # DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "uploads")
    
    
    
    def test_can_create_track_from_file_metadata_no_mbid(db, mocker):
    
        add_tags = mocker.patch("funkwhale_api.tags.models.add_tags")
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "title": "Test track",
    
            "artists": [{"name": "Test artist"}],
            "album": {"title": "Test album", "release_date": datetime.date(2012, 8, 15)},
            "position": 4,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "license": "Hello world: http://creativecommons.org/licenses/by-sa/4.0/",
            "copyright": "2018 Someone",
    
            "tags": ["Punk", "Rock"],
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        match_license = mocker.spy(licenses, "match")
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
        track = tasks.get_track_from_import_metadata(metadata)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        assert track.title == metadata["title"]
    
        assert track.mbid is None
        assert track.position == 4
    
        assert track.disc_number == 2
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        assert track.license.code == "cc-by-sa-4.0"
        assert track.copyright == metadata["copyright"]
    
        assert track.album.title == metadata["album"]["title"]
    
        assert track.album.mbid is None
        assert track.album.release_date == datetime.date(2012, 8, 15)
    
        assert track.artist.name == metadata["artists"][0]["name"]
    
        assert track.artist.mbid is None
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        assert track.artist.attributed_to is None
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        match_license.assert_called_once_with(metadata["license"], metadata["copyright"])
    
        add_tags.assert_called_once_with(track, *metadata["tags"])
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    def test_can_create_track_from_file_metadata_attributed_to(factories, mocker):
        actor = factories["federation.Actor"]()
        metadata = {
            "title": "Test track",
            "artists": [{"name": "Test artist"}],
            "album": {"title": "Test album", "release_date": datetime.date(2012, 8, 15)},
            "position": 4,
            "disc_number": 2,
            "copyright": "2018 Someone",
        }
    
        track = tasks.get_track_from_import_metadata(metadata, attributed_to=actor)
    
        assert track.title == metadata["title"]
        assert track.mbid is None
        assert track.position == 4
        assert track.disc_number == 2
        assert track.copyright == metadata["copyright"]
        assert track.attributed_to == actor
        assert track.album.title == metadata["album"]["title"]
        assert track.album.mbid is None
        assert track.album.release_date == datetime.date(2012, 8, 15)
        assert track.album.attributed_to == actor
        assert track.artist.name == metadata["artists"][0]["name"]
        assert track.artist.mbid is None
        assert track.artist.attributed_to == actor
    
    
    
    def test_can_create_track_from_file_metadata_featuring(factories):
        metadata = {
            "title": "Whole Lotta Love",
            "position": 1,
            "disc_number": 1,
            "mbid": "508704c0-81d4-4c94-ba58-3fc0b7da23eb",
            "album": {
                "title": "Guitar Heaven: The Greatest Guitar Classics of All Time",
                "mbid": "d06f2072-4148-488d-af6f-69ab6539ddb8",
                "release_date": datetime.date(2010, 9, 17),
                "artists": [
                    {"name": "Santana", "mbid": "9a3bf45c-347d-4630-894d-7cf3e8e0b632"}
                ],
            },
            "artists": [{"name": "Santana feat. Chris Cornell", "mbid": None}],
        }
        track = tasks.get_track_from_import_metadata(metadata)
    
        assert track.album.artist.name == "Santana"
        assert track.artist.name == "Santana feat. Chris Cornell"
    
    
    
    def test_can_create_track_from_file_metadata_mbid(factories, mocker):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        metadata = {
            "title": "Test track",
    
            "artists": [
                {"name": "Test artist", "mbid": "9c6bddde-6228-4d9f-ad0d-03f6fcb19e13"}
            ],
            "album": {
                "title": "Test album",
                "release_date": datetime.date(2012, 8, 15),
                "mbid": "9c6bddde-6478-4d9f-ad0d-03f6fcb19e15",
                "artists": [
                    {
                        "name": "Test album artist",
                        "mbid": "9c6bddde-6478-4d9f-ad0d-03f6fcb19e13",
                    }
                ],
            },
            "position": 4,
            "mbid": "f269d497-1cc0-4ae4-a0c4-157ec7d73fcb",
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "cover_data": {"content": b"image_content", "mimetype": "image/png"},
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        }
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        track = tasks.get_track_from_import_metadata(metadata)
    
        assert track.title == metadata["title"]
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        assert track.position == 4
    
        assert track.disc_number is None
    
        assert track.album.title == metadata["album"]["title"]
        assert track.album.mbid == metadata["album"]["mbid"]
        assert track.album.artist.mbid == metadata["album"]["artists"][0]["mbid"]
        assert track.album.artist.name == metadata["album"]["artists"][0]["name"]
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        assert track.album.release_date == datetime.date(2012, 8, 15)
    
        assert track.artist.name == metadata["artists"][0]["name"]
        assert track.artist.mbid == metadata["artists"][0]["mbid"]
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
    
    def test_can_create_track_from_file_metadata_mbid_existing_album_artist(
        factories, mocker
    ):
        artist = factories["music.Artist"]()
        album = factories["music.Album"]()
        metadata = {
    
            "album": {
                "mbid": album.mbid,
                "title": "",
                "artists": [{"name": "", "mbid": album.artist.mbid}],
            },
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "title": "Hello",
    
            "position": 4,
            "artists": [{"mbid": artist.mbid, "name": ""}],
            "mbid": "f269d497-1cc0-4ae4-a0c4-157ec7d73fcb",
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
        track = tasks.get_track_from_import_metadata(metadata)
    
        assert track.title == metadata["title"]
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        assert track.position == 4
        assert track.album == album
        assert track.artist == artist
    
    
    def test_can_create_track_from_file_metadata_fid_existing_album_artist(
        factories, mocker
    ):
        artist = factories["music.Artist"]()
        album = factories["music.Album"]()
    
            "artists": [{"name": "", "fid": artist.fid}],
            "album": {
                "title": "",
                "fid": album.fid,
                "artists": [{"name": "", "fid": album.artist.fid}],
            },
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "title": "Hello",
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "fid": "https://hello",
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        track = tasks.get_track_from_import_metadata(metadata)
    
        assert track.title == metadata["title"]
        assert track.fid == metadata["fid"]
    
        assert track.position == 4
        assert track.album == album
        assert track.artist == artist
    
    def test_can_create_track_from_file_metadata_distinct_release_mbid(factories):
        """Cf https://dev.funkwhale.audio/funkwhale/funkwhale/issues/772"""
        artist = factories["music.Artist"]()
        album = factories["music.Album"](artist=artist)
        track = factories["music.Track"](album=album, artist=artist)
        metadata = {
    
            "artists": [{"name": artist.name, "mbid": artist.mbid}],
            "album": {"title": album.title, "mbid": str(uuid.uuid4())},
    
            "fid": "https://hello",
        }
    
        new_track = tasks.get_track_from_import_metadata(metadata)
    
        # the returned track should be different from the existing one, and mapped
        # to a new album, because the albumid is different
        assert new_track.album != album
        assert new_track != track
    
    
    
    def test_can_create_track_from_file_metadata_distinct_position(factories):
        """Cf https://dev.funkwhale.audio/funkwhale/funkwhale/issues/740"""
        artist = factories["music.Artist"]()
        album = factories["music.Album"](artist=artist)
        track = factories["music.Track"](album=album, artist=artist)
        metadata = {
    
            "artists": [{"name": artist.name, "mbid": artist.mbid}],
            "album": {"title": album.title, "mbid": album.mbid},
    
        }
    
        new_track = tasks.get_track_from_import_metadata(metadata)
    
        assert new_track != track
    
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    def test_can_create_track_from_file_metadata_federation(factories, mocker, r_mock):
        metadata = {
    
            "artists": [
                {"name": "Artist", "fid": "https://artist.fid", "fdate": timezone.now()}
            ],
            "album": {
                "title": "Album",
                "fid": "https://album.fid",
                "fdate": timezone.now(),
                "artists": [
                    {
                        "name": "Album artist",
                        "fid": "https://album.artist.fid",
                        "fdate": timezone.now(),
                    }
                ],
            },
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "title": "Hello",
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "fid": "https://hello",
            "fdate": timezone.now(),
            "cover_data": {"url": "https://cover/hello.png", "mimetype": "image/png"},
        }
        r_mock.get(metadata["cover_data"]["url"], body=io.BytesIO(b"coucou"))
    
    
        track = tasks.get_track_from_import_metadata(metadata, update_cover=True)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
        assert track.title == metadata["title"]
        assert track.fid == metadata["fid"]
        assert track.creation_date == metadata["fdate"]
        assert track.position == 4
        assert track.album.cover.read() == b"coucou"
    
        assert track.album.cover_path.endswith(".png")
    
        assert track.album.fid == metadata["album"]["fid"]
        assert track.album.title == metadata["album"]["title"]
        assert track.album.creation_date == metadata["album"]["fdate"]
        assert track.album.artist.fid == metadata["album"]["artists"][0]["fid"]
        assert track.album.artist.name == metadata["album"]["artists"][0]["name"]
        assert track.album.artist.creation_date == metadata["album"]["artists"][0]["fdate"]
        assert track.artist.fid == metadata["artists"][0]["fid"]
        assert track.artist.name == metadata["artists"][0]["name"]
        assert track.artist.creation_date == metadata["artists"][0]["fdate"]
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
    
    def test_sort_candidates(factories):
        artist1 = factories["music.Artist"].build(fid=None, mbid=None)
        artist2 = factories["music.Artist"].build(fid=None)
        artist3 = factories["music.Artist"].build(mbid=None)
        result = tasks.sort_candidates([artist1, artist2, artist3], ["mbid", "fid"])
    
        assert result == [artist2, artist3, artist1]
    
    
    def test_upload_import(now, factories, temp_signal, mocker):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        outbox = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
    
        update_album_cover = mocker.patch("funkwhale_api.music.tasks.update_album_cover")
        get_picture = mocker.patch("funkwhale_api.music.metadata.Metadata.get_picture")
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        get_track_from_import_metadata = mocker.spy(tasks, "get_track_from_import_metadata")
    
        track = factories["music.Track"](album__cover="")
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        upload = factories["music.Upload"](
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            track=None, import_metadata={"funkwhale": {"track": {"uuid": str(track.uuid)}}}
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        with temp_signal(signals.upload_import_status_updated) as handler:
    
            tasks.process_upload(upload_id=upload.pk)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        upload.refresh_from_db()
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        assert upload.track == track
        assert upload.import_status == "finished"
        assert upload.import_date == now
    
        get_picture.assert_called_once_with("cover_front", "other")
        update_album_cover.assert_called_once_with(
            upload.track.album, cover_data=get_picture.return_value, source=upload.source
        )
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        assert (
            get_track_from_import_metadata.call_args[-1]["attributed_to"]
            == upload.library.actor
        )
    
        handler.assert_called_once_with(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            upload=upload,
    
            old_status="pending",
            new_status="finished",
            sender=None,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            signal=signals.upload_import_status_updated,
        )
        outbox.assert_called_once_with(
            {"type": "Create", "object": {"type": "Audio"}}, context={"upload": upload}
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    def test_upload_import_get_audio_data(factories, mocker):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "funkwhale_api.music.models.Upload.get_audio_data",
    
            return_value={"size": 23, "duration": 42, "bitrate": 66},
        )
        track = factories["music.Track"]()
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        upload = factories["music.Upload"](
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            track=None, import_metadata={"funkwhale": {"track": {"uuid": track.uuid}}}
        )
    
        tasks.process_upload(upload_id=upload.pk)
    
        upload.refresh_from_db()
        assert upload.size == 23
        assert upload.duration == 42
        assert upload.bitrate == 66
    
    
    def test_upload_import_in_place(factories, mocker):
        mocker.patch(
            "funkwhale_api.music.models.Upload.get_audio_data",
            return_value={"size": 23, "duration": 42, "bitrate": 66},
        )
        track = factories["music.Track"]()
        path = os.path.join(DATA_DIR, "test.ogg")
        upload = factories["music.Upload"](
            track=None,
            audio_file=None,
            source="file://{}".format(path),
            import_metadata={"funkwhale": {"track": {"uuid": track.uuid}}},
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        )
    
        tasks.process_upload(upload_id=upload.pk)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        upload.refresh_from_db()
        assert upload.size == 23
        assert upload.duration == 42
        assert upload.bitrate == 66
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        assert upload.mimetype == "audio/ogg"
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    def test_upload_import_skip_existing_track_in_own_library(factories, temp_signal):
    
        track = factories["music.Track"]()
        library = factories["music.Library"]()
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        existing = factories["music.Upload"](
    
            track=track,
            import_status="finished",
            library=library,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            import_metadata={"funkwhale": {"track": {"uuid": track.mbid}}},
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        duplicate = factories["music.Upload"](
    
            track=track,
            import_status="pending",
            library=library,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            import_metadata={"funkwhale": {"track": {"uuid": track.uuid}}},
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        with temp_signal(signals.upload_import_status_updated) as handler:
    
            tasks.process_upload(upload_id=duplicate.pk)
    
        duplicate.refresh_from_db()
    
        assert duplicate.import_status == "skipped"
        assert duplicate.import_details == {
            "code": "already_imported_in_owned_libraries",
            "duplicates": [str(existing.uuid)],
        }
    
        handler.assert_called_once_with(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            upload=duplicate,
    
            old_status="pending",
            new_status="skipped",
            sender=None,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            signal=signals.upload_import_status_updated,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    def test_upload_import_track_uuid(now, factories):
    
        track = factories["music.Track"]()
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        upload = factories["music.Upload"](
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            track=None, import_metadata={"funkwhale": {"track": {"uuid": track.uuid}}}
    
        tasks.process_upload(upload_id=upload.pk)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        upload.refresh_from_db()
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        assert upload.track == track
        assert upload.import_status == "finished"
        assert upload.import_date == now
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    def test_upload_import_skip_federation(now, factories, mocker):
        outbox = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
        track = factories["music.Track"]()
        upload = factories["music.Upload"](
            track=None,
            import_metadata={
                "funkwhale": {
                    "track": {"uuid": track.uuid},
                    "config": {"dispatch_outbox": False},
                }
            },
        )
    
        tasks.process_upload(upload_id=upload.pk)
    
        outbox.assert_not_called()
    
    
    def test_upload_import_skip_broadcast(now, factories, mocker):
        group_send = mocker.patch("funkwhale_api.common.channels.group_send")
        track = factories["music.Track"]()
        upload = factories["music.Upload"](
            library__actor__local=True,
            track=None,
            import_metadata={
                "funkwhale": {"track": {"uuid": track.uuid}, "config": {"broadcast": False}}
            },
        )
    
        tasks.process_upload(upload_id=upload.pk)
    
        group_send.assert_not_called()
    
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    def test_upload_import_error(factories, now, temp_signal):
        upload = factories["music.Upload"](
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            import_metadata={"funkwhale": {"track": {"uuid": uuid.uuid4()}}}
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        )
        with temp_signal(signals.upload_import_status_updated) as handler:
    
            tasks.process_upload(upload_id=upload.pk)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        upload.refresh_from_db()
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        assert upload.import_status == "errored"
        assert upload.import_date == now
    
        assert upload.import_details == {
            "error_code": "track_uuid_not_found",
            "detail": None,
        }
        handler.assert_called_once_with(
            upload=upload,
            old_status="pending",
            new_status="errored",
            sender=None,
            signal=signals.upload_import_status_updated,
        )
    
    
    def test_upload_import_error_metadata(factories, now, temp_signal, mocker):
        path = os.path.join(DATA_DIR, "test.ogg")
        upload = factories["music.Upload"](audio_file__frompath=path)
        mocker.patch.object(
            metadata.AlbumField,
            "to_internal_value",
            side_effect=metadata.serializers.ValidationError("Hello"),
        )
        with temp_signal(signals.upload_import_status_updated) as handler:
            tasks.process_upload(upload_id=upload.pk)
        upload.refresh_from_db()
    
        assert upload.import_status == "errored"
        assert upload.import_date == now
        assert upload.import_details == {
            "error_code": "invalid_metadata",
            "detail": {"album": ["Hello"]},
            "file_metadata": metadata.Metadata(path).all(),
        }
    
        handler.assert_called_once_with(
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            upload=upload,
    
            old_status="pending",
            new_status="errored",
            sender=None,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            signal=signals.upload_import_status_updated,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    def test_upload_import_updates_cover_if_no_cover(factories, mocker, now):
    
        mocked_update = mocker.patch("funkwhale_api.music.tasks.update_album_cover")
        album = factories["music.Album"](cover="")
        track = factories["music.Track"](album=album)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        upload = factories["music.Upload"](
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            track=None, import_metadata={"funkwhale": {"track": {"uuid": track.uuid}}}
    
        tasks.process_upload(upload_id=upload.pk)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        mocked_update.assert_called_once_with(album, source=None, cover_data=None)
    
    
    
    def test_update_album_cover_mbid(factories, mocker):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        album = factories["music.Album"](cover="")
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image")
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        tasks.update_album_cover(album=album)
    
    
        mocked_get.assert_called_once_with()
    
    
    def test_update_album_cover_file_data(factories, mocker):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        album = factories["music.Album"](cover="", mbid=None)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image")
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        tasks.update_album_cover(album=album, cover_data={"hello": "world"})
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        mocked_get.assert_called_once_with(data={"hello": "world"})
    
    
    @pytest.mark.parametrize("ext,mimetype", [("jpg", "image/jpeg"), ("png", "image/png")])
    def test_update_album_cover_file_cover_separate_file(ext, mimetype, factories, mocker):
        mocker.patch("funkwhale_api.music.tasks.IMAGE_TYPES", [(ext, mimetype)])
        image_path = os.path.join(DATA_DIR, "cover.{}".format(ext))
        with open(image_path, "rb") as f:
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        album = factories["music.Album"](cover="", mbid=None)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image")
        mocker.patch("funkwhale_api.music.metadata.Metadata.get_picture", return_value=None)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        tasks.update_album_cover(album=album, source="file://" + image_path)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            data={"mimetype": mimetype, "content": image_content}
        )
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    def test_federation_audio_track_to_metadata(now, mocker):
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        published = now
        released = now.date()
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        references = {
            "http://track.attributed": mocker.Mock(),
            "http://album.attributed": mocker.Mock(),
            "http://album-artist.attributed": mocker.Mock(),
            "http://artist.attributed": mocker.Mock(),
        }
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        payload = {
    
            "@context": jsonld.get_default_context(),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "type": "Track",
            "id": "http://hello.track",
            "musicbrainzId": str(uuid.uuid4()),
            "name": "Black in back",
            "position": 5,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "published": published.isoformat(),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "license": "http://creativecommons.org/licenses/by-sa/4.0/",
            "copyright": "2018 Someone",
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "attributedTo": "http://track.attributed",
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "album": {
                "published": published.isoformat(),
                "type": "Album",
                "id": "http://hello.album",
                "name": "Purple album",
                "musicbrainzId": str(uuid.uuid4()),
                "released": released.isoformat(),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "attributedTo": "http://album.attributed",
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "artists": [
                    {
                        "type": "Artist",
                        "published": published.isoformat(),
                        "id": "http://hello.artist",
                        "name": "John Smith",
                        "musicbrainzId": str(uuid.uuid4()),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                        "attributedTo": "http://album-artist.attributed",
    
                "cover": {
                    "type": "Link",
                    "href": "http://cover.test",
                    "mediaType": "image/png",
                },
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            },
            "artists": [
                {
                    "published": published.isoformat(),
                    "type": "Artist",
                    "id": "http://hello.trackartist",
                    "name": "Bob Smith",
                    "musicbrainzId": str(uuid.uuid4()),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                    "attributedTo": "http://artist.attributed",
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                }
            ],
        }
        serializer = federation_serializers.TrackSerializer(data=payload)
        serializer.is_valid(raise_exception=True)
        expected = {
            "title": payload["name"],
    
            "disc_number": payload["disc"],
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "license": "http://creativecommons.org/licenses/by-sa/4.0/",
            "copyright": "2018 Someone",
    
            "mbid": payload["musicbrainzId"],
            "fdate": serializer.validated_data["published"],
            "fid": payload["id"],
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "attributed_to": references["http://track.attributed"],
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                "attributed_to": references["http://album.attributed"],
    
                "release_date": released,
                "mbid": payload["album"]["musicbrainzId"],
                "fid": payload["album"]["id"],
                "fdate": serializer.validated_data["album"]["published"],
                "artists": [
                    {
                        "name": a["name"],
                        "mbid": a["musicbrainzId"],
                        "fid": a["id"],
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                        "attributed_to": references["http://album-artist.attributed"],
    
                        "fdate": serializer.validated_data["album"]["artists"][i][
                            "published"
                        ],
                    }
                    for i, a in enumerate(payload["album"]["artists"])
                ],
            },
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            # musicbrainz
            # federation
    
            "artists": [
                {
                    "name": a["name"],
                    "mbid": a["musicbrainzId"],
                    "fid": a["id"],
                    "fdate": serializer.validated_data["artists"][i]["published"],
    
    Eliot Berriot's avatar
    Eliot Berriot committed
                    "attributed_to": references["http://artist.attributed"],
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            ],
    
            "cover_data": {
                "mimetype": serializer.validated_data["album"]["cover"]["mediaType"],
                "url": serializer.validated_data["album"]["cover"]["href"],
            },
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        result = tasks.federation_audio_track_to_metadata(
            serializer.validated_data, references
        )
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        assert result == expected
    
    
    
    def test_scan_library_fetches_page_and_calls_scan_page(now, mocker, factories, r_mock):
        scan = factories["music.LibraryScan"]()
        collection_conf = {
            "actor": scan.library.actor,
            "id": scan.library.fid,
            "page_size": 10,
            "items": range(10),
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "type": "Library",
            "name": "hello",
    
        }
        collection = federation_serializers.PaginatedCollectionSerializer(collection_conf)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        data = collection.data
        data["followers"] = "https://followers.domain"
    
    
        scan_page = mocker.patch("funkwhale_api.music.tasks.scan_library_page.delay")
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        r_mock.get(collection_conf["id"], json=data)
    
        tasks.start_library_scan(library_scan_id=scan.pk)
    
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        scan_page.assert_called_once_with(library_scan_id=scan.pk, page_url=data["first"])
    
        scan.refresh_from_db()
    
        assert scan.status == "scanning"
        assert scan.total_files == len(collection_conf["items"])
        assert scan.modification_date == now
    
    
    def test_scan_page_fetches_page_and_creates_tracks(now, mocker, factories, r_mock):
        scan_page = mocker.patch("funkwhale_api.music.tasks.scan_library_page.delay")
        scan = factories["music.LibraryScan"](status="scanning", total_files=5)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        uploads = [
            factories["music.Upload"].build(
                fid="https://track.test/{}".format(i),
                size=42,
                bitrate=66,
                duration=99,
                library=scan.library,
            )
            for i in range(5)
        ]
    
    
        page_conf = {
            "actor": scan.library.actor,
            "id": scan.library.fid,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "page": Paginator(uploads, 3).page(1),
            "item_serializer": federation_serializers.UploadSerializer,
    
        }
        page = federation_serializers.CollectionPageSerializer(page_conf)
        r_mock.get(page.data["id"], json=page.data)
    
        tasks.scan_library_page(library_scan_id=scan.pk, page_url=page.data["id"])
    
        scan.refresh_from_db()
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        lts = list(scan.library.uploads.all().order_by("-creation_date"))
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        for upload in uploads[:3]:
            scan.library.uploads.get(fid=upload.fid)
    
    
        assert scan.status == "scanning"
        assert scan.processed_files == 3
        assert scan.modification_date == now
    
        scan_page.assert_called_once_with(
            library_scan_id=scan.pk, page_url=page.data["next"]
        )
    
    
    def test_scan_page_trigger_next_page_scan_skip_if_same(mocker, factories, r_mock):
        patched_scan = mocker.patch("funkwhale_api.music.tasks.scan_library_page.delay")
        scan = factories["music.LibraryScan"](status="scanning", total_files=5)
    
    Eliot Berriot's avatar
    Eliot Berriot committed
        uploads = factories["music.Upload"].build_batch(size=5, library=scan.library)
    
        page_conf = {
            "actor": scan.library.actor,
            "id": scan.library.fid,
    
    Eliot Berriot's avatar
    Eliot Berriot committed
            "page": Paginator(uploads, 3).page(1),
            "item_serializer": federation_serializers.UploadSerializer,
    
        }
        page = federation_serializers.CollectionPageSerializer(page_conf)
        data = page.data
        data["next"] = data["id"]
        r_mock.get(page.data["id"], json=data)
    
        tasks.scan_library_page(library_scan_id=scan.pk, page_url=data["id"])
        patched_scan.assert_not_called()
        scan.refresh_from_db()
    
        assert scan.status == "finished"
    
    
    
    def test_clean_transcoding_cache(preferences, now, factories):
    
        preferences["music__transcoding_cache_duration"] = 60
        u1 = factories["music.UploadVersion"](
    
            accessed_date=now - datetime.timedelta(minutes=61)
        )
    
        u2 = factories["music.UploadVersion"](
    
            accessed_date=now - datetime.timedelta(minutes=59)
        )
    
        tasks.clean_transcoding_cache()
    
        u2.refresh_from_db()
    
        with pytest.raises(u1.__class__.DoesNotExist):
    
    
    
    def test_get_prunable_tracks(factories):
        prunable_track = factories["music.Track"]()
        # non prunable tracks
        factories["music.Upload"]()
        factories["favorites.TrackFavorite"]()
        factories["history.Listening"]()
        factories["playlists.PlaylistTrack"]()
    
        assert list(tasks.get_prunable_tracks()) == [prunable_track]
    
    
    def test_get_prunable_tracks_include_favorites(factories):
        prunable_track = factories["music.Track"]()
        favorited = factories["favorites.TrackFavorite"]().track
        # non prunable tracks
        factories["favorites.TrackFavorite"](track__playable=True)
        factories["music.Upload"]()
        factories["history.Listening"]()
        factories["playlists.PlaylistTrack"]()
    
        qs = tasks.get_prunable_tracks(exclude_favorites=False).order_by("id")
        assert list(qs) == [prunable_track, favorited]
    
    
    def test_get_prunable_tracks_include_playlists(factories):
        prunable_track = factories["music.Track"]()
        in_playlist = factories["playlists.PlaylistTrack"]().track
        # non prunable tracks
        factories["favorites.TrackFavorite"]()
        factories["music.Upload"]()
        factories["history.Listening"]()
        factories["playlists.PlaylistTrack"](track__playable=True)
    
        qs = tasks.get_prunable_tracks(exclude_playlists=False).order_by("id")
        assert list(qs) == [prunable_track, in_playlist]
    
    
    def test_get_prunable_tracks_include_listenings(factories):
        prunable_track = factories["music.Track"]()
        listened = factories["history.Listening"]().track
        # non prunable tracks
        factories["favorites.TrackFavorite"]()
        factories["music.Upload"]()
        factories["history.Listening"](track__playable=True)
        factories["playlists.PlaylistTrack"]()
    
        qs = tasks.get_prunable_tracks(exclude_listenings=False).order_by("id")
        assert list(qs) == [prunable_track, listened]
    
    
    def test_get_prunable_albums(factories):
        prunable_album = factories["music.Album"]()
        # non prunable album
        factories["music.Track"]().album
    
        assert list(tasks.get_prunable_albums()) == [prunable_album]
    
    
    def test_get_prunable_artists(factories):
        prunable_artist = factories["music.Artist"]()
        # non prunable artist
        non_prunable_artist = factories["music.Artist"]()
        non_prunable_album_artist = factories["music.Artist"]()
        factories["music.Track"](artist=non_prunable_artist)
        factories["music.Track"](album__artist=non_prunable_album_artist)
    
        assert list(tasks.get_prunable_artists()) == [prunable_artist]
    
    Eliot Berriot's avatar
    Eliot Berriot committed
    
    
    def test_update_library_entity(factories, mocker):
        artist = factories["music.Artist"]()
        save = mocker.spy(artist, "save")
    
        tasks.update_library_entity(artist, {"name": "Hello"})
        save.assert_called_once_with(update_fields=["name"])
    
        artist.refresh_from_db()
        assert artist.name == "Hello"