funkwhale/api/tests/users/test_views.py

524 wiersze
17 KiB
Python

import pytest
from django.urls import reverse
from django.test import Client
from funkwhale_api.common import serializers as common_serializers
from funkwhale_api.common import utils as common_utils
from funkwhale_api.moderation import tasks as moderation_tasks
from funkwhale_api.users.models import User
def test_can_create_user_via_api(preferences, api_client, db):
url = reverse("rest_register")
data = {
"username": "test1",
"email": "test1@test.com",
"password1": "thisismypassword",
"password2": "thisismypassword",
}
preferences["users__registration_enabled"] = True
response = api_client.post(url, data)
assert response.status_code == 201
u = User.objects.get(email="test1@test.com")
assert u.username == "test1"
@pytest.mark.parametrize("username", ["wrong.name", "wrong-name", "éaeu", "wrong name"])
def test_username_only_accepts_letters_and_underscores(
username, preferences, api_client, db
):
url = reverse("rest_register")
data = {
"username": username,
"email": "test1@test.com",
"password1": "testtest",
"password2": "testtest",
}
preferences["users__registration_enabled"] = True
response = api_client.post(url, data)
assert response.status_code == 400
def test_can_restrict_usernames(settings, preferences, db, api_client):
url = reverse("rest_register")
preferences["users__registration_enabled"] = True
settings.ACCOUNT_USERNAME_BLACKLIST = ["funkwhale"]
data = {
"username": "funkwhale",
"email": "contact@funkwhale.io",
"password1": "testtest",
"password2": "testtest",
}
response = api_client.post(url, data)
assert response.status_code == 400
assert "username" in response.data
def test_can_disable_registration_view(preferences, api_client, db):
url = reverse("rest_register")
data = {
"username": "test1",
"email": "test1@test.com",
"password1": "testtest",
"password2": "testtest",
}
preferences["users__registration_enabled"] = False
response = api_client.post(url, data)
assert response.status_code == 403
def test_can_signup_with_invitation(preferences, factories, api_client):
url = reverse("rest_register")
invitation = factories["users.Invitation"](code="Hello")
data = {
"username": "test1",
"email": "test1@test.com",
"password1": "thisismypassword",
"password2": "thisismypassword",
"invitation": "hello",
}
preferences["users__registration_enabled"] = False
response = api_client.post(url, data)
assert response.status_code == 201
u = User.objects.get(email="test1@test.com")
assert u.username == "test1"
assert u.invitation == invitation
def test_can_signup_with_invitation_invalid(preferences, factories, api_client):
url = reverse("rest_register")
factories["users.Invitation"](code="hello")
data = {
"username": "test1",
"email": "test1@test.com",
"password1": "testtest",
"password2": "testtest",
"invitation": "nope",
}
response = api_client.post(url, data)
assert response.status_code == 400
assert "invitation" in response.data
def test_can_fetch_data_from_api(api_client, factories):
url = reverse("api:v1:users:users-me")
response = api_client.get(url)
# login required
assert response.status_code == 401
user = factories["users.User"](permission_library=True, with_actor=True)
summary = {"content_type": "text/plain", "text": "Hello"}
summary_obj = common_utils.attach_content(user.actor, "summary_obj", summary)
avatar = factories["common.Attachment"]()
user.actor.attachment_icon = avatar
user.actor.save()
api_client.login(username=user.username, password="test")
response = api_client.get(url)
assert response.status_code == 200
assert response.data["username"] == user.username
assert response.data["is_staff"] == user.is_staff
assert response.data["is_superuser"] == user.is_superuser
assert response.data["email"] == user.email
assert response.data["name"] == user.name
assert response.data["permissions"] == user.get_permissions()
assert (
response.data["avatar"] == common_serializers.AttachmentSerializer(avatar).data
)
assert (
response.data["summary"]
== common_serializers.ContentSerializer(summary_obj).data
)
def test_changing_password_updates_secret_key(logged_in_api_client):
user = logged_in_api_client.user
password = user.password
secret_key = user.secret_key
payload = {
"old_password": "test",
"new_password1": "thisismypassword",
"new_password2": "thisismypassword",
}
url = reverse("change_password")
response = logged_in_api_client.post(url, payload)
assert response.status_code == 200
user.refresh_from_db()
assert user.secret_key != secret_key
assert user.password != password
def test_can_request_password_reset(
factories, preferences, settings, api_client, mailoutbox
):
user = factories["users.User"]()
payload = {"email": user.email}
url = reverse("rest_password_reset")
preferences["instance__name"] = "Hello world"
response = api_client.post(url, payload)
assert response.status_code == 200
confirmation_message = mailoutbox[-1]
assert "Hello world" in confirmation_message.body
assert settings.FUNKWHALE_HOSTNAME in confirmation_message.body
def test_user_can_patch_his_own_settings(logged_in_api_client):
user = logged_in_api_client.user
payload = {"privacy_level": "me"}
url = reverse("api:v1:users:users-detail", kwargs={"username": user.username})
response = logged_in_api_client.patch(url, payload)
assert response.status_code == 200
user.refresh_from_db()
assert user.privacy_level == "me"
def test_user_can_patch_description(logged_in_api_client):
user = logged_in_api_client.user
payload = {"summary": {"content_type": "text/markdown", "text": "hello"}}
url = reverse("api:v1:users:users-detail", kwargs={"username": user.username})
response = logged_in_api_client.patch(url, payload, format="json")
assert response.status_code == 200
user.refresh_from_db()
assert user.actor.summary_obj.content_type == payload["summary"]["content_type"]
assert user.actor.summary_obj.text == payload["summary"]["text"]
def test_user_can_request_new_subsonic_token(logged_in_api_client):
user = logged_in_api_client.user
user.subsonic_api_token = "test"
user.save()
url = reverse(
"api:v1:users:users-subsonic-token", kwargs={"username": user.username}
)
response = logged_in_api_client.post(url)
assert response.status_code == 200
user.refresh_from_db()
assert user.subsonic_api_token != "test"
assert user.subsonic_api_token is not None
assert response.data == {"subsonic_api_token": user.subsonic_api_token}
def test_user_can_get_subsonic_token(logged_in_api_client):
user = logged_in_api_client.user
user.subsonic_api_token = "test"
user.save()
url = reverse(
"api:v1:users:users-subsonic-token", kwargs={"username": user.username}
)
response = logged_in_api_client.get(url)
assert response.status_code == 200
assert response.data == {"subsonic_api_token": "test"}
def test_user_can_request_new_subsonic_token_uncommon_username(logged_in_api_client):
user = logged_in_api_client.user
user.username = "firstname.lastname"
user.subsonic_api_token = "test"
user.save()
url = reverse(
"api:v1:users:users-subsonic-token", kwargs={"username": user.username}
)
response = logged_in_api_client.post(url)
assert response.status_code == 200
def test_user_can_delete_subsonic_token(logged_in_api_client):
user = logged_in_api_client.user
user.subsonic_api_token = "test"
user.save()
url = reverse(
"api:v1:users:users-subsonic-token", kwargs={"username": user.username}
)
response = logged_in_api_client.delete(url)
assert response.status_code == 204
user.refresh_from_db()
assert user.subsonic_api_token is None
@pytest.mark.parametrize("method", ["put", "patch"])
def test_user_cannot_patch_another_user(method, logged_in_api_client, factories):
user = factories["users.User"]()
payload = {"privacy_level": "me"}
url = reverse("api:v1:users:users-detail", kwargs={"username": user.username})
handler = getattr(logged_in_api_client, method)
response = handler(url, payload)
assert response.status_code == 403
def test_user_can_patch_their_own_avatar(logged_in_api_client, factories):
user = logged_in_api_client.user
actor = user.create_actor()
attachment = factories["common.Attachment"](actor=actor)
url = reverse("api:v1:users:users-detail", kwargs={"username": user.username})
payload = {"avatar": attachment.uuid}
response = logged_in_api_client.patch(url, payload)
assert response.status_code == 200
user.refresh_from_db()
assert user.actor.attachment_icon == attachment
assert "avatar" in response.data
def test_creating_user_creates_actor_as_well(
api_client, factories, mocker, preferences
):
actor = factories["federation.Actor"]()
url = reverse("rest_register")
data = {
"username": "test1",
"email": "test1@test.com",
"password1": "thisismypassword",
"password2": "thisismypassword",
}
preferences["users__registration_enabled"] = True
mocker.patch("funkwhale_api.users.models.create_actor", return_value=actor)
response = api_client.post(url, data)
assert response.status_code == 201
user = User.objects.get(username="test1")
assert user.actor == actor
def test_creating_user_sends_confirmation_email(
api_client, db, settings, preferences, mailoutbox
):
url = reverse("rest_register")
data = {
"username": "test1",
"email": "test1@test.com",
"password1": "thisismypassword",
"password2": "thisismypassword",
}
preferences["users__registration_enabled"] = True
preferences["instance__name"] = "Hello world"
response = api_client.post(url, data)
assert response.status_code == 201
confirmation_message = mailoutbox[-1]
assert "Hello world" in confirmation_message.body
assert settings.FUNKWHALE_HOSTNAME in confirmation_message.body
def test_user_account_deletion_requires_valid_password(logged_in_api_client):
user = logged_in_api_client.user
user.set_password("mypassword")
url = reverse("api:v1:users:users-me")
payload = {"password": "invalid", "confirm": True}
response = logged_in_api_client.delete(url, payload)
assert response.status_code == 400
def test_user_account_deletion_requires_confirmation(logged_in_api_client):
user = logged_in_api_client.user
user.set_password("mypassword")
url = reverse("api:v1:users:users-me")
payload = {"password": "mypassword", "confirm": False}
response = logged_in_api_client.delete(url, payload)
assert response.status_code == 400
def test_user_account_deletion_triggers_delete_account(logged_in_api_client, mocker):
user = logged_in_api_client.user
user.set_password("mypassword")
url = reverse("api:v1:users:users-me")
payload = {"password": "mypassword", "confirm": True}
delete_account = mocker.patch("funkwhale_api.users.tasks.delete_account.delay")
response = logged_in_api_client.delete(url, payload)
assert response.status_code == 204
delete_account.assert_called_once_with(user_id=user.pk)
def test_username_with_existing_local_account_are_invalid(
settings, preferences, factories, api_client
):
actor = factories["users.User"]().create_actor()
user = actor.user
user.delete()
url = reverse("rest_register")
preferences["users__registration_enabled"] = True
settings.ACCOUNT_USERNAME_BLACKLIST = []
data = {
"username": user.username,
"email": "contact@funkwhale.io",
"password1": "testtest",
"password2": "testtest",
}
response = api_client.post(url, data)
assert response.status_code == 400
assert "username" in response.data
def test_signup_with_approval_enabled(
preferences, factories, api_client, mocker, mailoutbox, settings
):
url = reverse("rest_register")
data = {
"username": "test1",
"email": "test1@test.com",
"password1": "thisismypassword",
"password2": "thisismypassword",
"request_fields": {"field1": "Value 1", "field2": "Value 2", "noop": "Noop"},
}
preferences["users__registration_enabled"] = True
preferences["moderation__signup_approval_enabled"] = True
preferences["moderation__signup_form_customization"] = {
"fields": [
{"label": "field1", "input_type": "short_text"},
{"label": "field2", "input_type": "short_text"},
]
}
on_commit = mocker.patch("funkwhale_api.common.utils.on_commit")
response = api_client.post(url, data, format="json")
assert response.status_code == 201
u = User.objects.get(email="test1@test.com")
assert u.username == "test1"
assert u.is_active is False
user_request = u.actor.requests.latest("id")
assert user_request.type == "signup"
assert user_request.status == "pending"
assert user_request.metadata == {
"field1": "Value 1",
"field2": "Value 2",
}
on_commit.assert_any_call(
moderation_tasks.user_request_handle.delay,
user_request_id=user_request.pk,
new_status="pending",
)
confirmation_message = mailoutbox[-1]
assert "confirm" in confirmation_message.body
assert settings.FUNKWHALE_HOSTNAME in confirmation_message.body
def test_signup_with_approval_enabled_validation_error(
preferences, factories, api_client
):
url = reverse("rest_register")
data = {
"username": "test1",
"email": "test1@test.com",
"password1": "thisismypassword",
"password2": "thisismypassword",
"request_fields": {"field1": "Value 1"},
}
preferences["users__registration_enabled"] = True
preferences["moderation__signup_approval_enabled"] = True
preferences["moderation__signup_form_customization"] = {
"fields": [
{"label": "field1", "input_type": "short_text"},
{"label": "field2", "input_type": "short_text"},
]
}
response = api_client.post(url, data, format="json")
assert response.status_code == 400
def test_login_via_api(api_client, factories):
user = factories["users.User"]()
url = reverse("api:v1:users:login")
payload = {"username": user.username, "password": "test"}
response = api_client.post(url, payload)
assert response.status_code == 200
assert api_client.session["_auth_user_id"] == str(user.pk)
def test_login_via_api_inactive(api_client, factories):
user = factories["users.User"](is_active=False)
url = reverse("api:v1:users:login")
payload = {"username": user.username, "password": "test"}
response = api_client.post(url, payload)
assert response.status_code == 400
def test_login_via_api_no_csrf(factories):
user = factories["users.User"]()
url = reverse("api:v1:users:login")
payload = {"username": user.username, "password": "test"}
api_client = Client(enforce_csrf_checks=True)
response = api_client.post(url, payload)
assert response.status_code == 403
def test_logout(api_client, factories, mocker):
auth_logout = mocker.patch("django.contrib.auth.logout")
url = reverse("api:v1:users:logout")
response = api_client.post(url)
assert response.status_code == 200
assert auth_logout.call_count == 1
def test_update_settings(logged_in_api_client, factories):
logged_in_api_client.user.set_settings(foo="bar")
url = reverse("api:v1:users:users-settings")
payload = {"theme": "dark"}
response = logged_in_api_client.post(url, payload, format="json")
assert response.status_code == 200
logged_in_api_client.user.refresh_from_db()
assert logged_in_api_client.user.settings == {"foo": "bar", "theme": "dark"}
def test_user_change_email_requires_valid_password(logged_in_api_client):
url = reverse("api:v1:users:users-change-email")
payload = {"password": "invalid", "email": "test@new.email"}
response = logged_in_api_client.post(url, payload)
assert response.status_code == 400
def test_user_change_email(logged_in_api_client, mocker, mailoutbox):
user = logged_in_api_client.user
user.set_password("mypassword")
url = reverse("api:v1:users:users-change-email")
payload = {"password": "mypassword", "email": "test@new.email"}
response = logged_in_api_client.post(url, payload)
address = user.emailaddress_set.latest("id")
assert address.email == payload["email"]
assert address.verified is False
assert response.status_code == 204
assert len(mailoutbox) == 1