Create a testing environment in production for ListenBrainz recommendation engine (troi-recommendation-playground)

This commit is contained in:
petitminion 2023-09-12 16:09:34 +00:00
commit f821dcbbc2
19 changed files with 1124 additions and 11 deletions

View file

@ -0,0 +1,116 @@
import pytest
import troi.core
from django.core.cache import cache
from django.db.models import Q
from requests.exceptions import ConnectTimeout
from funkwhale_api.music.models import Track
from funkwhale_api.radios import lb_recommendations
from funkwhale_api.typesense import factories as custom_factories
from funkwhale_api.typesense import utils
def test_can_build_radio_queryset_with_fw_db(factories, mocker):
factories["music.Track"](
title="I Want It That Way", mbid="87dfa566-21c3-45ed-bc42-1d345b8563fa"
)
factories["music.Track"](
title="The Perfect Kiss", mbid="ec0da94e-fbfe-4eb0-968e-024d4c32d1d0"
)
factories["music.Track"]()
qs = Track.objects.all()
mocker.patch("funkwhale_api.typesense.utils.resolve_recordings_to_fw_track")
radio_qs = lb_recommendations.build_radio_queryset(
custom_factories.DummyPatch(), {"min_recordings": 1}, qs
)
recommended_recording_mbids = [
"87dfa566-21c3-45ed-bc42-1d345b8563fa",
"ec0da94e-fbfe-4eb0-968e-024d4c32d1d0",
]
assert list(
Track.objects.all().filter(Q(mbid__in=recommended_recording_mbids))
) == list(radio_qs)
def test_build_radio_queryset_without_fw_db(mocker):
resolve_recordings_to_fw_track = mocker.patch.object(
utils, "resolve_recordings_to_fw_track", return_value=None
)
# mocker.patch.object(cache, "get_many", return_value=None)
qs = Track.objects.all()
with pytest.raises(ValueError):
lb_recommendations.build_radio_queryset(
custom_factories.DummyPatch(), {"min_recordings": 1}, qs
)
assert resolve_recordings_to_fw_track.called_once_with(
custom_factories.recommended_recording_mbids
)
def test_build_radio_queryset_with_redis_and_fw_db(factories, mocker):
factories["music.Track"](
pk="1", title="I Want It That Way", mbid="87dfa566-21c3-45ed-bc42-1d345b8563fa"
)
mocker.patch.object(utils, "resolve_recordings_to_fw_track", return_value=None)
redis_cache = {}
redis_cache["ec0da94e-fbfe-4eb0-968e-024d4c32d1d0"] = 2
mocker.patch.object(cache, "get_many", return_value=redis_cache)
qs = Track.objects.all()
assert list(
lb_recommendations.build_radio_queryset(
custom_factories.DummyPatch(), {"min_recordings": 1}, qs
)
) == list(Track.objects.all().filter(pk__in=[1, 2]))
def test_build_radio_queryset_with_redis_and_without_fw_db(factories, mocker):
factories["music.Track"](
pk="1", title="Super title", mbid="87dfaaaa-2aaa-45ed-bc42-1d34aaaaaaaa"
)
mocker.patch.object(utils, "resolve_recordings_to_fw_track", return_value=None)
redis_cache = {}
redis_cache["87dfa566-21c3-45ed-bc42-1d345b8563fa"] = 1
mocker.patch.object(cache, "get_many", return_value=redis_cache)
qs = Track.objects.all()
assert list(
lb_recommendations.build_radio_queryset(
custom_factories.DummyPatch(), {"min_recordings": 1}, qs
)
) == list(Track.objects.all().filter(pk=1))
def test_build_radio_queryset_catch_troi_ConnectTimeout(mocker):
mocker.patch.object(
troi.core,
"generate_playlist",
side_effect=ConnectTimeout,
)
qs = Track.objects.all()
with pytest.raises(ValueError):
lb_recommendations.build_radio_queryset(
custom_factories.DummyPatch(), {"min_recordings": 1}, qs
)
def test_build_radio_queryset_catch_troi_no_candidates(mocker):
mocker.patch.object(
troi.core,
"generate_playlist",
)
qs = Track.objects.all()
with pytest.raises(ValueError):
lb_recommendations.build_radio_queryset(
custom_factories.DummyPatch(), {"min_recordings": 1}, qs
)

