remove trailing whitespaces

This commit is contained in:
Peter Pfeufer
2021-05-17 09:46:11 +02:00
parent 10bd77d761
commit 8c3df89d52
146 changed files with 2088 additions and 2061 deletions

View File

@@ -12,15 +12,15 @@ logger = LoggerAddTag(logging.getLogger(__name__), __title__)
@admin.register(DiscordUser)
class DiscordUserAdmin(ServicesUserAdmin):
class DiscordUserAdmin(ServicesUserAdmin):
search_fields = ServicesUserAdmin.search_fields + ('uid', 'username')
list_display = ServicesUserAdmin.list_display + ('activated', '_username', '_uid')
list_filter = ServicesUserAdmin.list_filter + ('activated',)
ordering = ('-activated',)
def _uid(self, obj):
return obj.uid
_uid.short_description = 'Discord ID (UID)'
_uid.admin_order_field = 'uid'
@@ -29,6 +29,6 @@ class DiscordUserAdmin(ServicesUserAdmin):
return f'{obj.username}#{obj.discriminator}'
else:
return ''
_username.short_description = 'Discord Username'
_username.admin_order_field = 'username'

View File

@@ -1,6 +1,6 @@
import logging
from django.contrib.auth.models import User
from django.contrib.auth.models import User
from django.template.loader import render_to_string
from allianceauth import hooks
@@ -33,11 +33,11 @@ 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, 'notify_user': notify_user},
kwargs={'user_pk': user.pk, 'notify_user': notify_user},
priority=SINGLE_TASK_PRIORITY
)
def render_services_ctrl(self, request):
)
def render_services_ctrl(self, request):
if self.user_has_account(request.user):
user_has_account = True
username = request.user.discord.username
@@ -51,12 +51,12 @@ class DiscordService(ServicesHook):
user_has_account = False
return render_to_string(
self.service_ctrl_template,
self.service_ctrl_template,
{
'server_name': DiscordUser.objects.server_name(),
'user_has_account': user_has_account,
'discord_username': discord_username
},
},
request=request
)
@@ -71,15 +71,15 @@ class DiscordService(ServicesHook):
tasks.update_nickname.apply_async(
kwargs={
'user_pk': user.pk,
# since the new nickname is not yet in the DB we need to
# since the new nickname is not yet in the DB we need to
# provide it manually to the task
'nickname': DiscordUser.objects.user_formatted_nick(user)
},
},
priority=SINGLE_TASK_PRIORITY
)
def sync_nicknames_bulk(self, users: list):
"""Sync nickname for a list of users in bulk.
"""Sync nickname for a list of users in bulk.
Preferred over sync_nickname(), because it will not break the rate limit
"""
logger.debug(
@@ -92,21 +92,21 @@ class DiscordService(ServicesHook):
logger.debug('Update all %s groups called', self.name)
tasks.update_all_groups.delay()
def update_groups(self, user):
logger.debug('Processing %s groups for %s', self.name, user)
def update_groups(self, user):
logger.debug('Processing %s groups for %s', self.name, user)
if self.user_has_account(user):
tasks.update_groups.apply_async(
kwargs={
'user_pk': user.pk,
# since state changes may not yet be in the DB we need to
# since state changes may not yet be in the DB we need to
# provide the new state name manually to the task
'state_name': user.profile.state.name
},
},
priority=SINGLE_TASK_PRIORITY
)
def update_groups_bulk(self, users: list):
"""Updates groups for a list of users in bulk.
"""Updates groups for a list of users in bulk.
Preferred over update_groups(), because it will not break the rate limit
"""
logger.debug(
@@ -114,7 +114,7 @@ class DiscordService(ServicesHook):
)
user_pks = [user.pk for user in users]
tasks.update_groups_bulk.delay(user_pks)
@staticmethod
def user_has_account(user: User) -> bool:
result = DiscordUser.objects.user_has_account(user)

View File

@@ -26,7 +26,7 @@ DISCORD_OAUTH_TOKEN_URL = clean_setting(
'DISCORD_OAUTH_TOKEN_URL', 'https://discord.com/api/oauth2/token'
)
# How long the Discord guild names retrieved from the server are
# How long the Discord guild names retrieved from the server are
# caches locally in seconds.
DISCORD_GUILD_NAME_CACHE_MAX_AGE = clean_setting(
'DISCORD_GUILD_NAME_CACHE_MAX_AGE', 3600 * 24
@@ -38,7 +38,7 @@ DISCORD_ROLES_CACHE_MAX_AGE = clean_setting(
)
# Turns off creation of new roles. In case the rate limit for creating roles is
# exhausted, this setting allows the Discord service to continue to function
# exhausted, this setting allows the Discord service to continue to function
# and wait out the reset. Rate limit is about 250 per 48 hrs.
DISCORD_DISABLE_ROLE_CREATION = clean_setting(
'DISCORD_DISABLE_ROLE_CREATION', False

View File

@@ -19,9 +19,9 @@ from .app_settings import (
DISCORD_API_TIMEOUT_READ,
DISCORD_DISABLE_ROLE_CREATION,
DISCORD_GUILD_NAME_CACHE_MAX_AGE,
DISCORD_OAUTH_BASE_URL,
DISCORD_OAUTH_TOKEN_URL,
DISCORD_ROLES_CACHE_MAX_AGE,
DISCORD_OAUTH_BASE_URL,
DISCORD_OAUTH_TOKEN_URL,
DISCORD_ROLES_CACHE_MAX_AGE,
)
from .exceptions import DiscordRateLimitExhausted, DiscordTooManyRequestsError
from .helpers import DiscordRoles
@@ -31,7 +31,7 @@ from ..utils import LoggerAddTag
logger = LoggerAddTag(logging.getLogger(__name__), __title__)
# max requests that can be executed until reset
RATE_LIMIT_MAX_REQUESTS = 5
RATE_LIMIT_MAX_REQUESTS = 5
# Time until remaining requests are reset
RATE_LIMIT_RESETS_AFTER = 5000
@@ -39,7 +39,7 @@ RATE_LIMIT_RESETS_AFTER = 5000
# Delay used for API backoff in case no info returned from API on 429s
DEFAULT_BACKOFF_DELAY = 5000
# additional duration to compensate for potential clock discrepancies
# additional duration to compensate for potential clock discrepancies
# with the Discord server
DURATION_CONTINGENCY = 500
@@ -51,23 +51,23 @@ WAIT_THRESHOLD = 250
MINIMUM_BLOCKING_WAIT = 50
# If the rate limit resets soon we will wait it out and then retry to
# either get a remaining request from our cached counter
# either get a remaining request from our cached counter
# or again wait out a short reset time and retry again.
# This could happen several times within a high concurrency situation,
# This could happen several times within a high concurrency situation,
# but must fail after x tries to avoid an infinite loop
RATE_LIMIT_RETRIES = 1000
class DiscordClient:
"""This class provides a web client for interacting with the Discord API
The client has rate limiting that supports concurrency.
This means it is able to ensure the API rate limit is not violated,
This means it is able to ensure the API rate limit is not violated,
even when used concurrently, e.g. with multiple parallel celery tasks.
In addition the client support proper API backoff.
Synchronization of rate limit infos accross multiple processes
Synchronization of rate limit infos accross multiple processes
is implemented with Redis and thus requires Redis as Django cache backend.
All durations are in milliseconds.
@@ -79,25 +79,25 @@ class DiscordClient:
_KEY_GLOBAL_RATE_LIMIT_REMAINING = 'DISCORD_GLOBAL_RATE_LIMIT_REMAINING'
_KEYPREFIX_GUILD_NAME = 'DISCORD_GUILD_NAME'
_KEYPREFIX_GUILD_ROLES = 'DISCORD_GUILD_ROLES'
_KEYPREFIX_ROLE_NAME = 'DISCORD_ROLE_NAME'
_KEYPREFIX_ROLE_NAME = 'DISCORD_ROLE_NAME'
_NICK_MAX_CHARS = 32
_HTTP_STATUS_CODE_NOT_FOUND = 404
_HTTP_STATUS_CODE_RATE_LIMITED = 429
_DISCORD_STATUS_CODE_UNKNOWN_MEMBER = 10007
def __init__(
self,
access_token: str,
redis: Redis = None,
self,
access_token: str,
redis: Redis = None,
is_rate_limited: bool = True
) -> None:
"""
"""
Params:
- access_token: Discord access token used to authenticate all calls to the API
- redis: Redis instance to be used.
- redis: Redis instance to be used.
- is_rate_limited: Set to False to run of rate limiting (use with care)
If not specified will try to use the Redis instance
If not specified will try to use the Redis instance
from the default Django cache backend.
"""
self._access_token = str(access_token)
@@ -116,7 +116,7 @@ class DiscordClient:
lua_1 = """
if redis.call("exists", KEYS[1]) == 0 then
redis.call("set", KEYS[1], ARGV[1], 'px', ARGV[2])
end
end
return redis.call("decr", KEYS[1])
"""
self.__redis_script_decr_or_set = self._redis.register_script(lua_1)
@@ -138,24 +138,24 @@ class DiscordClient:
@property
def is_rate_limited(self):
return self._is_rate_limited
def __repr__(self):
return f'{type(self).__name__}(access_token=...{self.access_token[-5:]})'
def _redis_decr_or_set(self, name: str, value: str, px: int) -> bool:
"""decreases the key value if it exists and returns the result
else sets the key
Implemented as Lua script to ensure atomicity.
"""
return self.__redis_script_decr_or_set(
keys=[str(name)], args=[str(value), int(px)]
)
def _redis_set_if_longer(self, name: str, value: str, px: int) -> bool:
"""like set, but only goes through if either key doesn't exist
or px would be extended.
"""like set, but only goes through if either key doesn't exist
or px would be extended.
Implemented as Lua script to ensure atomicity.
"""
return self.__redis_script_set_longer(
@@ -163,7 +163,7 @@ class DiscordClient:
)
# users
def current_user(self) -> dict:
"""returns the user belonging to the current access_token"""
authorization = f'Bearer {self.access_token}'
@@ -171,7 +171,7 @@ class DiscordClient:
method='get', route='users/@me', authorization=authorization
)
return r.json()
# guild
def guild_infos(self, guild_id: int) -> dict:
@@ -181,7 +181,7 @@ class DiscordClient:
return r.json()
def guild_name(self, guild_id: int, use_cache: bool = True) -> str:
"""returns the name of this guild (cached)
"""returns the name of this guild (cached)
or an empty string if something went wrong
Params:
@@ -198,8 +198,8 @@ class DiscordClient:
if 'name' in guild_infos:
guild_name = guild_infos['name']
self._redis.set(
name=key_name,
value=guild_name,
name=key_name,
value=guild_name,
ex=DISCORD_GUILD_NAME_CACHE_MAX_AGE
)
else:
@@ -208,7 +208,7 @@ class DiscordClient:
return guild_name
@classmethod
def _guild_name_cache_key(cls, guild_id: int) -> str:
def _guild_name_cache_key(cls, guild_id: int) -> str:
"""Returns key for accessing role given by name in the role cache"""
gen_key = DiscordClient._generate_hash(f'{guild_id}')
return f'{cls._KEYPREFIX_GUILD_NAME}__{gen_key}'
@@ -217,38 +217,38 @@ class DiscordClient:
def guild_roles(self, guild_id: int, use_cache: bool = True) -> list:
"""Returns the list of all roles for this guild
If use_cache is set to False it will always hit the API to retrieve
fresh data and update the cache
"""
cache_key = self._guild_roles_cache_key(guild_id)
if use_cache:
if use_cache:
roles_raw = self._redis.get(name=cache_key)
if roles_raw:
logger.debug('Returning roles for guild %s from cache', guild_id)
return json.loads(self._redis_decode(roles_raw))
else:
logger.debug('No roles for guild %s in cache', guild_id)
route = f"guilds/{guild_id}/roles"
r = self._api_request(method='get', route=route)
r = self._api_request(method='get', route=route)
roles = r.json()
if roles and isinstance(roles, list):
self._redis.set(
name=cache_key,
value=json.dumps(roles),
name=cache_key,
value=json.dumps(roles),
ex=DISCORD_ROLES_CACHE_MAX_AGE
)
return roles
def create_guild_role(self, guild_id: int, role_name: str, **kwargs) -> dict:
"""Create a new guild role with the given name.
"""Create a new guild role with the given name.
See official documentation for additional optional parameters.
Note that Discord allows the creation of multiple roles with the same name,
so to avoid duplicates it's important to check existing roles
so to avoid duplicates it's important to check existing roles
before creating new one
returns a new role dict on success
"""
route = f"guilds/{guild_id}/roles"
@@ -269,9 +269,9 @@ class DiscordClient:
return True
else:
return False
def _invalidate_guild_roles_cache(self, guild_id: int) -> None:
cache_key = self._guild_roles_cache_key(guild_id)
def _invalidate_guild_roles_cache(self, guild_id: int) -> None:
cache_key = self._guild_roles_cache_key(guild_id)
self._redis.delete(cache_key)
logger.debug('Guild roles cache invalidated')
@@ -280,7 +280,7 @@ class DiscordClient:
"""Returns key for accessing cached roles for a guild"""
gen_key = cls._generate_hash(f'{guild_id}')
return f'{cls._KEYPREFIX_GUILD_ROLES}__{gen_key}'
def match_role_from_name(self, guild_id: int, role_name: str) -> dict:
"""returns Discord role matching the given name or an empty dict"""
guild_roles = DiscordRoles(self.guild_roles(guild_id))
@@ -288,12 +288,12 @@ class DiscordClient:
def match_or_create_roles_from_names(self, guild_id: int, role_names: list) -> list:
"""returns Discord roles matching the given names
Returns as list of tuple of role and created flag
Will try to match with existing roles names
Non-existing roles will be created, then created flag will be True
Params:
- guild_id: ID of guild
- role_names: list of name strings each defining a role
@@ -305,7 +305,7 @@ class DiscordClient:
}
for role_name in role_names_cleaned:
role, created = self.match_or_create_role_from_name(
guild_id=guild_id,
guild_id=guild_id,
role_name=DiscordRoles.sanitize_role_name(role_name),
guild_roles=guild_roles
)
@@ -321,46 +321,46 @@ class DiscordClient:
"""returns Discord role matching the given name
Returns as tuple of role and created flag
Will try to match with existing roles names
Non-existing roles will be created, then created flag will be True
Params:
- guild_id: ID of guild
- role_name: strings defining name of a role
- guild_roles: All known guild roles as DiscordRoles object.
Helps to void redundant lookups of guild roles
- guild_roles: All known guild roles as DiscordRoles object.
Helps to void redundant lookups of guild roles
when this method is used multiple times.
"""
if not isinstance(role_name, str):
raise TypeError('role_name must be of type string')
created = False
created = False
if guild_roles is None:
guild_roles = DiscordRoles(self.guild_roles(guild_id))
role = guild_roles.role_by_name(role_name)
if not role:
if not DISCORD_DISABLE_ROLE_CREATION:
logger.debug('Need to create missing role: %s', role_name)
role = self.create_guild_role(guild_id, role_name)
role = self.create_guild_role(guild_id, role_name)
created = True
else:
role = None
return role, created
# guild members
def add_guild_member(
self,
guild_id: int,
user_id: int,
access_token: str,
role_ids: list = None,
self,
guild_id: int,
user_id: int,
access_token: str,
role_ids: list = None,
nick: str = None
) -> bool:
) -> bool:
"""Adds a user to the guilds.
Returns:
- True when a new user was added
- None if the user already existed
@@ -370,13 +370,13 @@ class DiscordClient:
data = {
'access_token': str(access_token)
}
if role_ids:
if role_ids:
data['roles'] = self._sanitize_role_ids(role_ids)
if nick:
data['nick'] = str(nick)[:self._NICK_MAX_CHARS]
r = self._api_request(method='put', route=route, data=data)
r = self._api_request(method='put', route=route, data=data)
r.raise_for_status()
if r.status_code == 201:
return True
@@ -384,10 +384,10 @@ class DiscordClient:
return None
else:
return False
def guild_member(self, guild_id: int, user_id: int) -> dict:
"""returns the user info for a guild member
or None if the user is not a member of the guild
"""
route = f'guilds/{guild_id}/members/{user_id}'
@@ -411,14 +411,14 @@ class DiscordClient:
"""
if not role_ids and not nick:
raise ValueError('Must specify role_ids or nick')
if role_ids and not isinstance(role_ids, list):
raise TypeError('role_ids must be a list type')
data = dict()
if role_ids:
if role_ids:
data['roles'] = self._sanitize_role_ids(role_ids)
if nick:
data['nick'] = self._sanitize_nick(nick)
@@ -431,7 +431,7 @@ class DiscordClient:
return None
else:
r.raise_for_status()
if r.status_code == 204:
return True
else:
@@ -439,7 +439,7 @@ class DiscordClient:
def remove_guild_member(self, guild_id: int, user_id: int) -> bool:
"""Remove a member from a guild
Returns:
- True when successful
- None if member does not exist
@@ -448,7 +448,7 @@ class DiscordClient:
route = f"guilds/{guild_id}/members/{user_id}"
r = self._api_request(
method='delete', route=route, raise_for_status=False
)
)
if self._is_member_unknown_error(r):
logger.warning('User ID %s is not a member of this guild', user_id)
return None
@@ -461,12 +461,12 @@ class DiscordClient:
return False
# Guild member roles
def add_guild_member_role(
self, guild_id: int, user_id: int, role_id: int
) -> bool:
) -> bool:
"""Adds a role to a guild member
Returns:
- True when successful
- None if member does not exist
@@ -479,7 +479,7 @@ class DiscordClient:
return None
else:
r.raise_for_status()
if r.status_code == 204:
return True
else:
@@ -489,7 +489,7 @@ class DiscordClient:
self, guild_id: int, user_id: int, role_id: int
) -> bool:
"""Removes a role to a guild member
Returns:
- True when successful
- None if member does not exist
@@ -517,31 +517,31 @@ class DiscordClient:
)
except (ValueError, KeyError):
result = False
return result
# Internal methods
def _api_request(
self,
method: str,
route: str,
data: dict = None,
self,
method: str,
route: str,
data: dict = None,
authorization: str = None,
raise_for_status: bool = True
) -> requests.Response:
"""Core method for performing all API calls"""
uid = uuid1().hex
if not hasattr(requests, method):
raise ValueError('Invalid method: %s' % method)
if not authorization:
authorization = f'Bot {self.access_token}'
self._handle_ongoing_api_backoff(uid)
self._handle_ongoing_api_backoff(uid)
if self.is_rate_limited:
self._ensure_rate_limed_not_exhausted(uid)
self._ensure_rate_limed_not_exhausted(uid)
headers = {
'User-Agent': f'{AUTH_TITLE} ({__url__}, {__version__})',
'accept': 'application/json',
@@ -559,21 +559,21 @@ class DiscordClient:
}
if data:
args['json'] = data
logger.info('%s: sending %s request to url \'%s\'', uid, method.upper(), url)
logger.debug('%s: request headers: %s', uid, headers)
r = getattr(requests, method)(**args)
logger.debug(
'%s: returned status code %d with headers: %s',
uid,
r.status_code,
'%s: returned status code %d with headers: %s',
uid,
r.status_code,
r.headers
)
logger.debug('%s: response:\n%s', uid, r.text)
if not r.ok:
logger.warning(
'%s: Discord API returned error code %d and this response: %s',
uid,
uid,
r.status_code,
r.text
)
@@ -582,15 +582,15 @@ class DiscordClient:
self._handle_new_api_backoff(r, uid)
self._report_rate_limit_from_api(r, uid)
if raise_for_status:
r.raise_for_status()
return r
def _handle_ongoing_api_backoff(self, uid: str) -> None:
def _handle_ongoing_api_backoff(self, uid: str) -> None:
"""checks if api is currently on backoff
if on backoff: will do a blocking wait if it expires soon,
if on backoff: will do a blocking wait if it expires soon,
else raises exception
"""
global_backoff_duration = self._redis.pttl(self._KEY_GLOBAL_BACKOFF_UNTIL)
@@ -611,52 +611,52 @@ class DiscordClient:
raise DiscordTooManyRequestsError(retry_after=global_backoff_duration)
def _ensure_rate_limed_not_exhausted(self, uid: str) -> int:
"""ensures that the rate limit is not exhausted
if exhausted: will do a blocking wait if rate limit resets soon,
"""ensures that the rate limit is not exhausted
if exhausted: will do a blocking wait if rate limit resets soon,
else raises exception
returns requests remaining on success
"""
for _ in range(RATE_LIMIT_RETRIES):
requests_remaining = self._redis_decr_or_set(
name=self._KEY_GLOBAL_RATE_LIMIT_REMAINING,
value=RATE_LIMIT_MAX_REQUESTS,
name=self._KEY_GLOBAL_RATE_LIMIT_REMAINING,
value=RATE_LIMIT_MAX_REQUESTS,
px=RATE_LIMIT_RESETS_AFTER + DURATION_CONTINGENCY
)
)
resets_in = max(
MINIMUM_BLOCKING_WAIT,
MINIMUM_BLOCKING_WAIT,
self._redis.pttl(self._KEY_GLOBAL_RATE_LIMIT_REMAINING)
)
if requests_remaining >= 0:
logger.debug(
'%s: Got one of %d remaining requests until reset in %s ms',
uid,
uid,
requests_remaining + 1,
resets_in
)
return requests_remaining
elif resets_in < WAIT_THRESHOLD:
elif resets_in < WAIT_THRESHOLD:
sleep(resets_in / 1000)
logger.debug(
'%s: No requests remaining until reset in %d ms. '
'Waiting for reset.',
uid,
uid,
resets_in
)
continue
else:
else:
logger.debug(
'%s: No requests remaining until reset in %d ms. '
'Raising exception.',
uid,
uid,
resets_in
)
raise DiscordRateLimitExhausted(resets_in)
raise RuntimeError('Failed to handle rate limit after after too tries.')
def _handle_new_api_backoff(self, r: requests.Response, uid: str) -> None:
"""raises exception for new API backoff error"""
response = r.json()
@@ -669,7 +669,7 @@ class DiscordClient:
else:
retry_after = DEFAULT_BACKOFF_DELAY
self._redis_set_if_longer(
name=self._KEY_GLOBAL_BACKOFF_UNTIL,
name=self._KEY_GLOBAL_BACKOFF_UNTIL,
value='GLOBAL_API_BACKOFF',
px=retry_after
)
@@ -684,8 +684,8 @@ class DiscordClient:
"""Tries to log the current rate limit reported from API"""
if (
logger.getEffectiveLevel() <= logging.DEBUG
and 'x-ratelimit-limit' in r.headers
and 'x-ratelimit-remaining' in r.headers
and 'x-ratelimit-limit' in r.headers
and 'x-ratelimit-remaining' in r.headers
and 'x-ratelimit-reset-after' in r.headers
):
try:
@@ -701,7 +701,7 @@ class DiscordClient:
)
except ValueError:
pass
@staticmethod
def _redis_decode(value: str) -> str:
"""Decodes a string from Redis and passes through None and Booleans"""

View File

@@ -3,18 +3,18 @@ import math
class DiscordClientException(Exception):
"""Base Exception for the Discord client"""
class DiscordApiBackoff(DiscordClientException):
"""Exception signaling we need to backoff from sending requests to the API for now
"""
def __init__(self, retry_after: int):
"""
:param retry_after: int time to retry after in milliseconds
"""
super().__init__()
self.retry_after = int(retry_after)
self.retry_after = int(retry_after)
@property
def retry_after_seconds(self):
@@ -22,12 +22,12 @@ class DiscordApiBackoff(DiscordClientException):
class DiscordRateLimitExhausted(DiscordApiBackoff):
"""Exception signaling that the total number of requests allowed under the
"""Exception signaling that the total number of requests allowed under the
current rate limit have been exhausted and weed to wait until next reset.
"""
class DiscordTooManyRequestsError(DiscordApiBackoff):
"""API has responded with a 429 Too Many Requests Error.
"""API has responded with a 429 Too Many Requests Error.
Need to backoff for now.
"""
"""

