aa-fleetfinder 2.5.1__py3-none-any.whl → 2.6.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of aa-fleetfinder might be problematic. Click here for more details.

Files changed (27) hide show
  1. {aa_fleetfinder-2.5.1.dist-info → aa_fleetfinder-2.6.1.dist-info}/METADATA +2 -2
  2. {aa_fleetfinder-2.5.1.dist-info → aa_fleetfinder-2.6.1.dist-info}/RECORD +27 -25
  3. fleetfinder/__init__.py +1 -1
  4. fleetfinder/locale/cs_CZ/LC_MESSAGES/django.po +20 -7
  5. fleetfinder/locale/de/LC_MESSAGES/django.mo +0 -0
  6. fleetfinder/locale/de/LC_MESSAGES/django.po +23 -10
  7. fleetfinder/locale/django.pot +21 -8
  8. fleetfinder/locale/es/LC_MESSAGES/django.po +23 -8
  9. fleetfinder/locale/fr_FR/LC_MESSAGES/django.po +20 -7
  10. fleetfinder/locale/it_IT/LC_MESSAGES/django.po +20 -7
  11. fleetfinder/locale/ja/LC_MESSAGES/django.po +20 -7
  12. fleetfinder/locale/ko_KR/LC_MESSAGES/django.po +23 -8
  13. fleetfinder/locale/nl_NL/LC_MESSAGES/django.po +20 -7
  14. fleetfinder/locale/pl_PL/LC_MESSAGES/django.po +20 -7
  15. fleetfinder/locale/ru/LC_MESSAGES/django.po +23 -8
  16. fleetfinder/locale/sk/LC_MESSAGES/django.po +20 -7
  17. fleetfinder/locale/uk/LC_MESSAGES/django.po +23 -8
  18. fleetfinder/locale/zh_Hans/LC_MESSAGES/django.po +23 -8
  19. fleetfinder/tasks.py +121 -150
  20. fleetfinder/templates/fleetfinder/dashboard.html +60 -47
  21. fleetfinder/templates/fleetfinder/fleet-details.html +7 -1
  22. fleetfinder/templates/fleetfinder/partials/body/form-fleet-details.html +8 -8
  23. fleetfinder/tests/test_tasks.py +140 -0
  24. fleetfinder/tests/test_views.py +416 -0
  25. fleetfinder/views.py +176 -62
  26. {aa_fleetfinder-2.5.1.dist-info → aa_fleetfinder-2.6.1.dist-info}/WHEEL +0 -0
  27. {aa_fleetfinder-2.5.1.dist-info → aa_fleetfinder-2.6.1.dist-info}/licenses/LICENSE +0 -0
fleetfinder/tasks.py CHANGED
@@ -14,7 +14,6 @@ from celery import shared_task
14
14
  from django.utils import timezone
15
15
 
16
16
  # Alliance Auth
17
- from allianceauth.eveonline.models import EveCharacter
18
17
  from allianceauth.services.hooks import get_extension_logger
19
18
  from allianceauth.services.tasks import QueueOnce
20
19
  from esi.models import Token
@@ -148,10 +147,13 @@ def _get_fleet_aggregate(fleet_infos):
148
147
  for member in fleet_infos:
149
148
  type_ = member.get("ship_type_name")
150
149
 
151
- if type_ in counts:
152
- counts[type_] += 1
153
- else:
154
- counts[type_] = 1
150
+ if type_ and isinstance(type_, str) and type_.strip():
151
+ type_ = type_.strip() # Normalize ship type name
152
+
153
+ if type_ in counts:
154
+ counts[type_] += 1
155
+ else:
156
+ counts[type_] = 1
155
157
 
156
158
  return counts
157
159
 
@@ -187,110 +189,56 @@ def _check_for_esi_fleet(fleet: Fleet):
187
189
  return False
188
190
 
189
191
 
190
- def _process_fleet(fleet: Fleet):
192
+ def _process_fleet(fleet: Fleet) -> None:
191
193
  """
192
194
  Processing a fleet
193
195
 
194
- :param fleet:
195
- :type fleet:
196
- :return:
197
- :rtype:
196
+ :param fleet: Fleet object to process
197
+ :type fleet: Fleet
198
+ :return: None
199
+ :rtype: None
198
200
  """
