diff --git a/allianceauth/__init__.py b/allianceauth/__init__.py index c523a536..4fd57563 100644 --- a/allianceauth/__init__.py +++ b/allianceauth/__init__.py @@ -1,7 +1,7 @@ # This will make sure the app is always imported when # Django starts so that shared_task will use this app. -__version__ = '2.7.3a1' +__version__ = '2.7.3a2' __title__ = 'Alliance Auth' __url__ = 'https://gitlab.com/allianceauth/allianceauth' NAME = '%s v%s' % (__title__, __version__) diff --git a/allianceauth/authentication/models.py b/allianceauth/authentication/models.py index 320e130b..c71721ef 100755 --- a/allianceauth/authentication/models.py +++ b/allianceauth/authentication/models.py @@ -75,8 +75,8 @@ class UserProfile(models.Model): self.save(update_fields=['state']) notify( self.user, - _('State Changed'), - _('Your user state has been changed to %(state)s') + _('State changed to: %s' % state), + _('Your user\'s state is now: %(state)s') % ({'state': state}), 'info' ) diff --git a/allianceauth/authentication/templates/authentication/dashboard.html b/allianceauth/authentication/templates/authentication/dashboard.html index c7a0adbf..18f57d1f 100644 --- a/allianceauth/authentication/templates/authentication/dashboard.html +++ b/allianceauth/authentication/templates/authentication/dashboard.html @@ -14,7 +14,11 @@
-

{% trans "Main Character" %}

+

+ {% blocktrans with state=request.user.profile.state %} + Main Character (State: {{ state }}) + {% endblocktrans %} +

