Fix: Can not update biomassed characters

This commit is contained in:
Erik Kalkoken 2022-01-27 05:02:57 +00:00 committed by Ariel Rin
parent 86aaa3edda
commit f348b1a34c
7 changed files with 614 additions and 264 deletions

View File

@ -1,13 +1,27 @@
from django.db import models import logging
from typing import Union from typing import Union
from .managers import EveCharacterManager, EveCharacterProviderManager from django.core.exceptions import ObjectDoesNotExist
from .managers import EveCorporationManager, EveCorporationProviderManager from django.db import models
from .managers import EveAllianceManager, EveAllianceProviderManager from esi.models import Token
from allianceauth.notifications import notify
from . import providers from . import providers
from .evelinks import eveimageserver from .evelinks import eveimageserver
from .managers import (
EveAllianceManager,
EveAllianceProviderManager,
EveCharacterManager,
EveCharacterProviderManager,
EveCorporationManager,
EveCorporationProviderManager,
)
logger = logging.getLogger(__name__)
_DEFAULT_IMAGE_SIZE = 32 _DEFAULT_IMAGE_SIZE = 32
DOOMHEIM_CORPORATION_ID = 1000001
class EveFactionInfo(models.Model): class EveFactionInfo(models.Model):
@ -68,13 +82,12 @@ class EveAllianceInfo(models.Model):
for corp_id in alliance.corp_ids: for corp_id in alliance.corp_ids:
if not EveCorporationInfo.objects.filter(corporation_id=corp_id).exists(): if not EveCorporationInfo.objects.filter(corporation_id=corp_id).exists():
EveCorporationInfo.objects.create_corporation(corp_id) EveCorporationInfo.objects.create_corporation(corp_id)
EveCorporationInfo.objects.filter( EveCorporationInfo.objects.filter(corporation_id__in=alliance.corp_ids).update(
corporation_id__in=alliance.corp_ids).update(alliance=self alliance=self
) )
EveCorporationInfo.objects\ EveCorporationInfo.objects.filter(alliance=self).exclude(
.filter(alliance=self)\ corporation_id__in=alliance.corp_ids
.exclude(corporation_id__in=alliance.corp_ids)\ ).update(alliance=None)
.update(alliance=None)
def update_alliance(self, alliance: providers.Alliance = None): def update_alliance(self, alliance: providers.Alliance = None):
if alliance is None: if alliance is None:
@ -182,6 +195,7 @@ class EveCorporationInfo(models.Model):
class EveCharacter(models.Model): class EveCharacter(models.Model):
"""Character in Eve Online"""
character_id = models.PositiveIntegerField(unique=True) character_id = models.PositiveIntegerField(unique=True)
character_name = models.CharField(max_length=254, unique=True) character_name = models.CharField(max_length=254, unique=True)
corporation_id = models.PositiveIntegerField() corporation_id = models.PositiveIntegerField()
@ -205,6 +219,14 @@ class EveCharacter(models.Model):
models.Index(fields=['faction_id',]), models.Index(fields=['faction_id',]),
] ]
def __str__(self):
return self.character_name
@property
def is_biomassed(self) -> bool:
"""Whether this character is dead or not."""
return self.corporation_id == DOOMHEIM_CORPORATION_ID
@property @property
def alliance(self) -> Union[EveAllianceInfo, None]: def alliance(self) -> Union[EveAllianceInfo, None]:
""" """
@ -249,10 +271,36 @@ class EveCharacter(models.Model):
self.faction_id = character.faction.id self.faction_id = character.faction.id
self.faction_name = character.faction.name self.faction_name = character.faction.name
self.save() self.save()
if self.is_biomassed:
self._remove_tokens_of_biomassed_character()
return self return self
def __str__(self): def _remove_tokens_of_biomassed_character(self) -> None:
return self.character_name """Remove tokens of this biomassed character."""
try:
user = self.character_ownership.user
except ObjectDoesNotExist:
return
tokens_to_delete = Token.objects.filter(character_id=self.character_id)
tokens_count = tokens_to_delete.count()
if not tokens_count:
return
tokens_to_delete.delete()
logger.info(
"%d tokens from user %s for biomassed character %s [id:%s] deleted.",
tokens_count,
user,
self,
self.character_id,
)
notify(
user=user,
title=f"Character {self} biomassed",
message=(
f"Your former character {self} has been biomassed "
"and has been removed from the list of your alts."
)
)
@staticmethod @staticmethod
def generic_portrait_url( def generic_portrait_url(
@ -336,7 +384,6 @@ class EveCharacter(models.Model):
"""image URL for alliance of this character or empty string""" """image URL for alliance of this character or empty string"""
return self.alliance_logo_url(256) return self.alliance_logo_url(256)
def faction_logo_url(self, size=_DEFAULT_IMAGE_SIZE) -> str: def faction_logo_url(self, size=_DEFAULT_IMAGE_SIZE) -> str:
"""image URL for alliance of this character or empty string""" """image URL for alliance of this character or empty string"""
if self.faction_id: if self.faction_id:

View File

@ -170,7 +170,7 @@ class EveProvider:
""" """
:return: an ItemType object for the given ID :return: an ItemType object for the given ID
""" """
raise NotImplemented() raise NotImplementedError()
class EveSwaggerProvider(EveProvider): class EveSwaggerProvider(EveProvider):
@ -207,7 +207,8 @@ class EveSwaggerProvider(EveProvider):
def __str__(self): def __str__(self):
return 'esi' return 'esi'
def get_alliance(self, alliance_id): def get_alliance(self, alliance_id: int) -> Alliance:
"""Fetch alliance from ESI."""
try: try:
data = self.client.Alliance.get_alliances_alliance_id(alliance_id=alliance_id).result() data = self.client.Alliance.get_alliances_alliance_id(alliance_id=alliance_id).result()
corps = self.client.Alliance.get_alliances_alliance_id_corporations(alliance_id=alliance_id).result() corps = self.client.Alliance.get_alliances_alliance_id_corporations(alliance_id=alliance_id).result()
@ -223,7 +224,8 @@ class EveSwaggerProvider(EveProvider):
except HTTPNotFound: except HTTPNotFound:
raise ObjectNotFound(alliance_id, 'alliance') raise ObjectNotFound(alliance_id, 'alliance')
def get_corp(self, corp_id): def get_corp(self, corp_id: int) -> Corporation:
"""Fetch corporation from ESI."""
try: try:
data = self.client.Corporation.get_corporations_corporation_id(corporation_id=corp_id).result() data = self.client.Corporation.get_corporations_corporation_id(corporation_id=corp_id).result()
model = Corporation( model = Corporation(
@ -239,28 +241,42 @@ class EveSwaggerProvider(EveProvider):
except HTTPNotFound: except HTTPNotFound:
raise ObjectNotFound(corp_id, 'corporation') raise ObjectNotFound(corp_id, 'corporation')
def get_character(self, character_id): def get_character(self, character_id: int) -> Character:
"""Fetch character from ESI."""
try: try:
data = self.client.Character.get_characters_character_id(character_id=character_id).result() character_name = self._fetch_character_name(character_id)
affiliation = self.client.Character.post_characters_affiliation(characters=[character_id]).result()[0] affiliation = self.client.Character.post_characters_affiliation(characters=[character_id]).result()[0]
model = Character( model = Character(
id=character_id, id=character_id,
name=data['name'], name=character_name,
corp_id=affiliation['corporation_id'], corp_id=affiliation['corporation_id'],
alliance_id=affiliation['alliance_id'] if 'alliance_id' in affiliation else None, alliance_id=affiliation['alliance_id'] if 'alliance_id' in affiliation else None,
faction_id=affiliation['faction_id'] if 'faction_id' in affiliation else None, faction_id=affiliation['faction_id'] if 'faction_id' in affiliation else None,
) )
return model return model
except (HTTPNotFound, HTTPUnprocessableEntity): except (HTTPNotFound, HTTPUnprocessableEntity, ObjectNotFound):
raise ObjectNotFound(character_id, 'character') raise ObjectNotFound(character_id, 'character')
def _fetch_character_name(self, character_id: int) -> str:
"""Fetch character name from ESI."""
data = self.client.Universe.post_universe_names(ids=[character_id]).result()
character = data.pop() if data else None
if (
not character
or character["category"] != "character"
or character["id"] != character_id
):
raise ObjectNotFound(character_id, 'character')
return character["name"]
def get_all_factions(self): def get_all_factions(self):
"""Fetch all factions from ESI."""
if not self._faction_list: if not self._faction_list:
self._faction_list = self.client.Universe.get_universe_factions().result() self._faction_list = self.client.Universe.get_universe_factions().result()
return self._faction_list return self._faction_list
def get_faction(self, faction_id): def get_faction(self, faction_id: int):
"""Fetch faction from ESI."""
faction_id = int(faction_id) faction_id = int(faction_id)
try: try:
if not self._faction_list: if not self._faction_list:
@ -273,7 +289,8 @@ class EveSwaggerProvider(EveProvider):
except (HTTPNotFound, HTTPUnprocessableEntity, KeyError): except (HTTPNotFound, HTTPUnprocessableEntity, KeyError):
raise ObjectNotFound(faction_id, 'faction') raise ObjectNotFound(faction_id, 'faction')
def get_itemtype(self, type_id): def get_itemtype(self, type_id: int) -> ItemType:
"""Fetch inventory item from ESI."""
try: try:
data = self.client.Universe.get_universe_types_type_id(type_id=type_id).result() data = self.client.Universe.get_universe_types_type_id(type_id=type_id).result()
return ItemType(id=type_id, name=data['name']) return ItemType(id=type_id, name=data['name'])

View File

@ -1,12 +1,11 @@
import logging import logging
from celery import shared_task from celery import shared_task
from .models import EveAllianceInfo
from .models import EveCharacter
from .models import EveCorporationInfo
from .models import EveAllianceInfo, EveCharacter, EveCorporationInfo
from . import providers from . import providers
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
TASK_PRIORITY = 7 TASK_PRIORITY = 7
@ -32,8 +31,8 @@ def update_alliance(alliance_id):
@shared_task @shared_task
def update_character(character_id): def update_character(character_id: int) -> None:
"""Update given character from ESI""" """Update given character from ESI."""
EveCharacter.objects.update_character(character_id) EveCharacter.objects.update_character(character_id)
@ -65,7 +64,7 @@ def update_character_chunk(character_ids_chunk: list):
.post_characters_affiliation(characters=character_ids_chunk).result() .post_characters_affiliation(characters=character_ids_chunk).result()
character_names = providers.provider.client.Universe\ character_names = providers.provider.client.Universe\
.post_universe_names(ids=character_ids_chunk).result() .post_universe_names(ids=character_ids_chunk).result()
except: except OSError:
logger.info("Failed to bulk update characters. Attempting single updates") logger.info("Failed to bulk update characters. Attempting single updates")
for character_id in character_ids_chunk: for character_id in character_ids_chunk:
update_character.apply_async( update_character.apply_async(

View File

@ -0,0 +1,168 @@
from bravado.exception import HTTPNotFound
class BravadoResponseStub:
"""Stub for IncomingResponse in bravado, e.g. for HTTPError exceptions"""
def __init__(
self, status_code, reason="", text="", headers=None, raw_bytes=None
) -> None:
self.reason = reason
self.status_code = status_code
self.text = text
self.headers = headers if headers else dict()
self.raw_bytes = raw_bytes
def __str__(self):
return f"{self.status_code} {self.reason}"
class BravadoOperationStub:
"""Stub to simulate the operation object return from bravado via django-esi"""
class RequestConfig:
def __init__(self, also_return_response):
self.also_return_response = also_return_response
class ResponseStub:
def __init__(self, headers):
self.headers = headers
def __init__(self, data, headers: dict = None, also_return_response: bool = False):
self._data = data
self._headers = headers if headers else {"x-pages": 1}
self.request_config = BravadoOperationStub.RequestConfig(also_return_response)
def result(self, **kwargs):
if self.request_config.also_return_response:
return [self._data, self.ResponseStub(self._headers)]
else:
return self._data
def results(self, **kwargs):
return self.result(**kwargs)
class EsiClientStub:
"""Stub for an ESI client."""
class Alliance:
@staticmethod
def get_alliances_alliance_id(alliance_id):
data = {
3001: {
"name": "Wayne Enterprises",
"ticker": "WYE",
"executor_corporation_id": 2001
}
}
try:
return BravadoOperationStub(data[int(alliance_id)])
except KeyError:
response = BravadoResponseStub(
404, f"Alliance with ID {alliance_id} not found"
)
raise HTTPNotFound(response)
@staticmethod
def get_alliances_alliance_id_corporations(alliance_id):
data = [2001, 2002, 2003]
return BravadoOperationStub(data)
class Character:
@staticmethod
def get_characters_character_id(character_id):
data = {
1001: {
"corporation_id": 2001,
"name": "Bruce Wayne",
},
1002: {
"corporation_id": 2001,
"name": "Peter Parker",
},
1011: {
"corporation_id": 2011,
"name": "Lex Luthor",
}
}
try:
return BravadoOperationStub(data[int(character_id)])
except KeyError:
response = BravadoResponseStub(
404, f"Character with ID {character_id} not found"
)
raise HTTPNotFound(response)
@staticmethod
def post_characters_affiliation(characters: list):
data = [
{'character_id': 1001, 'corporation_id': 2001, 'alliance_id': 3001},
{'character_id': 1002, 'corporation_id': 2001, 'alliance_id': 3001},
{'character_id': 1011, 'corporation_id': 2011},
{'character_id': 1666, 'corporation_id': 1000001},
]
return BravadoOperationStub(
[x for x in data if x['character_id'] in characters]
)
class Corporation:
@staticmethod
def get_corporations_corporation_id(corporation_id):
data = {
2001: {
"ceo_id": 1091,
"member_count": 10,
"name": "Wayne Technologies",
"ticker": "WTE",
"alliance_id": 3001
},
2002: {
"ceo_id": 1092,
"member_count": 10,
"name": "Wayne Food",
"ticker": "WFO",
"alliance_id": 3001
},
2003: {
"ceo_id": 1093,
"member_count": 10,
"name": "Wayne Energy",
"ticker": "WEG",
"alliance_id": 3001
},
2011: {
"ceo_id": 1,
"member_count": 3,
"name": "LexCorp",
"ticker": "LC",
},
1000001: {
"ceo_id": 3000001,
"creator_id": 1,
"description": "The internal corporation used for characters in graveyard.",
"member_count": 6329026,
"name": "Doomheim",
"ticker": "666",
}
}
try:
return BravadoOperationStub(data[int(corporation_id)])
except KeyError:
response = BravadoResponseStub(
404, f"Corporation with ID {corporation_id} not found"
)
raise HTTPNotFound(response)
class Universe:
@staticmethod
def post_universe_names(ids: list):
data = [
{"category": "character", "id": 1001, "name": "Bruce Wayne"},
{"category": "character", "id": 1002, "name": "Peter Parker"},
{"category": "character", "id": 1011, "name": "Lex Luthor"},
{"category": "character", "id": 1666, "name": "Hal Jordan"},
{"category": "corporation", "id": 2001, "name": "Wayne Technologies"},
{"category": "corporation","id": 2002, "name": "Wayne Food"},
{"category": "corporation","id": 1000001, "name": "Doomheim"},
]
return BravadoOperationStub([x for x in data if x['id'] in ids])

View File

@ -1,12 +1,15 @@
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
from django.core.exceptions import ObjectDoesNotExist
from django.test import TestCase from django.test import TestCase
from esi.models import Token
from allianceauth.tests.auth_utils import AuthUtils
from ..models import (
EveCharacter, EveCorporationInfo, EveAllianceInfo, EveFactionInfo
)
from ..providers import Alliance, Corporation, Character
from ..evelinks import eveimageserver from ..evelinks import eveimageserver
from ..models import EveAllianceInfo, EveCharacter, EveCorporationInfo, EveFactionInfo
from ..providers import Alliance, Character, Corporation
from .esi_client_stub import EsiClientStub
class EveCharacterTestCase(TestCase): class EveCharacterTestCase(TestCase):
@ -402,8 +405,8 @@ class EveAllianceTestCase(TestCase):
my_alliance.save() my_alliance.save()
my_alliance.populate_alliance() my_alliance.populate_alliance()
for corporation in EveCorporationInfo.objects\ for corporation in (
.filter(corporation_id__in=[2001, 2002] EveCorporationInfo.objects.filter(corporation_id__in=[2001, 2002])
): ):
self.assertEqual(corporation.alliance, my_alliance) self.assertEqual(corporation.alliance, my_alliance)
@ -587,3 +590,98 @@ class EveCorporationTestCase(TestCase):
self.my_corp.logo_url_256, self.my_corp.logo_url_256,
'https://images.evetech.net/corporations/2001/logo?size=256' 'https://images.evetech.net/corporations/2001/logo?size=256'
) )
@patch('allianceauth.eveonline.providers.esi_client_factory')
@patch("allianceauth.eveonline.models.notify")
class TestCharacterUpdate(TestCase):
def test_should_update_normal_character(self, mock_notify, mock_esi_client_factory):
# given
mock_esi_client_factory.return_value = EsiClientStub()
my_character = EveCharacter.objects.create(
character_id=1001,
character_name="not my name",
corporation_id=2002,
corporation_name="Wayne Food",
corporation_ticker="WYF",
alliance_id=None
)
# when
my_character.update_character()
# then
my_character.refresh_from_db()
self.assertEqual(my_character.character_name, "Bruce Wayne")
self.assertEqual(my_character.corporation_id, 2001)
self.assertEqual(my_character.corporation_name, "Wayne Technologies")
self.assertEqual(my_character.corporation_ticker, "WTE")
self.assertEqual(my_character.alliance_id, 3001)
self.assertEqual(my_character.alliance_name, "Wayne Enterprises")
self.assertEqual(my_character.alliance_ticker, "WYE")
self.assertFalse(mock_notify.called)
def test_should_update_dead_character_with_owner(
self, mock_notify, mock_esi_client_factory
):
# given
mock_esi_client_factory.return_value = EsiClientStub()
character_1666 = EveCharacter.objects.create(
character_id=1666,
character_name="Hal Jordan",
corporation_id=2002,
corporation_name="Wayne Food",
corporation_ticker="WYF",
alliance_id=None
)
user = AuthUtils.create_user("Bruce Wayne")
token_1666 = Token.objects.create(
user=user,
character_id=character_1666.character_id,
character_name=character_1666.character_name,
character_owner_hash="ABC123-1666",
)
character_1001 = EveCharacter.objects.create(
character_id=1001,
character_name="Bruce Wayne",
corporation_id=2001,
corporation_name="Wayne Technologies",
corporation_ticker="WYT",
alliance_id=None
)
token_1001 = Token.objects.create(
user=user,
character_id=character_1001.character_id,
character_name=character_1001.character_name,
character_owner_hash="ABC123-1001",
)
# when
character_1666.update_character()
# then
character_1666.refresh_from_db()
self.assertTrue(character_1666.is_biomassed)
self.assertNotIn(token_1666, user.token_set.all())
self.assertIn(token_1001, user.token_set.all())
with self.assertRaises(ObjectDoesNotExist):
self.assertTrue(character_1666.character_ownership)
user.profile.refresh_from_db()
self.assertIsNone(user.profile.main_character)
self.assertTrue(mock_notify.called)
def test_should_handle_dead_character_without_owner(
self, mock_notify, mock_esi_client_factory
):
# given
mock_esi_client_factory.return_value = EsiClientStub()
character_1666 = EveCharacter.objects.create(
character_id=1666,
character_name="Hal Jordan",
corporation_id=1011,
corporation_name="LexCorp",
corporation_ticker='LC',
alliance_id=None
)
# when
character_1666.update_character()
# then
character_1666.refresh_from_db()
self.assertTrue(character_1666.is_biomassed)
self.assertFalse(mock_notify.called)

View File

@ -7,6 +7,7 @@ from jsonschema.exceptions import RefResolutionError
from django.test import TestCase from django.test import TestCase
from . import set_logger from . import set_logger
from .esi_client_stub import EsiClientStub
from ..providers import ( from ..providers import (
ObjectNotFound, ObjectNotFound,
Entity, Entity,
@ -632,13 +633,7 @@ class TestEveSwaggerProvider(TestCase):
@patch(MODULE_PATH + '.esi_client_factory') @patch(MODULE_PATH + '.esi_client_factory')
def test_get_character(self, mock_esi_client_factory): def test_get_character(self, mock_esi_client_factory):
mock_esi_client_factory.return_value \ mock_esi_client_factory.return_value = EsiClientStub()
.Character.get_characters_character_id \
= TestEveSwaggerProvider.esi_get_characters_character_id
mock_esi_client_factory.return_value \
.Character.post_characters_affiliation \
= TestEveSwaggerProvider.esi_post_characters_affiliation
my_provider = EveSwaggerProvider() my_provider = EveSwaggerProvider()
# character with alliance # character with alliance
@ -649,8 +644,8 @@ class TestEveSwaggerProvider(TestCase):
self.assertEqual(my_character.alliance_id, 3001) self.assertEqual(my_character.alliance_id, 3001)
# character wo/ alliance # character wo/ alliance
my_character = my_provider.get_character(1002) my_character = my_provider.get_character(1011)
self.assertEqual(my_character.id, 1002) self.assertEqual(my_character.id, 1011)
self.assertEqual(my_character.alliance_id, None) self.assertEqual(my_character.alliance_id, None)
# character not found # character not found

View File

@ -1,245 +1,271 @@
from unittest.mock import patch, Mock from unittest.mock import patch
from django.test import TestCase from django.test import TestCase, TransactionTestCase, override_settings
from ..models import EveCharacter, EveCorporationInfo, EveAllianceInfo from ..models import EveAllianceInfo, EveCharacter, EveCorporationInfo
from ..tasks import ( from ..tasks import (
run_model_update,
update_alliance, update_alliance,
update_corp,
update_character, update_character,
run_model_update update_character_chunk,
update_corp,
) )
from .esi_client_stub import EsiClientStub
class TestTasks(TestCase): @patch('allianceauth.eveonline.providers.esi_client_factory')
class TestUpdateTasks(TestCase):
def test_should_update_alliance(self, mock_esi_client_factory):
# given
mock_esi_client_factory.return_value = EsiClientStub()
my_alliance = EveAllianceInfo.objects.create(
alliance_id=3001,
alliance_name="Wayne Enterprises",
alliance_ticker="WYE",
executor_corp_id=2003
)
# when
update_alliance(my_alliance.alliance_id)
# then
my_alliance.refresh_from_db()
self.assertEqual(my_alliance.executor_corp_id, 2001)
@patch('allianceauth.eveonline.tasks.EveCorporationInfo') def test_should_update_character(self, mock_esi_client_factory):
def test_update_corp(self, mock_EveCorporationInfo): # given
update_corp(42) mock_esi_client_factory.return_value = EsiClientStub()
self.assertEqual( my_character = EveCharacter.objects.create(
mock_EveCorporationInfo.objects.update_corporation.call_count, 1 character_id=1001,
) character_name="Bruce Wayne",
self.assertEqual( corporation_id=2002,
mock_EveCorporationInfo.objects.update_corporation.call_args[0][0], 42 corporation_name="Wayne Food",
corporation_ticker="WYF",
alliance_id=None
) )
# when
update_character(my_character.character_id)
# then
my_character.refresh_from_db()
self.assertEqual(my_character.corporation_id, 2001)
@patch('allianceauth.eveonline.tasks.EveAllianceInfo') def test_should_update_corp(self, mock_esi_client_factory):
def test_update_alliance(self, mock_EveAllianceInfo): # given
update_alliance(42) mock_esi_client_factory.return_value = EsiClientStub()
self.assertEqual( EveAllianceInfo.objects.create(
mock_EveAllianceInfo.objects.update_alliance.call_args[0][0], 42 alliance_id=3001,
alliance_name="Wayne Enterprises",
alliance_ticker="WYE",
executor_corp_id=2003
) )
self.assertEqual( my_corporation = EveCorporationInfo.objects.create(
mock_EveAllianceInfo.objects corporation_id=2003,
.update_alliance.return_value.populate_alliance.call_count, 1 corporation_name="Wayne Food",
corporation_ticker="WFO",
member_count=1,
alliance=None,
ceo_id=1999
) )
# when
update_corp(my_corporation.corporation_id)
# then
my_corporation.refresh_from_db()
self.assertEqual(my_corporation.alliance.alliance_id, 3001)
@patch('allianceauth.eveonline.tasks.EveCharacter') # @patch('allianceauth.eveonline.tasks.EveCharacter')
def test_update_character(self, mock_EveCharacter): # def test_update_character(self, mock_EveCharacter):
update_character(42) # update_character(42)
self.assertEqual( # self.assertEqual(
mock_EveCharacter.objects.update_character.call_count, 1 # mock_EveCharacter.objects.update_character.call_count, 1
) # )
self.assertEqual( # self.assertEqual(
mock_EveCharacter.objects.update_character.call_args[0][0], 42 # mock_EveCharacter.objects.update_character.call_args[0][0], 42
) # )
@patch('allianceauth.eveonline.tasks.update_character') @override_settings(CELERY_ALWAYS_EAGER=True)
@patch('allianceauth.eveonline.tasks.update_alliance') @patch('allianceauth.eveonline.providers.esi_client_factory')
@patch('allianceauth.eveonline.tasks.update_corp') @patch('allianceauth.eveonline.tasks.providers')
@patch('allianceauth.eveonline.providers.provider')
@patch('allianceauth.eveonline.tasks.CHUNK_SIZE', 2) @patch('allianceauth.eveonline.tasks.CHUNK_SIZE', 2)
class TestRunModelUpdate(TestCase): class TestRunModelUpdate(TransactionTestCase):
def test_should_run_updates(self, mock_providers, mock_esi_client_factory):
@classmethod # given
def setUpClass(cls): mock_providers.provider.client = EsiClientStub()
super().setUpClass() mock_esi_client_factory.return_value = EsiClientStub()
EveCorporationInfo.objects.all().delete()
EveAllianceInfo.objects.all().delete()
EveCharacter.objects.all().delete()
EveCorporationInfo.objects.create( EveCorporationInfo.objects.create(
corporation_id=2345, corporation_id=2001,
corporation_name='corp.name', corporation_name="Wayne Technologies",
corporation_ticker='c.c.t', corporation_ticker="WTE",
member_count=10, member_count=10,
alliance=None, alliance=None,
) )
EveAllianceInfo.objects.create( alliance_3001 = EveAllianceInfo.objects.create(
alliance_id=3456, alliance_id=3001,
alliance_name='alliance.name', alliance_name="Wayne Enterprises",
alliance_ticker='a.t', alliance_ticker="WYE",
executor_corp_id=5, executor_corp_id=2003
) )
EveCharacter.objects.create( corporation_2003 = EveCorporationInfo.objects.create(
character_id=1, corporation_id=2003,
character_name='character.name1', corporation_name="Wayne Energy",
corporation_id=2345, corporation_ticker="WEG",
corporation_name='character.corp.name', member_count=99,
corporation_ticker='c.c.t', # max 5 chars alliance=None,
)
character_1001 = EveCharacter.objects.create(
character_id=1001,
character_name="Bruce Wayne",
corporation_id=2002,
corporation_name="Wayne Food",
corporation_ticker="WYF",
alliance_id=None alliance_id=None
) )
EveCharacter.objects.create( # when
character_id=2,
character_name='character.name2',
corporation_id=9876,
corporation_name='character.corp.name',
corporation_ticker='c.c.t', # max 5 chars
alliance_id=3456,
alliance_name='character.alliance.name',
)
EveCharacter.objects.create(
character_id=3,
character_name='character.name3',
corporation_id=9876,
corporation_name='character.corp.name',
corporation_ticker='c.c.t', # max 5 chars
alliance_id=3456,
alliance_name='character.alliance.name',
)
EveCharacter.objects.create(
character_id=4,
character_name='character.name4',
corporation_id=9876,
corporation_name='character.corp.name',
corporation_ticker='c.c.t', # max 5 chars
alliance_id=3456,
alliance_name='character.alliance.name',
)
"""
EveCharacter.objects.create(
character_id=5,
character_name='character.name5',
corporation_id=9876,
corporation_name='character.corp.name',
corporation_ticker='c.c.t', # max 5 chars
alliance_id=3456,
alliance_name='character.alliance.name',
)
"""
def setUp(self):
self.affiliations = [
{'character_id': 1, 'corporation_id': 5},
{'character_id': 2, 'corporation_id': 9876, 'alliance_id': 3456},
{'character_id': 3, 'corporation_id': 9876, 'alliance_id': 7456},
{'character_id': 4, 'corporation_id': 9876, 'alliance_id': 3456}
]
self.names = [
{'id': 1, 'name': 'character.name1'},
{'id': 2, 'name': 'character.name2'},
{'id': 3, 'name': 'character.name3'},
{'id': 4, 'name': 'character.name4_new'}
]
def test_normal_run(
self,
mock_provider,
mock_update_corp,
mock_update_alliance,
mock_update_character,
):
def get_affiliations(characters: list):
response = [x for x in self.affiliations if x['character_id'] in characters]
mock_operator = Mock(**{'result.return_value': response})
return mock_operator
def get_names(ids: list):
response = [x for x in self.names if x['id'] in ids]
mock_operator = Mock(**{'result.return_value': response})
return mock_operator
mock_provider.client.Character.post_characters_affiliation.side_effect \
= get_affiliations
mock_provider.client.Universe.post_universe_names.side_effect = get_names
run_model_update() run_model_update()
# then
character_1001.refresh_from_db()
self.assertEqual( self.assertEqual(
mock_provider.client.Character.post_characters_affiliation.call_count, 2 character_1001.corporation_id, 2001 # char has new corp
) )
corporation_2003.refresh_from_db()
self.assertEqual( self.assertEqual(
mock_provider.client.Universe.post_universe_names.call_count, 2 corporation_2003.alliance.alliance_id, 3001 # corp has new alliance
)
alliance_3001.refresh_from_db()
self.assertEqual(
alliance_3001.executor_corp_id, 2001 # alliance has been updated
) )
# character 1 has changed corp
# character 2 no change @override_settings(CELERY_ALWAYS_EAGER=True)
# character 3 has changed alliance @patch('allianceauth.eveonline.tasks.update_character', wraps=update_character)
# character 4 has changed name @patch('allianceauth.eveonline.providers.esi_client_factory')
self.assertEqual(mock_update_corp.apply_async.call_count, 1) @patch('allianceauth.eveonline.tasks.providers')
self.assertEqual( @patch('allianceauth.eveonline.tasks.CHUNK_SIZE', 2)
int(mock_update_corp.apply_async.call_args[1]['args'][0]), 2345 class TestUpdateCharacterChunk(TestCase):
) @staticmethod
self.assertEqual(mock_update_alliance.apply_async.call_count, 1) def _updated_character_ids(spy_update_character) -> set:
self.assertEqual( """Character IDs passed to update_character task for update."""
int(mock_update_alliance.apply_async.call_args[1]['args'][0]), 3456 return {
) x[1]["args"][0] for x in spy_update_character.apply_async.call_args_list
characters_updated = {
x[1]['args'][0] for x in mock_update_character.apply_async.call_args_list
} }
excepted = {1, 3, 4}
self.assertSetEqual(characters_updated, excepted)
def test_ignore_character_not_in_affiliations( def test_should_update_corp_change(
self, self, mock_providers, mock_esi_client_factory, spy_update_character
mock_provider,
mock_update_corp,
mock_update_alliance,
mock_update_character,
): ):
def get_affiliations(characters: list): # given
response = [x for x in self.affiliations if x['character_id'] in characters] mock_providers.provider.client = EsiClientStub()
mock_operator = Mock(**{'result.return_value': response}) mock_esi_client_factory.return_value = EsiClientStub()
return mock_operator character_1001 = EveCharacter.objects.create(
character_id=1001,
character_name="Bruce Wayne",
corporation_id=2003,
corporation_name="Wayne Energy",
corporation_ticker="WEG",
alliance_id=3001,
alliance_name="Wayne Enterprises",
alliance_ticker="WYE",
)
character_1002 = EveCharacter.objects.create(
character_id=1002,
character_name="Peter Parker",
corporation_id=2001,
corporation_name="Wayne Technologies",
corporation_ticker="WTE",
alliance_id=3001,
alliance_name="Wayne Enterprises",
alliance_ticker="WYE",
)
# when
update_character_chunk([
character_1001.character_id, character_1002.character_id
])
# then
character_1001.refresh_from_db()
self.assertEqual(character_1001.corporation_id, 2001)
self.assertSetEqual(self._updated_character_ids(spy_update_character), {1001})
def get_names(ids: list): def test_should_update_name_change(
response = [x for x in self.names if x['id'] in ids] self, mock_providers, mock_esi_client_factory, spy_update_character
mock_operator = Mock(**{'result.return_value': response})
return mock_operator
del self.affiliations[0]
mock_provider.client.Character.post_characters_affiliation.side_effect \
= get_affiliations
mock_provider.client.Universe.post_universe_names.side_effect = get_names
run_model_update()
characters_updated = {
x[1]['args'][0] for x in mock_update_character.apply_async.call_args_list
}
excepted = {3, 4}
self.assertSetEqual(characters_updated, excepted)
def test_ignore_character_not_in_names(
self,
mock_provider,
mock_update_corp,
mock_update_alliance,
mock_update_character,
): ):
def get_affiliations(characters: list): # given
response = [x for x in self.affiliations if x['character_id'] in characters] mock_providers.provider.client = EsiClientStub()
mock_operator = Mock(**{'result.return_value': response}) mock_esi_client_factory.return_value = EsiClientStub()
return mock_operator character_1001 = EveCharacter.objects.create(
character_id=1001,
character_name="Batman",
corporation_id=2001,
corporation_name="Wayne Technologies",
corporation_ticker="WTE",
alliance_id=3001,
alliance_name="Wayne Technologies",
alliance_ticker="WYT",
)
# when
update_character_chunk([character_1001.character_id])
# then
character_1001.refresh_from_db()
self.assertEqual(character_1001.character_name, "Bruce Wayne")
self.assertSetEqual(self._updated_character_ids(spy_update_character), {1001})
def get_names(ids: list): def test_should_update_alliance_change(
response = [x for x in self.names if x['id'] in ids] self, mock_providers, mock_esi_client_factory, spy_update_character
mock_operator = Mock(**{'result.return_value': response}) ):
return mock_operator # given
mock_providers.provider.client = EsiClientStub()
mock_esi_client_factory.return_value = EsiClientStub()
character_1001 = EveCharacter.objects.create(
character_id=1001,
character_name="Bruce Wayne",
corporation_id=2001,
corporation_name="Wayne Technologies",
corporation_ticker="WTE",
alliance_id=None,
)
# when
update_character_chunk([character_1001.character_id])
# then
character_1001.refresh_from_db()
self.assertEqual(character_1001.alliance_id, 3001)
self.assertSetEqual(self._updated_character_ids(spy_update_character), {1001})
del self.names[3] def test_should_not_update_when_not_changed(
self, mock_providers, mock_esi_client_factory, spy_update_character
):
# given
mock_providers.provider.client = EsiClientStub()
mock_esi_client_factory.return_value = EsiClientStub()
character_1001 = EveCharacter.objects.create(
character_id=1001,
character_name="Bruce Wayne",
corporation_id=2001,
corporation_name="Wayne Technologies",
corporation_ticker="WTE",
alliance_id=3001,
alliance_name="Wayne Technologies",
alliance_ticker="WYT",
)
# when
update_character_chunk([character_1001.character_id])
# then
self.assertSetEqual(self._updated_character_ids(spy_update_character), set())
mock_provider.client.Character.post_characters_affiliation.side_effect \ def test_should_fall_back_to_single_updates_when_bulk_update_failed(
= get_affiliations self, mock_providers, mock_esi_client_factory, spy_update_character
):
mock_provider.client.Universe.post_universe_names.side_effect = get_names # given
mock_providers.provider.client.Character.post_characters_affiliation\
run_model_update() .side_effect = OSError
characters_updated = { mock_esi_client_factory.return_value = EsiClientStub()
x[1]['args'][0] for x in mock_update_character.apply_async.call_args_list character_1001 = EveCharacter.objects.create(
} character_id=1001,
excepted = {1, 3} character_name="Bruce Wayne",
self.assertSetEqual(characters_updated, excepted) corporation_id=2001,
corporation_name="Wayne Technologies",
corporation_ticker="WTE",
alliance_id=3001,
alliance_name="Wayne Technologies",
alliance_ticker="WYT",
)
# when
update_character_chunk([character_1001.character_id])
# then
self.assertSetEqual(self._updated_character_ids(spy_update_character), {1001})