199
201
 
200
- fleet_id = fleet.fleet_id
201
- fleet_name = fleet.name
202
- fleet_commander = fleet.fleet_commander
203
-
204
202
  logger.info(
205
- f'Processing information for fleet "{fleet_name}" '
206
- f"of {fleet_commander} (ESI ID: {fleet_id})"
203
+ f'Processing information for fleet "{fleet.name}" '
204
+ f"of {fleet.fleet_commander} (ESI ID: {fleet.fleet_id})"
207
205
  )
208
206
 
209
207
  # Check if there is a fleet
210
208
  esi_fleet = _check_for_esi_fleet(fleet=fleet)
211
- if esi_fleet and fleet.fleet_id == esi_fleet["fleet"]["fleet_id"]:
212
- try:
213
- fleet_from_esi = esi.client.Fleets.get_characters_character_id_fleet(
214
- character_id=fleet.fleet_commander.character_id,
215
- token=esi_fleet["token"].valid_access_token(),
216
- ).result()
217
- except HTTPNotFound:
218
- _esi_fleet_error_handling(
219
- fleet=fleet, error_key=Fleet.EsiError.NOT_IN_FLEET
220
- )
221
- except Exception: # pylint: disable=broad-exception-caught
222
- _esi_fleet_error_handling(fleet=fleet, error_key=Fleet.EsiError.NO_FLEET)
223
-
224
- # We have a valid fleet result from ESI
225
- else:
226
- if fleet_id == fleet_from_esi["fleet_id"]:
227
- # Check if we deal with the fleet boss here
228
- try:
229
- _ = esi.client.Fleets.get_fleets_fleet_id_members(
230
- fleet_id=fleet_from_esi["fleet_id"],
231
- token=esi_fleet["token"].valid_access_token(),
232
- ).result()
233
- except Exception: # pylint: disable=broad-exception-caught
234
- _esi_fleet_error_handling(
235
- fleet=fleet, error_key=Fleet.EsiError.NOT_FLEETBOSS
236
- )
237
- else:
238
- _esi_fleet_error_handling(
239
- fleet=fleet, error_key=Fleet.EsiError.FC_CHANGED_FLEET
240
- )
241
-
242
-
243
- @shared_task
244
- def open_fleet(character_id, motd, free_move, name, groups):
245
- """
246
- Open a fleet
247
-
248
- :param character_id:
249
- :param motd:
250
- :param free_move:
251
- :param name:
252
- :param groups:
253
- :return:
254
- """
255
-
256
- required_scopes = ["esi-fleets.read_fleet.v1", "esi-fleets.write_fleet.v1"]
257
- token = Token.get_token(character_id=character_id, scopes=required_scopes)
258
209
 
259
- fleet_result = esi.client.Fleets.get_characters_character_id_fleet(
260
- character_id=token.character_id, token=token.valid_access_token()
261
- ).result()
262
- fleet_id = fleet_result.pop("fleet_id")
263
- fleet_role = fleet_result.pop("role")
264
-
265
- if fleet_id is None or fleet_role is None or fleet_role != "fleet_commander":
210
+ if not esi_fleet:
266
211
  return
267
212
 
268
- fleet_commander = EveCharacter.objects.get(character_id=token.character_id)
269
-
270
- fleet = Fleet(
271
- fleet_id=fleet_id,
272
- created_at=timezone.now(),
273
- motd=motd,
274
- is_free_move=free_move,
275
- fleet_commander=fleet_commander,
276
- name=name,
277
- )
278
- fleet.save()
279
- fleet.groups.set(groups)
213
+ # Fleet IDs don't match, FC changed fleets
214
+ if fleet.fleet_id != esi_fleet["fleet"]["fleet_id"]:
215
+ _esi_fleet_error_handling(
216
+ fleet=fleet, error_key=Fleet.EsiError.FC_CHANGED_FLEET
217
+ )
218
+ return
280
219
 