{% if request.user.profile.main_character %} diff --git a/allianceauth/authentication/tests/test_backend.py b/allianceauth/authentication/tests/test_backend.py index ef541684..2a846c7c 100644 --- a/allianceauth/authentication/tests/test_backend.py +++ b/allianceauth/authentication/tests/test_backend.py @@ -1,10 +1,13 @@ -from unittest.mock import Mock, patch - from django.contrib.auth.models import User, Group from django.test import TestCase + +from allianceauth.eveonline.models import EveCharacter from allianceauth.tests.auth_utils import AuthUtils +from esi.models import Token + from ..backends import StateBackend +from ..models import CharacterOwnership, UserProfile, OwnershipRecord MODULE_PATH = 'allianceauth.authentication' @@ -76,10 +79,71 @@ class TestStatePermissions(TestCase): self.assertTrue(user.has_perm(PERMISSION_2)) +class TestAuthenticate(TestCase): + @classmethod + def setUpTestData(cls): + cls.main_character = EveCharacter.objects.create( + character_id=1, + character_name='Main Character', + corporation_id=1, + corporation_name='Corp', + corporation_ticker='CORP', + ) + cls.alt_character = EveCharacter.objects.create( + character_id=2, + character_name='Alt Character', + corporation_id=1, + corporation_name='Corp', + corporation_ticker='CORP', + ) + cls.unclaimed_character = EveCharacter.objects.create( + character_id=3, + character_name='Unclaimed Character', + corporation_id=1, + corporation_name='Corp', + corporation_ticker='CORP', + ) + cls.user = AuthUtils.create_user('test_user', disconnect_signals=True) + cls.old_user = AuthUtils.create_user('old_user', disconnect_signals=True) + AuthUtils.disconnect_signals() + CharacterOwnership.objects.create(user=cls.user, character=cls.main_character, owner_hash='1') + CharacterOwnership.objects.create(user=cls.user, character=cls.alt_character, owner_hash='2') + UserProfile.objects.update_or_create(user=cls.user, defaults={'main_character': cls.main_character}) + AuthUtils.connect_signals() + def test_authenticate_main_character(self): + t = Token(character_id=self.main_character.character_id, character_owner_hash='1') + user = StateBackend().authenticate(token=t) + self.assertEquals(user, self.user) + def test_authenticate_alt_character(self): + t = Token(character_id=self.alt_character.character_id, character_owner_hash='2') + user = StateBackend().authenticate(token=t) + self.assertEquals(user, self.user) + def test_authenticate_unclaimed_character(self): + t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='3') + user = StateBackend().authenticate(token=t) + self.assertNotEqual(user, self.user) + self.assertEqual(user.username, 'Unclaimed_Character') + self.assertEqual(user.profile.main_character, self.unclaimed_character) - + def test_authenticate_character_record(self): + t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4') + OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4') + user = StateBackend().authenticate(token=t) + self.assertEqual(user, self.old_user) + self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists()) + self.assertTrue(user.profile.main_character) - + def test_iterate_username(self): + t = Token(character_id=self.unclaimed_character.character_id, + character_name=self.unclaimed_character.character_name, character_owner_hash='3') + username = StateBackend().authenticate(token=t).username + t.character_owner_hash = '4' + username_1 = StateBackend().authenticate(token=t).username + t.character_owner_hash = '5' + username_2 = StateBackend().authenticate(token=t).username + self.assertNotEqual(username, username_1, username_2) + self.assertTrue(username_1.endswith('_1')) + self.assertTrue(username_2.endswith('_2')) diff --git a/allianceauth/authentication/tests/test_commands.py b/allianceauth/authentication/tests/test_commands.py new file mode 100644 index 00000000..5131183d --- /dev/null +++ b/allianceauth/authentication/tests/test_commands.py @@ -0,0 +1,35 @@ +from io import StringIO + +from django.core.management import call_command +from django.test import TestCase + +from allianceauth.tests.auth_utils import AuthUtils + +from ..models import CharacterOwnership, UserProfile + + +class ManagementCommandTestCase(TestCase): + @classmethod + def setUpTestData(cls): + cls.user = AuthUtils.create_user('test user', disconnect_signals=True) + AuthUtils.add_main_character(cls.user, 'test character', '1', '2', 'test corp', 'test') + character = UserProfile.objects.get(user=cls.user).main_character + CharacterOwnership.objects.create(user=cls.user, character=character, owner_hash='test') + + def setUp(self): + self.stdout = StringIO() + + def test_ownership(self): + call_command('checkmains', stdout=self.stdout) + self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count()) + self.assertNotIn(self.user.username, self.stdout.getvalue()) + self.assertIn('All main characters', self.stdout.getvalue()) + + def test_no_ownership(self): + user = AuthUtils.create_user('v1 user', disconnect_signals=True) + AuthUtils.add_main_character(user, 'v1 character', '10', '20', 'test corp', 'test') + self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count()) + + call_command('checkmains', stdout=self.stdout) + self.assertEqual(UserProfile.objects.filter(main_character__isnull=True).count(), 1) + self.assertIn(user.username, self.stdout.getvalue()) diff --git a/allianceauth/authentication/tests/test_decorators.py b/allianceauth/authentication/tests/test_decorators.py new file mode 100644 index 00000000..69c3949e --- /dev/null +++ b/allianceauth/authentication/tests/test_decorators.py @@ -0,0 +1,68 @@ +from unittest import mock +from urllib import parse + +from django.conf import settings +from django.contrib.auth.models import AnonymousUser +from django.http.response import HttpResponse +from django.shortcuts import reverse +from django.test import TestCase +from django.test.client import RequestFactory + +from allianceauth.eveonline.models import EveCharacter +from allianceauth.tests.auth_utils import AuthUtils + +from ..decorators import main_character_required +from ..models import CharacterOwnership + + +MODULE_PATH = 'allianceauth.authentication' + + +class DecoratorTestCase(TestCase): + @staticmethod + @main_character_required + def dummy_view(*args, **kwargs): + return HttpResponse(status=200) + + @classmethod + def setUpTestData(cls): + cls.main_user = AuthUtils.create_user('main_user', disconnect_signals=True) + cls.no_main_user = AuthUtils.create_user( + 'no_main_user', disconnect_signals=True + ) + main_character = EveCharacter.objects.create( + character_id=1, + character_name='Main Character', + corporation_id=1, + corporation_name='Corp', + corporation_ticker='CORP', + ) + CharacterOwnership.objects.create( + user=cls.main_user, character=main_character, owner_hash='1' + ) + cls.main_user.profile.main_character = main_character + + def setUp(self): + self.request = RequestFactory().get('/test/') + + @mock.patch(MODULE_PATH + '.decorators.messages') + def test_login_redirect(self, m): + setattr(self.request, 'user', AnonymousUser()) + response = self.dummy_view(self.request) + self.assertEqual(response.status_code, 302) + url = getattr(response, 'url', None) + self.assertEqual(parse.urlparse(url).path, reverse(settings.LOGIN_URL)) + + @mock.patch(MODULE_PATH + '.decorators.messages') + def test_main_character_redirect(self, m): + setattr(self.request, 'user', self.no_main_user) + response = self.dummy_view(self.request) + self.assertEqual(response.status_code, 302) + url = getattr(response, 'url', None) + self.assertEqual(url, reverse('authentication:dashboard')) + + @mock.patch(MODULE_PATH + '.decorators.messages') + def test_successful_request(self, m): + setattr(self.request, 'user', self.main_user) + response = self.dummy_view(self.request) + self.assertEqual(response.status_code, 200) diff --git a/allianceauth/authentication/tests/test_all.py b/allianceauth/authentication/tests/test_models.py similarity index 59% rename from allianceauth/authentication/tests/test_all.py rename to allianceauth/authentication/tests/test_models.py index 72aa6771..60338806 100644 --- a/allianceauth/authentication/tests/test_all.py +++ b/allianceauth/authentication/tests/test_models.py @@ -1,147 +1,20 @@ from unittest import mock -from io import StringIO -from urllib import parse -from django.conf import settings -from django.contrib.auth.models import AnonymousUser, User -from django.core.management import call_command -from django.http.response import HttpResponse -from django.shortcuts import reverse +from django.contrib.auth.models import User from django.test import TestCase -from django.test.client import RequestFactory - -from allianceauth.authentication.decorators import main_character_required from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo,\ EveAllianceInfo from allianceauth.tests.auth_utils import AuthUtils from esi.errors import IncompleteResponseError from esi.models import Token -from ..backends import StateBackend -from ..models import CharacterOwnership, UserProfile, State, get_guest_state,\ - OwnershipRecord +from ..models import CharacterOwnership, State, get_guest_state from ..tasks import check_character_ownership MODULE_PATH = 'allianceauth.authentication' -class DecoratorTestCase(TestCase): - @staticmethod - @main_character_required - def dummy_view(*args, **kwargs): - return HttpResponse(status=200) - - @classmethod - def setUpTestData(cls): - cls.main_user = AuthUtils.create_user('main_user', disconnect_signals=True) - cls.no_main_user = AuthUtils.create_user('no_main_user', disconnect_signals=True) - main_character = EveCharacter.objects.create( - character_id=1, - character_name='Main Character', - corporation_id=1, - corporation_name='Corp', - corporation_ticker='CORP', - ) - CharacterOwnership.objects.create(user=cls.main_user, character=main_character, owner_hash='1') - cls.main_user.profile.main_character = main_character - - def setUp(self): - self.request = RequestFactory().get('/test/') - - @mock.patch(MODULE_PATH + '.decorators.messages') - def test_login_redirect(self, m): - setattr(self.request, 'user', AnonymousUser()) - response = self.dummy_view(self.request) - self.assertEqual(response.status_code, 302) - url = getattr(response, 'url', None) - self.assertEqual(parse.urlparse(url).path, reverse(settings.LOGIN_URL)) - - @mock.patch(MODULE_PATH + '.decorators.messages') - def test_main_character_redirect(self, m): - setattr(self.request, 'user', self.no_main_user) - response = self.dummy_view(self.request) - self.assertEqual(response.status_code, 302) - url = getattr(response, 'url', None) - self.assertEqual(url, reverse('authentication:dashboard')) - - @mock.patch(MODULE_PATH + '.decorators.messages') - def test_successful_request(self, m): - setattr(self.request, 'user', self.main_user) - response = self.dummy_view(self.request) - self.assertEqual(response.status_code, 200) - - -class BackendTestCase(TestCase): - @classmethod - def setUpTestData(cls): - cls.main_character = EveCharacter.objects.create( - character_id=1, - character_name='Main Character', - corporation_id=1, - corporation_name='Corp', - corporation_ticker='CORP', - ) - cls.alt_character = EveCharacter.objects.create( - character_id=2, - character_name='Alt Character', - corporation_id=1, - corporation_name='Corp', - corporation_ticker='CORP', - ) - cls.unclaimed_character = EveCharacter.objects.create( - character_id=3, - character_name='Unclaimed Character', - corporation_id=1, - corporation_name='Corp', - corporation_ticker='CORP', - ) - cls.user = AuthUtils.create_user('test_user', disconnect_signals=True) - cls.old_user = AuthUtils.create_user('old_user', disconnect_signals=True) - AuthUtils.disconnect_signals() - CharacterOwnership.objects.create(user=cls.user, character=cls.main_character, owner_hash='1') - CharacterOwnership.objects.create(user=cls.user, character=cls.alt_character, owner_hash='2') - UserProfile.objects.update_or_create(user=cls.user, defaults={'main_character': cls.main_character}) - AuthUtils.connect_signals() - - def test_authenticate_main_character(self): - t = Token(character_id=self.main_character.character_id, character_owner_hash='1') - user = StateBackend().authenticate(token=t) - self.assertEquals(user, self.user) - - def test_authenticate_alt_character(self): - t = Token(character_id=self.alt_character.character_id, character_owner_hash='2') - user = StateBackend().authenticate(token=t) - self.assertEquals(user, self.user) - - def test_authenticate_unclaimed_character(self): - t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='3') - user = StateBackend().authenticate(token=t) - self.assertNotEqual(user, self.user) - self.assertEqual(user.username, 'Unclaimed_Character') - self.assertEqual(user.profile.main_character, self.unclaimed_character) - - def test_authenticate_character_record(self): - t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4') - record = OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4') - user = StateBackend().authenticate(token=t) - self.assertEqual(user, self.old_user) - self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists()) - self.assertTrue(user.profile.main_character) - - def test_iterate_username(self): - t = Token(character_id=self.unclaimed_character.character_id, - character_name=self.unclaimed_character.character_name, character_owner_hash='3') - username = StateBackend().authenticate(token=t).username - t.character_owner_hash = '4' - username_1 = StateBackend().authenticate(token=t).username - t.character_owner_hash = '5' - username_2 = StateBackend().authenticate(token=t).username - self.assertNotEqual(username, username_1, username_2) - self.assertTrue(username_1.endswith('_1')) - self.assertTrue(username_2.endswith('_2')) - - class CharacterOwnershipTestCase(TestCase): @classmethod def setUpTestData(cls): @@ -378,30 +251,3 @@ class CharacterOwnershipCheckTestCase(TestCase): filter.return_value.exists.return_value = False check_character_ownership(self.ownership) self.assertTrue(filter.return_value.delete.called) - - -class ManagementCommandTestCase(TestCase): - @classmethod - def setUpTestData(cls): - cls.user = AuthUtils.create_user('test user', disconnect_signals=True) - AuthUtils.add_main_character(cls.user, 'test character', '1', '2', 'test corp', 'test') - character = UserProfile.objects.get(user=cls.user).main_character - CharacterOwnership.objects.create(user=cls.user, character=character, owner_hash='test') - - def setUp(self): - self.stdout = StringIO() - - def test_ownership(self): - call_command('checkmains', stdout=self.stdout) - self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count()) - self.assertNotIn(self.user.username, self.stdout.getvalue()) - self.assertIn('All main characters', self.stdout.getvalue()) - - def test_no_ownership(self): - user = AuthUtils.create_user('v1 user', disconnect_signals=True) - AuthUtils.add_main_character(user, 'v1 character', '10', '20', 'test corp', 'test') - self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count()) - - call_command('checkmains', stdout=self.stdout) - self.assertEqual(UserProfile.objects.filter(main_character__isnull=True).count(), 1) - self.assertIn(user.username, self.stdout.getvalue()) diff --git a/allianceauth/services/modules/discord/auth_hooks.py b/allianceauth/services/modules/discord/auth_hooks.py index 9a30eb1e..46c266c6 100644 --- a/allianceauth/services/modules/discord/auth_hooks.py +++ b/allianceauth/services/modules/discord/auth_hooks.py @@ -33,7 +33,8 @@ class DiscordService(ServicesHook): if self.user_has_account(user): logger.debug('Deleting user %s %s account', user, self.name) tasks.delete_user.apply_async( - kwargs={'user_pk': user.pk}, priority=SINGLE_TASK_PRIORITY + kwargs={'user_pk': user.pk, 'notify_user': notify_user}, + priority=SINGLE_TASK_PRIORITY ) def render_services_ctrl(self, request): diff --git a/allianceauth/services/modules/discord/tasks.py b/allianceauth/services/modules/discord/tasks.py index 99514fcc..41de134b 100644 --- a/allianceauth/services/modules/discord/tasks.py +++ b/allianceauth/services/modules/discord/tasks.py @@ -6,7 +6,6 @@ from requests.exceptions import HTTPError from django.contrib.auth.models import User from django.db.models.query import QuerySet -from allianceauth.authentication.models import UserProfile from allianceauth.services.tasks import QueueOnce from . import __title__ diff --git a/allianceauth/services/modules/discord/tests/test_auth_hooks.py b/allianceauth/services/modules/discord/tests/test_auth_hooks.py index 0853d3df..4aeeae8e 100644 --- a/allianceauth/services/modules/discord/tests/test_auth_hooks.py +++ b/allianceauth/services/modules/discord/tests/test_auth_hooks.py @@ -3,6 +3,7 @@ from unittest.mock import patch from django.test import TestCase, RequestFactory from django.test.utils import override_settings +from allianceauth.notifications.models import Notification from allianceauth.tests.auth_utils import AuthUtils from . import TEST_USER_NAME, TEST_USER_ID, add_permissions_to_members, MODULE_PATH @@ -30,6 +31,7 @@ class TestDiscordService(TestCase): self.service = DiscordService add_permissions_to_members() self.factory = RequestFactory() + Notification.objects.all().delete() def test_service_enabled(self): service = self.service() @@ -89,16 +91,17 @@ class TestDiscordService(TestCase): service = self.service() service.sync_nicknames_bulk([self.member]) self.assertTrue(mock_update_nicknames_bulk.delay.called) - + @patch(MODULE_PATH + '.managers.DiscordClient', spec=DiscordClient) - def test_delete_user_is_member(self, mock_DiscordClient): + def test_delete_user_is_member(self, mock_DiscordClient): mock_DiscordClient.return_value.remove_guild_member.return_value = True - service = self.service() - service.delete_user(self.member) + service = self.service() + service.delete_user(self.member, notify_user=True) self.assertTrue(mock_DiscordClient.return_value.remove_guild_member.called) - self.assertFalse(DiscordUser.objects.filter(user=self.member).exists()) + self.assertFalse(DiscordUser.objects.filter(user=self.member).exists()) + self.assertTrue(Notification.objects.filter(user=self.member).exists()) @patch(MODULE_PATH + '.managers.DiscordClient', spec=DiscordClient) def test_delete_user_is_not_member(self, mock_DiscordClient): diff --git a/allianceauth/services/modules/discord/tests/test_integration.py b/allianceauth/services/modules/discord/tests/test_integration.py index 24f78bc4..908cf198 100644 --- a/allianceauth/services/modules/discord/tests/test_integration.py +++ b/allianceauth/services/modules/discord/tests/test_integration.py @@ -21,6 +21,7 @@ from django.test.utils import override_settings from allianceauth.authentication.models import State from allianceauth.eveonline.models import EveCharacter +from allianceauth.notifications.models import Notification from allianceauth.tests.auth_utils import AuthUtils from . import ( @@ -97,6 +98,7 @@ def reset_testdata(): State.objects.all().delete() EveCharacter.objects.all().delete() AuthUtils.connect_signals() + Notification.objects.all().delete() @patch(MODULE_PATH + '.models.DISCORD_GUILD_ID', TEST_GUILD_ID) @@ -109,9 +111,10 @@ class TestServiceFeatures(TransactionTestCase): super().setUpClass() cls.maxDiff = None - def setUp(self): - """All tests: Given a user with member state, service permission and active Discord account""" + """All tests: Given a user with member state, + service permission and active Discord account + """ clear_cache() reset_testdata() self.group_charlie = Group.objects.create(name='charlie') @@ -200,8 +203,7 @@ class TestServiceFeatures(TransactionTestCase): # should not have called the API requests_made = [ - requests_made.append(DiscordRequest(r.method, r.url)) - for r in requests_mocker.request_history + DiscordRequest(r.method, r.url) for r in requests_mocker.request_history ] self.assertListEqual(requests_made, list()) @@ -230,7 +232,10 @@ class TestServiceFeatures(TransactionTestCase): requests_made = [ DiscordRequest(r.method, r.url) for r in requests_mocker.request_history ] - self.assertIn(remove_guild_member_request, requests_made) + self.assertIn(remove_guild_member_request, requests_made) + + # verify user has been notified + self.assertTrue(Notification.objects.filter(user=self.user).exists()) def test_when_member_changes_to_blue_state_then_roles_are_updated_accordingly( self, requests_mocker @@ -238,7 +243,7 @@ class TestServiceFeatures(TransactionTestCase): # request mocks requests_mocker.get( guild_member_request.url, - json={'user': create_user_info(),'roles': ['3', '13', '99']} + json={'user': create_user_info(), 'roles': ['3', '13', '99']} ) requests_mocker.get( guild_roles_request.url, @@ -322,7 +327,7 @@ class TestServiceFeatures(TransactionTestCase): self.user.groups.add(self.group_charlie) self.user.refresh_from_db() - # verify roles for user where updated + # verify roles for user where updated roles_updated = False for r in requests_mocker.request_history: my_request = DiscordRequest(r.method, r.url) @@ -346,8 +351,14 @@ class StateTestCase(TestCase): reset_testdata() self.user = AuthUtils.create_user('test_user', disconnect_signals=True) - AuthUtils.add_main_character(self.user, 'Perm Test Character', '99', corp_id='100', alliance_id='200', - corp_name='Perm Test Corp', alliance_name='Perm Test Alliance') + AuthUtils.add_main_character( + self.user, + 'Perm Test Character', '99', + corp_id='100', + alliance_id='200', + corp_name='Perm Test Corp', + alliance_name='Perm Test Alliance' + ) self.test_character = EveCharacter.objects.get(character_id='99') self.member_state = State.objects.create( name='Test Member', @@ -358,7 +369,9 @@ class StateTestCase(TestCase): self.member_state.member_characters.add(self.test_character) def _add_discord_user(self): - self.discord_user = DiscordUser.objects.create(user=self.user, uid="12345678910") + self.discord_user = DiscordUser.objects.create( + user=self.user, uid="12345678910" + ) def _refresh_user(self): self.user = User.objects.get(pk=self.user.pk) @@ -636,4 +649,3 @@ class TestUserFeatures(WebTest): # check we got can see the page and the "link server" button self.assertEqual(response.status_int, 200) self.assertIsNotNone(response.html.find(id='btnLinkDiscordServer')) - \ No newline at end of file