View File

@@ -3,10 +3,10 @@ from copy import copy
class DiscordRoles:
"""Container class that helps dealing with Discord roles.
Objects of this class are immutable and work in many ways like sets.
Ideally objects are initialized from raw API responses,
Ideally objects are initialized from raw API responses,
e.g. from DiscordClient.guild.roles()
"""
_ROLE_NAME_MAX_CHARS = 100
@@ -21,7 +21,7 @@ class DiscordRoles:
self._assert_valid_role(role)
self._roles[int(role['id'])] = role
self._roles_by_name[self.sanitize_role_name(role['name'])] = role
def __eq__(self, other):
if isinstance(other, type(self)):
return self.ids() == other.ids()
@@ -29,31 +29,31 @@ class DiscordRoles:
def __hash__(self):
return hash(tuple(sorted(self._roles.keys())))
def __iter__(self):
for role in self._roles.values():
yield role
def __contains__(self, item) -> bool:
def __contains__(self, item) -> bool:
return int(item) in self._roles
def __len__(self):
def __len__(self):
return len(self._roles.keys())
def has_roles(self, role_ids: set) -> bool:
"""returns true if this objects contains all roles defined by given role_ids
incl. managed roles
"""
role_ids = {int(id) for id in role_ids}
all_role_ids = self._roles.keys()
all_role_ids = self._roles.keys()
return role_ids.issubset(all_role_ids)
def ids(self) -> set:
"""return a set of all role IDs"""
return set(self._roles.keys())
def subset(self, role_ids: set = None, managed_only: bool = False) -> object:
"""returns a new object containing the subset of roles as defined
"""returns a new object containing the subset of roles as defined
by given role IDs and/or including managed roles only
"""
if role_ids is not None:
@@ -68,23 +68,23 @@ class DiscordRoles:
return type(self)([
role for _, role in self._roles.items() if role['managed']
])
elif role_ids is not None and managed_only:
return type(self)([
role for role_id, role in self._roles.items()
role for role_id, role in self._roles.items()
if role_id in role_ids and role['managed']
])
else:
return copy(self)
def union(self, other: object) -> object:
"""returns a new roles object that is the union of this roles object
"""returns a new roles object that is the union of this roles object
with other"""
return type(self)(list(self) + list(other))
def difference(self, other: object) -> object:
"""returns a new roles object that only contains the roles
"""returns a new roles object that only contains the roles
that exist in the current objects, but not in other
"""
new_ids = self.ids().difference(other.ids())
@@ -97,11 +97,11 @@ class DiscordRoles:
return self._roles_by_name[role_name]
else:
return dict()
@classmethod
def create_from_matched_roles(cls, matched_roles: list) -> None:
"""returns a new object created from the given list of matches roles
matches_roles must be a list of tuples in the form: (role, created)
"""
raw_roles = [x[0] for x in matched_roles]
@@ -111,10 +111,10 @@ class DiscordRoles:
def _assert_valid_role(role: dict):
if not isinstance(role, dict):
raise TypeError('Roles must be of type dict: %s' % role)
if 'id' not in role or 'name' not in role or 'managed' not in role:
raise ValueError('This role is not valid: %s' % role)
@classmethod
def sanitize_role_name(cls, role_name: str) -> str:
"""shortens too long strings if necessary"""

View File

@@ -27,8 +27,8 @@ ALL_ROLES = [ROLE_ALPHA, ROLE_BRAVO, ROLE_CHARLIE, ROLE_MIKE]
def create_user_info(
id: int = TEST_USER_ID,
username: str = TEST_USER_NAME,
id: int = TEST_USER_ID,
username: str = TEST_USER_NAME,
discriminator: str = TEST_USER_DISCRIMINATOR
):
return {

View File

@@ -5,11 +5,11 @@ The results can be analysed in a special log file.
This script is design to be run manually as unit test, e.g. by running the following:
python manage.py test
python manage.py test
allianceauth.services.modules.discord.discord_client.tests.piloting_concurrency
To make it work please set the below mentioned environment variables for your server.
Since this may cause lots of 429s we'd recommend NOT to use your
Since this may cause lots of 429s we'd recommend NOT to use your
alliance Discord server for this.
"""
@@ -49,7 +49,7 @@ def worker(num: int):
client = DiscordClient(DISCORD_BOT_TOKEN)
try:
runs = 0
while runs < NUMBER_OF_RUNS:
while runs < NUMBER_OF_RUNS:
run_info = '%s: run %d' % (worker_info, runs + 1)
my_jitter_secs = random() * MAX_JITTER_PER_RUN_SECS
logger.info('%s - waiting %s secs', run_info, f'{my_jitter_secs:.3f}')
@@ -67,18 +67,18 @@ def worker(num: int):
logger.info(message)
print()
print(message)
sleep(bo.retry_after / 1000)
sleep(bo.retry_after / 1000)
except Exception as ex:
logger.exception('%s: Processing aborted: %s', worker_info, ex)
logger.info('%s: finished', worker_info)
return
class TestMulti(TestCase):
def test_multi(self):
def test_multi(self):
logger.info('Starting multi test')
for num in range(NUMBER_OF_WORKERS):
x = threading.Thread(target=worker, args=(num + 1,))

View File

