From 127ec63d76a55ae98731baf48cb431f5555f9921 Mon Sep 17 00:00:00 2001 From: Erik Kalkoken Date: Thu, 2 Apr 2020 03:01:27 +0000 Subject: [PATCH] Add sorting to group view and add tests to group management --- allianceauth/authentication/admin.py | 2 +- allianceauth/authentication/tests/__init__.py | 11 +- .../authentication/tests/test_admin.py | 44 ++- allianceauth/groupmanagement/managers.py | 70 ++-- .../groupmanagement/tests/test_all.py | 107 ------ .../groupmanagement/tests/test_managers.py | 337 ++++++++++++++++++ .../groupmanagement/tests/test_models.py | 167 +++++++++ .../groupmanagement/tests/test_signals.py | 61 ++++ .../groupmanagement/tests/test_views.py | 22 ++ allianceauth/groupmanagement/views.py | 31 +- allianceauth/tests/auth_utils.py | 139 +++++++- allianceauth/tests/test_auth_utils.py | 60 ++++ 12 files changed, 884 insertions(+), 167 deletions(-) delete mode 100644 allianceauth/groupmanagement/tests/test_all.py create mode 100644 allianceauth/groupmanagement/tests/test_managers.py create mode 100644 allianceauth/groupmanagement/tests/test_models.py create mode 100644 allianceauth/groupmanagement/tests/test_signals.py create mode 100644 allianceauth/groupmanagement/tests/test_views.py create mode 100644 allianceauth/tests/test_auth_utils.py diff --git a/allianceauth/authentication/admin.py b/allianceauth/authentication/admin.py index 9f2ce766..1c1b26c0 100644 --- a/allianceauth/authentication/admin.py +++ b/allianceauth/authentication/admin.py @@ -506,7 +506,7 @@ class BaseOwnershipAdmin(admin.ModelAdmin): 'character', ) search_fields = ( - 'user__user', + 'user__username', 'character__character_name', 'character__corporation_name', 'character__alliance_name' diff --git a/allianceauth/authentication/tests/__init__.py b/allianceauth/authentication/tests/__init__.py index c886d262..ba343c6d 100644 --- a/allianceauth/authentication/tests/__init__.py +++ b/allianceauth/authentication/tests/__init__.py @@ -2,10 +2,17 @@ from django.urls import reverse def get_admin_change_view_url(obj: object) -> str: + """returns URL to admin change view for given object""" return reverse( 'admin:{}_{}_change'.format( - obj._meta.app_label, - type(obj).__name__.lower() + obj._meta.app_label, type(obj).__name__.lower() ), args=(obj.pk,) + ) + +def get_admin_search_url(ModelClass: type) -> str: + """returns URL to search URL for model of given object""" + return '{}{}/'.format( + reverse('admin:app_list', args=(ModelClass._meta.app_label,)), + ModelClass.__name__.lower() ) \ No newline at end of file diff --git a/allianceauth/authentication/tests/test_admin.py b/allianceauth/authentication/tests/test_admin.py index 21b65ef2..bbf83c84 100644 --- a/allianceauth/authentication/tests/test_admin.py +++ b/allianceauth/authentication/tests/test_admin.py @@ -1,3 +1,4 @@ +from urllib.parse import quote from unittest.mock import patch from django.conf import settings @@ -6,8 +7,9 @@ from django.contrib.admin.sites import AdminSite from django.contrib.auth.models import User as BaseUser, Group from django.test import TestCase, RequestFactory, Client -from allianceauth.authentication.models import CharacterOwnership, State, \ - OwnershipRecord +from allianceauth.authentication.models import ( + CharacterOwnership, State, OwnershipRecord +) from allianceauth.eveonline.models import ( EveCharacter, EveCorporationInfo, EveAllianceInfo ) @@ -28,7 +30,7 @@ from ..admin import ( user_username, update_main_character_model ) -from . import get_admin_change_view_url +from . import get_admin_change_view_url, get_admin_search_url if 'allianceauth.eveonline.autogroups' in settings.INSTALLED_APPS: _has_auto_groups = True @@ -175,6 +177,17 @@ def create_test_data(): return user_1, user_2, user_3, group_1, group_2 +def make_generic_search_request(ModelClass: type, search_term: str): + User.objects.create_superuser( + username='superuser', password='secret', email='admin@example.com' + ) + c = Client() + c.login(username='superuser', password='secret') + return c.get( + '%s?q=%s' % (get_admin_search_url(ModelClass), quote(search_term)) + ) + + class TestCharacterOwnershipAdmin(TestCase): @classmethod @@ -197,6 +210,14 @@ class TestCharacterOwnershipAdmin(TestCase): response = c.get(get_admin_change_view_url(ownership)) self.assertEqual(response.status_code, 200) + def test_search_works(self): + obj = CharacterOwnership.objects\ + .filter(user=self.user_1)\ + .first() + response = make_generic_search_request(type(obj), obj.user.username) + expected = 200 + self.assertEqual(response.status_code, expected) + class TestOwnershipRecordAdmin(TestCase): @@ -222,6 +243,12 @@ class TestOwnershipRecordAdmin(TestCase): response = c.get(get_admin_change_view_url(ownership_record)) self.assertEqual(response.status_code, 200) + def test_search_works(self): + obj = OwnershipRecord.objects.first() + response = make_generic_search_request(type(obj), obj.user.username) + expected = 200 + self.assertEqual(response.status_code, expected) + class TestStateAdmin(TestCase): @@ -250,6 +277,11 @@ class TestStateAdmin(TestCase): response = c.get(get_admin_change_view_url(member_state)) self.assertEqual(response.status_code, 200) + def test_search_works(self): + obj = State.objects.first() + response = make_generic_search_request(type(obj), obj.name) + expected = 200 + self.assertEqual(response.status_code, expected) class TestUserAdmin(TestCase): @@ -541,3 +573,9 @@ class TestUserAdmin(TestCase): c.login(username='superuser', password='secret') response = c.get(get_admin_change_view_url(self.user_1)) self.assertEqual(response.status_code, 200) + + def test_search_works(self): + obj = User.objects.first() + response = make_generic_search_request(type(obj), obj.username) + expected = 200 + self.assertEqual(response.status_code, expected) \ No newline at end of file diff --git a/allianceauth/groupmanagement/managers.py b/allianceauth/groupmanagement/managers.py index c85ba84b..89696364 100644 --- a/allianceauth/groupmanagement/managers.py +++ b/allianceauth/groupmanagement/managers.py @@ -1,27 +1,53 @@ -from django.contrib.auth.models import Group -from django.db.models import Q +import logging + +from django.contrib.auth.models import Group, User +from django.db.models import Q, QuerySet + +from allianceauth.authentication.models import State + + +logger = logging.getLogger(__name__) class GroupManager: - def __init__(self): - pass + + @classmethod + def get_joinable_groups_for_user( + cls, user: User, include_hidden = True + ) -> QuerySet: + """get groups a user could join incl. groups already joined""" + groups_qs = cls.get_joinable_groups(user.profile.state) + + if not user.has_perm('groupmanagement.request_groups'): + groups_qs = groups_qs.filter(authgroup__public=True) + + if not include_hidden: + groups_qs = groups_qs.filter(authgroup__hidden=False) + + return groups_qs @staticmethod - def get_joinable_groups(state): - return Group.objects.select_related('authgroup').exclude(authgroup__internal=True)\ + def get_joinable_groups(state: State) -> QuerySet: + """get groups that can be joined by user with given state""" + return Group.objects\ + .select_related('authgroup')\ + .exclude(authgroup__internal=True)\ .filter(Q(authgroup__states=state) | Q(authgroup__states=None)) @staticmethod - def get_all_non_internal_groups(): - return Group.objects.select_related('authgroup').exclude(authgroup__internal=True) + def get_all_non_internal_groups() -> QuerySet: + """get groups that are not internal""" + return Group.objects\ + .select_related('authgroup')\ + .exclude(authgroup__internal=True) @staticmethod - def get_group_leaders_groups(user): + def get_group_leaders_groups(user: User): return Group.objects.select_related('authgroup').filter(authgroup__group_leaders__in=[user]) | \ Group.objects.select_related('authgroup').filter(authgroup__group_leader_groups__in=user.groups.all()) @staticmethod - def joinable_group(group, state): + def joinable_group(group: Group, state: State) -> bool: """ Check if a group is a user/state joinable group, i.e. not an internal group for Corp, Alliance, Members etc, @@ -30,12 +56,15 @@ class GroupManager: :param state: allianceauth.authentication.State object :return: bool True if its joinable, False otherwise """ - if len(group.authgroup.states.all()) != 0 and state not in group.authgroup.states.all(): + if (len(group.authgroup.states.all()) != 0 + and state not in group.authgroup.states.all() + ): return False - return not group.authgroup.internal + else: + return not group.authgroup.internal @staticmethod - def check_internal_group(group): + def check_internal_group(group: Group) -> bool: """ Check if a group is auditable, i.e not an internal group :param group: django.contrib.auth.models.Group object @@ -44,20 +73,11 @@ class GroupManager: return not group.authgroup.internal @staticmethod - def check_internal_group(group): - """ - Check if a group is auditable, i.e not an internal group - :param group: django.contrib.auth.models.Group object - :return: bool True if it is auditable, false otherwise - """ - return not group.authgroup.internal - - @staticmethod - def has_management_permission(user): + def has_management_permission(user: User) -> bool: return user.has_perm('auth.group_management') @classmethod - def can_manage_groups(cls, user): + def can_manage_groups(cls, user:User ) -> bool: """ For use with user_passes_test decorator. Check if the user can manage groups. Either has the @@ -71,7 +91,7 @@ class GroupManager: return False @classmethod - def can_manage_group(cls, user, group): + def can_manage_group(cls, user: User, group: Group) -> bool: """ Check user has permission to manage the given group :param user: User object to test permission of diff --git a/allianceauth/groupmanagement/tests/test_all.py b/allianceauth/groupmanagement/tests/test_all.py deleted file mode 100644 index 47fdf4f8..00000000 --- a/allianceauth/groupmanagement/tests/test_all.py +++ /dev/null @@ -1,107 +0,0 @@ -from unittest import mock - -from django.test import TestCase -from allianceauth.tests.auth_utils import AuthUtils -from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo, EveCharacter -from django.contrib.auth.models import User, Group -from allianceauth.groupmanagement.managers import GroupManager -from allianceauth.groupmanagement.signals import check_groups_on_state_change - -class GroupManagementVisibilityTestCase(TestCase): - @classmethod - def setUpTestData(cls): - cls.user = AuthUtils.create_user('test') - AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST') - cls.user.profile.refresh_from_db() - cls.alliance = EveAllianceInfo.objects.create(alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2') - cls.corp = EveCorporationInfo.objects.create(corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1) - cls.group1 = Group.objects.create(name='group1') - cls.group2 = Group.objects.create(name='group2') - cls.group3 = Group.objects.create(name='group3') - - def setUp(self): - self.user.refresh_from_db() - - def _refresh_user(self): - self.user = User.objects.get(pk=self.user.pk) - - - def test_get_group_leaders_groups(self): - self.group1.authgroup.group_leaders.add(self.user) - self.group2.authgroup.group_leader_groups.add(self.group1) - self._refresh_user() - groups = GroupManager.get_group_leaders_groups(self.user) - - self.assertIn(self.group1, groups) #avail due to user - self.assertNotIn(self.group2, groups) #not avail due to group - self.assertNotIn(self.group3, groups) #not avail at all - - self.user.groups.add(self.group1) - self._refresh_user() - groups = GroupManager.get_group_leaders_groups(self.user) - - self.assertIn(self.group1, groups) #avail due to user - self.assertIn(self.group2, groups) #avail due to group1 - self.assertNotIn(self.group3, groups) #not avail at all - - def test_can_manage_group(self): - self.group1.authgroup.group_leaders.add(self.user) - self.user.groups.add(self.group1) - self._refresh_user() - - self.assertTrue(GroupManager.can_manage_group(self.user, self.group1)) - self.assertFalse(GroupManager.can_manage_group(self.user, self.group2)) - self.assertFalse(GroupManager.can_manage_group(self.user, self.group3)) - - self.group2.authgroup.group_leader_groups.add(self.group1) - self.group1.authgroup.group_leaders.remove(self.user) - self._refresh_user() - - self.assertFalse(GroupManager.can_manage_group(self.user, self.group1)) - self.assertTrue(GroupManager.can_manage_group(self.user, self.group2)) - self.assertFalse(GroupManager.can_manage_group(self.user, self.group3)) - - -class GroupManagementStateTestCase(TestCase): - @classmethod - def setUpTestData(cls): - cls.user = AuthUtils.create_user('test') - AuthUtils.add_main_character(cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST') - cls.user.profile.refresh_from_db() - cls.alliance = EveAllianceInfo.objects.create(alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2') - cls.corp = EveCorporationInfo.objects.create(corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1) - cls.state_group = Group.objects.create(name='state_group') - cls.open_group = Group.objects.create(name='open_group') - cls.state = AuthUtils.create_state('test state', 500) - cls.state_group.authgroup.states.add(cls.state) - cls.state_group.authgroup.internal = False - cls.state_group.save() - - def setUp(self): - self.user.refresh_from_db() - self.state.refresh_from_db() - - def _refresh_user(self): - self.user = User.objects.get(pk=self.user.pk) - - def _refresh_test_group(self): - self.state_group = Group.objects.get(pk=self.state_group.pk) - - def test_drop_state_group(self): - self.user.groups.add(self.open_group) - self.user.groups.add(self.state_group) - self.assertEqual(self.user.profile.state.name, "Guest") - - self.state.member_corporations.add(self.corp) - self._refresh_user() - self.assertEqual(self.user.profile.state, self.state) - groups = self.user.groups.all() - self.assertIn(self.state_group, groups) #keeps group - self.assertIn(self.open_group, groups) #public group unafected - - self.state.member_corporations.clear() - self._refresh_user() - self.assertEqual(self.user.profile.state.name, "Guest") - groups = self.user.groups.all() - self.assertNotIn(self.state_group, groups) #looses group - self.assertIn(self.open_group, groups) #public group unafected diff --git a/allianceauth/groupmanagement/tests/test_managers.py b/allianceauth/groupmanagement/tests/test_managers.py new file mode 100644 index 00000000..3dcc7831 --- /dev/null +++ b/allianceauth/groupmanagement/tests/test_managers.py @@ -0,0 +1,337 @@ +from unittest.mock import Mock, patch + +from django.contrib.auth.models import Group, User +from django.test import TestCase +from django.urls import reverse + +from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo +from allianceauth.tests.auth_utils import AuthUtils + +from ..models import AuthGroup +from ..managers import GroupManager + + +class MockUserNotAuthenticated(): + def __init__(self): + self.is_authenticated = False + +class GroupManagementVisibilityTestCase(TestCase): + @classmethod + def setUpTestData(cls): + cls.user = AuthUtils.create_user('test') + AuthUtils.add_main_character( + cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST' + ) + cls.user.profile.refresh_from_db() + cls.alliance = EveAllianceInfo.objects.create(alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2') + cls.corp = EveCorporationInfo.objects.create( + corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1 + ) + cls.group1 = Group.objects.create(name='group1') + cls.group2 = Group.objects.create(name='group2') + cls.group3 = Group.objects.create(name='group3') + + def setUp(self): + self.user.refresh_from_db() + + def _refresh_user(self): + self.user = User.objects.get(pk=self.user.pk) + + + def test_get_group_leaders_groups(self): + self.group1.authgroup.group_leaders.add(self.user) + self.group2.authgroup.group_leader_groups.add(self.group1) + self._refresh_user() + groups = GroupManager.get_group_leaders_groups(self.user) + + self.assertIn(self.group1, groups) #avail due to user + self.assertNotIn(self.group2, groups) #not avail due to group + self.assertNotIn(self.group3, groups) #not avail at all + + self.user.groups.add(self.group1) + self._refresh_user() + groups = GroupManager.get_group_leaders_groups(self.user) + + + def test_can_manage_group(self): + self.group1.authgroup.group_leaders.add(self.user) + self.user.groups.add(self.group1) + self._refresh_user() + + self.assertTrue(GroupManager.can_manage_group(self.user, self.group1)) + self.assertFalse(GroupManager.can_manage_group(self.user, self.group2)) + self.assertFalse(GroupManager.can_manage_group(self.user, self.group3)) + + self.group2.authgroup.group_leader_groups.add(self.group1) + self.group1.authgroup.group_leaders.remove(self.user) + self._refresh_user() + + self.assertFalse(GroupManager.can_manage_group(self.user, self.group1)) + self.assertTrue(GroupManager.can_manage_group(self.user, self.group2)) + self.assertFalse(GroupManager.can_manage_group(self.user, self.group3)) + + +class TestGroupManager(TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + + # group 1 + cls.group_default = Group.objects.create(name='default') + cls.group_default.authgroup.description = 'Default Group' + cls.group_default.authgroup.internal = False + cls.group_default.authgroup.hidden = False + cls.group_default.authgroup.save() + + # group 2 + cls.group_internal = Group.objects.create(name='internal') + cls.group_internal.authgroup.description = 'Internal Group' + cls.group_internal.authgroup.internal = True + cls.group_internal.authgroup.save() + + # group 3 + cls.group_hidden = Group.objects.create(name='hidden') + cls.group_hidden.authgroup.description = 'Hidden Group' + cls.group_hidden.authgroup.internal = False + cls.group_hidden.authgroup.hidden = True + cls.group_hidden.authgroup.save() + + # group 4 + cls.group_open = Group.objects.create(name='open') + cls.group_open.authgroup.description = 'Open Group' + cls.group_open.authgroup.internal = False + cls.group_open.authgroup.hidden = False + cls.group_open.authgroup.open = True + cls.group_open.authgroup.save() + + # group 5 + cls.group_public_1 = Group.objects.create(name='public 1') + cls.group_public_1.authgroup.description = 'Public Group 1' + cls.group_public_1.authgroup.internal = False + cls.group_public_1.authgroup.hidden = False + cls.group_public_1.authgroup.public = True + cls.group_public_1.authgroup.save() + + # group 6 + cls.group_public_2 = Group.objects.create(name='public 2') + cls.group_public_2.authgroup.description = 'Public Group 2' + cls.group_public_2.authgroup.internal = False + cls.group_public_2.authgroup.hidden = True + cls.group_public_2.authgroup.open = True + cls.group_public_2.authgroup.public = True + cls.group_public_2.authgroup.save() + + # group 7 + cls.group_default_member = Group.objects.create(name='default members') + cls.group_default_member.authgroup.description = \ + 'Default Group for members only' + cls.group_default_member.authgroup.internal = False + cls.group_default_member.authgroup.hidden = False + cls.group_default_member.authgroup.open = False + cls.group_default_member.authgroup.public = False + cls.group_default_member.authgroup.states.add( + AuthUtils.get_member_state() + ) + cls.group_default_member.authgroup.save() + + def setUp(self): + self.user = AuthUtils.create_user('Bruce Wayne') + + def test_get_joinable_group_member(self): + result = GroupManager.get_joinable_groups( + AuthUtils.get_member_state() + ) + expected = { + self.group_default, + self.group_hidden, + self.group_open, + self.group_public_1, + self.group_public_2, + self.group_default_member + } + self.assertSetEqual(set(result), expected) + + def test_get_joinable_group_guest(self): + result = GroupManager.get_joinable_groups( + AuthUtils.get_guest_state() + ) + expected = { + self.group_default, + self.group_hidden, + self.group_open, + self.group_public_1, + self.group_public_2 + } + self.assertSetEqual(set(result), expected) + + def test_joinable_group_member(self): + member_state = AuthUtils.get_member_state() + for x in [ + self.group_default, + self.group_hidden, + self.group_open, + self.group_public_1, + self.group_public_2, + self.group_default_member + ]: + self.assertTrue(GroupManager.joinable_group(x, member_state)) + + for x in [ + self.group_internal, + ]: + self.assertFalse(GroupManager.joinable_group(x, member_state)) + + + def test_joinable_group_guest(self): + guest_state = AuthUtils.get_guest_state() + for x in [ + self.group_default, + self.group_hidden, + self.group_open, + self.group_public_1, + self.group_public_2 + ]: + self.assertTrue(GroupManager.joinable_group(x, guest_state)) + + for x in [ + self.group_internal, + self.group_default_member + ]: + self.assertFalse(GroupManager.joinable_group(x, guest_state)) + + + def test_get_all_non_internal_groups(self): + result = GroupManager.get_all_non_internal_groups() + expected = { + self.group_default, + self.group_hidden, + self.group_open, + self.group_public_1, + self.group_public_2, + self.group_default_member + } + self.assertSetEqual(set(result), expected) + + def test_check_internal_group(self): + self.assertTrue( + GroupManager.check_internal_group(self.group_default) + ) + self.assertFalse( + GroupManager.check_internal_group(self.group_internal) + ) + + def test_get_joinable_groups_for_user_no_permission(self): + AuthUtils.assign_state(self.user, AuthUtils.get_guest_state()) + result = GroupManager.get_joinable_groups_for_user(self.user) + expected= {self.group_public_1, self.group_public_2} + self.assertSetEqual(set(result), expected) + + def test_get_joinable_groups_for_user_guest_w_permission_(self): + AuthUtils.assign_state(self.user, AuthUtils.get_guest_state()) + AuthUtils.add_permission_to_user_by_name( + 'groupmanagement.request_groups', self.user + ) + result = GroupManager.get_joinable_groups_for_user(self.user) + expected = { + self.group_default, + self.group_hidden, + self.group_open, + self.group_public_1, + self.group_public_2 + } + self.assertSetEqual(set(result), expected) + + def test_get_joinable_groups_for_user_member_w_permission(self): + AuthUtils.assign_state(self.user, AuthUtils.get_member_state(), True) + AuthUtils.add_permission_to_user_by_name( + 'groupmanagement.request_groups', self.user + ) + result = GroupManager.get_joinable_groups_for_user(self.user) + expected = { + self.group_default, + self.group_hidden, + self.group_open, + self.group_public_1, + self.group_public_2, + self.group_default_member + } + self.assertSetEqual(set(result), expected) + + def test_get_joinable_groups_for_user_member_w_permission_no_hidden(self): + AuthUtils.assign_state(self.user, AuthUtils.get_member_state(), True) + AuthUtils.add_permission_to_user_by_name( + 'groupmanagement.request_groups', self.user + ) + result = GroupManager.get_joinable_groups_for_user( + self.user, include_hidden=False + ) + expected = { + self.group_default, + self.group_open, + self.group_public_1, + self.group_default_member + } + self.assertSetEqual(set(result), expected) + + def test_has_management_permission(self): + user = AuthUtils.create_user('Clark Kent') + AuthUtils.add_permission_to_user_by_name( + 'auth.group_management', user + ) + self.assertTrue(GroupManager.has_management_permission(user)) + + def test_can_manage_groups_no_perm_no_group(self): + user = AuthUtils.create_user('Clark Kent') + self.assertFalse(GroupManager.can_manage_groups(user)) + + def test_can_manage_groups_user_not_authenticated(self): + user = MockUserNotAuthenticated() + self.assertFalse(GroupManager.can_manage_groups(user)) + + def test_can_manage_groups_has_perm(self): + user = AuthUtils.create_user('Clark Kent') + AuthUtils.add_permission_to_user_by_name( + 'auth.group_management', user + ) + self.assertTrue(GroupManager.can_manage_groups(user)) + + def test_can_manage_groups_no_perm_leads_group(self): + user = AuthUtils.create_user('Clark Kent') + self.group_default.authgroup.group_leaders.add(user) + self.assertTrue(GroupManager.can_manage_groups(user)) + + def test_can_manage_group_no_perm_no_group(self): + user = AuthUtils.create_user('Clark Kent') + self.assertFalse( + GroupManager.can_manage_group(user, self.group_default) + ) + + def test_can_manage_group_has_perm(self): + user = AuthUtils.create_user('Clark Kent') + AuthUtils.add_permission_to_user_by_name( + 'auth.group_management', user + ) + self.assertTrue( + GroupManager.can_manage_group(user, self.group_default) + ) + + def test_can_manage_group_no_perm_leads_correct_group(self): + user = AuthUtils.create_user('Clark Kent') + self.group_default.authgroup.group_leaders.add(user) + self.assertTrue( + GroupManager.can_manage_group(user, self.group_default) + ) + + def test_can_manage_group_no_perm_leads_other_group(self): + user = AuthUtils.create_user('Clark Kent') + self.group_hidden.authgroup.group_leaders.add(user) + self.assertFalse( + GroupManager.can_manage_group(user, self.group_default) + ) + + def test_can_manage_group_user_not_authenticated(self): + user = MockUserNotAuthenticated() + self.assertFalse( + GroupManager.can_manage_group(user, self.group_default) + ) diff --git a/allianceauth/groupmanagement/tests/test_models.py b/allianceauth/groupmanagement/tests/test_models.py new file mode 100644 index 00000000..01511f97 --- /dev/null +++ b/allianceauth/groupmanagement/tests/test_models.py @@ -0,0 +1,167 @@ +from unittest import mock + +from django.contrib.auth.models import User, Group +from django.test import TestCase + +from allianceauth.tests.auth_utils import AuthUtils +from allianceauth.eveonline.models import ( + EveCorporationInfo, EveAllianceInfo, EveCharacter +) + +from ..models import GroupRequest, RequestLog + + +def create_testdata(): + # clear DB + User.objects.all().delete() + Group.objects.all().delete() + EveCharacter.objects.all().delete() + EveCorporationInfo.objects.all().delete() + EveAllianceInfo.objects.all().delete() + + # group 1 + group = Group.objects.create(name='Superheros') + group.authgroup.description = 'Default Group' + group.authgroup.internal = False + group.authgroup.hidden = False + group.authgroup.save() + + # user 1 + user_1 = AuthUtils.create_user('Bruce Wayne') + AuthUtils.add_main_character_2( + user_1, + name='Bruce Wayne', + character_id=1001, + corp_id=2001, + corp_name='Wayne Technologies' + ) + user_1.groups.add(group) + group.authgroup.group_leaders.add(user_1) + + # user 2 + user_2 = AuthUtils.create_user('Clark Kent') + AuthUtils.add_main_character_2( + user_2, + name='Clark Kent', + character_id=1002, + corp_id=2002, + corp_name='Wayne Technologies' + ) + return group, user_1, user_2 + + + +class TestGroupRequest(TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.group, cls.user_1, _ = create_testdata() + + def test_main_char(self): + group_request = GroupRequest.objects.create( + status='Pending', + user=self.user_1, + group=self.group + ) + expected = self.user_1.profile.main_character + self.assertEqual(group_request.main_char, expected) + + def test_str(self): + group_request = GroupRequest.objects.create( + status='Pending', + user=self.user_1, + group=self.group + ) + expected = 'Bruce Wayne:Superheros' + self.assertEqual(str(group_request), expected) + + +class TestRequestLog(TestCase): + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.group, cls.user_1, cls.user_2 = create_testdata() + + def test_requestor(self): + request_log = RequestLog.objects.create( + group=self.group, + request_info='Clark Kent:Superheros', + request_actor=self.user_1 + ) + expected = 'Clark Kent' + self.assertEqual(request_log.requestor(), expected) + + def test_type_to_str_removed(self): + request_log = RequestLog.objects.create( + request_type=None, + group=self.group, + request_info='Clark Kent:Superheros', + request_actor=self.user_1 + ) + expected = 'Removed' + self.assertEqual(request_log.type_to_str(), expected) + + def test_type_to_str_leave(self): + request_log = RequestLog.objects.create( + request_type=True, + group=self.group, + request_info='Clark Kent:Superheros', + request_actor=self.user_1 + ) + expected = 'Leave' + self.assertEqual(request_log.type_to_str(), expected) + + def test_type_to_str_join(self): + request_log = RequestLog.objects.create( + request_type=False, + group=self.group, + request_info='Clark Kent:Superheros', + request_actor=self.user_1 + ) + expected = 'Join' + self.assertEqual(request_log.type_to_str(), expected) + + def test_action_to_str_accept(self): + request_log = RequestLog.objects.create( + group=self.group, + request_info='Clark Kent:Superheros', + request_actor=self.user_1, + action = True + ) + expected = 'Accept' + self.assertEqual(request_log.action_to_str(), expected) + + def test_action_to_str_reject(self): + request_log = RequestLog.objects.create( + group=self.group, + request_info='Clark Kent:Superheros', + request_actor=self.user_1, + action = False + ) + expected = 'Reject' + self.assertEqual(request_log.action_to_str(), expected) + + def test_req_char(self): + request_log = RequestLog.objects.create( + group=self.group, + request_info='Clark Kent:Superheros', + request_actor=self.user_1, + action = False + ) + expected = self.user_2.profile.main_character + self.assertEqual(request_log.req_char(), expected) + + +class TestAuthGroup(TestCase): + + def test_str(self): + group = Group.objects.create(name='Superheros') + group.authgroup.description = 'Default Group' + group.authgroup.internal = False + group.authgroup.hidden = False + group.authgroup.save() + + expected = 'Superheros' + self.assertEqual(str(group.authgroup), expected) diff --git a/allianceauth/groupmanagement/tests/test_signals.py b/allianceauth/groupmanagement/tests/test_signals.py new file mode 100644 index 00000000..6c553e17 --- /dev/null +++ b/allianceauth/groupmanagement/tests/test_signals.py @@ -0,0 +1,61 @@ +from unittest import mock + +from django.test import TestCase +from django.contrib.auth.models import User, Group + +from allianceauth.eveonline.models import EveCorporationInfo, EveAllianceInfo +from allianceauth.tests.auth_utils import AuthUtils + +from ..signals import check_groups_on_state_change + + +class GroupManagementStateTestCase(TestCase): + + @classmethod + def setUpTestData(cls): + cls.user = AuthUtils.create_user('test') + AuthUtils.add_main_character( + cls.user, 'test character', '1', corp_id='2', corp_name='test_corp', corp_ticker='TEST', alliance_id='3', alliance_name='TEST' + ) + cls.user.profile.refresh_from_db() + cls.alliance = EveAllianceInfo.objects.create( + alliance_id='3', alliance_name='test alliance', alliance_ticker='TEST', executor_corp_id='2' + ) + cls.corp = EveCorporationInfo.objects.create( + corporation_id='2', corporation_name='test corp', corporation_ticker='TEST', alliance=cls.alliance, member_count=1 + ) + cls.state_group = Group.objects.create(name='state_group') + cls.open_group = Group.objects.create(name='open_group') + cls.state = AuthUtils.create_state('test state', 500) + cls.state_group.authgroup.states.add(cls.state) + cls.state_group.authgroup.internal = False + cls.state_group.save() + + def setUp(self): + self.user.refresh_from_db() + self.state.refresh_from_db() + + def _refresh_user(self): + self.user = User.objects.get(pk=self.user.pk) + + def _refresh_test_group(self): + self.state_group = Group.objects.get(pk=self.state_group.pk) + + def test_drop_state_group(self): + self.user.groups.add(self.open_group) + self.user.groups.add(self.state_group) + self.assertEqual(self.user.profile.state.name, "Guest") + + self.state.member_corporations.add(self.corp) + self._refresh_user() + self.assertEqual(self.user.profile.state, self.state) + groups = self.user.groups.all() + self.assertIn(self.state_group, groups) #keeps group + self.assertIn(self.open_group, groups) #public group unafected + + self.state.member_corporations.clear() + self._refresh_user() + self.assertEqual(self.user.profile.state.name, "Guest") + groups = self.user.groups.all() + self.assertNotIn(self.state_group, groups) #looses group + self.assertIn(self.open_group, groups) #public group unafected diff --git a/allianceauth/groupmanagement/tests/test_views.py b/allianceauth/groupmanagement/tests/test_views.py new file mode 100644 index 00000000..258a79b9 --- /dev/null +++ b/allianceauth/groupmanagement/tests/test_views.py @@ -0,0 +1,22 @@ +from unittest.mock import Mock, patch + +from django.test import RequestFactory, TestCase +from django.urls import reverse + +from allianceauth.tests.auth_utils import AuthUtils +from esi.models import Token + +from .. import views + + +class TestViews(TestCase): + + def setUp(self): + self.factory = RequestFactory() + self.user = AuthUtils.create_user('Bruce Wayne') + + def test_groups_view_can_load(self): + request = self.factory.get(reverse('groupmanagement:groups')) + request.user = self.user + response = views.groups_view(request) + self.assertEqual(response.status_code, 200) \ No newline at end of file diff --git a/allianceauth/groupmanagement/views.py b/allianceauth/groupmanagement/views.py index 1ce2092e..31c66f30 100755 --- a/allianceauth/groupmanagement/views.py +++ b/allianceauth/groupmanagement/views.py @@ -315,24 +315,23 @@ def group_leave_reject_request(request, group_request_id): @login_required def groups_view(request): logger.debug("groups_view called by user %s" % request.user) + + groups_qs = GroupManager.get_joinable_groups_for_user( + request.user, include_hidden=False + ) + groups_qs = groups_qs.order_by('name') groups = [] + for group in groups_qs: + group_request = GroupRequest.objects\ + .filter(user=request.user)\ + .filter(group=group) + groups.append({ + 'group': group, + 'request': group_request[0] if group_request else None + }) - group_query = GroupManager.get_joinable_groups(request.user.profile.state) - - if not request.user.has_perm('groupmanagement.request_groups'): - # Filter down to public groups only for non-members - group_query = group_query.filter(authgroup__public=True) - logger.debug("Not a member, only public groups will be available") - - for group in group_query: - # Exclude hidden - if not group.authgroup.hidden: - group_request = GroupRequest.objects.filter(user=request.user).filter(group=group) - - groups.append({'group': group, 'request': group_request[0] if group_request else None}) - - render_items = {'groups': groups} - return render(request, 'groupmanagement/groups.html', context=render_items) + context = {'groups': groups} + return render(request, 'groupmanagement/groups.html', context=context) @login_required diff --git a/allianceauth/tests/auth_utils.py b/allianceauth/tests/auth_utils.py index 08a10f19..83d8efe7 100644 --- a/allianceauth/tests/auth_utils.py +++ b/allianceauth/tests/auth_utils.py @@ -1,27 +1,45 @@ -from allianceauth.authentication.models import UserProfile, State, get_guest_state -from allianceauth.authentication.signals import state_member_alliances_changed, state_member_characters_changed, \ - state_member_corporations_changed, state_saved -from django.contrib.auth.models import User, Group +from django.contrib.auth.models import User, Group, Permission from django.db.models.signals import m2m_changed, pre_save, post_save from django.test import TestCase -from esi.models import Token -from allianceauth.eveonline.models import EveCharacter -from allianceauth.services.signals import m2m_changed_group_permissions, m2m_changed_user_permissions, \ - m2m_changed_state_permissions -from allianceauth.services.signals import m2m_changed_user_groups, disable_services_on_inactive +from esi.models import Token + +from allianceauth.authentication.models import ( + UserProfile, State, get_guest_state +) +from allianceauth.eveonline.models import EveCharacter +from allianceauth.authentication.signals import ( + state_member_alliances_changed, + state_member_characters_changed, + state_member_corporations_changed, + state_saved, + reassess_on_profile_save, + assign_state_on_active_change, + check_state_on_character_update +) +from allianceauth.services.signals import ( + m2m_changed_group_permissions, + m2m_changed_user_permissions, + m2m_changed_state_permissions, + m2m_changed_user_groups, disable_services_on_inactive +) class AuthUtils: - def __init__(self): - pass + """Utilities for making it easier to create tests for Alliance Auth""" @staticmethod - def _create_user(username): + def _create_user(username): return User.objects.create(username=username) @classmethod def create_user(cls, username, disconnect_signals=False): + """create a new user + + username: Name of the user + + disconnect_signals: whether to run process without signals + """ if disconnect_signals: cls.disconnect_signals() user = cls._create_user(username) @@ -95,6 +113,11 @@ class AuthUtils: m2m_changed.disconnect(state_member_characters_changed, sender=State.member_characters.through) m2m_changed.disconnect(state_member_alliances_changed, sender=State.member_alliances.through) post_save.disconnect(state_saved, sender=State) + post_save.disconnect(reassess_on_profile_save, sender=UserProfile) + pre_save.disconnect(assign_state_on_active_change, sender=User) + post_save.disconnect( + check_state_on_character_update, sender=EveCharacter + ) @classmethod def connect_signals(cls): @@ -107,6 +130,9 @@ class AuthUtils: m2m_changed.connect(state_member_characters_changed, sender=State.member_characters.through) m2m_changed.connect(state_member_alliances_changed, sender=State.member_alliances.through) post_save.connect(state_saved, sender=State) + post_save.connect(reassess_on_profile_save, sender=UserProfile) + pre_save.connect(assign_state_on_active_change, sender=User) + post_save.connect(check_state_on_character_update, sender=EveCharacter) @classmethod def add_main_character(cls, user, name, character_id, corp_id='', corp_name='', corp_ticker='', alliance_id='', @@ -122,6 +148,40 @@ class AuthUtils: ) UserProfile.objects.update_or_create(user=user, defaults={'main_character': char}) + @classmethod + def add_main_character_2( + cls, + user, + name, + character_id, + corp_id='', + corp_name='', + corp_ticker='', + alliance_id='', + alliance_name='', + disconnect_signals=False + ): + """new version that works in all cases""" + if disconnect_signals: + cls.disconnect_signals() + + char = EveCharacter.objects.create( + character_id=character_id, + character_name=name, + corporation_id=corp_id, + corporation_name=corp_name, + corporation_ticker=corp_ticker, + alliance_id=alliance_id, + alliance_name=alliance_name, + ) + user.profile.main_character = char + user.profile.save() + + if disconnect_signals: + cls.connect_signals() + + return char + @classmethod def add_permissions_to_groups(cls, perms, groups, disconnect_signals=True): if disconnect_signals: @@ -130,14 +190,67 @@ class AuthUtils: for group in groups: for perm in perms: group.permissions.add(perm) + group = Group.objects.get(pk=group.pk) # reload permission cache if disconnect_signals: cls.connect_signals() @classmethod def add_permissions_to_state(cls, perms, states, disconnect_signals=True): - return cls.add_permissions_to_groups(perms, states, disconnect_signals=disconnect_signals) + return cls.add_permissions_to_groups( + perms, states, disconnect_signals=disconnect_signals + ) + @classmethod + def add_permissions_to_user(cls, perms, user, disconnect_signals=True): + """add list of permissions to user + + perms: list of Permission objects + + user: user object + + disconnect_signals: whether to run process without signals + """ + if disconnect_signals: + cls.disconnect_signals() + + for perm in perms: + user.user_permissions.add(perm) + + user = User.objects.get(pk=user.pk) # reload permission cache + if disconnect_signals: + cls.connect_signals() + + @classmethod + def add_permission_to_user_by_name( + cls, perm, user, disconnect_signals=True + ): + """returns permission specified by qualified name + + perm: Permission name as 'app_label.codename' + + user: user object + + disconnect_signals: whether to run process without signals + """ + p = cls.get_permission_by_name(perm) + cls.add_permissions_to_user([p], user, disconnect_signals) + + @staticmethod + def get_permission_by_name(perm: str) -> Permission: + """returns permission specified by qualified name + + perm: Permission name as 'app_label.codename' + + Returns: Permission object or throws exception if not found + """ + perm_parts = perm.split('.') + if len(perm_parts) != 2: + raise ValueError('Invalid format for permission name') + + return Permission.objects.get( + content_type__app_label=perm_parts[0], codename=perm_parts[1] + ) class BaseViewTestCase(TestCase): def setUp(self): diff --git a/allianceauth/tests/test_auth_utils.py b/allianceauth/tests/test_auth_utils.py new file mode 100644 index 00000000..dc90a51b --- /dev/null +++ b/allianceauth/tests/test_auth_utils.py @@ -0,0 +1,60 @@ +from unittest import mock + +from django.contrib.auth.models import User, Group, Permission +from django.test import TestCase + +from allianceauth.eveonline.models import ( + EveCorporationInfo, EveAllianceInfo, EveCharacter +) + +from .auth_utils import AuthUtils + + +class TestAuthUtils(TestCase): + + def test_can_create_user(self): + user = AuthUtils.create_user('Bruce Wayne') + self.assertTrue(User.objects.filter(username='Bruce Wayne').exists()) + + def test_can_add_main_character_2(self): + user = AuthUtils.create_user('Bruce Wayne') + character = AuthUtils.add_main_character_2( + user, + name='Bruce Wayne', + character_id=1001, + corp_id=2001, + corp_name='Wayne Technologies', + corp_ticker='WYT', + alliance_id=3001, + alliance_name='Wayne Enterprises' + ) + expected = character + self.assertEqual(user.profile.main_character, expected) + + def test_can_add_permission_to_group(self): + group = Group.objects.create(name='Dummy Group') + p = AuthUtils.get_permission_by_name('auth.group_management') + AuthUtils.add_permissions_to_groups([p], [group]) + self.assertTrue(group.permissions.filter(pk=p.pk).exists()) + + def test_can_add_permission_to_user_by_name(self): + user = AuthUtils.create_user('Bruce Wayne') + AuthUtils.add_permission_to_user_by_name( + 'auth.timer_management', user + ) + self.assertTrue(user.has_perm('auth.timer_management')) + + +class TestGetPermissionByName(TestCase): + + def test_can_get_permission_by_name(self): + expected = Permission.objects.get( + content_type__app_label='auth', codename='timer_management' + ) + self.assertEqual( + AuthUtils.get_permission_by_name('auth.timer_management'), expected + ) + + def test_raises_exception_on_invalid_permission_format(self): + with self.assertRaises(ValueError): + AuthUtils.get_permission_by_name('timer_management') \ No newline at end of file