Skip to content
Snippets Groups Projects
conftest.py 10.6 KiB
Newer Older
import io
import PIL
import random
Eliot Berriot's avatar
Eliot Berriot committed
import uuid
Eliot Berriot's avatar
Eliot Berriot committed
from faker.providers import internet as internet_provider
Eliot Berriot's avatar
Eliot Berriot committed
import factory
import pytest
from django.core.management import call_command
from django.contrib.auth.models import AnonymousUser
from django.core.cache import cache as django_cache, caches
from django.core.files import uploadedfile
from django.utils import timezone
from django.test import client
from django.db import connection
from django.db.migrations.executor import MigrationExecutor
from django.db.models import QuerySet
from aioresponses import aioresponses
from dynamic_preferences.registries import global_preferences_registry
from rest_framework import fields as rest_fields
Eliot Berriot's avatar
Eliot Berriot committed
from rest_framework.test import APIClient, APIRequestFactory
from funkwhale_api.activity import record
from funkwhale_api.federation import actors
from funkwhale_api.music import licenses
from funkwhale_api.moderation import mrf
pytest_plugins = "aiohttp.pytest_plugin"


Eliot Berriot's avatar
Eliot Berriot committed
class FunkwhaleProvider(internet_provider.Provider):
    """
    Our own faker data generator, since built-in ones are sometimes
    not random enough
    """

    def federation_url(self, prefix=""):
        def path_generator():
            return "{}/{}".format(prefix, uuid.uuid4())

        domain = self.domain_name()
        protocol = "https"
        path = path_generator()
        return "{}://{}/{}".format(protocol, domain, path)


factory.Faker.add_provider(FunkwhaleProvider)


@pytest.fixture
def queryset_equal_queries():
    """
    Unitting querysets is hard because we have to compare queries
    by hand. Let's monkey patch querysets to do that for us.
    """

    def __eq__(self, other):
        if isinstance(other, QuerySet):
            return str(other.query) == str(self.query)
        else:
            return False

    setattr(QuerySet, "__eq__", __eq__)
    yield __eq__
    delattr(QuerySet, "__eq__")


@pytest.fixture
def queryset_equal_list():
    """
    Unitting querysets is hard because we usually simply wants to ensure
    a querysets contains the same objects as a list, let's monkey patch
    querysets to to that for us.
    """

    def __eq__(self, other):
        if isinstance(other, (list, tuple)):
            return list(self) == list(other)
        else:
            return False

    setattr(QuerySet, "__eq__", __eq__)
    yield __eq__
    delattr(QuerySet, "__eq__")


@pytest.fixture(scope="session", autouse=True)
def factories_autodiscover():
    from django.apps import apps
    from funkwhale_api import factories
Eliot Berriot's avatar
Eliot Berriot committed

    app_names = [app.name for app in apps.app_configs.values()]
    factories.registry.autodiscover(app_names)


@pytest.fixture(autouse=True)
def cache():
    """
    Returns a django Cache instance for cache-related operations
    """
@pytest.fixture(autouse=True)
def local_cache():
    yield caches["local"]
    caches["local"].clear()


@pytest.fixture
def factories(db):
    """
    Returns a dictionnary containing all registered factories with keys such as
    users.User or music.Track
    """
    from funkwhale_api import factories
Eliot Berriot's avatar
Eliot Berriot committed

    for v in factories.registry.values():
        try:
            v._meta.strategy = factory.CREATE_STRATEGY
        except AttributeError:
            # probably not a class based factory
            pass
    yield factories.registry


@pytest.fixture
def nodb_factories():
    """
    Returns a dictionnary containing all registered factories with a build strategy
    that does not require access to the database
    """
    from funkwhale_api import factories
Eliot Berriot's avatar
Eliot Berriot committed

    for v in factories.registry.values():
        try:
            v._meta.strategy = factory.BUILD_STRATEGY
        except AttributeError:
            # probably not a class based factory
            pass
    yield factories.registry