@@ -1,7 +1,7 @@
"""This script is for functional testing of the Discord client with a Discord server
It will run single requests of the various functions to validate
that they actually work - excluding those that require Oauth, or does not work
It will run single requests of the various functions to validate
that they actually work - excluding those that require Oauth, or does not work
with a bot token. The results can be also seen in a special log file.
This script is design to be run manually as unit test, e.g. by running the following:
@@ -10,7 +10,7 @@ python manage.py test
allianceauth.services.modules.discord.discord_self.client.tests.piloting_functionality
To make it work please set the below mentioned environment variables for your server.
Since this may cause lots of 429s we'd recommend NOT to use your
Since this may cause lots of 429s we'd recommend NOT to use your
alliance Discord server for this.
"""
@@ -46,7 +46,7 @@ class TestDiscordApiLive(TestCase):
"""runs features that have not been run in any of the other tests"""
self.client.guild_infos(DISCORD_GUILD_ID)
sleep(RATE_LIMIT_DELAY_SECS)
self.client.guild_name(DISCORD_GUILD_ID)
sleep(RATE_LIMIT_DELAY_SECS)
@@ -57,9 +57,9 @@ class TestDiscordApiLive(TestCase):
DISCORD_GUILD_ID, ['Testrole A', 'Testrole B']
)
sleep(RATE_LIMIT_DELAY_SECS)
def test_create_and_remove_roles(self):
# get base
def test_create_and_remove_roles(self):
# get base
logger.info('guild_roles')
expected = {role['id'] for role in self.client.guild_roles(DISCORD_GUILD_ID)}
@@ -70,8 +70,8 @@ class TestDiscordApiLive(TestCase):
guild_id=DISCORD_GUILD_ID, role_name=role_name
)
sleep(RATE_LIMIT_DELAY_SECS)
self.assertEqual(new_role['name'], role_name)
self.assertEqual(new_role['name'], role_name)
# remove role again
logger.info('delete_guild_role')
self.client.delete_guild_role(
@@ -102,7 +102,7 @@ class TestDiscordApiLive(TestCase):
sleep(RATE_LIMIT_DELAY_SECS)
self.assertEqual(user['nick'], new_nick)
def test_member_add_remove_roles(self):
def test_member_add_remove_roles(self):
# create new guild role
logger.info('create_guild_role')
new_role = self.client.create_guild_role(
@@ -110,7 +110,7 @@ class TestDiscordApiLive(TestCase):
)
sleep(RATE_LIMIT_DELAY_SECS)
new_role_id = new_role['id']
# add to member
logger.info('add_guild_member_role')
self.assertTrue(
@@ -119,7 +119,7 @@ class TestDiscordApiLive(TestCase):
)
)
sleep(RATE_LIMIT_DELAY_SECS)
# remove again
logger.info('remove_guild_member_role')
self.assertTrue(

View File

@@ -1,8 +1,8 @@
from unittest import TestCase
from ..exceptions import (
DiscordApiBackoff,
DiscordClientException,
DiscordApiBackoff,
DiscordClientException,
DiscordRateLimitExhausted,
DiscordTooManyRequestsError
)

View File

@@ -25,7 +25,7 @@ class TestDiscordRoles(TestCase):
def test_raises_exception_if_roles_raw_of_wrong_type(self):
with self.assertRaises(TypeError):
DiscordRoles({'id': 1})
def test_raises_exception_if_list_contains_non_dict(self):
roles_raw = [ROLE_ALPHA, 'not_valid']
with self.assertRaises(TypeError):
@@ -45,7 +45,7 @@ class TestDiscordRoles(TestCase):
roles_raw = [{'id': 1, 'name': 'alpha'}]
with self.assertRaises(ValueError):
DiscordRoles(roles_raw)
def test_roles_are_equal(self):
roles_a = DiscordRoles([ROLE_ALPHA, ROLE_BRAVO])
roles_b = DiscordRoles([ROLE_ALPHA, ROLE_BRAVO])
@@ -57,19 +57,19 @@ class TestDiscordRoles(TestCase):
self.assertNotEqual(roles_a, roles_b)
def test_different_objects_are_not_equal(self):
roles_a = DiscordRoles([ROLE_ALPHA, ROLE_BRAVO])
roles_a = DiscordRoles([ROLE_ALPHA, ROLE_BRAVO])
self.assertFalse(roles_a == "invalid")
def test_len(self):
def test_len(self):
self.assertEqual(len(self.all_roles), 4)
def test_contains(self):
self.assertTrue(1 in self.all_roles)
self.assertFalse(99 in self.all_roles)
def test_sanitize_role_name(self):
def test_sanitize_role_name(self):
role_name_input = 'x' * 110
role_name_expected = 'x' * 100
role_name_expected = 'x' * 100
result = DiscordRoles.sanitize_role_name(role_name_input)
self.assertEqual(result, role_name_expected)
@@ -94,8 +94,8 @@ class TestIds(TestCase):
def setUp(self):
self.all_roles = DiscordRoles(ALL_ROLES)
def test_return_role_ids_default(self):
def test_return_role_ids_default(self):
result = self.all_roles.ids()
expected = {1, 2, 3, 13}
self.assertSetEqual(result, expected)
@@ -154,7 +154,7 @@ class TestHasRoles(TestCase):
def test_true_if_all_roles_exit_str(self):
self.assertTrue(self.all_roles.has_roles(['1', '2']))
def test_false_if_role_does_not_exit(self):
self.assertFalse(self.all_roles.has_roles([99]))
@@ -163,7 +163,7 @@ class TestHasRoles(TestCase):
def test_true_for_empty_roles(self):
self.assertTrue(self.all_roles.has_roles([]))
class TestGetMatchingRolesByName(TestCase):

View File

@@ -16,7 +16,7 @@ from .app_settings import (
DISCORD_APP_SECRET,
DISCORD_BOT_TOKEN,
DISCORD_CALLBACK_URL,
DISCORD_GUILD_ID,
DISCORD_GUILD_ID,
DISCORD_SYNC_NAMES
)
from .discord_client import DiscordClient
@@ -41,9 +41,9 @@ class DiscordUserManager(models.Manager):
]
def add_user(
self,
user: User,
authorization_code: str,
self,
user: User,
authorization_code: str,
is_rate_limited: bool = True
) -> bool:
"""adds a new Discord user
@@ -52,10 +52,10 @@ class DiscordUserManager(models.Manager):
- user: Auth user to join
- authorization_code: authorization code returns from oauth
- is_rate_limited: When False will disable default rate limiting (use with care)
Returns: True on success, else False or raises exception
"""
try:
try:
nickname = self.user_formatted_nick(user) if DISCORD_SYNC_NAMES else None
group_names = self.user_group_names(user)
access_token = self._exchange_auth_code_for_token(authorization_code)
@@ -63,18 +63,18 @@ class DiscordUserManager(models.Manager):
discord_user = user_client.current_user()
user_id = discord_user['id']
bot_client = self._bot_client(is_rate_limited=is_rate_limited)
if group_names:
if group_names:
role_ids = match_or_create_roles_from_names(
client=bot_client,
guild_id=DISCORD_GUILD_ID,
client=bot_client,
guild_id=DISCORD_GUILD_ID,
role_names=group_names
).ids()
else:
role_ids = None
created = bot_client.add_guild_member(
guild_id=DISCORD_GUILD_ID,
guild_id=DISCORD_GUILD_ID,
user_id=user_id,
access_token=access_token,
role_ids=role_ids,
@@ -88,10 +88,10 @@ class DiscordUserManager(models.Manager):
user_id,
)
self.update_or_create(
user=user,
user=user,
defaults={
'uid': user_id,
'username': discord_user['username'][:32],
'uid': user_id,
'username': discord_user['username'][:32],
'discriminator': discord_user['discriminator'][:4],
'activated': now()
}
@@ -121,7 +121,7 @@ class DiscordUserManager(models.Manager):
or None if user has no main
"""
from .auth_hooks import DiscordService
if user.profile.main_character:
return NameFormatter(DiscordService(), user).format_name()
else:
@@ -139,10 +139,10 @@ class DiscordUserManager(models.Manager):
"Group names for roles updates of user %s are: %s", user, group_names
)
return group_names
def user_has_account(self, user: User) -> bool:
"""Returns True if the user has an Discord account, else False
only checks locally, does not hit the API
"""
if not isinstance(user, User):
@@ -157,7 +157,7 @@ class DiscordUserManager(models.Manager):
'permissions': str(cls.BOT_PERMISSIONS)
})
return f'{DiscordClient.OAUTH_BASE_URL}?{params}'
return f'{DiscordClient.OAUTH_BASE_URL}?{params}'
@classmethod
def generate_oauth_redirect_url(cls) -> str:
@@ -171,16 +171,16 @@ class DiscordUserManager(models.Manager):
def _exchange_auth_code_for_token(authorization_code: str) -> str:
oauth = OAuth2Session(DISCORD_APP_ID, redirect_uri=DISCORD_CALLBACK_URL)
token = oauth.fetch_token(
DiscordClient.OAUTH_TOKEN_URL,
client_secret=DISCORD_APP_SECRET,
DiscordClient.OAUTH_TOKEN_URL,
client_secret=DISCORD_APP_SECRET,
code=authorization_code
)
logger.debug("Received token from OAuth")
return token['access_token']
@classmethod
def server_name(cls, use_cache: bool = True) -> str:
"""returns the name of the current Discord server
"""returns the name of the current Discord server
or an empty string if the name could not be retrieved
Params:
@@ -194,7 +194,7 @@ class DiscordUserManager(models.Manager):
server_name = ""
except Exception:
logger.warning(
"Unexpected error when trying to retrieve the server name from Discord",
"Unexpected error when trying to retrieve the server name from Discord",
exc_info=True
)
server_name = ""

View File

@@ -22,11 +22,11 @@ logger = LoggerAddTag(logging.getLogger(__name__), __title__)
class DiscordUser(models.Model):
USER_RELATED_NAME = 'discord'
user = models.OneToOneField(
User,
primary_key=True,
on_delete=models.CASCADE,
User,
primary_key=True,
on_delete=models.CASCADE,
related_name=USER_RELATED_NAME,
help_text='Auth user owning this Discord account'
)
@@ -35,21 +35,21 @@ class DiscordUser(models.Model):
help_text='user\'s ID on Discord'
)
username = models.CharField(
max_length=32,
default='',
max_length=32,
default='',
blank=True,
db_index=True,
help_text='user\'s username on Discord'
)
discriminator = models.CharField(
max_length=4,
default='',
blank=True,
max_length=4,
default='',
blank=True,
help_text='user\'s discriminator on Discord'
)
activated = models.DateTimeField(
default=None,
null=True,
default=None,
null=True,
blank=True,
help_text='Date & time this service account was activated'
)
@@ -77,11 +77,11 @@ class DiscordUser(models.Model):
- True on success
- None if user is no longer a member of the Discord server
- False on error or raises exception
"""
"""
if not nickname:
nickname = DiscordUser.objects.user_formatted_nick(self.user)
if nickname:
client = DiscordUser.objects._bot_client()
if nickname:
client = DiscordUser.objects._bot_client()
success = client.modify_guild_member(
guild_id=DISCORD_GUILD_ID,
user_id=self.uid,
@@ -92,14 +92,14 @@ class DiscordUser(models.Model):
else:
logger.warning('Failed to update nickname for %s', self.user)
return success
else:
return False
def update_groups(self, state_name: str = None) -> bool:
"""update groups for a user based on his current group memberships.
"""update groups for a user based on his current group memberships.
Will add or remove roles of a user as needed.
Params:
- state_name: optional state name to be used
@@ -107,13 +107,13 @@ class DiscordUser(models.Model):
- True on success
- None if user is no longer a member of the Discord server
- False on error or raises exception
"""
client = DiscordUser.objects._bot_client()
"""
client = DiscordUser.objects._bot_client()
member_info = client.guild_member(guild_id=DISCORD_GUILD_ID, user_id=self.uid)
if member_info is None:
if member_info is None:
# User is no longer a member
return None
guild_roles = DiscordRoles(client.guild_roles(guild_id=DISCORD_GUILD_ID))
logger.debug('Current guild roles: %s', guild_roles.ids())
if 'roles' in member_info:
@@ -124,17 +124,17 @@ class DiscordUser(models.Model):
if not guild_roles.has_roles(member_info['roles']):
raise RuntimeError(
'Member %s has unknown roles: %s' % (
self.user,
self.user,
set(member_info['roles']).difference(guild_roles.ids())
)
)
member_roles = guild_roles.subset(member_info['roles'])
else:
raise RuntimeError('member_info from %s is not valid' % self.user)
requested_roles = match_or_create_roles_from_names(
client=client,
guild_id=DISCORD_GUILD_ID,
client=client,
guild_id=DISCORD_GUILD_ID,
role_names=DiscordUser.objects.user_group_names(
user=self.user, state_name=state_name
)
@@ -142,7 +142,7 @@ class DiscordUser(models.Model):
logger.debug(
'Requested roles for user %s: %s', self.user, requested_roles.ids()
)
logger.debug('Current roles user %s: %s', self.user, member_roles.ids())
logger.debug('Current roles user %s: %s', self.user, member_roles.ids())
member_roles_managed = member_roles.subset(managed_only=True)
if requested_roles != member_roles.difference(member_roles_managed):
logger.debug('Need to update roles for user %s', self.user)
@@ -163,25 +163,25 @@ class DiscordUser(models.Model):
return True
def update_username(self) -> bool:
"""Updates the username incl. the discriminator
"""Updates the username incl. the discriminator
from the Discord server and saves it
Returns:
- True on success
- None if user is no longer a member of the Discord server
- False on error or raises exception
"""
client = DiscordUser.objects._bot_client()
"""
client = DiscordUser.objects._bot_client()
user_info = client.guild_member(guild_id=DISCORD_GUILD_ID, user_id=self.uid)
if user_info is None:
success = None
elif (
user_info
and 'user' in user_info
and 'username' in user_info['user']
user_info
and 'user' in user_info
and 'username' in user_info['user']
and 'discriminator' in user_info['user']
):
):
self.username = user_info['user']['username']
self.discriminator = user_info['user']['discriminator']
self.save()
@@ -191,22 +191,22 @@ class DiscordUser(models.Model):
logger.warning('Failed to update username for %s', self.user)
success = False
return success
def delete_user(
self,
notify_user: bool = False,
self,
notify_user: bool = False,
is_rate_limited: bool = True,
handle_api_exceptions: bool = False
) -> bool:
"""Deletes the Discount user both on the server and locally
Params:
- notify_user: When True will sent a notification to the user
- notify_user: When True will sent a notification to the user
informing him about the deleting of his account
- is_rate_limited: When False will disable default rate limiting (use with care)
- handle_api_exceptions: When True method will return False
- handle_api_exceptions: When True method will return False
when an API exception occurs
Returns True when successful, otherwise False or raises exceptions
Return None if user does no longer exist
"""
@@ -221,8 +221,8 @@ class DiscordUser(models.Model):
if deleted_count > 0:
if notify_user:
notify(
user=_user,
title=gettext_lazy('Discord Account Disabled'),
user=_user,
title=gettext_lazy('Discord Account Disabled'),
message=gettext_lazy(
'Your Discord account was disabled automatically '
'by Auth. If you think this was a mistake, '
@@ -235,18 +235,18 @@ class DiscordUser(models.Model):
else:
logger.debug('Account for user %s was already deleted.', _user)
return None
else:
logger.warning(
'Failed to remove user %s from the Discord server', _user
)
return False
except (HTTPError, ConnectionError, DiscordApiBackoff) as ex:
if handle_api_exceptions:
logger.exception(
'Failed to remove user %s from Discord server: %s',self.user, ex
)
return False
return False
else:
raise ex

View File

@@ -29,11 +29,11 @@ BULK_TASK_PRIORITY = 6
)
def update_groups(self, user_pk: int, state_name: str = None) -> None:
"""Update roles on Discord for given user according to his current groups
Params:
- user_pk: PK of given user
- state_name: optional state name to be used
"""
"""
_task_perform_user_action(self, user_pk, 'update_groups', state_name=state_name)
@@ -42,7 +42,7 @@ def update_groups(self, user_pk: int, state_name: str = None) -> None:
)
def update_nickname(self, user_pk: int, nickname: str = None) -> None:
"""Set nickname on Discord for given user to his main character name
Params:
- user_pk: PK of given user
- nickname: optional nickname to be used instead of user's main
@@ -55,7 +55,7 @@ def update_nickname(self, user_pk: int, nickname: str = None) -> None:
)
def update_username(self, user_pk: int) -> None:
"""Update locally stored Discord username from Discord server for given user
Params:
- user_pk: PK of given user
"""
@@ -67,7 +67,7 @@ def update_username(self, user_pk: int) -> None:
)
def delete_user(self, user_pk: int, notify_user: bool = False) -> None:
"""Delete Discord user
Params:
- user_pk: PK of given user
"""
@@ -77,13 +77,13 @@ def delete_user(self, user_pk: int, notify_user: bool = False) -> None:
def _task_perform_user_action(self, user_pk: int, method: str, **kwargs) -> None:
"""perform a user related action incl. managing all exceptions"""
logger.debug("Starting %s for user with pk %s", method, user_pk)
user = User.objects.get(pk=user_pk)
user = User.objects.get(pk=user_pk)
# logger.debug("user %s has state %s", user, user.profile.state)
if DiscordUser.objects.user_has_account(user):
logger.info("Running %s for user %s", method, user)
try:
success = getattr(user.discord, method)(**kwargs)
except DiscordApiBackoff as bo:
logger.info(
"API back off for %s wth user %s due to %r, retrying in %s seconds",
@@ -92,26 +92,26 @@ def _task_perform_user_action(self, user_pk: int, method: str, **kwargs) -> None
bo,
bo.retry_after_seconds
)
raise self.retry(countdown=bo.retry_after_seconds)
raise self.retry(countdown=bo.retry_after_seconds)
except AttributeError:
raise ValueError(f'{method} not a valid method for DiscordUser')
except (HTTPError, ConnectionError):
except (HTTPError, ConnectionError):
logger.warning(
'%s failed for user %s, retrying in %d secs',
'%s failed for user %s, retrying in %d secs',
method,
user,
user,
DISCORD_TASKS_RETRY_PAUSE,
exc_info=True
)
if self.request.retries < DISCORD_TASKS_MAX_RETRIES:
raise self.retry(countdown=DISCORD_TASKS_RETRY_PAUSE)
else:
else:
logger.error(
'%s failed for user %s after max retries',
method,
user,
user,
exc_info=True
)
except Exception:
@@ -120,8 +120,8 @@ def _task_perform_user_action(self, user_pk: int, method: str, **kwargs) -> None
method,
user,
exc_info=True
)
)
else:
if success is None and method != 'delete_user':
delete_user.delay(user.pk, notify_user=True)
@@ -134,14 +134,14 @@ def _task_perform_user_action(self, user_pk: int, method: str, **kwargs) -> None
@shared_task(name='discord.update_all_groups')
def update_all_groups() -> None:
"""Update roles for all known users with a Discord account."""
"""Update roles for all known users with a Discord account."""
discord_users_qs = DiscordUser.objects.all()
_bulk_update_groups_for_users(discord_users_qs)
@shared_task(name='discord.update_groups_bulk')
def update_groups_bulk(user_pks: list) -> None:
"""Update roles for list of users with a Discord account in bulk."""
"""Update roles for list of users with a Discord account in bulk."""
discord_users_qs = DiscordUser.objects\
.filter(user__pk__in=user_pks)\
.select_related()
@@ -155,7 +155,7 @@ def _bulk_update_groups_for_users(discord_users_qs: QuerySet) -> None:
update_groups_chain = list()
for discord_user in discord_users_qs:
update_groups_chain.append(update_groups.si(discord_user.user.pk))
chain(update_groups_chain).apply_async(priority=BULK_TASK_PRIORITY)
@@ -164,11 +164,11 @@ def update_all_nicknames() -> None:
"""Update nicknames for all known users with a Discord account."""
discord_users_qs = DiscordUser.objects.all()
_bulk_update_nicknames_for_users(discord_users_qs)
@shared_task(name='discord.update_nicknames_bulk')
def update_nicknames_bulk(user_pks: list) -> None:
"""Update nicknames for list of users with a Discord account in bulk."""
"""Update nicknames for list of users with a Discord account in bulk."""
discord_users_qs = DiscordUser.objects\
.filter(user__pk__in=user_pks)\
.select_related()
@@ -177,27 +177,27 @@ def update_nicknames_bulk(user_pks: list) -> None:
def _bulk_update_nicknames_for_users(discord_users_qs: QuerySet) -> None:
logger.info(
"Starting to bulk update discord nicknames for %d users",
"Starting to bulk update discord nicknames for %d users",
discord_users_qs.count()
)
update_nicknames_chain = list()
for discord_user in discord_users_qs:
update_nicknames_chain.append(update_nickname.si(discord_user.user.pk))
chain(update_nicknames_chain).apply_async(priority=BULK_TASK_PRIORITY)
def _task_perform_users_action(self, method: str, **kwargs) -> Any:
"""Perform an action that concerns a group of users or the whole server
def _task_perform_users_action(self, method: str, **kwargs) -> Any:
"""Perform an action that concerns a group of users or the whole server
and that hits the API
"""
"""
result = None
try:
result = getattr(DiscordUser.objects, method)(**kwargs)
except AttributeError:
raise ValueError(f'{method} not a valid method for DiscordUser.objects')
except DiscordApiBackoff as bo:
logger.info(
"API back off for %s due to %r, retrying in %s seconds",
@@ -205,23 +205,23 @@ def _task_perform_users_action(self, method: str, **kwargs) -> Any:
bo,
bo.retry_after_seconds
)
raise self.retry(countdown=bo.retry_after_seconds)
except (HTTPError, ConnectionError):
raise self.retry(countdown=bo.retry_after_seconds)
except (HTTPError, ConnectionError):
logger.warning(
'%s failed, retrying in %d secs',
'%s failed, retrying in %d secs',
method,
DISCORD_TASKS_RETRY_PAUSE,
exc_info=True
)
if self.request.retries < DISCORD_TASKS_MAX_RETRIES:
raise self.retry(countdown=DISCORD_TASKS_RETRY_PAUSE)
else:
else:
logger.error('%s failed after max retries', method, exc_info=True)
except Exception:
logger.error('%s failed due to unexpected exception', method, exc_info=True)
return result
@@ -235,17 +235,17 @@ def update_servername(self) -> None:
@shared_task(name='discord.update_all_usernames')
def update_all_usernames() -> None:
"""Update all usernames for all known users with a Discord account.
"""Update all usernames for all known users with a Discord account.
Also updates the server name
"""
update_servername.delay()
discord_users_qs = DiscordUser.objects.all()
_bulk_update_usernames_for_users(discord_users_qs)
@shared_task(name='discord.update_usernames_bulk')
def update_usernames_bulk(user_pks: list) -> None:
"""Update usernames for list of users with a Discord account in bulk."""
"""Update usernames for list of users with a Discord account in bulk."""
discord_users_qs = DiscordUser.objects\
.filter(user__pk__in=user_pks)\
.select_related()
@@ -254,13 +254,13 @@ def update_usernames_bulk(user_pks: list) -> None:
def _bulk_update_usernames_for_users(discord_users_qs: QuerySet) -> None:
logger.info(
"Starting to bulk update discord usernames for %d users",
"Starting to bulk update discord usernames for %d users",
discord_users_qs.count()
)
update_usernames_chain = list()
for discord_user in discord_users_qs:
update_usernames_chain.append(update_username.si(discord_user.user.pk))
chain(update_usernames_chain).apply_async(priority=BULK_TASK_PRIORITY)
@@ -277,5 +277,5 @@ def update_all() -> None:
update_all_chain.append(update_username.si(discord_user.user.pk))
if DISCORD_SYNC_NAMES:
update_all_chain.append(update_nickname.si(discord_user.user.pk))
chain(update_all_chain).apply_async(priority=BULK_TASK_PRIORITY)