281
- esi_fleet = {"is_free_move": free_move, "motd": motd}
282
- esi.client.Fleets.put_fleets_fleet_id(
283
- fleet_id=fleet_id, token=token.valid_access_token(), new_settings=esi_fleet
284
- ).result()
220
+ # Check if we deal with the fleet boss here
221
+ try:
222
+ _ = esi.client.Fleets.get_fleets_fleet_id_members(
223
+ fleet_id=fleet.fleet_id,
224
+ token=esi_fleet["token"].valid_access_token(),
225
+ ).result()
226
+ except Exception: # pylint: disable=broad-exception-caught
227
+ _esi_fleet_error_handling(fleet=fleet, error_key=Fleet.EsiError.NOT_FLEETBOSS)
285
228
 
286
229
 
287
230
  @shared_task
288
- def send_fleet_invitation(character_ids, fleet_id):
231
+ def send_fleet_invitation(fleet_id: int, character_ids: list) -> None:
289
232
  """
290
- Send a fleet invitation through the eve client
291
-
292
- :param character_ids:
293
- :param fleet_id:
233
+ Send fleet invitations to characters through ESI
234
+ This task sends fleet invitations to a list of character IDs using the ESI API.
235
+
236
+ :param fleet_id: The ID of the fleet to which invitations are sent
237
+ :type fleet_id: int
238
+ :param character_ids: List of character IDs to invite to the fleet
239
+ :type character_ids: list[int]
240
+ :return: None
241
+ :rtype: None
294
242
  """
295
243
 
296
244
  required_scopes = ["esi-fleets.write_fleet.v1"]
@@ -298,95 +246,118 @@ def send_fleet_invitation(character_ids, fleet_id):
298
246
  fleet_commander_token = Token.get_token(
299
247
  character_id=fleet.fleet_commander.character_id, scopes=required_scopes
300
248
  )
301
- _processes = []
302
249
 
303
250
  with ThreadPoolExecutor(max_workers=50) as ex:
304
- for _character_id in character_ids:
305
- _processes.append(
306
- ex.submit(
307
- _send_invitation,
308
- character_id=_character_id,
309
- fleet_commander_token=fleet_commander_token,
310
- fleet_id=fleet_id,
311
- )
251
+ futures = [
252
+ ex.submit(
253
+ _send_invitation,
254
+ character_id=character_id,
255
+ fleet_commander_token=fleet_commander_token,
256
+ fleet_id=fleet_id,
312
257
  )
258
+ for character_id in character_ids
259
+ ]
313
260
 
314
- for item in as_completed(_processes):
315
- _ = item.result()
261
+ for future in as_completed(futures):
262
+ future.result() # This will raise any exceptions that occurred
316
263
 
317
264
 
318
265
  @shared_task(**{**TASK_DEFAULT_KWARGS}, **{"base": QueueOnce})
319
- def check_fleet_adverts():
266
+ def check_fleet_adverts() -> None:
320
267
  """
321
- Scheduled task :: Check for fleets adverts
268
+ Check all registered fleets and process them
269
+
270
+ :return: None
271
+ :rtype: None
322
272
  """
323
273
 
324
274
  fleets = Fleet.objects.all()
325
- fleet_count = fleets.count()
326
275
 
327
- processing_text = "Processing..." if fleet_count > 0 else "Nothing to do..."
276
+ if not fleets.exists():
277
+ logger.info("No registered fleets found. Nothing to do...")
328
278
 
329
- logger.info(msg=f"{fleet_count} registered fleets found. {processing_text}")
279
+ return
330
280
 
331
- if fleet_count > 0:
332
- # Abort if ESI seems to be offline or above the error limit
333
- if not fetch_esi_status().is_ok:
334
- logger.warning(
335
- msg="ESI doesn't seem to be available at this time. Aborting."
336
- )
281
+ logger.info(f"Processing {fleets.count()} registered fleets...")
337
282
 
338
- return
283
+ # Abort if ESI seems to be offline or above the error limit
284
+ if not fetch_esi_status().is_ok:
285
+ logger.warning("ESI doesn't seem to be available at this time. Aborting.")
339
286
 
340
- for fleet in fleets:
341
- _process_fleet(fleet=fleet)
287
+ return
288
+
289
+ for fleet in fleets:
290
+ _process_fleet(fleet=fleet)
342
291
 
343
292
 
344
293
  @shared_task
345
- def get_fleet_composition(fleet_id):
294
+ def get_fleet_composition(fleet_id: int) -> FleetViewAggregate | None:
346
295
  """
347
- Getting the fleet composition
296
+ Get the composition of a fleet by its ID
297
+ This task retrieves the composition of a fleet using its ESI ID.
348
298
 
349
- :param fleet_id:
350
- :return:
299
+ :param fleet_id: The ESI ID of the fleet to retrieve
300
+ :type fleet_id: int
301
+ :return: FleetViewAggregate containing fleet members and aggregate data
302
+ :rtype: FleetViewAggregate | None
351
303
  """
352
304
 
353
- required_scopes = ["esi-fleets.read_fleet.v1", "esi-fleets.write_fleet.v1"]
354
- fleet = Fleet.objects.get(fleet_id=fleet_id)
355
- token = Token.get_token(
356
- character_id=fleet.fleet_commander.character_id, scopes=required_scopes
305
+ try:
306
+ fleet = Fleet.objects.get(fleet_id=fleet_id)
307
+ except Fleet.DoesNotExist:
308
+ logger.error(f"Fleet with ID {fleet_id} not found")
309
+
310
+ return None
311
+
312
+ logger.info(
313
+ f'Getting fleet composition for fleet "{fleet.name}" '
314
+ f"of {fleet.fleet_commander.character_name} (ESI ID: {fleet_id})"
357
315
  )
358
- fleet_infos = esi.client.Fleets.get_fleets_fleet_id_members(
359
- fleet_id=fleet_id, token=token.valid_access_token()
360
- ).result()
361
316
 
362
- characters = {}
363
- systems = {}
364
- ship_type = {}
317
+ required_scopes = ["esi-fleets.read_fleet.v1"]
365
318
 
366
- for member in fleet_infos:
367
- characters[member["character_id"]] = ""
368
- systems[member["solar_system_id"]] = ""
369
- ship_type[member["ship_type_id"]] = ""
319
+ try:
320
+ token = Token.get_token(
321
+ character_id=fleet.fleet_commander.character_id, scopes=required_scopes
322
+ )
370
323
 
371
- ids = []
372
- ids.extend(list(characters.keys()))
373
- ids.extend(list(systems.keys()))
374
- ids.extend(list(ship_type.keys()))
324
+ fleet_infos = esi.client.Fleets.get_fleets_fleet_id_members(
325
+ fleet_id=fleet_id, token=token.valid_access_token()
326
+ ).result()
375
327
 
376
- ids_to_name = esi.client.Universe.post_universe_names(ids=ids).result()
328
+ # Get all unique IDs and fetch names in one call
329
+ all_ids = {
330
+ item_id
331
+ for member in fleet_infos
332
+ for item_id in [
333
+ member["character_id"],
334
+ member["solar_system_id"],
335
+ member["ship_type_id"],
336
+ ]
337
+ }
338
+
339
+ ids_to_name = esi.client.Universe.post_universe_names(
340
+ ids=list(all_ids)
341
+ ).result()
377
342
 
378
- for member in fleet_infos:
379
- index_character = [x["id"] for x in ids_to_name].index(member["character_id"])
380
- member["character_name"] = ids_to_name[index_character]["name"]
343
+ # Create a lookup dictionary for names
344
+ name_lookup = {item["id"]: item["name"] for item in ids_to_name}
345
+
346
+ # Add names to fleet members
347
+ for member in fleet_infos:
348
+ member.update(
349
+ {
350
+ "character_name": name_lookup[member["character_id"]],
351
+ "solar_system_name": name_lookup[member["solar_system_id"]],
352
+ "ship_type_name": name_lookup[member["ship_type_id"]],
353
+ }
354
+ )
381
355
 
382
- index_solar_system = [x["id"] for x in ids_to_name].index(
383
- member["solar_system_id"]
384
- )
385
- member["solar_system_name"] = ids_to_name[index_solar_system]["name"]
356
+ aggregate = _get_fleet_aggregate(fleet_infos=fleet_infos)
386
357
 
387
- index_ship_type = [x["id"] for x in ids_to_name].index(member["ship_type_id"])
388
- member["ship_type_name"] = ids_to_name[index_ship_type]["name"]
358
+ return FleetViewAggregate(fleet=fleet_infos, aggregate=aggregate)
389
359
 
390
- aggregate = _get_fleet_aggregate(fleet_infos=fleet_infos)
360
+ except Exception as e: # pylint: disable=broad-exception-caught
361
+ logger.error(f"Failed to get fleet composition for fleet {fleet_id}: {e}")
391
362
 
392
- return FleetViewAggregate(fleet=fleet_infos, aggregate=aggregate)
363
+ return None
@@ -51,52 +51,57 @@
51
51
  const DATETIME_FORMAT = 'YYYY-MM-DD, HH:mm';
52
52
  const table_fleet_overview = $('#table_available-fleets');
53
53
 
54
- const fleetfinderFleetsTable = table_fleet_overview.DataTable({
55
- language: {
56
- url: '{{ DT_LANG_PATH }}'
57
- },
58
- ajax: {
59
- url: "{% url 'fleetfinder:ajax_dashboard' %}",
60
- dataSrc: '',
61
- cache: false
62
- },
63
- columns: [
64
- {
65
- data: 'fleet_commander',
66
- render: {
67
- _: 'html',
68
- sort: 'sort'
69
- }
70
- },
71
- {data: 'fleet_name'},
72
- {
73
- data: 'created_at',
74
- },
75
- {data: 'join'},
76
- {% if perms.fleetfinder.manage_fleets %}
77
- {data: 'details'},
78
- {data: 'edit'},
79
- {% endif %}
80
- ],
81
- columnDefs: [
82
- {
83
- targets: 2,
84
- render: DataTable.render.date(DATETIME_FORMAT)
85
- },
86
- {
87
- orderable: false,
88
- targets: [3]
89
- },
90
- {% if perms.fleetfinder.manage_fleets %}
91
- {
92
- orderable: false,
93
- targets: [4, 5]
54
+ /**
55
+ * Initialize the datatable for fleet overview
56
+ */
57
+ fetchGet({url: '{% url "fleetfinder:ajax_dashboard" %}'})
58
+ .then((data) => {
59
+ table_fleet_overview.DataTable({
60
+ language: {
61
+ url: '{{ DT_LANG_PATH }}'
94
62
  },
95
- {% endif %}
96
- ],
97
- order: [[0, 'asc']],
98
- paging: false
99
- });
63
+ data: data,
64
+ columns: [
65
+ {
66
+ data: 'fleet_commander',
67
+ render: {
68
+ _: 'html',
69
+ sort: 'sort'
70
+ }
71
+ },
72
+ {data: 'fleet_name'},
73
+ {
74
+ data: 'created_at',
75
+ },
76
+ {data: 'join'},
77
+ {% if perms.fleetfinder.manage_fleets %}
78
+ {data: 'details'},
79
+ {data: 'edit'},
80
+ {% endif %}
81
+ ],
82
+ columnDefs: [
83
+ {
84
+ targets: 2,
85
+ render: DataTable.render.date(DATETIME_FORMAT)
86
+ },
87
+ {
88
+ orderable: false,
89
+ targets: [3]
90
+ },
91
+ {% if perms.fleetfinder.manage_fleets %}
92
+ {
93
+ orderable: false,
94
+ targets: [4, 5]
95
+ },
96
+ {% endif %}
97
+ ],
98
+ order: [[0, 'asc']],
99
+ paging: false
100
+ });
101
+ })
102
+ .catch((error) => {
103
+ console.error('Error fetching fleet data:', error);
104
+ });
100
105
 
101
106
  /**
102
107
  * Refresh the datatable information every 30 seconds
@@ -108,7 +113,7 @@
108
113
  * reload datatable "fleetfinderFleetsTable"
109
114
  */
110
115
  const reloadDataTable = () => {
111
- let dt = Date.now() - expectedReloadDatatable; // The drift (positive for overshooting)
116
+ const dt = Date.now() - expectedReloadDatatable; // The drift (positive for overshooting)
112
117
 
113
118
  if (dt > intervalReloadDatatable) {
114
119
  // Something awful happened. Maybe the browser (tab) was inactive?
@@ -120,7 +125,15 @@
120
125
  );
121
126
  }
122
127
 
123
- fleetfinderFleetsTable.ajax.reload();
128
+ fetchGet({url: '{% url "fleetfinder:ajax_dashboard" %}'})
129
+ .then((newData) => {
130
+ const dataTable = table_fleet_overview.DataTable();
131
+
132
+ dataTable.clear().rows.add(newData).draw();
133
+ })
134
+ .catch((error) => {
135
+ console.error('Error fetching updated data:', error);
136
+ });
124
137
 
125
138
  expectedReloadDatatable += intervalReloadDatatable;
126
139
 
@@ -78,7 +78,10 @@
78
78
  const tabe_fleet_composition = $('#table_fleet_composition');
79
79
 
80
80
  const populateDatatables = () => {
81
- $.get('{% url "fleetfinder:ajax_fleet_details" fleet_id %}', (data) => {
81
+ fetchGet({
82
+ url: '{% url "fleetfinder:ajax_fleet_details" fleet_id %}'
83
+ })
84
+ .then((data) => {
82
85
  table_fleet_members.DataTable({
83
86
  language: {
84
87
  url: '{{ DT_LANG_PATH }}'
@@ -106,6 +109,9 @@
106
109
  ],
107
110
  order: [[1, 'desc']]
108
111
  });
112
+ })
113
+ .catch((error) => {
114
+ console.error('Error fetching fleet details:', error);
109
115
  });
110
116
  };
111
117
 
@@ -33,16 +33,16 @@
33
33
  </p>
34
34
  </div>
35
35
 
36
- <div class="mb-3">
37
- <label for="motd" class="control-label">{% translate "Fleet MOTD" %}</label>
38
-
39
- <div>
40
- <textarea class="form-control" name="motd" id="motd" rows="10">{% if motd %}{{ motd }}{% endif %}</textarea>
41
- </div>
42
- </div>
36
+ {# <div class="mb-3">#}
37
+ {# <label for="motd" class="control-label">{% translate "Fleet MOTD" %}</label>#}
38
+ {##}
39
+ {# <div>#}
40
+ {# <textarea class="form-control" name="motd" id="motd" rows="10">{% if motd %}{{ motd }}{% endif %}</textarea>#}
41
+ {# </div>#}
42
+ {# </div>#}
43
43
 
44
44
  <div class="mb-3">
45
- <input type="checkbox" style="margin-right: 5px;" class="form-check-input" name="free_move" id="free_move" {% if is_free_move %}checked{% endif %}>
45
+ <input type="checkbox" style="margin-right: 5px;" class="form-check-input" name="free_move" id="free_move" {% if fleet.is_free_move %}checked{% endif %}>
46
46
  <label for="free_move" class="form-check-label">{% translate "Enable free move" %}</label>
47
47
  </div>
48
48
 
@@ -0,0 +1,140 @@
1
+ """
2
+ Tests for the fleetfinder.tasks module.
3
+ """
4
+
5
+ # Standard Library
6
+ from unittest import TestCase
7
+ from unittest.mock import Mock, patch
8
+
9
+ # AA Fleet Finder
10
+ from fleetfinder.tasks import (
11
+ _get_fleet_aggregate,
12
+ check_fleet_adverts,
13
+ )
14
+
15
+
16
+ class TestGetFleetAggregate(TestCase):
17
+ """
18
+ Tests for the _get_fleet_aggregate function.
19
+ """
20
+
21
+ def test_returns_correct_counts_for_valid_fleet_infos(self):
22
+ """
23
+ Test that _get_fleet_aggregate returns correct counts for valid fleet_infos.
24
+
25
+ :return:
26
+ :rtype:
27
+ """
28
+
29
+ fleet_infos = [
30
+ {"ship_type_name": "Cruiser"},
31
+ {"ship_type_name": "Cruiser"},
32
+ {"ship_type_name": "Battleship"},
33
+ ]
34
+
35
+ result = _get_fleet_aggregate(fleet_infos)
36
+
37
+ assert result == {"Cruiser": 2, "Battleship": 1}
38
+
39
+ def test_returns_empty_dict_for_empty_fleet_infos(self):
40
+ """
41
+ Test that _get_fleet_aggregate returns an empty dictionary for empty fleet_infos.
42
+
43
+ :return:
44
+ :rtype:
45
+ """
46
+
47
+ fleet_infos = []
48
+
49
+ result = _get_fleet_aggregate(fleet_infos)
50
+
51
+ assert result == {}
52
+
53
+ def test_returns_only_valid_ship_type_names(self):
54
+ """
55
+ Test that _get_fleet_aggregate returns only valid ship type names.
56
+
57
+ :return:
58
+ :rtype:
59
+ """
60
+
61
+ fleet_infos = [
62
+ {"ship_type_name": "Cruiser"},
63
+ {},
64
+ {"ship_type_name": None},
65
+ {"ship_type_name": "Battleship"},
66
+ {"other_key": "Frigate"},
67
+ ]
68
+
69
+ result = _get_fleet_aggregate(fleet_infos)
70
+
71
+ assert result == {"Cruiser": 1, "Battleship": 1}
72
+
73
+
74
+ class TestCheckFleetAdvert(TestCase):
75
+ """
76
+ Tests for the check_fleet_adverts function.
77
+ """
78
+
79
+ @patch("fleetfinder.models.Fleet.objects.all")
80
+ @patch("fleetfinder.tasks.fetch_esi_status")
81
+ def test_processes_registered_fleets_when_available(
82
+ self, mock_fetch_esi_status, mock_fleet_objects
83
+ ):
84
+ """
85
+ Test that check_fleet_adverts processes registered fleets when ESI is available.
86
+
87
+ :param mock_fetch_esi_status:
88
+ :type mock_fetch_esi_status:
89
+ :param mock_fleet_objects:
90
+ :type mock_fleet_objects:
91
+ :return:
92
+ :rtype:
93
+ """
94
+
95
+ mock_fetch_esi_status.return_value.is_ok = True
96
+ mock_fleet_objects.return_value.exists.return_value = True
97
+ mock_fleet_objects.return_value.count.return_value = 2
98
+ mock_fleet_objects.return_value.__iter__.return_value = iter([Mock(), Mock()])
99
+
100
+ check_fleet_adverts()
101
+
102
+ mock_fleet_objects.return_value.__iter__.assert_called_once()
103
+ mock_fetch_esi_status.assert_called_once()
104
+
105
+ @patch("fleetfinder.models.Fleet.objects.all")
106
+ @patch("fleetfinder.tasks.fetch_esi_status")
107
+ def test_logs_no_registered_fleets_when_none_exist(
108
+ self, mock_fetch_esi_status, mock_fleet_objects
109
+ ):
110
+ """
111
+ Test that check_fleet_adverts logs a message when no registered fleets exist.
112
+
113
+ :param mock_fetch_esi_status:
114
+ :type mock_fetch_esi_status:
115
+ :param mock_fleet_objects:
116
+ :type mock_fleet_objects:
117
+ :return:
118
+ :rtype:
119
+ """
120
+
121
+ mock_fetch_esi_status.return_value.is_ok = True
122
+ mock_fleet_objects.return_value.exists.return_value = False
123
+
124
+ check_fleet_adverts()
125
+
126
+ mock_fleet_objects.return_value.exists.assert_called_once()
127
+ mock_fetch_esi_status.assert_not_called()
128
+
129
+ @patch("fleetfinder.models.Fleet.objects.all")
130
+ @patch("fleetfinder.tasks.fetch_esi_status")
131
+ def test_aborts_processing_when_esi_is_unavailable(
132
+ self, mock_fetch_esi_status, mock_fleet_objects
133
+ ):
134
+ mock_fetch_esi_status.return_value.is_ok = False
135
+ mock_fleet_objects.return_value.exists.return_value = True
136
+
137
+ check_fleet_adverts()
138
+
139
+ mock_fetch_esi_status.assert_called_once()
140
+ mock_fleet_objects.return_value.__iter__.assert_not_called()