View file

@ -429,3 +429,28 @@ def test_can_start_custom_multiple_radio_from_api(api_client, factories):
format="json",
)
assert response.status_code == 201
def test_can_start_periodic_jams_troi_radio_from_api(api_client, factories):
factories["music.Track"].create_batch(5)
url = reverse("api:v1:radios:sessions-list")
config = {"patch": "periodic-jams", "type": "daily-jams"}
response = api_client.post(
url,
{"radio_type": "troi", "config": config},
format="json",
)
assert response.status_code == 201
# to do : send error to api ?
def test_can_catch_troi_radio_error(api_client, factories):
factories["music.Track"].create_batch(5)
url = reverse("api:v1:radios:sessions-list")
config = {"patch": "periodic-jams", "type": "not_existing_type"}
response = api_client.post(
url,
{"radio_type": "troi", "config": config},
format="json",
)
assert response.status_code == 201

View file

@ -0,0 +1,58 @@
import logging
import requests_mock
import typesense
from funkwhale_api.typesense import tasks
def test_add_tracks_to_index_fails(mocker, caplog):
logger = logging.getLogger("funkwhale_api.typesense.tasks")
caplog.set_level(logging.INFO)
logger.addHandler(caplog.handler)
client = typesense.Client(
{
"api_key": "api_key",
"nodes": [{"host": "host", "port": "port", "protocol": "protocol"}],
"connection_timeout_seconds": 2,
}
)
with requests_mock.Mocker() as r_mocker:
r_mocker.post(
"protocol://host:port/collections/canonical_fw_data/documents/import",
json=[{"name": "data"}],
)
mocker.patch.object(typesense, "Client", return_value=client)
mocker.patch.object(
typesense.client.ApiCall,
"post",
side_effect=typesense.exceptions.TypesenseClientError("Hello"),
)
tasks.add_tracks_to_index([1, 2, 3])
assert "Can't build index" in caplog.text
def test_build_canonical_index_success(mocker, caplog, factories):
logger = logging.getLogger("funkwhale_api.typesense.tasks")
caplog.set_level(logging.INFO)
logger.addHandler(caplog.handler)
client = typesense.Client(
{
"api_key": "api_key",
"nodes": [{"host": "host", "port": "port", "protocol": "protocol"}],
"connection_timeout_seconds": 2,
}
)
factories["music.Track"].create_batch(size=5)
with requests_mock.Mocker() as r_mocker:
mocker.patch.object(typesense, "Client", return_value=client)
r_mocker.post("protocol://host:port/collections", json={"name": "data"})
tasks.build_canonical_index()
assert "Launching async task to add " in caplog.text

View file

@ -0,0 +1,43 @@
import requests_mock
import typesense
from django.core.cache import cache
from funkwhale_api.typesense import factories as custom_factories
from funkwhale_api.typesense import utils
def test_resolve_recordings_to_fw_track(mocker, factories):
artist = factories["music.Artist"](name="artist_name")
factories["music.Track"](
pk=1,
title="I Want It That Way",
artist=artist,
mbid="87dfa566-21c3-45ed-bc42-1d345b8563fa",
)
factories["music.Track"](
pk=2,
title="I Want It That Way",
artist=artist,
)
client = typesense.Client(
{
"api_key": "api_key",
"nodes": [{"host": "host", "port": "port", "protocol": "protocol"}],
"connection_timeout_seconds": 2,
}
)
with requests_mock.Mocker() as r_mocker:
mocker.patch.object(typesense, "Client", return_value=client)
mocker.patch.object(
typesense.client.ApiCall,
"post",
return_value=custom_factories.typesense_search_result,
)
r_mocker.get(
"protocol://host:port/collections/canonical_fw_data/documents/search",
json=custom_factories.typesense_search_result,
)
utils.resolve_recordings_to_fw_track(custom_factories.recording_list)
assert cache.get("87dfa566-21c3-45ed-bc42-1d345b8563fa") == "1"