View File

@@ -5,14 +5,14 @@ from ..discord_client.tests import ( # noqa
TEST_USER_ID,
TEST_USER_NAME,
TEST_USER_DISCRIMINATOR,
create_role,
ROLE_ALPHA,
ROLE_BRAVO,
ROLE_CHARLIE,
ROLE_MIKE,
create_role,
ROLE_ALPHA,
ROLE_BRAVO,
ROLE_CHARLIE,
ROLE_MIKE,
ALL_ROLES,
create_user_info
)
)
DEFAULT_AUTH_GROUP = 'Member'
MODULE_PATH = 'allianceauth.services.modules.discord'

View File

@@ -53,21 +53,21 @@ def run_many_updates(runs):
logger.info('Starting piloting_tasks for %d runs', runs)
users = list()
all_groups = Group.objects.all()
for i in range(runs):
for i in range(runs):
if not users:
users = list(User.objects.filter(discord__isnull=False))
user = users.pop()
logger.info('%d/%d: Starting run with user %s', i + 1, runs, user)
# force change of nick
# force change of nick
new_nick = f'Testnick {uuid1().hex}'[:32]
logger.info(
'%d/%d: Changing nickname of %s to "%s"', i + 1, runs, user, new_nick
)
user.profile.main_character.character_name = new_nick
user.profile.main_character.save()
user.profile.main_character.save()
# force change of groups
user_groups = user.groups.all()
user_groups = user.groups.all()
user.groups.remove(random.choice(user_groups))
while True:
new_group = random.choice(all_groups)

View File

@@ -9,9 +9,9 @@ from allianceauth.eveonline.models import (
)
from ....admin import (
user_profile_pic,
user_username,
user_main_organization,
user_profile_pic,
user_username,
user_main_organization,
ServicesUserAdmin,
MainCorporationsFilter,
MainAllianceFilter
@@ -31,7 +31,7 @@ class TestDataMixin(TestCase):
EveAllianceInfo.objects.all().delete()
User.objects.all().delete()
DiscordUser.objects.all().delete()
# user 1 - corp and alliance, normal user
cls.character_1 = EveCharacter.objects.create(
character_id=1001,
@@ -56,16 +56,16 @@ class TestDataMixin(TestCase):
alliance = EveAllianceInfo.objects.create(
alliance_id=3001,
alliance_name='Wayne Enterprises',
alliance_ticker='WE',
alliance_ticker='WE',
executor_corp_id=2001
)
EveCorporationInfo.objects.create(
corporation_id=2001,
corporation_name='Wayne Technologies',
corporation_ticker='WT',
corporation_ticker='WT',
member_count=42,
alliance=alliance
)
)
cls.user_1 = User.objects.create_user(
cls.character_1.character_name.replace(' ', '_'),
'abc@example.com',
@@ -103,7 +103,7 @@ class TestDataMixin(TestCase):
EveCorporationInfo.objects.create(
corporation_id=2002,
corporation_name='Daily Plane',
corporation_ticker='DP',
corporation_ticker='DP',
member_count=99,
alliance=None
)
@@ -123,7 +123,7 @@ class TestDataMixin(TestCase):
user=cls.user_2,
uid=1002
)
# user 3 - no main, no group, superuser
cls.character_3 = EveCharacter.objects.create(
character_id=1101,
@@ -136,7 +136,7 @@ class TestDataMixin(TestCase):
EveCorporationInfo.objects.create(
corporation_id=2101,
corporation_name='Lex Corp',
corporation_ticker='LC',
corporation_ticker='LC',
member_count=666,
alliance=None
)
@@ -160,13 +160,13 @@ class TestDataMixin(TestCase):
user=cls.user_3,
uid=1003
)
def setUp(self):
self.factory = RequestFactory()
self.modeladmin = DiscordUserAdmin(
model=DiscordUser, admin_site=AdminSite()
)
)
class TestColumnRendering(TestDataMixin, TestCase):
@@ -230,12 +230,12 @@ class TestColumnRendering(TestDataMixin, TestCase):
class TestFilters(TestDataMixin, TestCase):
def test_filter_main_corporations(self):
class DiscordUserAdminTest(ServicesUserAdmin):
class DiscordUserAdminTest(ServicesUserAdmin):
list_filter = (MainCorporationsFilter,)
my_modeladmin = DiscordUserAdminTest(DiscordUser, AdminSite())
# Make sure the lookups are correct
@@ -244,7 +244,7 @@ class TestFilters(TestDataMixin, TestCase):
changelist = my_modeladmin.get_changelist_instance(request)
filters = changelist.get_filters(request)
filterspec = filters[0][0]
expected = [
expected = [
(2002, 'Daily Planet'),
(2001, 'Wayne Technologies'),
]
@@ -254,19 +254,19 @@ class TestFilters(TestDataMixin, TestCase):
request = self.factory.get(
'/', {'main_corporation_id__exact': self.character_1.corporation_id}
)
request.user = self.user_1
request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request)
queryset = changelist.get_queryset(request)
expected = [self.user_1.discord]
self.assertSetEqual(set(queryset), set(expected))
def test_filter_main_alliances(self):
class DiscordUserAdminTest(ServicesUserAdmin):
class DiscordUserAdminTest(ServicesUserAdmin):
list_filter = (MainAllianceFilter,)
my_modeladmin = DiscordUserAdminTest(DiscordUser, AdminSite())
# Make sure the lookups are correct
request = self.factory.get('/')
request.user = self.user_1
@@ -282,7 +282,7 @@ class TestFilters(TestDataMixin, TestCase):
request = self.factory.get(
'/', {'main_alliance_id__exact': self.character_1.alliance_id}
)
request.user = self.user_1
request.user = self.user_1
changelist = my_modeladmin.get_changelist_instance(request)
queryset = changelist.get_queryset(request)
expected = [self.user_1.discord]

View File

@@ -19,11 +19,11 @@ logger = set_logger_to_file(MODULE_PATH + '.auth_hooks', __file__)
@override_settings(CELERY_ALWAYS_EAGER=True)
class TestDiscordService(TestCase):
def setUp(self):
def setUp(self):
self.member = AuthUtils.create_member(TEST_USER_NAME)
DiscordUser.objects.create(
user=self.member,
uid=TEST_USER_ID,
user=self.member,
uid=TEST_USER_ID,
username=TEST_USER_NAME,
discriminator='1234'
)
@@ -34,33 +34,33 @@ class TestDiscordService(TestCase):
Notification.objects.all().delete()
def test_service_enabled(self):
service = self.service()
service = self.service()
self.assertTrue(service.service_active_for_user(self.member))
self.assertFalse(service.service_active_for_user(self.none_member))
@patch(MODULE_PATH + '.tasks.update_all_groups')
def test_update_all_groups(self, mock_update_all_groups):
service = self.service()
service.update_all_groups()
service.update_all_groups()
self.assertTrue(mock_update_all_groups.delay.called)
@patch(MODULE_PATH + '.tasks.update_groups_bulk')
def test_update_groups_bulk(self, mock_update_groups_bulk):
service = self.service()
service = self.service()
service.update_groups_bulk([self.member])
self.assertTrue(mock_update_groups_bulk.delay.called)
self.assertTrue(mock_update_groups_bulk.delay.called)
@patch(MODULE_PATH + '.tasks.update_groups')
def test_update_groups_for_member(self, mock_update_groups):
service = self.service()
def test_update_groups_for_member(self, mock_update_groups):
service = self.service()
service.update_groups(self.member)
self.assertTrue(mock_update_groups.apply_async.called)
self.assertTrue(mock_update_groups.apply_async.called)
@patch(MODULE_PATH + '.tasks.update_groups')
def test_update_groups_for_none_member(self, mock_update_groups):
def test_update_groups_for_none_member(self, mock_update_groups):
service = self.service()
service.update_groups(self.none_member)
self.assertFalse(mock_update_groups.apply_async.called)
self.assertFalse(mock_update_groups.apply_async.called)
@patch(MODULE_PATH + '.models.notify')
@patch(MODULE_PATH + '.tasks.DiscordUser')
@@ -69,52 +69,52 @@ class TestDiscordService(TestCase):
self, mock_DiscordClient, mock_DiscordUser, mock_notify
):
mock_DiscordClient.return_value.remove_guild_member.return_value = True
# Test member is not deleted
service = self.service()
service.validate_user(self.member)
self.assertTrue(DiscordUser.objects.filter(user=self.member).exists())
# Test none member is deleted
# Test none member is deleted
DiscordUser.objects.create(user=self.none_member, uid=TEST_USER_ID)
service.validate_user(self.none_member)
service.validate_user(self.none_member)
self.assertFalse(DiscordUser.objects.filter(user=self.none_member).exists())
@patch(MODULE_PATH + '.tasks.update_nickname')
def test_sync_nickname(self, mock_update_nickname):
service = self.service()
service = self.service()
service.sync_nickname(self.member)
self.assertTrue(mock_update_nickname.apply_async.called)
@patch(MODULE_PATH + '.tasks.update_nicknames_bulk')
def test_sync_nicknames_bulk(self, mock_update_nicknames_bulk):
service = self.service()
service.sync_nicknames_bulk([self.member])
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, 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):
mock_DiscordClient.return_value.remove_guild_member.return_value = True
service = self.service()
service = self.service()
service.delete_user(self.none_member)
self.assertFalse(mock_DiscordClient.return_value.remove_guild_member.called)
@patch(MODULE_PATH + '.managers.DiscordClient', spec=DiscordClient)
def test_render_services_ctrl_with_username(self, mock_DiscordClient):
service = self.service()
service = self.service()
request = self.factory.get('/services/')
request.user = self.member

View File