def preferences(db, cache):
    """
    return a dynamic_preferences manager for global_preferences
    """
    manager = global_preferences_registry.manager()
    manager.all()
    yield manager
@pytest.fixture
def tmpdir():
    """
    Returns a temporary directory path where you can write things during your
    test
    """
    d = tempfile.mkdtemp()
    yield d
    shutil.rmtree(d)


Eliot Berriot's avatar
Eliot Berriot committed
@pytest.fixture
def tmpfile():
    """
    Returns a temporary file where you can write things during your test
    """
Eliot Berriot's avatar
Eliot Berriot committed
    yield tempfile.NamedTemporaryFile()


@pytest.fixture
def logged_in_client(db, factories, client):
    """
    Returns a logged-in, non-API client with an authenticated ``User``
    stored in the ``user`` attribute
    """
Eliot Berriot's avatar
Eliot Berriot committed
    user = factories["users.User"]()
    assert client.login(username=user.username, password="test")
    setattr(client, "user", user)
Eliot Berriot's avatar
Eliot Berriot committed
    delattr(client, "user")
@pytest.fixture
def anonymous_user():
    """Returns a AnonymousUser() instance"""
    return AnonymousUser()


@pytest.fixture
def api_client(client):
    """
    Return an API client without any authentication
    """
Eliot Berriot's avatar
Eliot Berriot committed
@pytest.fixture
def logged_in_api_client(db, factories, api_client):
    """
    Return a logged-in API client with an authenticated ``User``
    stored in the ``user`` attribute
    """
Eliot Berriot's avatar
Eliot Berriot committed
    user = factories["users.User"]()
    assert api_client.login(username=user.username, password="test")
Eliot Berriot's avatar
Eliot Berriot committed
    setattr(api_client, "user", user)
Eliot Berriot's avatar
Eliot Berriot committed
    yield api_client
Eliot Berriot's avatar
Eliot Berriot committed
    delattr(api_client, "user")
@pytest.fixture
def superuser_api_client(db, factories, api_client):
    """
    Return a logged-in API client with an authenticated superuser
    stored in the ``user`` attribute
    """
Eliot Berriot's avatar
Eliot Berriot committed
    user = factories["users.SuperUser"]()
    assert api_client.login(username=user.username, password="test")
    setattr(api_client, "user", user)
    yield api_client
Eliot Berriot's avatar
Eliot Berriot committed
    delattr(api_client, "user")
@pytest.fixture
def superuser_client(db, factories, client):
    """
    Return a logged-in, non-API client with an authenticated ``User``
    stored in the ``user`` attribute
    """
Eliot Berriot's avatar
Eliot Berriot committed
    user = factories["users.SuperUser"]()
    assert client.login(username=user.username, password="test")
    setattr(client, "user", user)
Eliot Berriot's avatar
Eliot Berriot committed
    delattr(client, "user")
    """
    Returns a dummy API request object you can pass to API views
    """
@pytest.fixture
def fake_request():
    """
    Returns a dummy, non-API request object you can pass to regular views
    """
    return client.RequestFactory()


@pytest.fixture
def activity_registry():
    state = list(record.registry.items())
    yield record.registry
    record.registry.clear()
    for key, value in state:
        record.registry[key] = value


@pytest.fixture
def activity_muted(activity_registry, mocker):
Eliot Berriot's avatar
Eliot Berriot committed
    yield mocker.patch.object(record, "send")
Eliot Berriot's avatar
Eliot Berriot committed


@pytest.fixture(autouse=True)
def media_root(settings):
    """
    Sets settings.MEDIA_ROOT to a temporary path and returns this path
    """
Eliot Berriot's avatar
Eliot Berriot committed
    tmp_dir = tempfile.mkdtemp()
    settings.MEDIA_ROOT = tmp_dir
    yield settings.MEDIA_ROOT
    shutil.rmtree(tmp_dir)
