diff --git a/CONTRIBUTING b/CONTRIBUTING new file mode 100644 index 000000000..9f4ec8850 --- /dev/null +++ b/CONTRIBUTING @@ -0,0 +1,442 @@ +Contibute to Funkwhale development +================================== + +First of all, thank you for your interest in the project! We really +appreciate the fact that you're about to take some time to read this +and hack on the project. + +This document will guide you through common operations such as: + +- Setup your development environment +- Working on your first issue +- Writing unit tests to validate your work +- Submit your work + + +Setup your development environment +---------------------------------- + +If you want to fix a bug or implement a feature, you'll need +to run a local, development copy of funkwhale. + +We provide a docker based development environment, which should +be both easy to setup and work similarly regardless of your +development machine setup. + +Instructions for bare-metal setup will come in the future (Merge requests +are welcome). + +Installing docker and docker-compose +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This is already cover in the relevant documentations: + +- https://docs.docker.com/install/ +- https://docs.docker.com/compose/install/ + +Cloning the project +^^^^^^^^^^^^^^^^^^^ + +Visit https://code.eliotberriot.com/funkwhale/funkwhale and clone the repository using SSH or HTTPS. Exemple using SSH:: + + git clone ssh://git@code.eliotberriot.com:2222/funkwhale/funkwhale.git + cd funkwhale + + +A note about branches +^^^^^^^^^^^^^^^^^^^^^ + +Next release development occurs on the "develop" branch, and releases are made on the "master" branch. Therefor, when submitting Merge Requests, ensure you are merging on the develop branch. + + +Working with docker +^^^^^^^^^^^^^^^^^^^ + +In developpement, we use the docker-compose file named ``dev.yml``, and this is why all our docker-compose commands will look like this:: + + docker-compose -f dev.yml logs + +If you do not want to add the ``-f dev.yml`` snippet everytime, you can run this command before starting your work:: + + export COMPOSE_FILE=dev.yml + + +Building the containers +^^^^^^^^^^^^^^^^^^^^^^^ + +On your initial clone, or if there have been some changes in the +app dependencies, you will have to rebuild your containers. This is done +via the following command:: + + docker-compose -f dev.yml build + + +Creating your env file +^^^^^^^^^^^^^^^^^^^^^^ + +We provide a working .env.dev configuration file that is suitable for +development. However, to enable customization on your machine, you should +also create a .env file that will hold your personal environment +variables (those will not be commited to the project). + +Create it like this:: + + touch .env + + +Database management +^^^^^^^^^^^^^^^^^^^ + +To setup funkwhale's database schema, run this:: + + docker-compose -f dev.yml run --rm api python manage.py migrate + +This will create all the tables needed for the API to run proprely. +You will also need to run this whenever changes are made on the database +schema. + +It is safe to run this command multiple times, so you can run it whenever +you fetch develop. + + +Development data +^^^^^^^^^^^^^^^^ + +You'll need at least an admin user and some artists/tracks/albums to work +locally. + +Create an admin user with the following command:: + + docker-compose -f dev.yml run --rm api python manage.py createsuperuser + +Injecting fake data is done by running the fllowing script:: + + artists=25 + command="from funkwhale_api.music import fake_data; fake_data.create_data($artists)" + echo $command | docker-compose -f dev.yml run --rm api python manage.py shell -i python + +The previous command will create 25 artists with random albums, tracks +and metadata. + + +Launch all services +^^^^^^^^^^^^^^^^^^^ + +Then you can run everything with:: + + docker-compose -f dev.yml up + +This will launch all services, and output the logs in your current terminal window. +If you prefer to launch them in the background instead, use the ``-d`` flag, and access the logs when you need it via ``docker-compose -f dev.yml logs --tail=50 --follow``. + +Once everything is up, you can access the various funkwhale's components: + +- The Vue webapp, on http://localhost:8080 +- The API, on http://localhost:8080/api/v1/ +- The django admin, on http://localhost:8080/api/admin/ + +Stopping everything +^^^^^^^^^^^^^^^^^^^ + +Once you're down with your work, you can stop running containers, if any, with:: + + docker-compose -f dev.yml stop + + +Removing everything +^^^^^^^^^^^^^^^^^^^ + +If you want to wipe your development environment completely (e.g. if you want to start over from scratch), just run:: + + docker-compose -f dev.yml down -v + +This will wipe your containers and data, so please be careful before running it. + +You can keep your data by removing the ``-v`` flag. + + +Working with federation locally +------------------------------- + +This is not needed unless you need to work on federation-related features. + +To achieve that, you'll need: + +1. to update your dns resolver to resolve all your .dev hostnames locally +2. a reverse proxy (such as traefik) to catch those .dev requests and + and with https certificate +3. two instances (or more) running locally, following the regular dev setup + +Resolve .dev names locally +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you use dnsmasq, this is as simple as doing:: + + echo "address=/test/172.17.0.1" | sudo tee /etc/dnsmasq.d/test.conf + sudo systemctl restart dnsmasq + +If you use NetworkManager with dnsmasq integration, use this instead:: + + echo "address=/test/172.17.0.1" | sudo tee /etc/NetworkManager/dnsmasq.d/test.conf + sudo systemctl restart NetworkManager + +Add wildcard certificate to the trusted certificates +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Simply copy bundled certificates:: + + sudo cp docker/ssl/test.crt /usr/local/share/ca-certificates/ + sudo update-ca-certificates + +This certificate is a wildcard for ``*.funkwhale.test`` + +Run a reverse proxy for your instances +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + +Create docker network +^^^^^^^^^^^^^^^^^^^^ + +Create the federation network:: + + docker network create federation + +Launch everything +^^^^^^^^^^^^^^^^^ + +Launch the traefik proxy:: + + docker-compose -f docker/traefik.yml up -d + +Then, in separate terminals, you can setup as many different instances as you +need:: + + export COMPOSE_PROJECT_NAME=node2 + docker-compose -f dev.yml run --rm api python manage.py migrate + docker-compose -f dev.yml run --rm api python manage.py createsuperuser + docker-compose -f dev.yml up nginx api front nginx api celeryworker + +Note that by default, if you don't export the COMPOSE_PROJECT_NAME, +we will default to node1 as the name of your instance. + +Assuming your project name is ``node1``, your server will be reachable +at ``https://node1.funkwhale.test/``. Not that you'll have to trust +the SSL Certificate as it's self signed. + +When working on federation with traefik, ensure you have this in your ``env``:: + + # This will ensure we don't bind any port on the host, and thus enable + # multiple instances of funkwhale to be spawned concurrently. + WEBPACK_DEVSERVER_PORT_BINDING= + # This disable certificate verification + EXTERNAL_REQUESTS_VERIFY_SSL=false + # this ensure you don't have incorrect urls pointing to http resources + FUNKWHALE_PROTOCOL=https + + +Typical workflow for a contribution +----------------------------------- + +0. Fork the project if you did not already or if you do not have access to the main repository +1. Checkout the development branch and pull most recent changes: ``git checkout develop && git pull`` +2. If working on an issue, assign yourself to the issue. Otherwise, consider open an issue before starting to work on something, especially for new features. +3. Create a dedicated branch for your work ``42-awesome-fix``. It is good practice to prefix your branch name with the ID of the issue you are solving. +4. Work on your stuff +5. Commit small, atomic changes to make it easier to review your contribution +6. Add a changelog fragment to summarize your changes: ``echo "Implemented awesome stuff (#42)" > changes/changelog.d/42.feature"`` +7. Push your branch +8. Create your merge request +9. Take a step back and enjoy, we're really grateful you did all of this and took the time to contribute! + + +Internationalization +-------------------- + +When working on the front-end, any end-user string should be translated +using either ```` or the ``$t('yourstring')`` +function. + +Extraction is done by calling ``yarn run i18n-extract``, which +will pull all the strings from source files and put them in a PO file. + +Contributing to the API +----------------------- + +Project structure +^^^^^^^^^^^^^^^^^ + +.. code-block:: shell + + tree api -L 2 -d + api + ├── config # configuration directory (settings, urls, wsgi server) + │ └── settings # Django settings files + ├── funkwhale_api # project directory, all funkwhale logic is here + ├── requirements # python requirements files + └── tests # test files, matches the structure of the funkwhale_api directory + +.. note:: + + Unless trivial, API contributions must include unittests to ensure + your fix or feature is working as expected and won't break in the future + +Running tests +^^^^^^^^^^^^^ + +To run the pytest test suite, use the following command:: + + docker-compose -f dev.yml run --rm api pytest + +This is regular pytest, so you can use any arguments/options that pytest usually accept:: + + # get some help + docker-compose -f dev.yml run --rm api pytest -h + # Stop on first failure + docker-compose -f dev.yml run --rm api pytest -x + # Run a specific test file + docker-compose -f dev.yml run --rm api pytest tests/test_acoustid.py + +Writing tests +^^^^^^^^^^^^^ + +Although teaching you how to write unit tests is outside of the scope of this +document, you'll find below a collection of tips, snippets and resources +you can use if you want to learn on that subject. + +Useful links: + +- `A quick introduction to unit test writing with pytest `_ +- `A complete guide to Test-Driven Development (although not using Pytest) `_ +- `pytest `_: documentation of our testing engine and runner +- `pytest-mock `_: project page of our mocking engine +- `factory-boy `_: documentation of factory-boy, which we use to easily generate fake objects and data + +Recommendations: + +- Test files must target a module and mimic ``funkwhale_api`` directory structure: if you're writing tests for ``funkwhale_api/myapp/views.py``, you should put thoses tests in ``tests/myapp/test_views.py`` +- Tests should be small and test one thing. If you need to test multiple things, write multiple tests. + +We provide a lot of utils and fixtures to make the process of writing tests as +painless as possible. You'll find some usage examples below. + +Use factories to create arbitrary objects: + +.. code-block:: python + + # funkwhale_api/myapp/users.py + + def downgrade_user(user): + """ + A simple function that remove superuser status from users + and return True if user was actually downgraded + """ + downgraded = user.is_superuser + user.is_superuser = False + user.save() + return downgraded + + # tests/myapp/test_users.py + from funkwhale_api.myapp import users + + def test_downgrade_superuser(factories): + user = factories['users.User'](is_superuser=True) + downgraded = users.downgrade_user(user) + + assert downgraded is True + assert user.is_superuser is False + + def test_downgrade_normal_user_does_nothing(factories): + user = factories['users.User'](is_superuser=False) + downgraded = something.downgrade_user(user) + + assert downgraded is False + assert user.is_superuser is False + +.. note:: + + We offer factories for almost if not all models. Factories are located + in a ``factories.py`` file inside each app. + +Mocking: mocking is the process of faking some logic in our code. This is +useful when testing components that depend on each other: + +.. code-block:: python + + # funkwhale_api/myapp/notifications.py + + def notify(email, message): + """ + A function that sends an email to the given recipient + with the given message + """ + + # our email sending logic here + # ... + + # funkwhale_api/myapp/users.py + from . import notifications + + def downgrade_user(user): + """ + A simple function that remove superuser status from users + and return True if user was actually downgraded + """ + downgraded = user.is_superuser + user.is_superuser = False + user.save() + if downgraded: + notifications.notify(user.email, 'You have been downgraded!') + return downgraded + + # tests/myapp/test_users.py + def test_downgrade_superuser_sends_email(factories, mocker): + """ + Your downgrade logic is already tested, however, we want to ensure + an email is sent when user is downgraded, but we don't have any email + server available in our testing environment. Thus, we need to mock + the email sending process. + """ + mocked_notify = mocker.patch('funkwhale_api.myapp.notifications.notify') + user = factories['users.User'](is_superuser=True) + users.downgrade_user(user) + + # here, we ensure our notify function was called with proper arguments + mocked_notify.assert_called_once_with(user.email, 'You have been downgraded') + + + def test_downgrade_not_superuser_skips_email(factories, mocker): + mocked_notify = mocker.patch('funkwhale_api.myapp.notifications.notify') + user = factories['users.User'](is_superuser=True) + users.downgrade_user(user) + + # here, we ensure no email was sent + mocked_notify.assert_not_called() + +Views: you can find some readable views tests in :file:`tests/users/test_views.py` + +.. note:: + + A complete list of available-fixtures is available by running + ``docker-compose -f dev.yml run --rm api pytest --fixtures`` + + +Contributing to the front-end +----------------------------- + +Running tests +^^^^^^^^^^^^^ + +To run the front-end test suite, use the following command:: + + docker-compose -f dev.yml run --rm front yarn run unit + +We also support a "watch and test" mode were we continually relaunch +tests when changes are recorded on the file system:: + + docker-compose -f dev.yml run --rm front yarn run unit-watch + +The latter is especially useful when you are debugging failing tests. + +.. note:: + + The front-end test suite coverage is still pretty low diff --git a/README.rst b/README.rst index 8a0ea4932..c8a126451 100644 --- a/README.rst +++ b/README.rst @@ -15,282 +15,8 @@ We offer various Matrix.org rooms to discuss about funkwhale: Please join those rooms if you have any questions! -Running the development version -------------------------------- +Contribute +---------- -If you want to fix a bug or implement a feature, you'll need -to run a local, development copy of funkwhale. - -We provide a docker based development environment, which should -be both easy to setup and work similarly regardless of your -development machine setup. - -Instructions for bare-metal setup will come in the future (Merge requests -are welcome). - -Installing docker and docker-compose -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This is already cover in the relevant documentations: - -- https://docs.docker.com/install/ -- https://docs.docker.com/compose/install/ - -Cloning the project -^^^^^^^^^^^^^^^^^^^ - -Visit https://code.eliotberriot.com/funkwhale/funkwhale and clone the repository using SSH or HTTPS. Exemple using SSH:: - - git clone ssh://git@code.eliotberriot.com:2222/funkwhale/funkwhale.git - cd funkwhale - - -A note about branches -^^^^^^^^^^^^^^^^^^^^^ - -Next release development occurs on the "develop" branch, and releases are made on the "master" branch. Therefor, when submitting Merge Requests, ensure you are merging on the develop branch. - - -Working with docker -^^^^^^^^^^^^^^^^^^^ - -In developpement, we use the docker-compose file named ``dev.yml``, and this is why all our docker-compose commands will look like this:: - - docker-compose -f dev.yml logs - -If you do not want to add the ``-f dev.yml`` snippet everytime, you can run this command before starting your work:: - - export COMPOSE_FILE=dev.yml - - -Building the containers -^^^^^^^^^^^^^^^^^^^^^^^ - -On your initial clone, or if there have been some changes in the -app dependencies, you will have to rebuild your containers. This is done -via the following command:: - - docker-compose -f dev.yml build - - -Creating your env file -^^^^^^^^^^^^^^^^^^^^^^ - -We provide a working .env.dev configuration file that is suitable for -development. However, to enable customization on your machine, you should -also create a .env file that will hold your personal environment -variables (those will not be commited to the project). - -Create it like this:: - - touch .env - - -Database management -^^^^^^^^^^^^^^^^^^^ - -To setup funkwhale's database schema, run this:: - - docker-compose -f dev.yml run --rm api python manage.py migrate - -This will create all the tables needed for the API to run proprely. -You will also need to run this whenever changes are made on the database -schema. - -It is safe to run this command multiple times, so you can run it whenever -you fetch develop. - - -Development data -^^^^^^^^^^^^^^^^ - -You'll need at least an admin user and some artists/tracks/albums to work -locally. - -Create an admin user with the following command:: - - docker-compose -f dev.yml run --rm api python manage.py createsuperuser - -Injecting fake data is done by running the fllowing script:: - - artists=25 - command="from funkwhale_api.music import fake_data; fake_data.create_data($artists)" - echo $command | docker-compose -f dev.yml run --rm api python manage.py shell -i python - -The previous command will create 25 artists with random albums, tracks -and metadata. - - -Launch all services -^^^^^^^^^^^^^^^^^^^ - -Then you can run everything with:: - - docker-compose -f dev.yml up - -This will launch all services, and output the logs in your current terminal window. -If you prefer to launch them in the background instead, use the ``-d`` flag, and access the logs when you need it via ``docker-compose -f dev.yml logs --tail=50 --follow``. - -Once everything is up, you can access the various funkwhale's components: - -- The Vue webapp, on http://localhost:8080 -- The API, on http://localhost:8080/api/v1/ -- The django admin, on http://localhost:8080/api/admin/ - - -Running API tests -^^^^^^^^^^^^^^^^^ - -To run the pytest test suite, use the following command:: - - docker-compose -f dev.yml run --rm api pytest - -This is regular pytest, so you can use any arguments/options that pytest usually accept:: - - # get some help - docker-compose -f dev.yml run --rm api pytest -h - # Stop on first failure - docker-compose -f dev.yml run --rm api pytest -x - # Run a specific test file - docker-compose -f dev.yml run --rm api pytest tests/test_acoustid.py - - -Running front-end tests -^^^^^^^^^^^^^^^^^^^^^^^ - -To run the front-end test suite, use the following command:: - - docker-compose -f dev.yml run --rm front yarn run unit - -We also support a "watch and test" mode were we continually relaunch -tests when changes are recorded on the file system:: - - docker-compose -f dev.yml run --rm front yarn run unit-watch - -The latter is especially useful when you are debugging failing tests. - -.. note:: - - The front-end test suite coverage is still pretty low - - -Stopping everything -^^^^^^^^^^^^^^^^^^^ - -Once you're down with your work, you can stop running containers, if any, with:: - - docker-compose -f dev.yml stop - - -Removing everything -^^^^^^^^^^^^^^^^^^^ - -If you want to wipe your development environment completely (e.g. if you want to start over from scratch), just run:: - - docker-compose -f dev.yml down -v - -This will wipe your containers and data, so please be careful before running it. - -You can keep your data by removing the ``-v`` flag. - - -Typical workflow for a merge request -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -0. Fork the project if you did not already or if you do not have access to the main repository -1. Checkout the development branch and pull most recent changes: ``git checkout develop && git pull`` -2. Create a dedicated branch for your work ``42-awesome-fix``. It is good practice to prefix your branch name with the ID of the issue you are solving. -3. Work on your stuff -4. Commit small, atomic changes to make it easier to review your contribution -5. Add a changelog fragment to summarize your changes: ``echo "Implemented awesome stuff (#42)" > changes/changelog.d/42.feature"`` -6. Push your branch -7. Create your merge request -8. Take a step back and enjoy, we're really grateful you did all of this and took the time to contribute! - - -Internationalization --------------------- - -When working on the front-end, any end-user string should be translated -using either ```` or the ``$t('yourstring')`` -function. - -Extraction is done by calling ``yarn run i18n-extract``, which -will pull all the strings from source files and put them in a PO file. - - -Working with federation locally -------------------------------- - -To achieve that, you'll need: - -1. to update your dns resolver to resolve all your .dev hostnames locally -2. a reverse proxy (such as traefik) to catch those .dev requests and - and with https certificate -3. two instances (or more) running locally, following the regular dev setup - -Resolve .dev names locally -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -If you use dnsmasq, this is as simple as doing:: - - echo "address=/test/172.17.0.1" | sudo tee /etc/dnsmasq.d/test.conf - sudo systemctl restart dnsmasq - -If you use NetworkManager with dnsmasq integration, use this instead:: - - echo "address=/test/172.17.0.1" | sudo tee /etc/NetworkManager/dnsmasq.d/test.conf - sudo systemctl restart NetworkManager - -Add wildcard certificate to the trusted certificates -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Simply copy bundled certificates:: - - sudo cp docker/ssl/test.crt /usr/local/share/ca-certificates/ - sudo update-ca-certificates - -This certificate is a wildcard for ``*.funkwhale.test`` - -Run a reverse proxy for your instances -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - -Create docker network -^^^^^^^^^^^^^^^^^^^^ - -Create the federation network:: - - docker network create federation - -Launch everything -^^^^^^^^^^^^^^^^^ - -Launch the traefik proxy:: - - docker-compose -f docker/traefik.yml up -d - -Then, in separate terminals, you can setup as many different instances as you -need:: - - export COMPOSE_PROJECT_NAME=node2 - docker-compose -f dev.yml run --rm api python manage.py migrate - docker-compose -f dev.yml run --rm api python manage.py createsuperuser - docker-compose -f dev.yml up nginx api front nginx api celeryworker - -Note that by default, if you don't export the COMPOSE_PROJECT_NAME, -we will default to node1 as the name of your instance. - -Assuming your project name is ``node1``, your server will be reachable -at ``https://node1.funkwhale.test/``. Not that you'll have to trust -the SSL Certificate as it's self signed. - -When working on federation with traefik, ensure you have this in your ``env``:: - - # This will ensure we don't bind any port on the host, and thus enable - # multiple instances of funkwhale to be spawned concurrently. - WEBPACK_DEVSERVER_PORT_BINDING= - # This disable certificate verification - EXTERNAL_REQUESTS_VERIFY_SSL=false - # this ensure you don't have incorrect urls pointing to http resources - FUNKWHALE_PROTOCOL=https +Contribution guidelines as well as development installation instructions +are outlined in `CONTRIBUTING `_ diff --git a/api/tests/conftest.py b/api/tests/conftest.py index 64dc394e7..51a1bc4c7 100644 --- a/api/tests/conftest.py +++ b/api/tests/conftest.py @@ -27,12 +27,19 @@ def factories_autodiscover(): @pytest.fixture(autouse=True) def cache(): + """ + Returns a django Cache instance for cache-related operations + """ yield django_cache django_cache.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 for v in factories.registry.values(): try: @@ -45,6 +52,10 @@ def factories(db): @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 for v in factories.registry.values(): try: @@ -57,6 +68,9 @@ def nodb_factories(): @pytest.fixture def preferences(db, cache): + """ + return a dynamic_preferences manager for global_preferences + """ manager = global_preferences_registry.manager() manager.all() yield manager @@ -64,6 +78,10 @@ def preferences(db, cache): @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) @@ -71,11 +89,18 @@ def tmpdir(): @pytest.fixture def tmpfile(): + """ + Returns a temporary file where you can write things during your test + """ 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 + """ user = factories['users.User']() assert client.login(username=user.username, password='test') setattr(client, 'user', user) @@ -85,16 +110,24 @@ def logged_in_client(db, factories, client): @pytest.fixture def anonymous_user(): + """Returns a AnonymousUser() instance""" return AnonymousUser() @pytest.fixture def api_client(client): + """ + Return an API client without any authentication + """ return APIClient() @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 + """ user = factories['users.User']() assert api_client.login(username=user.username, password='test') setattr(api_client, 'user', user) @@ -104,6 +137,10 @@ def logged_in_api_client(db, factories, api_client): @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 + """ user = factories['users.SuperUser']() assert api_client.login(username=user.username, password='test') setattr(api_client, 'user', user) @@ -113,6 +150,10 @@ def superuser_api_client(db, factories, api_client): @pytest.fixture def superuser_client(db, factories, client): + """ + Return a logged-in, non-API client with an authenticated ``User`` + stored in the ``user`` attribute + """ user = factories['users.SuperUser']() assert client.login(username=user.username, password='test') setattr(client, 'user', user) @@ -122,11 +163,17 @@ def superuser_client(db, factories, client): @pytest.fixture def api_request(): + """ + Returns a dummy API request object you can pass to API views + """ return APIRequestFactory() @pytest.fixture def fake_request(): + """ + Returns a dummy, non-API request object you can pass to regular views + """ return client.RequestFactory() @@ -140,16 +187,6 @@ def activity_registry(): record.registry[key] = value -@pytest.fixture -def activity_registry(): - r = record.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): yield mocker.patch.object(record, 'send') @@ -157,6 +194,9 @@ def activity_muted(activity_registry, mocker): @pytest.fixture(autouse=True) def media_root(settings): + """ + Sets settings.MEDIA_ROOT to a temporary path and returns this path + """ tmp_dir = tempfile.mkdtemp() settings.MEDIA_ROOT = tmp_dir yield settings.MEDIA_ROOT @@ -165,12 +205,19 @@ def media_root(settings): @pytest.fixture def r_mock(): + """ + Returns a requests_mock.mock() object you can use to mock HTTP calls made + using python-requests + """ with requests_mock.mock() as m: yield m @pytest.fixture def authenticated_actor(factories, mocker): + """ + Returns an authenticated ActivityPub actor + """ actor = factories['federation.Actor']() mocker.patch( 'funkwhale_api.federation.authentication.SignatureAuthentication.authenticate_actor', diff --git a/api/tests/music/conftest.py b/api/tests/music/conftest.py index 1d0fa4e38..4eea8effe 100644 --- a/api/tests/music/conftest.py +++ b/api/tests/music/conftest.py @@ -508,21 +508,25 @@ _works['get']['chop_suey'] = {'work': {'id': 'e2ecabc4-1b9d-30b2-8f30-3596ec423d @pytest.fixture() def artists(): + """Artists as they would be returned by the Musicbrainz API""" return _artists @pytest.fixture() def albums(): + """Releases as they would be returned by the Musicbrainz API""" return _albums @pytest.fixture() def tracks(): + """Recordings as they would be returned by the Musicbrainz API""" return _tracks @pytest.fixture() def works(): + """Works as they would be returned by the Musicbrainz API""" return _works @@ -563,4 +567,7 @@ def lyricswiki_content(): @pytest.fixture() def binary_cover(): + """ + Return an album cover image in form of a binary string + """ return b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x02\x01\x00H\x00H\x00\x00\xff\xed\x08\xaePhotoshop 3.0\x008BIM\x03\xe9\x00\x00\x00\x00\x00x\x00\x03\x00\x00\x00H\x00H\x00\x00\x00\x00\x02\xd8\x02(\xff\xe1\xff\xe2\x02\xf9\x02F\x03G\x05(\x03\xfc\x00\x02\x00\x00\x00H\x00H\x00\x00\x00\x00\x02\xd8\x02(\x00\x01\x00\x00\x00d\x00\x00\x00\x01\x00\x03\x03\x03\x00\x00\x00\x01\'\x0f\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x08\x00\x19\x01\x90\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008BIM\x03\xed\x00\x00\x00\x00\x00\x10\x00H\x00\x00\x00\x01\x00\x01\x00H\x00\x00\x00\x01\x00\x018BIM\x03\xf3\x00\x00\x00\x00\x00\x08\x00\x00\x00\x00\x00\x00\x00\x008BIM\x04\n\x00\x00\x00\x00\x00\x01\x00\x008BIM\'\x10\x00\x00\x00\x00\x00\n\x00\x01\x00\x00\x00\x00\x00\x00\x00\x028BIM\x03\xf5\x00\x00\x00\x00\x00H\x00/ff\x00\x01\x00lff\x00\x06\x00\x00\x00\x00\x00\x01\x00/ff\x00\x01\x00\xa1\x99\x9a\x00\x06\x00\x00\x00\x00\x00\x01\x002\x00\x00\x00\x01\x00Z\x00\x00\x00\x06\x00\x00\x00\x00\x00\x01\x005\x00\x00\x00\x01\x00-\x00\x00\x00\x06\x00\x00\x00\x00\x00\x018BIM\x03\xf8\x00\x00\x00\x00\x00p\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xe8\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xe8\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xe8\x00\x00\x00\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x03\xe8\x00\x008BIM\x04\x00\x00\x00\x00\x00\x00\x02\x00\x018BIM\x04\x02\x00\x00\x00\x00\x00\x04\x00\x00\x00\x008BIM\x04\x08\x00\x00\x00\x00\x00\x10\x00\x00\x00\x01\x00\x00\x02@\x00\x00\x02@\x00\x00\x00\x008BIM\x04\t\x00\x00\x00\x00\x06\x9b\x00\x00\x00\x01\x00\x00\x00\x80\x00\x00\x00\x80\x00\x00\x01\x80\x00\x00\xc0\x00\x00\x00\x06\x7f\x00\x18\x00\x01\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x02\x01\x00H\x00H\x00\x00\xff\xfe\x00\'File written by Adobe Photoshop\xa8 4.0\x00\xff\xee\x00\x0eAdobe\x00d\x80\x00\x00\x00\x01\xff\xdb\x00\x84\x00\x0c\x08\x08\x08\t\x08\x0c\t\t\x0c\x11\x0b\n\x0b\x11\x15\x0f\x0c\x0c\x0f\x15\x18\x13\x13\x15\x13\x13\x18\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x01\r\x0b\x0b\r\x0e\r\x10\x0e\x0e\x10\x14\x0e\x0e\x0e\x14\x14\x0e\x0e\x0e\x0e\x14\x11\x0c\x0c\x0c\x0c\x0c\x11\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\xff\xc0\x00\x11\x08\x00\x80\x00\x80\x03\x01"\x00\x02\x11\x01\x03\x11\x01\xff\xdd\x00\x04\x00\x08\xff\xc4\x01?\x00\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x03\x00\x01\x02\x04\x05\x06\x07\x08\t\n\x0b\x01\x00\x01\x05\x01\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x10\x00\x01\x04\x01\x03\x02\x04\x02\x05\x07\x06\x08\x05\x03\x0c3\x01\x00\x02\x11\x03\x04!\x121\x05AQa\x13"q\x812\x06\x14\x91\xa1\xb1B#$\x15R\xc1b34r\x82\xd1C\x07%\x92S\xf0\xe1\xf1cs5\x16\xa2\xb2\x83&D\x93TdE\xc2\xa3t6\x17\xd2U\xe2e\xf2\xb3\x84\xc3\xd3u\xe3\xf3F\'\x94\xa4\x85\xb4\x95\xc4\xd4\xe4\xf4\xa5\xb5\xc5\xd5\xe5\xf5Vfv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf67GWgw\x87\x97\xa7\xb7\xc7\xd7\xe7\xf7\x11\x00\x02\x02\x01\x02\x04\x04\x03\x04\x05\x06\x07\x07\x06\x055\x01\x00\x02\x11\x03!1\x12\x04AQaq"\x13\x052\x81\x91\x14\xa1\xb1B#\xc1R\xd1\xf03$b\xe1r\x82\x92CS\x15cs4\xf1%\x06\x16\xa2\xb2\x83\x07&5\xc2\xd2D\x93T\xa3\x17dEU6te\xe2\xf2\xb3\x84\xc3\xd3u\xe3\xf3F\x94\xa4\x85\xb4\x95\xc4\xd4\xe4\xf4\xa5\xb5\xc5\xd5\xe5\xf5Vfv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf6\'7GWgw\x87\x97\xa7\xb7\xc7\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\xf5T\x92I%)$\x92IJI$\x92R\x92I$\x94\xa4\x92I%)$\x92IJI$\x92R\x92I$\x94\xff\x00\xff\xd0\xf5T\x92I%)$\x92IJI%\xe7\xff\x00Z\x7f\xc6\xbf\xfc\xde\xeb\xb9]\x1f\xf6_\xda~\xcd\xe9\xfe\x9b\xed\x1e\x9e\xefR\xba\xef\xfeo\xec\xf6\xed\xdb\xea\xec\xfeq%>\x80\x92\xf2\xaf\xfc}?\xf3I\xff\x00\xb3_\xfb\xe8\x97\xfe>\x9f\xf9\xa4\xff\x00\xd9\xaf\xfd\xf4IO\xaa\xa4\xbc\xab\xff\x00\x1fO\xfc\xd2\x7f\xec\xd7\xfe\xfa%\xff\x00\x8f\xa7\xfei?\xf6k\xff\x00}\x12S\xea\xa9.+\xeaW\xf8\xc8\xff\x00\x9d}V\xde\x9d\xfb;\xec~\x96;\xb2=O[\xd5\x9d\xaf\xaa\xad\x9b=\n\x7f\xd3}-\xeb\xb5IJI$\x92R\x92I$\x94\xff\x00\xff\xd1\xf5T\x92I%)$\x97\x9f\xff\x00\x8d\x7f\xad=w\xea\xf7\xec\xbf\xd8\xf9_f\xfbO\xda=o\xd1\xd7f\xefO\xec\xfe\x9f\xf3\xf5\xdb\xb7o\xabg\xd0IO\xa0/\x9f\xff\x00\xc6\x97\xfe.\xfa\x9f\xfdc\xff\x00m\xf1\xd2\xff\x00\xc7K\xeb\xdf\xfeY\xff\x00\xe0\x18\xff\x00\xfb\xce\xb9\xfe\xa9\xd53\xfa\xbe}\xbdG\xa8\xdb\xeb\xe5\xdf\xb7\xd4\xb3kY;\x1a\xda\x99\xec\xa9\xac\xaf\xf9\xb63\xf3\x12SU$\x92IJI$\x92S\xdf\xff\x00\x89O\xfcUe\x7f\xe1\x0b?\xf3\xf6*\xf6\xb5\xf3/D\xeb\xfd[\xa0\xe5?3\xa4\xdf\xf6l\x8b+59\xfb\x18\xf9a-\xb1\xcd\xdb{-g\xd3\xa9\x8bk\xff\x00\x1d/\xaf\x7f\xf9g\xff\x00\x80c\xff\x00\xef:J~\x80Iq\xff\x00\xe2\xbf\xaf\xf5n\xbd\xd023:\xb5\xff\x00i\xc8\xaf-\xf55\xfb\x18\xc8`\xae\x8b\x1a\xdd\xb42\xa6};^\xbb\x04\x94\xa4\x92I%?\xff\xd2\xf5T\x92I%)yW\xf8\xf4\xff\x00\xbcO\xfd\n\xff\x00\xddE\xea\xab\xca\xbf\xc7\xa7\xfd\xe2\x7f\xe8W\xfe\xea$\xa7\xca\x92I$\x94\xa4\x92I%)$\x92IJI$\x92S\xed_\xe2S\xff\x00\x12\xb9_\xf8~\xcf\xfc\xf3\x8a\xbd\x01y\xff\x00\xf8\x94\xff\x00\xc4\xaeW\xfe\x1f\xb3\xff\x00<\xe2\xaf@IJI$\x92S\xff\xd3\xf5T\x92I%)yW\xf8\xf4\xff\x00\xbcO\xfd\n\xff\x00\xddE\xea\xab\xca\xbf\xc7\xa7\xfd\xe2\x7f\xe8W\xfe\xea$\xa7\xca\x92I$\x94\xa4\x92I%)$\x92IJI$\x92S\xed_\xe2S\xff\x00\x12\xb9_\xf8~\xcf\xfc\xf3\x8a\xbd\x01y\xff\x00\xf8\x94\xff\x00\xc4\xaeW\xfe\x1f\xb3\xff\x00<\xe2\xaf@IJI$\x92S\xff\xd4\xf5T\x92I%)q_\xe3#\xeaWU\xfa\xd7\xfb;\xf6u\xb8\xf5}\x8f\xd6\xf5>\xd0\xe7\xb6}_Cf\xcfJ\xab\xbf\xd0\xbfr\xedRIO\x8a\x7f\xe3)\xf5\xab\xfe\xe5`\x7f\xdb\x97\x7f\xef*\xe4:\xff\x00D\xca\xe8=Z\xfe\x93\x98\xfa\xec\xc8\xc6\xd9\xbd\xd5\x12Xw\xb1\x97\xb7k\xacmO\xfa\x16\xfe\xe2\xfai|\xff\x00\xfe4\xbf\xf1w\xd4\xff\x00\xeb\x1f\xfbo\x8e\x92\x9eU$\x92IJI$\x92S\xb1\xf5_\xea\xbfP\xfa\xd1\xd4,\xc0\xc0\xb2\x9a\xad\xaa\x93{\x9dys[\xb5\xae\xae\xa2\x01\xaa\xbb\x9d\xbfu\xcd\xfc\xd5\xd3\xff\x00\xe3)\xf5\xab\xfe\xe5`\x7f\xdb\x97\x7f\xef*_\xe2S\xff\x00\x15Y_\xf8B\xcf\xfc\xfd\x8a\xbd\xad%<\xbf\xf8\xbc\xfa\xaf\xd4>\xab\xf4[\xb03\xec\xa6\xdbm\xc9u\xedu\x05\xcen\xd7WM@\x13mt\xbb~\xea]\xf9\xab\xa8I$\x94\xa4\x92I%?\xff\xd5\xf5T\x92I%)$\x92IJ\\\x7f_\xff\x00\x15\xfd\x03\xafuk\xfa\xb6fF]y\x19;7\xb6\xa7\xd6\x1861\x947kl\xa2\xd7\xfd\n\xbf}v\t$\xa7\xcf\xff\x00\xf1\x94\xfa\xab\xff\x00r\xb3\xff\x00\xed\xca\x7f\xf7\x95/\xfce>\xaa\xff\x00\xdc\xac\xff\x00\xfbr\x9f\xfd\xe5^\x80\x92J|\xff\x00\xff\x00\x19O\xaa\xbf\xf7+?\xfe\xdc\xa7\xff\x00yR\xff\x00\xc6S\xea\xaf\xfd\xca\xcf\xff\x00\xb7)\xff\x00\xdeU\xe8\t$\xa7\x97\xfa\xaf\xfe/:/\xd5~\xa1f~\x05\xd96\xdbm&\x876\xf7V\xe6\xeds\xab\xb4\x90*\xa6\x97o\xddK\x7f9u\t$\x92\x94\x92I$\xa5$\x92I)\xff\xd6\xf5T\x92I%)$\x92IJI$\x92R\x92I$\x94\xa4\x92I%)$\x92IJI$\x92R\x92I$\x94\xff\x00\xff\xd9\x008BIM\x04\x06\x00\x00\x00\x00\x00\x07\x00\x03\x00\x00\x00\x01\x01\x00\xff\xfe\x00\'File written by Adobe Photoshop\xa8 4.0\x00\xff\xee\x00\x0eAdobe\x00d\x00\x00\x00\x00\x01\xff\xdb\x00\x84\x00\n\x07\x07\x07\x08\x07\n\x08\x08\n\x0f\n\x08\n\x0f\x12\r\n\n\r\x12\x14\x10\x10\x12\x10\x10\x14\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x01\x0b\x0c\x0c\x15\x13\x15"\x18\x18"\x14\x0e\x0e\x0e\x14\x14\x0e\x0e\x0e\x0e\x14\x11\x0c\x0c\x0c\x0c\x0c\x11\x11\x0c\x0c\x0c\x0c\x0c\x0c\x11\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\x0c\xff\xc0\x00\x11\x08\x00\t\x00\t\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01\xff\xdd\x00\x04\x00\x02\xff\xc4\x01\xa2\x00\x00\x00\x07\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x04\x05\x03\x02\x06\x01\x00\x07\x08\t\n\x0b\x01\x00\x02\x02\x03\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x01\x00\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x10\x00\x02\x01\x03\x03\x02\x04\x02\x06\x07\x03\x04\x02\x06\x02s\x01\x02\x03\x11\x04\x00\x05!\x121AQ\x06\x13a"q\x81\x142\x91\xa1\x07\x15\xb1B#\xc1R\xd1\xe13\x16b\xf0$r\x82\xf1%C4S\x92\xa2\xb2cs\xc25D\'\x93\xa3\xb36\x17Tdt\xc3\xd2\xe2\x08&\x83\t\n\x18\x19\x84\x94EF\xa4\xb4V\xd3U(\x1a\xf2\xe3\xf3\xc4\xd4\xe4\xf4eu\x85\x95\xa5\xb5\xc5\xd5\xe5\xf5fv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf67GWgw\x87\x97\xa7\xb7\xc7\xd7\xe7\xf78HXhx\x88\x98\xa8\xb8\xc8\xd8\xe8\xf8)9IYiy\x89\x99\xa9\xb9\xc9\xd9\xe9\xf9*:JZjz\x8a\x9a\xaa\xba\xca\xda\xea\xfa\x11\x00\x02\x02\x01\x02\x03\x05\x05\x04\x05\x06\x04\x08\x03\x03m\x01\x00\x02\x11\x03\x04!\x121A\x05Q\x13a"\x06q\x81\x912\xa1\xb1\xf0\x14\xc1\xd1\xe1#B\x15Rbr\xf13$4C\x82\x16\x92S%\xa2c\xb2\xc2\x07s\xd25\xe2D\x83\x17T\x93\x08\t\n\x18\x19&6E\x1a\'dtU7\xf2\xa3\xb3\xc3()\xd3\xe3\xf3\x84\x94\xa4\xb4\xc4\xd4\xe4\xf4eu\x85\x95\xa5\xb5\xc5\xd5\xe5\xf5FVfv\x86\x96\xa6\xb6\xc6\xd6\xe6\xf6GWgw\x87\x97\xa7\xb7\xc7\xd7\xe7\xf78HXhx\x88\x98\xa8\xb8\xc8\xd8\xe8\xf89IYiy\x89\x99\xa9\xb9\xc9\xd9\xe9\xf9*:JZjz\x8a\x9a\xaa\xba\xca\xda\xea\xfa\xff\xda\x00\x0c\x03\x01\x00\x02\x11\x03\x11\x00?\x00\x91\xea\xfa\xbf\xe6D_\x99\x16\x96\x16\x16\x8c\xdeWf\x84;\x88U\xa1hY\x7f\xd3\'\x9e\xf3\xedCq\x0bz\xfe\x94^\xbc?\xdc\xdb\xff\x00\xa3\xcd\xeb\x7f\xa4\xaa\xf4