@@ -26,11 +26,11 @@ from allianceauth.tests.auth_utils import AuthUtils
from . import (
TEST_GUILD_ID,
TEST_USER_NAME,
TEST_USER_NAME,
TEST_USER_ID,
TEST_USER_DISCRIMINATOR,
TEST_MAIN_NAME,
TEST_MAIN_ID,
TEST_USER_DISCRIMINATOR,
TEST_MAIN_NAME,
TEST_MAIN_ID,
MODULE_PATH,
add_permissions_to_members,
ROLE_ALPHA,
@@ -75,11 +75,11 @@ guild_member_request = DiscordRequest(
add_guild_member_request = DiscordRequest(
method='PUT',
url=f'{DISCORD_API_BASE_URL}guilds/{TEST_GUILD_ID}/members/{TEST_USER_ID}'
)
)
modify_guild_member_request = DiscordRequest(
method='PATCH',
url=f'{DISCORD_API_BASE_URL}guilds/{TEST_GUILD_ID}/members/{TEST_USER_ID}'
)
)
remove_guild_member_request = DiscordRequest(
method='DELETE',
url=f'{DISCORD_API_BASE_URL}guilds/{TEST_GUILD_ID}/members/{TEST_USER_ID}'
@@ -93,7 +93,7 @@ def clear_cache():
logger.info('Cache flushed')
def reset_testdata():
def reset_testdata():
AuthUtils.disconnect_signals()
Group.objects.all().delete()
User.objects.all().delete()
@@ -112,15 +112,15 @@ class TestServiceFeatures(TransactionTestCase):
def setUpClass(cls):
super().setUpClass()
cls.maxDiff = None
def setUp(self):
"""All tests: Given a user with member state,
"""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')
self.group_charlie = Group.objects.create(name='charlie')
# States
self.member_state = AuthUtils.get_member_state()
self.guest_state = AuthUtils.get_guest_state()
@@ -132,64 +132,64 @@ class TestServiceFeatures(TransactionTestCase):
# Test user
self.user = AuthUtils.create_user(TEST_USER_NAME)
self.main = AuthUtils.add_main_character_2(
self.user,
TEST_MAIN_NAME,
TEST_MAIN_ID,
corp_id='2',
corp_name='test_corp',
self.user,
TEST_MAIN_NAME,
TEST_MAIN_ID,
corp_id='2',
corp_name='test_corp',
corp_ticker='TEST',
disconnect_signals=True
)
)
self.member_state.member_characters.add(self.main)
# verify user is a member and has an account
self.user = User.objects.get(pk=self.user.pk)
self.assertEqual(self.user.profile.state, self.member_state)
self.discord_user = DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
self.assertTrue(DiscordUser.objects.user_has_account(self.user))
def test_when_name_of_main_changes_then_discord_nick_is_updated(
self, requests_mocker
):
):
requests_mocker.patch(modify_guild_member_request.url, status_code=204)
# changing nick to trigger signals
new_nick = f'Testnick {uuid1().hex}'[:32]
self.user.profile.main_character.character_name = new_nick
self.user.profile.main_character.save()
# verify Discord nick was updates
nick_updated = False
for r in requests_mocker.request_history:
my_request = DiscordRequest(r.method, r.url)
for r in requests_mocker.request_history:
my_request = DiscordRequest(r.method, r.url)
if my_request == modify_guild_member_request and "nick" in r.json():
nick_updated = True
self.assertEqual(r.json()["nick"], new_nick)
self.assertTrue(nick_updated)
self.assertTrue(DiscordUser.objects.user_has_account(self.user))
def test_when_name_of_main_changes_and_user_deleted_then_account_is_deleted(
self, requests_mocker
):
):
requests_mocker.patch(
modify_guild_member_request.url, status_code=404, json={'code': 10007}
)
)
requests_mocker.delete(remove_guild_member_request.url, status_code=204)
# changing nick to trigger signals
new_nick = f'Testnick {uuid1().hex}'[:32]
self.user.profile.main_character.character_name = new_nick
self.user.profile.main_character.save()
self.assertFalse(DiscordUser.objects.user_has_account(self.user))
def test_when_name_of_main_changes_and_and_rate_limited_then_dont_call_api(
self, requests_mocker
):
):
requests_mocker.patch(modify_guild_member_request.url, status_code=204)
# exhausting rate limit
client = DiscordUser.objects._bot_client()
client._redis.set(
@@ -197,7 +197,7 @@ class TestServiceFeatures(TransactionTestCase):
value=0,
px=2000
)
# changing nick to trigger signals
new_nick = f'Testnick {uuid1().hex}'[:32]
self.user.profile.main_character.character_name = new_nick
@@ -207,55 +207,55 @@ class TestServiceFeatures(TransactionTestCase):
requests_made = [
DiscordRequest(r.method, r.url) for r in requests_mocker.request_history
]
self.assertListEqual(requests_made, list())
def test_when_member_is_demoted_to_guest_then_his_account_is_deleted(
self, requests_mocker
):
):
requests_mocker.patch(modify_guild_member_request.url, status_code=204)
requests_mocker.delete(remove_guild_member_request.url, status_code=204)
# our user is a member and has an account
# our user is a member and has an account
self.assertTrue(self.user.has_perm('discord.access_discord'))
# now we demote him to guest
# now we demote him to guest
self.member_state.member_characters.remove(self.main)
# verify user is now guest
self.user = User.objects.get(pk=self.user.pk)
self.user = User.objects.get(pk=self.user.pk)
self.assertEqual(self.user.profile.state, AuthUtils.get_guest_state())
# verify user has no longer access to Discord and no account
self.assertFalse(self.user.has_perm('discord.access_discord'))
self.assertFalse(DiscordUser.objects.user_has_account(self.user))
self.assertFalse(DiscordUser.objects.user_has_account(self.user))
# verify account was actually deleted from Discord server
requests_made = [
DiscordRequest(r.method, r.url) for r in requests_mocker.request_history
]
]
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
):
):
# request mocks
requests_mocker.get(
guild_member_request.url,
json={'user': create_user_info(), 'roles': ['3', '13', '99']}
)
)
requests_mocker.get(
guild_roles_request.url,
json=[
ROLE_ALPHA, ROLE_BRAVO, ROLE_CHARLIE, ROLE_MIKE, ROLE_MEMBER, ROLE_BLUE
]
)
requests_mocker.post(create_guild_role_request.url, json=ROLE_CHARLIE)
)
requests_mocker.post(create_guild_role_request.url, json=ROLE_CHARLIE)
requests_mocker.patch(modify_guild_member_request.url, status_code=204)
AuthUtils.disconnect_signals()
self.user.groups.add(self.group_charlie)
AuthUtils.connect_signals()
@@ -264,101 +264,101 @@ class TestServiceFeatures(TransactionTestCase):
self.blue_state.member_characters.add(self.main)
self.member_state.member_characters.remove(self.main)
# 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)
for r in requests_mocker.request_history:
my_request = DiscordRequest(r.method, r.url)
if my_request == modify_guild_member_request and "roles" in r.json():
roles_updated = True
self.assertSetEqual(set(r.json()["roles"]), {3, 13, 98})
break
self.assertTrue(roles_updated)
self.assertTrue(DiscordUser.objects.user_has_account(self.user))
def test_when_group_added_to_member_and_role_known_then_his_roles_are_updated(
self, requests_mocker
):
):
requests_mocker.get(
guild_member_request.url,
json={
'user': create_user_info(),
'roles': ['13', '99']
}
)
)
requests_mocker.get(
guild_roles_request.url,
guild_roles_request.url,
json=[ROLE_ALPHA, ROLE_BRAVO, ROLE_CHARLIE, ROLE_MIKE, ROLE_MEMBER]
)
requests_mocker.post(create_guild_role_request.url, json=ROLE_CHARLIE)
)
requests_mocker.post(create_guild_role_request.url, json=ROLE_CHARLIE)
requests_mocker.patch(modify_guild_member_request.url, status_code=204)
# adding new group to trigger signals
self.user.groups.add(self.group_charlie)
# verify roles for user where updated
roles_updated = False
for r in requests_mocker.request_history:
my_request = DiscordRequest(r.method, r.url)
for r in requests_mocker.request_history:
my_request = DiscordRequest(r.method, r.url)
if my_request == modify_guild_member_request and "roles" in r.json():
roles_updated = True
self.assertSetEqual(set(r.json()["roles"]), {3, 13, 99})
break
self.assertTrue(roles_updated)
self.assertTrue(DiscordUser.objects.user_has_account(self.user))
def test_when_group_added_to_member_and_role_unknown_then_his_roles_are_updated(
self, requests_mocker
):
):
requests_mocker.get(
guild_member_request.url,
json={
'user': {'id': str(TEST_USER_ID), 'username': TEST_MAIN_NAME},
'roles': ['13', '99']
}
)
)
requests_mocker.get(
guild_roles_request.url,
guild_roles_request.url,
json=[ROLE_ALPHA, ROLE_BRAVO, ROLE_MIKE, ROLE_MEMBER]
)
requests_mocker.post(create_guild_role_request.url, json=ROLE_CHARLIE)
)
requests_mocker.post(create_guild_role_request.url, json=ROLE_CHARLIE)
requests_mocker.patch(modify_guild_member_request.url, status_code=204)
# adding new group to trigger signals
self.user.groups.add(self.group_charlie)
self.user.refresh_from_db()
# verify roles for user where updated
roles_updated = False
for r in requests_mocker.request_history:
my_request = DiscordRequest(r.method, r.url)
for r in requests_mocker.request_history:
my_request = DiscordRequest(r.method, r.url)
if my_request == modify_guild_member_request and "roles" in r.json():
roles_updated = True
self.assertSetEqual(set(r.json()["roles"]), {3, 13, 99})
break
self.assertTrue(roles_updated)
self.assertTrue(DiscordUser.objects.user_has_account(self.user))
@override_settings(CELERY_ALWAYS_EAGER=True)
@patch(MODULE_PATH + '.managers.DISCORD_GUILD_ID', TEST_GUILD_ID)
@patch(MODULE_PATH + '.models.DISCORD_GUILD_ID', TEST_GUILD_ID)
@requests_mock.Mocker()
class StateTestCase(TestCase):
def setUp(self):
def setUp(self):
clear_cache()
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',
self.user,
'Perm Test Character', '99',
corp_id='100',
alliance_id='200',
corp_name='Perm Test Corp',
corp_name='Perm Test Corp',
alliance_name='Perm Test Alliance'
)
self.test_character = EveCharacter.objects.get(character_id='99')
@@ -369,7 +369,7 @@ class StateTestCase(TestCase):
self.access_discord = AuthUtils.get_permission_by_name('discord.access_discord')
self.member_state.permissions.add(self.access_discord)
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"
@@ -434,57 +434,57 @@ class StateTestCase(TestCase):
@patch(MODULE_PATH + '.models.DISCORD_GUILD_ID', TEST_GUILD_ID)
@requests_mock.Mocker()
class TestUserFeatures(WebTest):
def setUp(self):
clear_cache()
reset_testdata()
self.member = AuthUtils.create_member(TEST_USER_NAME)
AuthUtils.add_main_character_2(
self.member,
TEST_MAIN_NAME,
self.member,
TEST_MAIN_NAME,
TEST_MAIN_ID,
disconnect_signals=True
)
add_permissions_to_members()
@patch(MODULE_PATH + '.views.messages')
@patch(MODULE_PATH + '.views.messages')
@patch(MODULE_PATH + '.managers.OAuth2Session')
def test_user_activation_normal(
self, requests_mocker, mock_OAuth2Session, mock_messages
):
):
# setup
requests_mocker.get(
guild_infos_request.url, json={'id': TEST_GUILD_ID, 'name': 'Test Guild'}
)
)
requests_mocker.get(
user_get_current_request.url,
user_get_current_request.url,
json=create_user_info(
TEST_USER_ID, TEST_USER_NAME, TEST_USER_DISCRIMINATOR
)
)
)
requests_mocker.get(
guild_roles_request.url,
guild_roles_request.url,
json=[ROLE_ALPHA, ROLE_BRAVO, ROLE_MIKE, ROLE_MEMBER]
)
)
requests_mocker.put(add_guild_member_request.url, status_code=201)
authentication_code = 'auth_code'
authentication_code = 'auth_code'
oauth_url = 'https://www.example.com/oauth'
state = ''
mock_OAuth2Session.return_value.authorization_url.return_value = \
oauth_url, state
# login
self.app.set_user(self.member)
# user opens services page
services_page = self.app.get(reverse('services:services'))
self.assertEqual(services_page.status_code, 200)
# user clicks Discord service activation link on page
# user clicks Discord service activation link on page
response = services_page.click(href=reverse('discord:activate'))
# check we got a redirect to Discord OAuth
# check we got a redirect to Discord OAuth
self.assertRedirects(
response, expected_url=oauth_url, fetch_redirect_response=False
)
@@ -493,66 +493,66 @@ class TestUserFeatures(WebTest):
response = self.app.get(
reverse('discord:callback'), params={'code': authentication_code}
)
# user got a success message
self.assertTrue(mock_messages.success.called)
self.assertFalse(mock_messages.error.called)
requests_made = list()
for r in requests_mocker.request_history:
obj = DiscordRequest(r.method, r.url)
for r in requests_mocker.request_history:
obj = DiscordRequest(r.method, r.url)
requests_made.append(obj)
expected = [
guild_infos_request,
user_get_current_request,
guild_roles_request,
guild_infos_request,
user_get_current_request,
guild_roles_request,
add_guild_member_request
]
self.assertListEqual(requests_made, expected)
@patch(MODULE_PATH + '.views.messages')
@patch(MODULE_PATH + '.views.messages')
@patch(MODULE_PATH + '.managers.OAuth2Session')
def test_user_activation_failed(
self, requests_mocker, mock_OAuth2Session, mock_messages
):
):
# setup
requests_mocker.get(
guild_infos_request.url, json={'id': TEST_GUILD_ID, 'name': 'Test Guild'}
)
requests_mocker.get(
user_get_current_request.url,
user_get_current_request.url,
json=create_user_info(
TEST_USER_ID, TEST_USER_NAME, TEST_USER_DISCRIMINATOR
)
)
)
requests_mocker.get(
guild_roles_request.url,
guild_roles_request.url,
json=[ROLE_ALPHA, ROLE_BRAVO, ROLE_MIKE, ROLE_MEMBER]
)
)
mock_exception = HTTPError('error')
mock_exception.response = Mock()
mock_exception.response.status_code = 503
requests_mocker.put(add_guild_member_request.url, exc=mock_exception)
authentication_code = 'auth_code'
authentication_code = 'auth_code'
oauth_url = 'https://www.example.com/oauth'
state = ''
mock_OAuth2Session.return_value.authorization_url.return_value = \
oauth_url, state
# login
self.app.set_user(self.member)
# user opens services page
services_page = self.app.get(reverse('services:services'))
self.assertEqual(services_page.status_code, 200)
# click activate on the service page
response = services_page.click(href=reverse('discord:activate'))
# check we got a redirect to Discord OAuth
# check we got a redirect to Discord OAuth
self.assertRedirects(
response, expected_url=oauth_url, fetch_redirect_response=False
)
@@ -561,124 +561,124 @@ class TestUserFeatures(WebTest):
response = self.app.get(
reverse('discord:callback'), params={'code': authentication_code}
)
# user got a success message
self.assertFalse(mock_messages.success.called)
self.assertTrue(mock_messages.error.called)
requests_made = list()
for r in requests_mocker.request_history:
obj = DiscordRequest(r.method, r.url)
for r in requests_mocker.request_history:
obj = DiscordRequest(r.method, r.url)
requests_made.append(obj)
expected = [
guild_infos_request,
user_get_current_request,
guild_roles_request,
user_get_current_request,
guild_roles_request,
add_guild_member_request
]
self.assertListEqual(requests_made, expected)
@patch(MODULE_PATH + '.views.messages')
def test_user_deactivation_normal(self, requests_mocker, mock_messages):
def test_user_deactivation_normal(self, requests_mocker, mock_messages):
# setup
requests_mocker.get(
guild_infos_request.url, json={'id': TEST_GUILD_ID, 'name': 'Test Guild'}
)
)
requests_mocker.delete(remove_guild_member_request.url, status_code=204)
DiscordUser.objects.create(user=self.member, uid=TEST_USER_ID)
# login
self.app.set_user(self.member)
# user opens services page
services_page = self.app.get(reverse('services:services'))
self.assertEqual(services_page.status_code, 200)
# click deactivate on the service page
# click deactivate on the service page
response = services_page.click(href=reverse('discord:deactivate'))
# check we got a redirect to service page
self.assertRedirects(response, expected_url=reverse('services:services'))
# user got a success message
self.assertTrue(mock_messages.success.called)
self.assertFalse(mock_messages.error.called)
requests_made = list()
for r in requests_mocker.request_history:
obj = DiscordRequest(r.method, r.url)
for r in requests_mocker.request_history:
obj = DiscordRequest(r.method, r.url)
requests_made.append(obj)
expected = [guild_infos_request, remove_guild_member_request]
self.assertListEqual(requests_made, expected)
@patch(MODULE_PATH + '.views.messages')
def test_user_deactivation_fails(self, requests_mocker, mock_messages):
def test_user_deactivation_fails(self, requests_mocker, mock_messages):
# setup
requests_mocker.get(
guild_infos_request.url, json={'id': TEST_GUILD_ID, 'name': 'Test Guild'}
)
)
mock_exception = HTTPError('error')
mock_exception.response = Mock()
mock_exception.response.status_code = 503
requests_mocker.delete(remove_guild_member_request.url, exc=mock_exception)
DiscordUser.objects.create(user=self.member, uid=TEST_USER_ID)
# login
self.app.set_user(self.member)
# user opens services page
services_page = self.app.get(reverse('services:services'))
self.assertEqual(services_page.status_code, 200)
# click deactivate on the service page
# click deactivate on the service page
response = services_page.click(href=reverse('discord:deactivate'))
# check we got a redirect to service page
self.assertRedirects(response, expected_url=reverse('services:services'))
# user got a success message
self.assertFalse(mock_messages.success.called)
self.assertTrue(mock_messages.error.called)
requests_made = list()
for r in requests_mocker.request_history:
obj = DiscordRequest(r.method, r.url)
for r in requests_mocker.request_history:
obj = DiscordRequest(r.method, r.url)
requests_made.append(obj)
expected = [guild_infos_request, remove_guild_member_request]
self.assertListEqual(requests_made, expected)
@patch(MODULE_PATH + '.views.messages')
def test_user_add_new_server(self, requests_mocker, mock_messages):
# setup
def test_user_add_new_server(self, requests_mocker, mock_messages):
# setup
mock_exception = HTTPError(Mock(**{"response.status_code": 400}))
requests_mocker.get(guild_infos_request.url, exc=mock_exception)
# login
# login
self.member.is_superuser = True
self.member.is_staff = True
self.member.save()
self.app.set_user(self.member)
# click deactivate on the service page
response = self.app.get(reverse('services:services'))
# 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'))
def test_when_server_name_fails_user_can_still_see_service_page(
self, requests_mocker
):
):
# setup
requests_mocker.get(guild_infos_request.url, exc=DiscordApiBackoff(1000))
# login
self.app.set_user(self.member)
# user opens services page
services_page = self.app.get(reverse('services:services'))
self.assertEqual(services_page.status_code, 200)
@@ -686,7 +686,7 @@ class TestUserFeatures(WebTest):
@override_settings(CELERY_ALWAYS_EAGER=True)
def test_server_name_is_updated_by_task(
self, requests_mocker
):
):
# setup
requests_mocker.get(
guild_infos_request.url, json={'id': TEST_GUILD_ID, 'name': 'Test Guild'}
@@ -696,11 +696,11 @@ class TestUserFeatures(WebTest):
# login
self.app.set_user(self.member)
# disable API call to make sure server name is not retrieved from API
mock_exception = HTTPError(Mock(**{"response.status_code": 400}))
requests_mocker.get(guild_infos_request.url, exc=mock_exception)
# user opens services page
services_page = self.app.get(reverse('services:services'))
self.assertEqual(services_page.status_code, 200)

View File

@@ -9,21 +9,21 @@ from django.test import TestCase
from allianceauth.tests.auth_utils import AuthUtils
from . import (
TEST_GUILD_ID,
TEST_USER_NAME,
TEST_USER_ID,
TEST_MAIN_NAME,
TEST_MAIN_ID,
TEST_GUILD_ID,
TEST_USER_NAME,
TEST_USER_ID,
TEST_MAIN_NAME,
TEST_MAIN_ID,
MODULE_PATH,
ROLE_ALPHA,
ROLE_BRAVO,
ROLE_CHARLIE,
ROLE_CHARLIE,
)
from ..discord_client.tests import create_matched_role
from ..app_settings import (
DISCORD_APP_ID,
DISCORD_APP_SECRET,
DISCORD_CALLBACK_URL,
DISCORD_APP_ID,
DISCORD_APP_SECRET,
DISCORD_CALLBACK_URL,
)
from ..discord_client import DiscordClient, DiscordApiBackoff
from ..models import DiscordUser
@@ -39,32 +39,32 @@ logger = set_logger_to_file(MODULE_PATH + '.managers', __file__)
@patch(MODULE_PATH + '.models.DiscordUser.objects.user_group_names')
@patch(MODULE_PATH + '.models.DiscordUser.objects.user_formatted_nick')
class TestAddUser(TestCase):
def setUp(self):
self.user = AuthUtils.create_user(TEST_USER_NAME)
self.user_info = {
'id': TEST_USER_ID,
'id': TEST_USER_ID,
'name': TEST_USER_NAME,
'username': TEST_USER_NAME,
'discriminator': '1234',
}
self.access_token = 'accesstoken'
def test_can_create_user_no_roles_no_nick(
self,
self,
mock_user_formatted_nick,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_DiscordClient
):
mock_user_formatted_nick.return_value = None
mock_user_group_names.return_value = []
mock_user_group_names.return_value = []
mock_exchange_auth_code_for_token.return_value = self.access_token
mock_DiscordClient.return_value.current_user.return_value = self.user_info
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = []
mock_DiscordClient.return_value.add_guild_member.return_value = True
result = DiscordUser.objects.add_user(self.user, authorization_code='abcdef')
self.assertTrue(result)
self.assertTrue(
@@ -79,25 +79,25 @@ class TestAddUser(TestCase):
self.assertIsNone(kwargs['nick'])
def test_can_create_user_with_roles_no_nick(
self,
self,
mock_user_formatted_nick,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_DiscordClient
):
roles = [
create_matched_role(ROLE_ALPHA),
create_matched_role(ROLE_BRAVO),
create_matched_role(ROLE_ALPHA),
create_matched_role(ROLE_BRAVO),
create_matched_role(ROLE_CHARLIE)
]
mock_user_formatted_nick.return_value = None
mock_user_group_names.return_value = ['a', 'b', 'c']
mock_exchange_auth_code_for_token.return_value = self.access_token
mock_user_group_names.return_value = ['a', 'b', 'c']
mock_exchange_auth_code_for_token.return_value = self.access_token
mock_DiscordClient.return_value.current_user.return_value = self.user_info
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = roles
mock_DiscordClient.return_value.add_guild_member.return_value = True
result = DiscordUser.objects.add_user(self.user, authorization_code='abcdef')
self.assertTrue(result)
self.assertTrue(
@@ -113,20 +113,20 @@ class TestAddUser(TestCase):
@patch(MODULE_PATH + '.managers.DISCORD_SYNC_NAMES', True)
def test_can_create_user_no_roles_with_nick(
self,
self,
mock_user_formatted_nick,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_DiscordClient
):
):
mock_user_formatted_nick.return_value = TEST_MAIN_NAME
mock_user_group_names.return_value = []
mock_user_group_names.return_value = []
mock_exchange_auth_code_for_token.return_value = self.access_token
mock_DiscordClient.return_value.current_user.return_value = self.user_info
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = []
mock_DiscordClient.return_value.add_guild_member.return_value = True
result = DiscordUser.objects.add_user(self.user, authorization_code='abcdef')
self.assertTrue(result)
self.assertTrue(
@@ -142,20 +142,20 @@ class TestAddUser(TestCase):
@patch(MODULE_PATH + '.managers.DISCORD_SYNC_NAMES', False)
def test_can_create_user_no_roles_and_without_nick_if_turned_off(
self,
self,
mock_user_formatted_nick,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_DiscordClient
):
):
mock_user_formatted_nick.return_value = TEST_MAIN_NAME
mock_user_group_names.return_value = []
mock_user_group_names.return_value = []
mock_exchange_auth_code_for_token.return_value = self.access_token
mock_DiscordClient.return_value.current_user.return_value = self.user_info
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = []
mock_DiscordClient.return_value.add_guild_member.return_value = True
result = DiscordUser.objects.add_user(self.user, authorization_code='abcdef')
self.assertTrue(result)
self.assertTrue(
@@ -168,44 +168,44 @@ class TestAddUser(TestCase):
self.assertEqual(kwargs['access_token'], self.access_token)
self.assertIsNone(kwargs['role_ids'])
self.assertIsNone(kwargs['nick'])
def test_can_activate_existing_guild_member(
self,
self,
mock_user_formatted_nick,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_DiscordClient
):
mock_user_formatted_nick.return_value = None
mock_user_group_names.return_value = []
mock_user_group_names.return_value = []
mock_exchange_auth_code_for_token.return_value = self.access_token
mock_DiscordClient.return_value.current_user.return_value = self.user_info
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = []
mock_DiscordClient.return_value.add_guild_member.return_value = None
result = DiscordUser.objects.add_user(self.user, authorization_code='abcdef')
self.assertTrue(result)
self.assertTrue(
DiscordUser.objects.filter(user=self.user, uid=TEST_USER_ID).exists()
)
self.assertTrue(mock_DiscordClient.return_value.add_guild_member.called)
def test_return_false_when_user_creation_fails(
self,
self,
mock_user_formatted_nick,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_DiscordClient
):
):
mock_user_formatted_nick.return_value = None
mock_user_group_names.return_value = []
mock_user_group_names.return_value = []
mock_exchange_auth_code_for_token.return_value = self.access_token
mock_DiscordClient.return_value.current_user.return_value = self.user_info
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = []
mock_DiscordClient.return_value.add_guild_member.return_value = False
result = DiscordUser.objects.add_user(self.user, authorization_code='abcdef')
self.assertFalse(result)
self.assertFalse(
@@ -214,21 +214,21 @@ class TestAddUser(TestCase):
self.assertTrue(mock_DiscordClient.return_value.add_guild_member.called)
def test_return_false_when_on_api_backoff(
self,
self,
mock_user_formatted_nick,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_DiscordClient
):
):
mock_user_formatted_nick.return_value = None
mock_user_group_names.return_value = []
mock_user_group_names.return_value = []
mock_exchange_auth_code_for_token.return_value = self.access_token
mock_DiscordClient.return_value.current_user.return_value = self.user_info
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = []
mock_DiscordClient.return_value.add_guild_member.side_effect = \
DiscordApiBackoff(999)
result = DiscordUser.objects.add_user(self.user, authorization_code='abcdef')
self.assertFalse(result)
self.assertFalse(
@@ -237,14 +237,14 @@ class TestAddUser(TestCase):
self.assertTrue(mock_DiscordClient.return_value.add_guild_member.called)
def test_return_false_on_http_error(
self,
self,
mock_user_formatted_nick,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_user_group_names,
mock_exchange_auth_code_for_token,
mock_DiscordClient
):
):
mock_user_formatted_nick.return_value = None
mock_user_group_names.return_value = []
mock_user_group_names.return_value = []
mock_exchange_auth_code_for_token.return_value = self.access_token
mock_DiscordClient.return_value.current_user.return_value = self.user_info
mock_DiscordClient.return_value.match_or_create_roles_from_names\
@@ -253,18 +253,18 @@ class TestAddUser(TestCase):
mock_exception.response = Mock()
mock_exception.response.status_code = 500
mock_DiscordClient.return_value.add_guild_member.side_effect = mock_exception
result = DiscordUser.objects.add_user(self.user, authorization_code='abcdef')
self.assertFalse(result)
self.assertFalse(
DiscordUser.objects.filter(user=self.user, uid=TEST_USER_ID).exists()
)
self.assertTrue(mock_DiscordClient.return_value.add_guild_member.called)
class TestOauthHelpers(TestCase):
@patch(MODULE_PATH + '.managers.DISCORD_APP_ID', '123456')
@patch(MODULE_PATH + '.managers.DISCORD_APP_ID', '123456')
def test_generate_bot_add_url(self):
bot_add_url = DiscordUser.objects.generate_bot_add_url()
@@ -303,18 +303,18 @@ class TestOauthHelpers(TestCase):
class TestUserFormattedNick(TestCase):
def setUp(self):
def setUp(self):
self.user = AuthUtils.create_user(TEST_USER_NAME)
def test_return_nick_when_user_has_main(self):
AuthUtils.add_main_character_2(self.user, TEST_MAIN_NAME, TEST_MAIN_ID)
result = DiscordUser.objects.user_formatted_nick(self.user)
expected = TEST_MAIN_NAME
self.assertEqual(result, expected)
def test_return_none_if_user_has_no_main(self):
result = DiscordUser.objects.user_formatted_nick(self.user)
def test_return_none_if_user_has_no_main(self):
result = DiscordUser.objects.user_formatted_nick(self.user)
self.assertIsNone(result)
@@ -325,16 +325,16 @@ class TestUserGroupNames(TestCase):
super().setUpClass()
cls.group_1 = Group.objects.create(name='Group 1')
cls.group_2 = Group.objects.create(name='Group 2')
def setUp(self):
self.user = AuthUtils.create_member(TEST_USER_NAME)
self.user = AuthUtils.create_member(TEST_USER_NAME)
def test_return_groups_and_state_names_for_user(self):
self.user.groups.add(self.group_1)
result = DiscordUser.objects.user_group_names(self.user)
expected = ['Group 1', 'Member']
self.assertSetEqual(set(result), set(expected))
def test_return_state_only_if_user_has_no_groups(self):
result = DiscordUser.objects.user_group_names(self.user)
expected = ['Member']
@@ -355,11 +355,11 @@ class TestUserHasAccount(TestCase):
def test_return_false_if_user_has_no_account(self):
self.assertFalse(DiscordUser.objects.user_has_account(self.user))
def test_return_false_if_user_does_not_exist(self):
def test_return_false_if_user_does_not_exist(self):
my_user = User(username='Dummy')
self.assertFalse(DiscordUser.objects.user_has_account(my_user))
def test_return_false_if_not_called_with_user_object(self):
def test_return_false_if_not_called_with_user_object(self):
self.assertFalse(DiscordUser.objects.user_has_account('abc'))
@@ -371,7 +371,7 @@ class TestServerName(TestCase):
def setUpClass(cls):
super().setUpClass()
cls.user = AuthUtils.create_user(TEST_USER_NAME)
def test_returns_name_when_api_returns_it(self, mock_logger, mock_DiscordClient):
server_name = "El Dorado"
mock_DiscordClient.return_value.guild_name.return_value = server_name
@@ -383,7 +383,7 @@ class TestServerName(TestCase):
self, mock_logger, mock_DiscordClient
):
mock_exception = HTTPError('Test exception')
mock_exception.response = Mock(**{"status_code": 440})
mock_exception.response = Mock(**{"status_code": 440})
mock_DiscordClient.return_value.guild_name.side_effect = mock_exception
self.assertEqual(DiscordUser.objects.server_name(), "")
@@ -407,7 +407,7 @@ class TestServerName(TestCase):
@patch(MODULE_PATH + '.managers.DiscordClient', spec=DiscordClient)
class TestRoleForGroup(TestCase):
class TestRoleForGroup(TestCase):
def test_return_role_if_found(self, mock_DiscordClient):
mock_DiscordClient.return_value.match_role_from_name.return_value = ROLE_ALPHA

View File

@@ -7,15 +7,15 @@ from django.test import TestCase
from allianceauth.tests.auth_utils import AuthUtils
from . import (
TEST_USER_NAME,
TEST_USER_ID,
TEST_MAIN_NAME,
TEST_MAIN_ID,
TEST_USER_NAME,
TEST_USER_ID,
TEST_MAIN_NAME,
TEST_MAIN_ID,
MODULE_PATH,
ROLE_ALPHA,
ROLE_BRAVO,
ROLE_CHARLIE,
ROLE_MIKE
ROLE_ALPHA,
ROLE_BRAVO,
ROLE_CHARLIE,
ROLE_MIKE
)
from ..discord_client import DiscordClient, DiscordApiBackoff
from ..discord_client.tests import create_matched_role
@@ -44,39 +44,39 @@ class TestBasicsAndHelpers(TestCase):
@patch(MODULE_PATH + '.managers.DiscordClient', spec=DiscordClient)
class TestUpdateNick(TestCase):
def setUp(self):
def setUp(self):
self.user = AuthUtils.create_user(TEST_USER_NAME)
self.discord_user = DiscordUser.objects.create(
user=self.user, uid=TEST_USER_ID
)
def test_can_update(self, mock_DiscordClient):
AuthUtils.add_main_character_2(self.user, TEST_MAIN_NAME, TEST_MAIN_ID)
AuthUtils.add_main_character_2(self.user, TEST_MAIN_NAME, TEST_MAIN_ID)
mock_DiscordClient.return_value.modify_guild_member.return_value = True
result = self.discord_user.update_nickname()
self.assertTrue(result)
self.assertTrue(mock_DiscordClient.return_value.modify_guild_member.called)
def test_dont_update_if_user_has_no_main(self, mock_DiscordClient):
def test_dont_update_if_user_has_no_main(self, mock_DiscordClient):
mock_DiscordClient.return_value.modify_guild_member.return_value = False
result = self.discord_user.update_nickname()
self.assertFalse(result)
self.assertFalse(mock_DiscordClient.return_value.modify_guild_member.called)
def test_return_none_if_user_no_longer_a_member(self, mock_DiscordClient):
AuthUtils.add_main_character_2(self.user, TEST_MAIN_NAME, TEST_MAIN_ID)
mock_DiscordClient.return_value.modify_guild_member.return_value = None
result = self.discord_user.update_nickname()
self.assertIsNone(result)
self.assertTrue(mock_DiscordClient.return_value.modify_guild_member.called)
self.assertTrue(mock_DiscordClient.return_value.modify_guild_member.called)
def test_return_false_if_api_returns_false(self, mock_DiscordClient):
AuthUtils.add_main_character_2(self.user, TEST_MAIN_NAME, TEST_MAIN_ID)
AuthUtils.add_main_character_2(self.user, TEST_MAIN_NAME, TEST_MAIN_ID)
mock_DiscordClient.return_value.modify_guild_member.return_value = False
result = self.discord_user.update_nickname()
self.assertFalse(result)
self.assertTrue(mock_DiscordClient.return_value.modify_guild_member.called)
@@ -89,16 +89,16 @@ class TestUpdateUsername(TestCase):
def setUpClass(cls):
super().setUpClass()
cls.user = AuthUtils.create_user(TEST_USER_NAME)
def setUp(self):
def setUp(self):
self.discord_user = DiscordUser.objects.create(
user=self.user,
uid=TEST_USER_ID,
username=TEST_MAIN_NAME,
user=self.user,
uid=TEST_USER_ID,
username=TEST_MAIN_NAME,
discriminator='1234'
)
def test_can_update(self, mock_DiscordClient):
def test_can_update(self, mock_DiscordClient):
new_username = 'New name'
new_discriminator = '9876'
user_info = {
@@ -109,7 +109,7 @@ class TestUpdateUsername(TestCase):
}
}
mock_DiscordClient.return_value.guild_member.return_value = user_info
result = self.discord_user.update_username()
self.assertTrue(result)
self.assertTrue(mock_DiscordClient.return_value.guild_member.called)
@@ -138,7 +138,7 @@ class TestUpdateUsername(TestCase):
def test_return_false_if_api_returns_corrput_data_2(self, mock_DiscordClient):
user_info = {
'user': {
'id': str(TEST_USER_ID),
'id': str(TEST_USER_ID),
'discriminator': '1234',
}
}
@@ -150,7 +150,7 @@ class TestUpdateUsername(TestCase):
def test_return_false_if_api_returns_corrput_data_3(self, mock_DiscordClient):
user_info = {
'user': {
'id': str(TEST_USER_ID),
'id': str(TEST_USER_ID),
'username': TEST_USER_NAME,
}
}
@@ -169,12 +169,12 @@ class TestDeleteUser(TestCase):
super().setUpClass()
cls.user = AuthUtils.create_user(TEST_USER_NAME)
def setUp(self):
def setUp(self):
self.discord_user = DiscordUser.objects.create(
user=self.user, uid=TEST_USER_ID
)
def test_can_delete_user(self, mock_DiscordClient, mock_notify):
def test_can_delete_user(self, mock_DiscordClient, mock_notify):
mock_DiscordClient.return_value.remove_guild_member.return_value = True
result = self.discord_user.delete_user()
self.assertTrue(result)
@@ -192,7 +192,7 @@ class TestDeleteUser(TestCase):
def test_can_delete_user_when_member_is_unknown(
self, mock_DiscordClient, mock_notify
):
):
mock_DiscordClient.return_value.remove_guild_member.return_value = None
result = self.discord_user.delete_user()
self.assertTrue(result)
@@ -219,7 +219,7 @@ class TestDeleteUser(TestCase):
)
self.assertTrue(mock_DiscordClient.return_value.remove_guild_member.called)
self.assertFalse(mock_notify.called)
def test_raise_exception_on_api_backoff(
self, mock_DiscordClient, mock_notify
):
@@ -227,7 +227,7 @@ class TestDeleteUser(TestCase):
DiscordApiBackoff(999)
with self.assertRaises(DiscordApiBackoff):
self.discord_user.delete_user()
def test_return_false_on_api_backoff_and_exception_handling_on(
self, mock_DiscordClient, mock_notify
):
@@ -244,9 +244,9 @@ class TestDeleteUser(TestCase):
mock_exception.response.status_code = 500
mock_DiscordClient.return_value.remove_guild_member.side_effect = \
mock_exception
with self.assertRaises(HTTPError):
self.discord_user.delete_user()
self.discord_user.delete_user()
def test_return_false_on_http_error_and_exception_handling_on(
self, mock_DiscordClient, mock_notify
@@ -264,7 +264,7 @@ class TestDeleteUser(TestCase):
@patch(MODULE_PATH + '.models.DiscordUser.objects.user_group_names')
class TestUpdateGroups(TestCase):
def setUp(self):
def setUp(self):
self.user = AuthUtils.create_user(TEST_USER_NAME)
self.discord_user = DiscordUser.objects.create(
user=self.user, uid=TEST_USER_ID
@@ -272,14 +272,14 @@ class TestUpdateGroups(TestCase):
self.guild_roles = [ROLE_ALPHA, ROLE_BRAVO, ROLE_CHARLIE, ROLE_MIKE]
self.roles_requested = [
create_matched_role(ROLE_ALPHA), create_matched_role(ROLE_BRAVO)
]
]
def test_update_if_needed(
self,
mock_user_group_names,
self,
mock_user_group_names,
mock_DiscordClient
):
roles_current = [1]
roles_current = [1]
mock_user_group_names.return_value = []
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = self.roles_requested
@@ -287,7 +287,7 @@ class TestUpdateGroups(TestCase):
mock_DiscordClient.return_value.guild_member.return_value = \
{'roles': roles_current}
mock_DiscordClient.return_value.modify_guild_member.return_value = True
result = self.discord_user.update_groups()
self.assertTrue(result)
self.assertTrue(mock_DiscordClient.return_value.modify_guild_member.called)
@@ -295,11 +295,11 @@ class TestUpdateGroups(TestCase):
self.assertEqual(set(kwargs['role_ids']), {1, 2})
def test_update_if_needed_and_preserve_managed_roles(
self,
mock_user_group_names,
self,
mock_user_group_names,
mock_DiscordClient
):
roles_current = [1, 13]
roles_current = [1, 13]
mock_user_group_names.return_value = []
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = self.roles_requested
@@ -307,7 +307,7 @@ class TestUpdateGroups(TestCase):
mock_DiscordClient.return_value.guild_member.return_value = \
{'roles': roles_current}
mock_DiscordClient.return_value.modify_guild_member.return_value = True
result = self.discord_user.update_groups()
self.assertTrue(result)
self.assertTrue(mock_DiscordClient.return_value.modify_guild_member.called)
@@ -315,28 +315,28 @@ class TestUpdateGroups(TestCase):
self.assertEqual(set(kwargs['role_ids']), {1, 2, 13})
def test_dont_update_if_not_needed(
self,
mock_user_group_names,
self,
mock_user_group_names,
mock_DiscordClient
):
roles_current = [1, 2, 13]
roles_current = [1, 2, 13]
mock_user_group_names.return_value = []
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = self.roles_requested
mock_DiscordClient.return_value.guild_roles.return_value = self.guild_roles
mock_DiscordClient.return_value.guild_member.return_value = \
{'roles': roles_current}
result = self.discord_user.update_groups()
self.assertTrue(result)
self.assertFalse(mock_DiscordClient.return_value.modify_guild_member.called)
def test_update_if_user_has_no_roles_on_discord(
self,
mock_user_group_names,
self,
mock_user_group_names,
mock_DiscordClient
):
roles_current = []
roles_current = []
mock_user_group_names.return_value = []
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = self.roles_requested
@@ -344,30 +344,30 @@ class TestUpdateGroups(TestCase):
mock_DiscordClient.return_value.guild_member.return_value = \
{'roles': roles_current}
mock_DiscordClient.return_value.modify_guild_member.return_value = True
result = self.discord_user.update_groups()
self.assertTrue(result)
self.assertTrue(mock_DiscordClient.return_value.modify_guild_member.called)
args, kwargs = mock_DiscordClient.return_value.modify_guild_member.call_args
self.assertEqual(set(kwargs['role_ids']), {1, 2})
def test_return_none_if_user_no_longer_a_member(
self,
mock_user_group_names,
self,
mock_user_group_names,
mock_DiscordClient
):
):
mock_DiscordClient.return_value.guild_member.return_value = None
result = self.discord_user.update_groups()
self.assertIsNone(result)
self.assertFalse(mock_DiscordClient.return_value.modify_guild_member.called)
def test_return_false_if_api_returns_false(
self,
mock_user_group_names,
self,
mock_user_group_names,
mock_DiscordClient
):
roles_current = [1]
roles_current = [1]
mock_user_group_names.return_value = []
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = self.roles_requested
@@ -375,17 +375,17 @@ class TestUpdateGroups(TestCase):
mock_DiscordClient.return_value.guild_member.return_value = \
{'roles': roles_current}
mock_DiscordClient.return_value.modify_guild_member.return_value = False
result = self.discord_user.update_groups()
self.assertFalse(result)
self.assertTrue(mock_DiscordClient.return_value.modify_guild_member.called)
def test_raise_exception_if_member_has_unknown_roles(
self,
mock_user_group_names,
self,
mock_user_group_names,
mock_DiscordClient
):
roles_current = [99]
roles_current = [99]
mock_user_group_names.return_value = []
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = self.roles_requested
@@ -393,21 +393,21 @@ class TestUpdateGroups(TestCase):
mock_DiscordClient.return_value.guild_member.return_value = \
{'roles': roles_current}
mock_DiscordClient.return_value.modify_guild_member.return_value = True
with self.assertRaises(RuntimeError):
self.discord_user.update_groups()
def test_refresh_guild_roles_user_roles_dont_not_match(
self,
mock_user_group_names,
self,
mock_user_group_names,
mock_DiscordClient
):
):
def my_guild_roles(guild_id, use_cache=True):
if use_cache:
return [ROLE_ALPHA, ROLE_BRAVO, ROLE_MIKE]
else:
return [ROLE_ALPHA, ROLE_BRAVO, ROLE_CHARLIE, ROLE_MIKE]
roles_current = [3]
mock_user_group_names.return_value = []
mock_DiscordClient.return_value.match_or_create_roles_from_names\
@@ -421,10 +421,10 @@ class TestUpdateGroups(TestCase):
self.assertEqual(mock_DiscordClient.return_value.guild_roles.call_count, 2)
def test_raise_exception_if_member_info_is_invalid(
self,
mock_user_group_names,
self,
mock_user_group_names,
mock_DiscordClient
):
):
mock_user_group_names.return_value = []
mock_DiscordClient.return_value.match_or_create_roles_from_names\
.return_value = self.roles_requested
@@ -432,6 +432,6 @@ class TestUpdateGroups(TestCase):
mock_DiscordClient.return_value.guild_member.return_value = \
{'user': 'dummy'}
mock_DiscordClient.return_value.modify_guild_member.return_value = True
with self.assertRaises(RuntimeError):
self.discord_user.update_groups()