Eliot Berriot's avatar
Eliot Berriot committed
@pytest.fixture(autouse=True)
def disabled_musicbrainz(mocker):
    # we ensure no music brainz requests gets out
    yield mocker.patch(
        "musicbrainzngs.musicbrainz._safe_read",
        side_effect=Exception("Disabled network calls"),
    )


Eliot Berriot's avatar
Eliot Berriot committed
@pytest.fixture(autouse=True)
def r_mock(requests_mock):
    """
    Returns a requests_mock.mock() object you can use to mock HTTP calls made
    using python-requests
    """
Eliot Berriot's avatar
Eliot Berriot committed
    yield requests_mock


@pytest.fixture
def authenticated_actor(factories, mocker):
    """
    Returns an authenticated ActivityPub actor
    """
Eliot Berriot's avatar
Eliot Berriot committed
    actor = factories["federation.Actor"]()
Eliot Berriot's avatar
Eliot Berriot committed
        "funkwhale_api.federation.authentication.SignatureAuthentication.authenticate_actor",
        return_value=actor,
    )
@pytest.fixture
def to_api_date():
    def inner(value):
        if isinstance(value, datetime.datetime):
            f = rest_fields.DateTimeField()
            return f.to_representation(value)
        if isinstance(value, datetime.date):
            f = rest_fields.DateField()
            return f.to_representation(value)
Eliot Berriot's avatar
Eliot Berriot committed
        raise ValueError("Invalid value: {}".format(value))



@pytest.fixture()
def now(mocker):
    now = timezone.now()
    mocker.patch("django.utils.timezone.now", return_value=now)
    return now


@pytest.fixture()
def avatar():
    i = PIL.Image.new("RGBA", (400, 400), random.choice(["red", "blue", "yellow"]))
    f = io.BytesIO()
    i.save(f, "png")
    f.name = "avatar.png"
    f.seek(0)
    yield f
    f.close()


@pytest.fixture()
def audio_file():
    data_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "music")
    path = os.path.join(data_dir, "test.ogg")
    assert os.path.exists(path)
    with open(path, "rb") as f:
        yield f


@pytest.fixture()
def uploaded_audio_file(audio_file):
    yield uploadedfile.SimpleUploadedFile(
        name=audio_file.name, content=audio_file.read()
    )


@pytest.fixture()
def temp_signal(mocker):
    """
    Connect a temporary handler to a given signal. This is helpful to validate
    a signal is dispatched properly, without mocking.
    """

    @contextlib.contextmanager
    def connect(signal):
        stub = mocker.stub()
        signal.connect(stub)
        try:
            yield stub
        finally:
            signal.disconnect(stub)

    return connect


@pytest.fixture()
def stdout():
    yield io.StringIO()
Eliot Berriot's avatar
Eliot Berriot committed
    settings.FUNKWHALE_SPA_HTML_ROOT = "http://noop/"
    yield r_mock.get(
        settings.FUNKWHALE_SPA_HTML_ROOT + "index.html", text="<head></head>"
    )


@pytest.fixture
def no_api_auth(preferences):
    preferences["common__api_authentication_required"] = False


@pytest.fixture()
def migrator(transactional_db):
    yield MigrationExecutor(connection)
    call_command("migrate", interactive=False)


@pytest.fixture(autouse=True)
def rsa_small_key(settings):
    # smaller size for faster generation, since it's CPU hungry
    settings.RSA_KEY_SIZE = 512


@pytest.fixture(autouse=True)
def a_responses():
    with aioresponses() as m:
        yield m


@pytest.fixture
def service_actor(db):
    return actors.get_service_actor()


@pytest.fixture
def mrf_inbox_registry(mocker):
    registry = mrf.Registry()
    mocker.patch("funkwhale_api.moderation.mrf.inbox", registry)
    return registry


@pytest.fixture
def mrf_outbox_registry(mocker):
    registry = mrf.Registry()
    mocker.patch("funkwhale_api.moderation.mrf.outbox", registry)
    return registry


@pytest.fixture(autouse=True)
def clear_license_cache(db):
    licenses._cache = None
    yield
    licenses._cache = None