mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2026-08-27 21:23:20 +00:00
745 lines
23 KiB
Python
745 lines
23 KiB
Python
from unittest.mock import Mock
|
|
|
|
from django.contrib.auth.models import Group
|
|
from django.contrib.auth.models import User
|
|
from django.http import HttpRequest
|
|
from django.test import TestCase
|
|
from django.test import override_settings
|
|
|
|
from documents.models import UiSettings
|
|
from paperless.signals import handle_failed_login
|
|
from paperless.signals import handle_social_account_updated
|
|
|
|
|
|
class TestFailedLoginLogging(TestCase):
|
|
def setUp(self) -> None:
|
|
super().setUp()
|
|
|
|
self.creds = {
|
|
"username": "john lennon",
|
|
}
|
|
|
|
def test_unauthenticated(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Request with no authentication provided
|
|
WHEN:
|
|
- Request provided to signal handler
|
|
THEN:
|
|
- Unable to determine logged for unauthenticated user
|
|
"""
|
|
request = HttpRequest()
|
|
request.META = {}
|
|
with self.assertLogs("paperless.auth") as logs:
|
|
handle_failed_login(None, {}, request)
|
|
self.assertEqual(
|
|
logs.output,
|
|
[
|
|
"INFO:paperless.auth:No authentication provided. Unable to determine IP address.",
|
|
],
|
|
)
|
|
|
|
def test_none(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Request with no IP possible
|
|
WHEN:
|
|
- Request provided to signal handler
|
|
THEN:
|
|
- Unable to determine logged
|
|
"""
|
|
request = HttpRequest()
|
|
request.META = {}
|
|
with self.assertLogs("paperless.auth") as logs:
|
|
handle_failed_login(None, self.creds, request)
|
|
|
|
self.assertEqual(
|
|
logs.output,
|
|
[
|
|
"INFO:paperless.auth:Login failed for user `john lennon`. Unable to determine IP address.",
|
|
],
|
|
)
|
|
|
|
def test_public(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Request with publicly routeable IP
|
|
WHEN:
|
|
- Request provided to signal handler
|
|
THEN:
|
|
- Expected IP is logged
|
|
"""
|
|
request = HttpRequest()
|
|
request.META = {
|
|
"HTTP_X_FORWARDED_FOR": "177.139.233.139",
|
|
}
|
|
with self.assertLogs("paperless.auth") as logs:
|
|
handle_failed_login(None, self.creds, request)
|
|
|
|
self.assertEqual(
|
|
logs.output,
|
|
[
|
|
"INFO:paperless.auth:Login failed for user `john lennon` from IP `177.139.233.139`.",
|
|
],
|
|
)
|
|
|
|
def test_private(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Request with private range IP
|
|
WHEN:
|
|
- Request provided to signal handler
|
|
THEN:
|
|
- Expected IP is logged
|
|
- IP is noted to be a private IP
|
|
"""
|
|
request = HttpRequest()
|
|
request.META = {
|
|
"HTTP_X_FORWARDED_FOR": "10.0.0.1",
|
|
}
|
|
with self.assertLogs("paperless.auth") as logs:
|
|
handle_failed_login(None, self.creds, request)
|
|
|
|
self.assertEqual(
|
|
logs.output,
|
|
[
|
|
"INFO:paperless.auth:Login failed for user `john lennon` from private IP `10.0.0.1`.",
|
|
],
|
|
)
|
|
|
|
|
|
class TestSyncSocialLoginGroups(TestCase):
|
|
@override_settings(SOCIAL_ACCOUNT_SYNC_GROUPS=True)
|
|
def test_sync_enabled(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Enabled group syncing, a user, and a social login
|
|
WHEN:
|
|
- The social login is updated via signal after login
|
|
THEN:
|
|
- The user's groups are updated to match the social login's groups
|
|
"""
|
|
group = Group.objects.create(name="group1")
|
|
user = User.objects.create_user(username="testuser")
|
|
sociallogin = Mock(
|
|
user=user,
|
|
account=Mock(
|
|
extra_data={
|
|
"groups": ["group1"],
|
|
},
|
|
),
|
|
)
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin,
|
|
)
|
|
self.assertEqual(list(user.groups.all()), [group])
|
|
|
|
@override_settings(SOCIAL_ACCOUNT_SYNC_GROUPS=False)
|
|
def test_sync_disabled(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Disabled group syncing, a user, and a social login
|
|
WHEN:
|
|
- The social login is updated via signal after login
|
|
THEN:
|
|
- The user's groups are not updated
|
|
"""
|
|
Group.objects.create(name="group1")
|
|
user = User.objects.create_user(username="testuser")
|
|
sociallogin = Mock(
|
|
user=user,
|
|
account=Mock(
|
|
extra_data={
|
|
"groups": ["group1"],
|
|
},
|
|
),
|
|
)
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin,
|
|
)
|
|
self.assertEqual(list(user.groups.all()), [])
|
|
|
|
@override_settings(
|
|
SOCIAL_ACCOUNT_SYNC_GROUPS=True,
|
|
SOCIAL_ACCOUNT_SYNC_SUPERUSER_GROUP="admin-group",
|
|
SOCIAL_ACCOUNT_SYNC_STAFF_GROUP="staff-group",
|
|
)
|
|
def test_no_sync_for_inactive_user(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Enabled group, superuser, and staff syncing
|
|
- A deactivated user with a matching social login
|
|
WHEN:
|
|
- The social login is updated via signal
|
|
THEN:
|
|
- Groups and roles are left untouched, since the login itself
|
|
would be rejected for a deactivated user anyway
|
|
"""
|
|
Group.objects.create(name="admin-group")
|
|
user = User.objects.create_user(
|
|
username="inactive_user",
|
|
is_active=False,
|
|
is_superuser=False,
|
|
is_staff=False,
|
|
)
|
|
sociallogin = Mock(
|
|
user=user,
|
|
account=Mock(
|
|
extra_data={
|
|
"groups": ["admin-group", "staff-group"],
|
|
},
|
|
),
|
|
)
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin,
|
|
)
|
|
user.refresh_from_db()
|
|
self.assertEqual(list(user.groups.all()), [])
|
|
self.assertFalse(user.is_superuser)
|
|
self.assertFalse(user.is_staff)
|
|
|
|
@override_settings(SOCIAL_ACCOUNT_SYNC_GROUPS=True)
|
|
def test_no_groups(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Enabled group syncing, a user, and a social login with no groups
|
|
WHEN:
|
|
- The social login is updated via signal after login
|
|
THEN:
|
|
- The user's groups are cleared to match the social login's groups
|
|
"""
|
|
group = Group.objects.create(name="group1")
|
|
user = User.objects.create_user(username="testuser")
|
|
user.groups.add(group)
|
|
user.save()
|
|
sociallogin = Mock(
|
|
user=user,
|
|
account=Mock(
|
|
extra_data={
|
|
"groups": [],
|
|
},
|
|
),
|
|
)
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin,
|
|
)
|
|
self.assertEqual(list(user.groups.all()), [])
|
|
|
|
@override_settings(SOCIAL_ACCOUNT_SYNC_GROUPS=True)
|
|
def test_userinfo_groups(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Enabled group syncing, and `groups` nested under `userinfo`
|
|
WHEN:
|
|
- The social login is updated via signal after login
|
|
THEN:
|
|
- The user's groups are updated using `userinfo.groups`
|
|
"""
|
|
group = Group.objects.create(name="group1")
|
|
user = User.objects.create_user(username="testuser")
|
|
sociallogin = Mock(
|
|
user=user,
|
|
account=Mock(
|
|
extra_data={
|
|
"userinfo": {
|
|
"groups": ["group1"],
|
|
},
|
|
},
|
|
),
|
|
)
|
|
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin,
|
|
)
|
|
|
|
self.assertEqual(list(user.groups.all()), [group])
|
|
|
|
@override_settings(SOCIAL_ACCOUNT_SYNC_GROUPS=True)
|
|
def test_id_token_groups_fallback(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Enabled group syncing, and `groups` only under `id_token`
|
|
WHEN:
|
|
- The social login is updated via signal after login
|
|
THEN:
|
|
- The user's groups are updated using `id_token.groups`
|
|
"""
|
|
group = Group.objects.create(name="group1")
|
|
user = User.objects.create_user(username="testuser")
|
|
sociallogin = Mock(
|
|
user=user,
|
|
account=Mock(
|
|
extra_data={
|
|
"id_token": {
|
|
"groups": ["group1"],
|
|
},
|
|
},
|
|
),
|
|
)
|
|
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin,
|
|
)
|
|
|
|
self.assertEqual(list(user.groups.all()), [group])
|
|
|
|
@override_settings(
|
|
SOCIAL_ACCOUNT_SYNC_SUPERUSER_GROUP="admin",
|
|
SOCIAL_ACCOUNT_SYNC_STAFF_GROUP="admin",
|
|
)
|
|
def test_sync_superuser_claim_no_substring_match(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Configured superuser group sync
|
|
- Provider emits the groups claim as a bare string, and the user's
|
|
only group merely *contains* the configured name
|
|
WHEN:
|
|
- Social login updated via signal
|
|
THEN:
|
|
- User is not promoted, since only an exact group match counts
|
|
"""
|
|
user = User.objects.create_user(
|
|
username="testuser",
|
|
is_superuser=False,
|
|
is_staff=False,
|
|
)
|
|
sociallogin = Mock(
|
|
user=user,
|
|
account=Mock(
|
|
extra_data={
|
|
"groups": "paperless-admins-readonly",
|
|
},
|
|
),
|
|
)
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin,
|
|
)
|
|
user.refresh_from_db()
|
|
self.assertFalse(user.is_superuser)
|
|
self.assertFalse(user.is_staff)
|
|
|
|
@override_settings(
|
|
SOCIAL_ACCOUNT_SYNC_SUPERUSER_GROUP="admin-group",
|
|
SOCIAL_ACCOUNT_SYNC_STAFF_GROUP=None,
|
|
)
|
|
def test_sync_superuser_enabled(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Configured superuser group sync, and user with that group
|
|
WHEN:
|
|
- Social login updated via signal
|
|
THEN:
|
|
- User becomes superuser and staff
|
|
"""
|
|
user = User.objects.create_user(
|
|
username="testuser_s_e",
|
|
is_superuser=False,
|
|
is_staff=False,
|
|
)
|
|
sociallogin = Mock(
|
|
user=user,
|
|
account=Mock(
|
|
extra_data={
|
|
"groups": ["admin-group"],
|
|
},
|
|
),
|
|
)
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin,
|
|
)
|
|
user.refresh_from_db()
|
|
self.assertTrue(user.is_superuser)
|
|
self.assertTrue(user.is_staff)
|
|
|
|
@override_settings(
|
|
SOCIAL_ACCOUNT_SYNC_SUPERUSER_GROUP="admin-group",
|
|
SOCIAL_ACCOUNT_SYNC_STAFF_GROUP=None,
|
|
)
|
|
def test_sync_superuser_disabled(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Configured superuser group sync, and user without that group
|
|
WHEN:
|
|
- Social login updated via signal
|
|
THEN:
|
|
- User loses superuser status but preserves staff status if they had it
|
|
"""
|
|
user = User.objects.create_user(
|
|
username="testuser_s_d",
|
|
is_superuser=True,
|
|
is_staff=True,
|
|
)
|
|
sociallogin = Mock(
|
|
user=user,
|
|
account=Mock(
|
|
extra_data={
|
|
"groups": ["other-group"],
|
|
},
|
|
),
|
|
)
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin,
|
|
)
|
|
user.refresh_from_db()
|
|
self.assertFalse(user.is_superuser)
|
|
self.assertTrue(user.is_staff)
|
|
|
|
@override_settings(
|
|
SOCIAL_ACCOUNT_SYNC_SUPERUSER_GROUP=None,
|
|
SOCIAL_ACCOUNT_SYNC_STAFF_GROUP="staff-group",
|
|
)
|
|
def test_sync_staff_enabled(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Configured staff group sync, and user with that group
|
|
WHEN:
|
|
- Social login updated via signal
|
|
THEN:
|
|
- User becomes staff
|
|
"""
|
|
user = User.objects.create_user(
|
|
username="testuser_st_e",
|
|
is_superuser=False,
|
|
is_staff=False,
|
|
)
|
|
sociallogin = Mock(
|
|
user=user,
|
|
account=Mock(
|
|
extra_data={
|
|
"groups": ["staff-group"],
|
|
},
|
|
),
|
|
)
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin,
|
|
)
|
|
user.refresh_from_db()
|
|
self.assertTrue(user.is_staff)
|
|
self.assertFalse(user.is_superuser)
|
|
|
|
@override_settings(
|
|
SOCIAL_ACCOUNT_SYNC_SUPERUSER_GROUP=None,
|
|
SOCIAL_ACCOUNT_SYNC_STAFF_GROUP="staff-group",
|
|
)
|
|
def test_sync_staff_disabled(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Configured staff group sync, and user without that group
|
|
WHEN:
|
|
- Social login updated via signal
|
|
THEN:
|
|
- User loses staff status
|
|
"""
|
|
user = User.objects.create_user(
|
|
username="testuser_st_d",
|
|
is_superuser=False,
|
|
is_staff=True,
|
|
)
|
|
sociallogin = Mock(
|
|
user=user,
|
|
account=Mock(
|
|
extra_data={
|
|
"groups": ["other-group"],
|
|
},
|
|
),
|
|
)
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin,
|
|
)
|
|
user.refresh_from_db()
|
|
self.assertFalse(user.is_staff)
|
|
|
|
@override_settings(
|
|
SOCIAL_ACCOUNT_SYNC_SUPERUSER_GROUP="admin-group",
|
|
SOCIAL_ACCOUNT_SYNC_STAFF_GROUP="staff-group",
|
|
)
|
|
def test_sync_both_groups(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Configured both superuser and staff group sync
|
|
WHEN:
|
|
- Social login updated via signal
|
|
THEN:
|
|
- Roles are correctly assigned/revoked according to groups
|
|
"""
|
|
# Case 1: has both
|
|
user = User.objects.create_user(
|
|
username="testuser_b_1",
|
|
is_superuser=False,
|
|
is_staff=False,
|
|
)
|
|
sociallogin = Mock(
|
|
user=user,
|
|
account=Mock(extra_data={"groups": ["admin-group", "staff-group"]}),
|
|
)
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin,
|
|
)
|
|
user.refresh_from_db()
|
|
self.assertTrue(user.is_superuser)
|
|
self.assertTrue(user.is_staff)
|
|
|
|
# Case 2: has only staff
|
|
user2 = User.objects.create_user(
|
|
username="testuser_b_2",
|
|
is_superuser=True,
|
|
is_staff=True,
|
|
)
|
|
sociallogin2 = Mock(
|
|
user=user2,
|
|
account=Mock(extra_data={"groups": ["staff-group"]}),
|
|
)
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin2,
|
|
)
|
|
user2.refresh_from_db()
|
|
self.assertFalse(user2.is_superuser)
|
|
self.assertTrue(user2.is_staff)
|
|
|
|
# Case 3: has neither
|
|
user3 = User.objects.create_user(
|
|
username="testuser_b_3",
|
|
is_superuser=True,
|
|
is_staff=True,
|
|
)
|
|
sociallogin3 = Mock(
|
|
user=user3,
|
|
account=Mock(extra_data={"groups": ["other-group"]}),
|
|
)
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin3,
|
|
)
|
|
user3.refresh_from_db()
|
|
self.assertFalse(user3.is_superuser)
|
|
self.assertFalse(user3.is_staff)
|
|
|
|
@override_settings(
|
|
SOCIAL_ACCOUNT_SYNC_SUPERUSER_GROUP=None,
|
|
SOCIAL_ACCOUNT_SYNC_STAFF_GROUP=None,
|
|
)
|
|
def test_no_sync_when_not_configured(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- No sync settings configured
|
|
WHEN:
|
|
- Social login updated via signal
|
|
THEN:
|
|
- Existing roles are not modified
|
|
"""
|
|
user = User.objects.create_user(
|
|
username="testuser_n_s",
|
|
is_superuser=True,
|
|
is_staff=True,
|
|
)
|
|
sociallogin = Mock(
|
|
user=user,
|
|
account=Mock(extra_data={"groups": ["admin-group", "staff-group"]}),
|
|
)
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin,
|
|
)
|
|
user.refresh_from_db()
|
|
self.assertTrue(user.is_superuser)
|
|
self.assertTrue(user.is_staff)
|
|
|
|
@override_settings(
|
|
SOCIAL_ACCOUNT_SYNC_SUPERUSER_GROUP="admin-group",
|
|
SOCIAL_ACCOUNT_SYNC_STAFF_GROUP=None,
|
|
)
|
|
def test_sync_superuser_demotes_local_user_without_group(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Configured superuser group sync
|
|
- User with a usable (local) password, but without the group
|
|
WHEN:
|
|
- Social login updated via signal
|
|
THEN:
|
|
- User's superuser status is demoted, matching the group claim exactly
|
|
"""
|
|
user = User.objects.create_user(
|
|
username="local_admin",
|
|
password="password123",
|
|
is_superuser=True,
|
|
is_staff=True,
|
|
)
|
|
sociallogin = Mock(
|
|
user=user,
|
|
account=Mock(extra_data={"groups": ["other-group"]}),
|
|
)
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin,
|
|
)
|
|
user.refresh_from_db()
|
|
self.assertFalse(user.is_superuser)
|
|
|
|
@override_settings(
|
|
SOCIAL_ACCOUNT_SYNC_SUPERUSER_GROUP="admin-group",
|
|
SOCIAL_ACCOUNT_SYNC_STAFF_GROUP=None,
|
|
)
|
|
def test_sync_superuser_demotes_last_admin(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Configured superuser group sync
|
|
- User without the group, and no other active superuser exists
|
|
WHEN:
|
|
- Social login updated via signal
|
|
THEN:
|
|
- User's superuser status is demoted, even though they are the last admin
|
|
"""
|
|
user = User.objects.create_user(
|
|
username="last_admin",
|
|
is_superuser=True,
|
|
is_staff=True,
|
|
)
|
|
user.set_unusable_password()
|
|
user.save()
|
|
|
|
sociallogin = Mock(
|
|
user=user,
|
|
account=Mock(extra_data={"groups": ["other-group"]}),
|
|
)
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin,
|
|
)
|
|
user.refresh_from_db()
|
|
self.assertFalse(user.is_superuser)
|
|
|
|
@override_settings(
|
|
SOCIAL_ACCOUNT_SYNC_SUPERUSER_GROUP=None,
|
|
SOCIAL_ACCOUNT_SYNC_STAFF_GROUP="staff-group",
|
|
)
|
|
def test_sync_staff_demotes_local_user_without_group(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Configured staff group sync
|
|
- User with a usable (local) password, but without the group
|
|
WHEN:
|
|
- Social login updated via signal
|
|
THEN:
|
|
- User's staff status is demoted, matching the group claim exactly
|
|
"""
|
|
user = User.objects.create_user(
|
|
username="local_staff",
|
|
password="password123",
|
|
is_superuser=False,
|
|
is_staff=True,
|
|
)
|
|
sociallogin = Mock(
|
|
user=user,
|
|
account=Mock(extra_data={"groups": ["other-group"]}),
|
|
)
|
|
handle_social_account_updated(
|
|
sender=None,
|
|
request=HttpRequest(),
|
|
sociallogin=sociallogin,
|
|
)
|
|
user.refresh_from_db()
|
|
self.assertFalse(user.is_staff)
|
|
|
|
|
|
class TestUserGroupDeletionCleanup(TestCase):
|
|
"""
|
|
Test that when a user or group is deleted, references are cleaned up properly
|
|
from ui_settings
|
|
"""
|
|
|
|
def test_user_group_deletion_cleanup(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Existing user
|
|
- Existing group
|
|
WHEN:
|
|
- The user is deleted
|
|
- The group is deleted
|
|
THEN:
|
|
- References in ui_settings are cleaned up
|
|
"""
|
|
user = User.objects.create_user(username="testuser")
|
|
user2 = User.objects.create_user(username="testuser2")
|
|
group = Group.objects.create(name="testgroup")
|
|
|
|
ui_settings = UiSettings.objects.create(
|
|
user=user,
|
|
settings={
|
|
"permissions": {
|
|
"default_owner": user2.id,
|
|
"default_view_users": [user2.id],
|
|
"default_change_users": [user2.id],
|
|
"default_view_groups": [group.id],
|
|
"default_change_groups": [group.id],
|
|
},
|
|
},
|
|
)
|
|
|
|
user2.delete()
|
|
ui_settings.refresh_from_db()
|
|
permissions = ui_settings.settings.get("permissions", {})
|
|
self.assertIsNone(permissions.get("default_owner"))
|
|
self.assertEqual(permissions.get("default_view_users"), [])
|
|
self.assertEqual(permissions.get("default_change_users"), [])
|
|
|
|
group.delete()
|
|
ui_settings.refresh_from_db()
|
|
permissions = ui_settings.settings.get("permissions", {})
|
|
self.assertEqual(permissions.get("default_view_groups"), [])
|
|
self.assertEqual(permissions.get("default_change_groups"), [])
|
|
|
|
def test_user_group_deletion_error_handling(self) -> None:
|
|
"""
|
|
GIVEN:
|
|
- Existing user and group
|
|
WHEN:
|
|
- The user is deleted and an error occurs during the signal handling
|
|
THEN:
|
|
- Error is logged and the system remains stable
|
|
"""
|
|
user = User.objects.create_user(username="testuser")
|
|
user2 = User.objects.create_user(username="testuser2")
|
|
user2_id = user2.id
|
|
Group.objects.create(name="testgroup")
|
|
|
|
UiSettings.objects.create(
|
|
user=user,
|
|
) # invalid, no settings, this probably should not happen in production
|
|
|
|
with self.assertLogs("paperless.handlers", level="ERROR") as cm:
|
|
user2.delete()
|
|
self.assertIn(
|
|
f"Error while cleaning up user {user2_id}",
|
|
cm.output[0],
|
|
)
|