View File

@@ -23,7 +23,7 @@ logger = set_logger_to_file(MODULE_PATH, __file__)
@patch(MODULE_PATH + '.DiscordUser.update_groups')
@patch(MODULE_PATH + ".logger")
class TestUpdateGroups(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
@@ -32,7 +32,7 @@ class TestUpdateGroups(TestCase):
cls.group_2 = Group.objects.create(name='Group 2')
cls.group_1.user_set.add(cls.user)
cls.group_2.user_set.add(cls.user)
def test_can_update_groups(self, mock_logger, mock_update_groups):
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
tasks.update_groups(self.user.pk)
@@ -43,7 +43,7 @@ class TestUpdateGroups(TestCase):
):
tasks.update_groups(self.user.pk)
self.assertFalse(mock_update_groups.called)
def test_retries_on_api_backoff(self, mock_logger, mock_update_groups):
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
mock_exception = DiscordApiBackoff(999)
@@ -63,7 +63,7 @@ class TestUpdateGroups(TestCase):
tasks.update_groups(self.user.pk)
self.assertTrue(mock_logger.warning.called)
def test_retry_on_http_error_404_when_user_not_deleted(
self, mock_logger, mock_update_groups
):
@@ -77,8 +77,8 @@ class TestUpdateGroups(TestCase):
tasks.update_groups(self.user.pk)
self.assertTrue(mock_logger.warning.called)
def test_retry_on_non_http_error(self, mock_logger, mock_update_groups):
def test_retry_on_non_http_error(self, mock_logger, mock_update_groups):
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
mock_update_groups.side_effect = ConnectionError
@@ -86,23 +86,23 @@ class TestUpdateGroups(TestCase):
tasks.update_groups(self.user.pk)
self.assertTrue(mock_logger.warning.called)
@patch(MODULE_PATH + '.DISCORD_TASKS_MAX_RETRIES', 3)
def test_log_error_if_retries_exhausted(self, mock_logger, mock_update_groups):
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
mock_task = MagicMock(**{'request.retries': 3})
mock_update_groups.side_effect = ConnectionError
update_groups_inner = tasks.update_groups.__wrapped__.__func__
update_groups_inner(mock_task, self.user.pk)
self.assertTrue(mock_logger.error.called)
@patch(MODULE_PATH + '.delete_user.delay')
def test_delete_user_if_user_is_no_longer_member_of_discord_server(
self, mock_delete_user, mock_logger, mock_update_groups
):
mock_update_groups.return_value = None
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
tasks.update_groups(self.user.pk)
self.assertTrue(mock_update_groups.called)
@@ -111,45 +111,45 @@ class TestUpdateGroups(TestCase):
@patch(MODULE_PATH + '.DiscordUser.update_nickname')
class TestUpdateNickname(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.user = AuthUtils.create_member(TEST_USER_NAME)
AuthUtils.add_main_character_2(
cls.user,
TEST_MAIN_NAME,
TEST_MAIN_ID,
corp_id='2',
corp_name='test_corp',
cls.user,
TEST_MAIN_NAME,
TEST_MAIN_ID,
corp_id='2',
corp_name='test_corp',
corp_ticker='TEST',
disconnect_signals=True
)
cls.discord_user = DiscordUser.objects.create(user=cls.user, uid=TEST_USER_ID)
def test_can_update_nickname(self, mock_update_nickname):
mock_update_nickname.return_value = True
def test_can_update_nickname(self, mock_update_nickname):
mock_update_nickname.return_value = True
tasks.update_nickname(self.user.pk)
self.assertTrue(mock_update_nickname.called)
def test_no_action_when_user_had_no_account(self, mock_update_nickname):
my_user = AuthUtils.create_user('Dummy User')
mock_update_nickname.return_value = False
tasks.update_nickname(my_user.pk)
self.assertFalse(mock_update_nickname.called)
def test_retries_on_api_backoff(self, mock_update_nickname):
def test_retries_on_api_backoff(self, mock_update_nickname):
mock_exception = DiscordApiBackoff(999)
mock_update_nickname.side_effect = mock_exception
with self.assertRaises(Retry):
tasks.update_nickname(self.user.pk)
def test_retries_on_general_exception(self, mock_update_nickname):
def test_retries_on_general_exception(self, mock_update_nickname):
mock_update_nickname.side_effect = ConnectionError
with self.assertRaises(Retry):
tasks.update_nickname(self.user.pk)
@@ -158,9 +158,9 @@ class TestUpdateNickname(TestCase):
mock_task = MagicMock(**{'request.retries': 3})
mock_update_nickname.side_effect = ConnectionError
update_nickname_inner = tasks.update_nickname.__wrapped__.__func__
update_nickname_inner(mock_task, self.user.pk)
@patch(MODULE_PATH + '.DiscordUser.update_username')
class TestUpdateUsername(TestCase):
@@ -206,7 +206,7 @@ class TestDeleteUser(TestCase):
self, mock_delete_user_delay, mock_delete_user
):
mock_delete_user.return_value = None
tasks.delete_user(self.user.pk)
self.assertTrue(mock_delete_user.called)
self.assertFalse(mock_delete_user_delay.called)
@@ -218,15 +218,15 @@ class TestTaskPerformUserAction(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.user = AuthUtils.create_member('Peter Parker')
cls.user = AuthUtils.create_member('Peter Parker')
cls.discord_user = DiscordUser.objects.create(user=cls.user, uid=TEST_USER_ID)
def test_raise_value_error_on_unknown_method(self, mock_update_groups):
mock_task = MagicMock(**{'request.retries': 0})
with self.assertRaises(ValueError):
tasks._task_perform_user_action(mock_task, self.user.pk, 'invalid_method')
def test_catch_and_log_unexpected_exceptions(self, mock_update_groups):
mock_task = MagicMock(**{'request.retries': 0})
mock_update_groups.side_effect = RuntimeError
@@ -253,8 +253,8 @@ class TestTaskUpdateServername(TestCase):
self.assertFalse(mock_logger.error.called)
def test_retry_on_http_error(self, mock_logger, mock_server_name):
mock_exception = HTTPError(MagicMock(**{"response.status_code": 500}))
def test_retry_on_http_error(self, mock_logger, mock_server_name):
mock_exception = HTTPError(MagicMock(**{"response.status_code": 500}))
mock_server_name.side_effect = mock_exception
with self.assertRaises(Retry):
@@ -262,20 +262,20 @@ class TestTaskUpdateServername(TestCase):
self.assertTrue(mock_logger.warning.called)
def test_retry_on_connection_error(self, mock_logger, mock_server_name):
def test_retry_on_connection_error(self, mock_logger, mock_server_name):
mock_server_name.side_effect = ConnectionError
with self.assertRaises(Retry):
tasks.update_servername()
self.assertTrue(mock_logger.warning.called)
@patch(MODULE_PATH + '.DISCORD_TASKS_MAX_RETRIES', 3)
def test_log_error_if_retries_exhausted(self, mock_logger, mock_server_name):
mock_task = MagicMock(**{'request.retries': 3})
mock_server_name.side_effect = ConnectionError
update_groups_inner = tasks.update_servername.__wrapped__.__func__
update_groups_inner(mock_task)
self.assertTrue(mock_logger.error.called)
@@ -285,24 +285,24 @@ class TestTaskPerformUsersAction(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
super().setUpClass()
def test_raise_value_error_on_unknown_method(self, mock_server_name):
mock_task = MagicMock(**{'request.retries': 0})
with self.assertRaises(ValueError):
tasks._task_perform_users_action(mock_task, 'invalid_method')
def test_catch_and_log_unexpected_exceptions(self, mock_server_name):
mock_server_name.side_effect = RuntimeError
mock_task = MagicMock(**{'request.retries': 0})
tasks._task_perform_users_action(mock_task, 'server_name')
@override_settings(CELERY_ALWAYS_EAGER=True)
class TestBulkTasks(TestCase):
@classmethod
def setUpClass(cls):
super().setUpClass()
@@ -321,7 +321,7 @@ class TestBulkTasks(TestCase):
tasks.update_groups_bulk(expected_pks)
self.assertEqual(mock_update_groups.call_count, 2)
current_pks = [args[0][0] for args in mock_update_groups.call_args_list]
self.assertSetEqual(set(current_pks), set(expected_pks))
@patch(MODULE_PATH + '.update_groups.si')
@@ -347,7 +347,7 @@ class TestBulkTasks(TestCase):
self.assertEqual(mock_update_nickname.call_count, 2)
current_pks = [
args[0][0] for args in mock_update_nickname.call_args_list
]
]
self.assertSetEqual(set(current_pks), set(expected_pks))
@patch(MODULE_PATH + '.update_nickname.si')
@@ -374,11 +374,11 @@ class TestBulkTasks(TestCase):
tasks.update_usernames_bulk(expected_pks)
self.assertEqual(mock_update_username.call_count, 2)
current_pks = [args[0][0] for args in mock_update_username.call_args_list]
self.assertSetEqual(set(current_pks), set(expected_pks))
@patch(MODULE_PATH + '.update_username')
@patch(MODULE_PATH + '.update_servername')
@patch(MODULE_PATH + '.update_servername')
def test_can_update_all_usernames(
self, mock_update_servername, mock_update_username
):
@@ -405,7 +405,7 @@ class TestBulkTasks(TestCase):
du_3 = DiscordUser.objects.create(user=self.user_3, uid=789)
tasks.update_all()
self.assertEqual(mock_update_groups.si.call_count, 3)
self.assertEqual(mock_update_groups.si.call_count, 3)
current_pks = [args[0][0] for args in mock_update_groups.si.call_args_list]
expected_pks = [du_1.pk, du_2.pk, du_3.pk]
self.assertSetEqual(set(current_pks), set(expected_pks))
@@ -415,7 +415,7 @@ class TestBulkTasks(TestCase):
expected_pks = [du_1.pk, du_2.pk, du_3.pk]
self.assertSetEqual(set(current_pks), set(expected_pks))
self.assertEqual(mock_update_usernames.si.call_count, 3)
self.assertEqual(mock_update_usernames.si.call_count, 3)
current_pks = [args[0][0] for args in mock_update_usernames.si.call_args_list]
expected_pks = [du_1.pk, du_2.pk, du_3.pk]
self.assertSetEqual(set(current_pks), set(expected_pks))
@@ -432,14 +432,14 @@ class TestBulkTasks(TestCase):
du_3 = DiscordUser.objects.create(user=self.user_3, uid=789)
tasks.update_all()
self.assertEqual(mock_update_groups.si.call_count, 3)
self.assertEqual(mock_update_groups.si.call_count, 3)
current_pks = [args[0][0] for args in mock_update_groups.si.call_args_list]
expected_pks = [du_1.pk, du_2.pk, du_3.pk]
self.assertSetEqual(set(current_pks), set(expected_pks))
self.assertEqual(mock_update_nickname.si.call_count, 0)
self.assertEqual(mock_update_usernames.si.call_count, 3)
self.assertEqual(mock_update_usernames.si.call_count, 3)
current_pks = [args[0][0] for args in mock_update_usernames.si.call_args_list]
expected_pks = [du_1.pk, du_2.pk, du_3.pk]
self.assertSetEqual(set(current_pks), set(expected_pks))

View File

@@ -9,19 +9,19 @@ MODULE_PATH = 'allianceauth.services.modules.discord.utils'
class TestCleanSetting(TestCase):
@patch(MODULE_PATH + '.settings')
def test_default_if_not_set(self, mock_settings):
def test_default_if_not_set(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = Mock(spec=None)
result = clean_setting(
'TEST_SETTING_DUMMY',
False,
'TEST_SETTING_DUMMY',
False,
)
self.assertEqual(result, False)
@patch(MODULE_PATH + '.settings')
def test_default_if_not_set_for_none(self, mock_settings):
def test_default_if_not_set_for_none(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = Mock(spec=None)
result = clean_setting(
'TEST_SETTING_DUMMY',
'TEST_SETTING_DUMMY',
None,
required_type=int
)
@@ -31,8 +31,8 @@ class TestCleanSetting(TestCase):
def test_true_stays_true(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = True
result = clean_setting(
'TEST_SETTING_DUMMY',
False,
'TEST_SETTING_DUMMY',
False,
)
self.assertEqual(result, True)
@@ -40,7 +40,7 @@ class TestCleanSetting(TestCase):
def test_false_stays_false(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = False
result = clean_setting(
'TEST_SETTING_DUMMY',
'TEST_SETTING_DUMMY',
False
)
self.assertEqual(result, False)
@@ -49,7 +49,7 @@ class TestCleanSetting(TestCase):
def test_default_for_invalid_type_bool(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = 'invalid type'
result = clean_setting(
'TEST_SETTING_DUMMY',
'TEST_SETTING_DUMMY',
False
)
self.assertEqual(result, False)
@@ -58,7 +58,7 @@ class TestCleanSetting(TestCase):
def test_default_for_invalid_type_int(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = 'invalid type'
result = clean_setting(
'TEST_SETTING_DUMMY',
'TEST_SETTING_DUMMY',
50
)
self.assertEqual(result, 50)
@@ -67,7 +67,7 @@ class TestCleanSetting(TestCase):
def test_default_if_below_minimum_1(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = -5
result = clean_setting(
'TEST_SETTING_DUMMY',
'TEST_SETTING_DUMMY',
default_value=50
)
self.assertEqual(result, 50)
@@ -76,7 +76,7 @@ class TestCleanSetting(TestCase):
def test_default_if_below_minimum_2(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = -50
result = clean_setting(
'TEST_SETTING_DUMMY',
'TEST_SETTING_DUMMY',
default_value=50,
min_value=-10
)
@@ -86,7 +86,7 @@ class TestCleanSetting(TestCase):
def test_default_for_invalid_type_int_2(self, mock_settings):
mock_settings.TEST_SETTING_DUMMY = 1000
result = clean_setting(
'TEST_SETTING_DUMMY',
'TEST_SETTING_DUMMY',
default_value=50,
max_value=100
)
@@ -97,6 +97,6 @@ class TestCleanSetting(TestCase):
mock_settings.TEST_SETTING_DUMMY = 'invalid type'
with self.assertRaises(ValueError):
clean_setting(
'TEST_SETTING_DUMMY',
'TEST_SETTING_DUMMY',
default_value=None
)

View File

@@ -11,10 +11,10 @@ from ..discord_client import DiscordClient
from ..models import DiscordUser
from ..utils import set_logger_to_file
from ..views import (
discord_callback,
reset_discord,
deactivate_discord,
discord_add_bot,
discord_callback,
reset_discord,
deactivate_discord,
discord_add_bot,
activate_discord
)
@@ -49,10 +49,10 @@ class TestActivateDiscord(SetupClassMixin, TestCase):
@patch(MODULE_PATH + '.views.messages')
@patch(MODULE_PATH + '.managers.DiscordClient', spec=DiscordClient)
class TestDeactivateDiscord(SetupClassMixin, TestCase):
def setUp(self):
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
def test_when_successful_show_success_message(
self, mock_DiscordClient, mock_messages
):
@@ -81,7 +81,7 @@ class TestDeactivateDiscord(SetupClassMixin, TestCase):
@patch(MODULE_PATH + '.views.messages')
@patch(MODULE_PATH + '.managers.DiscordClient')
class TestResetDiscord(SetupClassMixin, TestCase):
def setUp(self):
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
@@ -93,7 +93,7 @@ class TestResetDiscord(SetupClassMixin, TestCase):
request.user = self.user
response = reset_discord(request)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse("discord:activate"))
self.assertEqual(response.url, reverse("discord:activate"))
self.assertFalse(mock_messages.error.called)
def test_when_unsuccessful_message_error_and_redirect_to_service(
@@ -111,7 +111,7 @@ class TestResetDiscord(SetupClassMixin, TestCase):
@patch(MODULE_PATH + '.views.messages')
@patch(MODULE_PATH + '.views.DiscordUser.objects.add_user')
class TestDiscordCallback(SetupClassMixin, TestCase):
def setUp(self):
DiscordUser.objects.create(user=self.user, uid=TEST_USER_ID)
@@ -126,7 +126,7 @@ class TestDiscordCallback(SetupClassMixin, TestCase):
self.assertEqual(response.url, self.services_url)
self.assertTrue(mock_messages.success.called)
self.assertFalse(mock_messages.error.called)
def test_handle_no_code(self, mock_add_user, mock_messages):
mock_add_user.return_value = True
request = self.factory.get(
@@ -138,7 +138,7 @@ class TestDiscordCallback(SetupClassMixin, TestCase):
self.assertEqual(response.url, self.services_url)
self.assertFalse(mock_messages.success.called)
self.assertTrue(mock_messages.error.called)
def test_error_message_when_user_creation_failed(
self, mock_add_user, mock_messages
):
@@ -156,7 +156,7 @@ class TestDiscordCallback(SetupClassMixin, TestCase):
@patch(MODULE_PATH + '.views.DiscordUser.objects.generate_bot_add_url')
class TestDiscordAddBot(TestCase):
def test_add_bot(self, mock_generate_bot_add_url):
bot_url = 'https://www.example.com/bot'
mock_generate_bot_add_url.return_value = bot_url

View File

@@ -15,7 +15,7 @@ class LoggerAddTag(logging.LoggerAdapter):
def process(self, msg, kwargs):
return '[%s] %s' % (self.prefix, msg), kwargs
def clean_setting(
name: str,
@@ -58,7 +58,7 @@ def clean_setting(
'You setting for %s it not valid. Please correct it. '
'Using default for now: %s',
name,
default_value
default_value
)
cleaned_value = default_value
return cleaned_value
@@ -66,15 +66,15 @@ def clean_setting(
def set_logger_to_file(logger_name: str, name: str) -> object:
"""set logger for current module to log into a file. Useful for tests.
Args:
- logger: current logger object
- name: name of current module, e.g. __file__
Returns:
- amended logger
"""
# reconfigure logger so we get logging from tested module
f_format = logging.Formatter(
'%(asctime)s - %(levelname)s - %(module)s:%(funcName)s - %(message)s'

View File

@@ -47,11 +47,11 @@ def reset_discord(request):
):
logger.info(
"Successfully deleted discord user for user %s - "
"forwarding to discord activation.",
"forwarding to discord activation.",
request.user
)
return redirect("discord:activate")
logger.error(
"Unsuccessful attempt to reset discord for user %s", request.user
)
@@ -74,7 +74,7 @@ def discord_callback(request):
logger.debug(
"Received Discord callback for activation of user %s", request.user
)
authorization_code = request.GET.get('code', None)
authorization_code = request.GET.get('code', None)
if not authorization_code:
logger.warning(
"Did not receive OAuth code from callback for user %s", request.user
@@ -82,34 +82,34 @@ def discord_callback(request):
success = False
else:
if DiscordUser.objects.add_user(
user=request.user,
authorization_code=authorization_code,
user=request.user,
authorization_code=authorization_code,
is_rate_limited=False
):
logger.info(
"Successfully activated Discord account for user %s", request.user
)
success = True
else:
logger.error(
"Failed to activate Discord account for user %s", request.user
)
success = False
if success:
messages.success(
request, _('Your Discord account has been successfully activated.')
)
else:
messages.error(
request,
request,
_(
'An error occurred while trying to activate your Discord account. '
'Please try again.'
)
)
return redirect("services:services")

View File

@@ -6,6 +6,6 @@ from ...admin import ServicesUserAdmin
@admin.register(DiscourseUser)
class DiscourseUserAdmin(ServicesUserAdmin):
list_display = ServicesUserAdmin.list_display + (
'enabled',
)
list_display = ServicesUserAdmin.list_display + (
'enabled',
)

View File

@@ -27,7 +27,7 @@ class ExampleService(ServicesHook):
urls = self.Urls()
# urls.auth_activate = 'auth_example_activate'
# urls.auth_deactivate = 'auth_example_deactivate'
# urls.auth_reset_password = 'auth_example_reset_password'
# urls.auth_reset_password = 'auth_example_reset_password'
# urls.auth_set_password = 'auth_example_set_password'
return render_to_string(self.service_ctrl_template, {
'service_name': self.title,

View File

@@ -17,7 +17,7 @@ def migrate_service_enabled(apps, schema_editor):
app_config.models_module = True
create_permissions(app_config, apps=apps, verbosity=0)
app_config.models_module = None
Group = apps.get_model("auth", "Group")
Permission = apps.get_model("auth", "Permission")
Ips4User = apps.get_model("ips4", "Ips4User")

View File

@@ -5,12 +5,12 @@ from ...admin import ServicesUserAdmin
@admin.register(MumbleUser)
class MumbleUserAdmin(ServicesUserAdmin):
list_display = ServicesUserAdmin.list_display + (
'username',
class MumbleUserAdmin(ServicesUserAdmin):
list_display = ServicesUserAdmin.list_display + (
'username',
'groups',
)
search_fields = ServicesUserAdmin.search_fields + (
)
search_fields = ServicesUserAdmin.search_fields + (
'username',
'groups'
)

View File

@@ -44,7 +44,7 @@ class MumbleService(ServicesHook):
logger.debug("Updating %s nickname for %s" % (self.name, user))
if MumbleTasks.has_account(user):
MumbleTasks.update_display_name.apply_async(args=[user.pk], countdown=5) # cooldown on this task to ensure DB clean when syncing
def validate_user(self, user):
if MumbleTasks.has_account(user) and not self.service_active_for_user(user):
self.delete_user(user, notify_user=True)

View File

@@ -18,11 +18,11 @@ class MumbleManager(models.Manager):
def get_display_name(user):
from .auth_hooks import MumbleService
return NameFormatter(MumbleService(), user).format_name()
@staticmethod
def get_username(user):
return user.profile.main_character.character_name # main character as the user.username may be incorect
@staticmethod
def sanitise_username(username):
return username.replace(" ", "_")

View File

@@ -6,6 +6,6 @@ from ...admin import ServicesUserAdmin
@admin.register(OpenfireUser)
class OpenfireUserAdmin(ServicesUserAdmin):
list_display = ServicesUserAdmin.list_display + ('username',)
list_display = ServicesUserAdmin.list_display + ('username',)
search_fields = ServicesUserAdmin.search_fields + ('username', )

View File

@@ -17,7 +17,7 @@ def migrate_service_enabled(apps, schema_editor):
app_config.models_module = True
create_permissions(app_config, apps=apps, verbosity=0)
app_config.models_module = None
Group = apps.get_model("auth", "Group")
Permission = apps.get_model("auth", "Permission")
OpenfireUser = apps.get_model("openfire", "OpenfireUser")

View File

@@ -5,5 +5,5 @@ from ...admin import ServicesUserAdmin
@admin.register(Phpbb3User)
class Phpbb3UserAdmin(ServicesUserAdmin):
list_display = ServicesUserAdmin.list_display + ('username',)
list_display = ServicesUserAdmin.list_display + ('username',)
search_fields = ServicesUserAdmin.search_fields + ('username', )

View File

@@ -6,5 +6,5 @@ from ...admin import ServicesUserAdmin
@admin.register(SmfUser)
class SmfUserAdmin(ServicesUserAdmin):
list_display = ServicesUserAdmin.list_display + ('username',)
list_display = ServicesUserAdmin.list_display + ('username',)
search_fields = ServicesUserAdmin.search_fields + ('username', )

View File

@@ -102,7 +102,7 @@ class SmfManager:
@classmethod
def add_avatar(cls, member_name, characterid):
logger.debug("Adding EVE character id %s portrait as smf avatar for user %s" % (characterid, member_name))
logger.debug("Adding EVE character id %s portrait as smf avatar for user %s" % (characterid, member_name))
avatar_url = EveCharacter.generic_portrait_url(characterid, 64)
cursor = connections['smf'].cursor()
id_member = cls.get_user_id(member_name)

View File

@@ -17,7 +17,7 @@ def migrate_service_enabled(apps, schema_editor):
app_config.models_module = True
create_permissions(app_config, apps=apps, verbosity=0)
app_config.models_module = None
Group = apps.get_model("auth", "Group")
Permission = apps.get_model("auth", "Permission")
SmfUser = apps.get_model("smf", "SmfUser")

View File

@@ -5,28 +5,28 @@ from ...admin import ServicesUserAdmin
@admin.register(Teamspeak3User)
class Teamspeak3UserAdmin(ServicesUserAdmin):
list_display = ServicesUserAdmin.list_display + (
class Teamspeak3UserAdmin(ServicesUserAdmin):
list_display = ServicesUserAdmin.list_display + (
'uid',
'perm_key'
'perm_key'
)
search_fields = ServicesUserAdmin.search_fields + ('uid', )
@admin.register(AuthTS)
class AuthTSgroupAdmin(admin.ModelAdmin):
ordering = ('auth_group__name', )
list_select_related = True
list_select_related = True
list_display = ('auth_group', '_ts_group')
list_filter = ('ts_group', )
fields = ('auth_group', 'ts_group')
filter_horizontal = ('ts_group',)
def _ts_group(self, obj):
return [x for x in obj.ts_group.all().order_by('ts_group_id')]
_ts_group.short_description = 'ts groups'
#_ts_group.admin_order_field = 'profile__state'

View File

@@ -304,21 +304,20 @@ class Teamspeak3ManagerTestCase(TestCase):
@mock.patch.object(Teamspeak3Manager, '_group_list')
@mock.patch.object(Teamspeak3Manager, '_group_id_by_name')
def test_add_user_exception(self, _group_id_by_name, _group_list):
def test_add_user_exception(self, _group_id_by_name, _group_list):
"""test 1st exception occuring in add_user()"""
# set mocks in Teamspeak3Manager class
_group_list.return_value = ['Member', 'Guest']
_group_id_by_name.return_value = 99
_group_id_by_name.return_value = 99
manager = Teamspeak3Manager()
server = mock.MagicMock()
server._connected.return_value = True
server.send_command = mock.Mock(side_effect=Teamspeak3ManagerTestCase.my_side_effect)
manager._server = server
# create test data
# create test data
user = User.objects.create_user("dummy")
user.profile.state = State.objects.filter(name="Member").first()
# perform test
manager.add_user(user, "Dummy User")

View File

@@ -6,5 +6,5 @@ from ...admin import ServicesUserAdmin
@admin.register(XenforoUser)
class XenforoUserAdmin(ServicesUserAdmin):
list_display = ServicesUserAdmin.list_display + ('username',)
list_display = ServicesUserAdmin.list_display + ('username',)
search_fields = ServicesUserAdmin.search_fields + ('username', )