aa-ledger 1.0.4__py3-none-any.whl → 2.0.0__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.
- {aa_ledger-1.0.4.dist-info → aa_ledger-2.0.0.dist-info}/METADATA +5 -6
- aa_ledger-2.0.0.dist-info/RECORD +267 -0
- ledger/__init__.py +2 -2
- ledger/admin.py +23 -18
- ledger/api/__init__.py +23 -7
- ledger/api/{ledger/admin.py → admin.py} +25 -31
- ledger/api/alliance.py +755 -0
- ledger/api/character.py +786 -0
- ledger/api/corporation.py +1141 -0
- ledger/api/{helpers.py → helpers/core.py} +33 -33
- ledger/api/helpers/icons.py +372 -0
- ledger/api/helpers/planetary_helper.py +354 -0
- ledger/api/planetary.py +354 -0
- ledger/api/schema.py +240 -15
- ledger/app_settings.py +11 -27
- ledger/auth_hooks.py +2 -2
- ledger/constants.py +50 -177
- ledger/decorators.py +2 -46
- ledger/forms.py +133 -39
- ledger/helpers/billboard.py +194 -144
- ledger/helpers/cache.py +105 -0
- ledger/helpers/discord.py +2 -4
- ledger/helpers/eveonline.py +160 -0
- ledger/helpers/ledger_data.py +23 -0
- ledger/helpers/ref_type.py +53 -78
- ledger/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
- ledger/locale/cs_CZ/LC_MESSAGES/django.po +349 -193
- ledger/locale/de/LC_MESSAGES/django.mo +0 -0
- ledger/locale/de/LC_MESSAGES/django.po +528 -379
- ledger/locale/django.pot +721 -546
- ledger/locale/es/LC_MESSAGES/django.mo +0 -0
- ledger/locale/es/LC_MESSAGES/django.po +349 -194
- ledger/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
- ledger/locale/fr_FR/LC_MESSAGES/django.po +349 -193
- ledger/locale/it_IT/LC_MESSAGES/django.mo +0 -0
- ledger/locale/it_IT/LC_MESSAGES/django.po +349 -193
- ledger/locale/ja/LC_MESSAGES/django.mo +0 -0
- ledger/locale/ja/LC_MESSAGES/django.po +348 -193
- ledger/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
- ledger/locale/ko_KR/LC_MESSAGES/django.po +349 -193
- ledger/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
- ledger/locale/nl_NL/LC_MESSAGES/django.po +349 -193
- ledger/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
- ledger/locale/pl_PL/LC_MESSAGES/django.po +350 -193
- ledger/locale/ru/LC_MESSAGES/django.mo +0 -0
- ledger/locale/ru/LC_MESSAGES/django.po +348 -193
- ledger/locale/sk/LC_MESSAGES/django.mo +0 -0
- ledger/locale/sk/LC_MESSAGES/django.po +348 -193
- ledger/locale/uk/LC_MESSAGES/django.mo +0 -0
- ledger/locale/uk/LC_MESSAGES/django.po +348 -193
- ledger/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- ledger/locale/zh_Hans/LC_MESSAGES/django.po +348 -193
- ledger/managers/character_audit_manager.py +28 -20
- ledger/managers/character_journal_manager.py +185 -357
- ledger/managers/character_mining_manager.py +52 -26
- ledger/managers/character_planetary_manager.py +178 -136
- ledger/managers/corporation_audit_manager.py +36 -27
- ledger/managers/corporation_journal_manager.py +92 -56
- ledger/managers/general_manager.py +8 -7
- ledger/migrations/0018_remove_characterplanet_ledger_char_planet__58a5b6_idx_and_more.py +44 -0
- ledger/migrations/0019_rename_characteraudit_characterowner_and_more.py +48 -0
- ledger/models/__init__.py +5 -11
- ledger/models/characteraudit.py +101 -109
- ledger/models/corporationaudit.py +94 -49
- ledger/models/general.py +105 -211
- ledger/models/helpers/update_manager.py +302 -0
- ledger/models/planetary.py +60 -205
- ledger/providers.py +101 -0
- ledger/static/ledger/css/{ledger.css → aa-ledger.css} +54 -28
- ledger/static/ledger/js/aa-ledger.js +124 -0
- ledger/static/ledger/js/charts.js +25 -1
- ledger/static/ledger/js/view-alliance-ledger.js +383 -0
- ledger/static/ledger/js/view-character-ledger.js +388 -0
- ledger/static/ledger/js/view-corporation-ledger.js +402 -0
- ledger/static/ledger/js/view-planetary.js +492 -0
- ledger/static/ledger/libs/amCharts/5.14.4/js/flow.js +2 -0
- ledger/static/ledger/libs/amCharts/5.14.4/js/index.js +2 -0
- ledger/static/ledger/libs/amCharts/5.14.4/js/percent.js +2 -0
- ledger/static/ledger/libs/amCharts/5.14.4/js/themes/Animated.js +2 -0
- ledger/static/ledger/libs/amCharts/5.14.4/js/themes/Dark.js +2 -0
- ledger/static/ledger/libs/amCharts/5.14.4/js/xy.js +2 -0
- ledger/static/ledger/libs/datatables/2.3.5/css/dataTables.bootstrap5.css +610 -0
- ledger/static/ledger/libs/datatables/2.3.5/js/dataTables.bootstrap5.js +122 -0
- ledger/static/ledger/libs/datatables/2.3.5/js/dataTables.js +14127 -0
- ledger/static/ledger/libs/datatables/Extensions/ColumnControl/1.1.1/css/columnControl.bootstrap5.css +516 -0
- ledger/static/ledger/libs/datatables/Extensions/ColumnControl/1.1.1/css/columnControl.dataTables.css +529 -0
- ledger/static/ledger/libs/datatables/Extensions/ColumnControl/1.1.1/js/columnControl.bootstrap5.js +73 -0
- ledger/static/ledger/libs/datatables/Extensions/ColumnControl/1.1.1/js/dataTables.columnControl.js +3090 -0
- ledger/static/ledger/libs/datatables/Extensions/FixedHeader/4.0.4/css/fixedHeader.bootstrap5.css +20 -0
- ledger/static/ledger/libs/datatables/Extensions/FixedHeader/4.0.4/js/dataTables.fixedHeader.js +1203 -0
- ledger/static/ledger/libs/datatables/Extensions/FixedHeader/4.0.4/js/fixedHeader.bootstrap5.js +59 -0
- ledger/tasks.py +157 -141
- ledger/templates/ledger/base.html +59 -21
- ledger/templates/ledger/bundles/aa-ledger-css.html +3 -0
- ledger/templates/ledger/bundles/aa-ledger-js.html +3 -0
- ledger/templates/ledger/bundles/view-alliance-ledger-js.html +14 -0
- ledger/templates/ledger/bundles/view-character-ledger-js.html +15 -0
- ledger/templates/ledger/bundles/view-character-planetary-css.html +3 -0
- ledger/templates/ledger/bundles/view-character-planetary-js.html +4 -0
- ledger/templates/ledger/bundles/view-corporation-ledger-js.html +15 -0
- ledger/templates/ledger/partials/modal/confirm.html +0 -1
- ledger/templates/ledger/partials/modal/request-accept-delete-alliance.html +38 -0
- ledger/templates/ledger/partials/modal/request-accept-delete-character.html +38 -0
- ledger/templates/ledger/partials/modal/request-accept-delete-corporation.html +38 -0
- ledger/templates/ledger/partials/modal/request-accept-switch-notification.html +38 -0
- ledger/templates/ledger/partials/modal/request-view-alliance-details.html +26 -0
- ledger/templates/ledger/partials/modal/request-view-character-details.html +26 -0
- ledger/templates/ledger/partials/modal/request-view-corporation-details.html +26 -0
- ledger/templates/ledger/partials/modal/request-view-extractor.html +32 -0
- ledger/templates/ledger/partials/modal/request-view-factory.html +31 -0
- ledger/templates/ledger/partials/{menu → navigation}/administration.html +8 -0
- ledger/templates/ledger/partials/{menu → navigation}/navigation.html +2 -2
- ledger/templates/ledger/partials/{administration → view-alliance-administration}/alliance_corporations.html +3 -3
- ledger/templates/ledger/partials/view-alliance-administration/dashboard.html +81 -0
- ledger/templates/ledger/partials/view-alliance-ledger/alliance-billboard.html +25 -0
- ledger/templates/ledger/partials/view-alliance-ledger/alliance-ledger-details.html +21 -0
- ledger/templates/ledger/partials/view-alliance-ledger/alliance-table.html +24 -0
- ledger/templates/ledger/partials/view-alliance-ledger/information/daily.html +18 -0
- ledger/templates/ledger/partials/view-alliance-ledger/information/hourly.html +18 -0
- ledger/templates/ledger/partials/view-alliance-ledger/information/summary.html +19 -0
- ledger/templates/ledger/partials/{administration → view-character-administration}/character.html +1 -9
- ledger/templates/ledger/partials/{administration → view-character-administration}/dashboard.html +0 -34
- ledger/templates/ledger/partials/view-character-ledger/character-billboard.html +25 -0
- ledger/templates/ledger/partials/view-character-ledger/character-ledger-details.html +21 -0
- ledger/templates/ledger/partials/view-character-ledger/character-table.html +25 -0
- ledger/templates/ledger/partials/view-character-ledger/information/daily.html +18 -0
- ledger/templates/ledger/partials/view-character-ledger/information/hourly.html +18 -0
- ledger/templates/ledger/partials/view-character-ledger/information/summary.html +19 -0
- ledger/templates/ledger/partials/view-character-planetary/extractor-table.html +24 -0
- ledger/templates/ledger/partials/view-character-planetary/factory-table.html +24 -0
- ledger/templates/ledger/partials/view-character-planetary/planetary-table.html +22 -0
- ledger/templates/ledger/partials/view-character-planetary/storage-table.html +23 -0
- ledger/templates/ledger/partials/{administration → view-corporation-administration}/corporation.html +5 -13
- ledger/templates/ledger/partials/{administration → view-corporation-administration}/corporation_characters.html +1 -1
- ledger/templates/ledger/partials/view-corporation-administration/dashboard.html +81 -0
- ledger/templates/ledger/partials/view-corporation-ledger/corporation-billboard.html +25 -0
- ledger/templates/ledger/partials/view-corporation-ledger/corporation-ledger-details.html +21 -0
- ledger/templates/ledger/partials/view-corporation-ledger/corporation-table.html +26 -0
- ledger/templates/ledger/partials/view-corporation-ledger/information/daily.html +18 -0
- ledger/templates/ledger/partials/view-corporation-ledger/information/hourly.html +18 -0
- ledger/templates/ledger/partials/view-corporation-ledger/information/summary.html +19 -0
- ledger/templates/ledger/view-administration.html +62 -0
- ledger/templates/ledger/view-alliance-administration.html +49 -0
- ledger/templates/ledger/view-alliance-ledger.html +72 -0
- ledger/templates/ledger/view-alliance-overview.html +131 -0
- ledger/templates/ledger/view-character-administration.html +42 -0
- ledger/templates/ledger/view-character-ledger.html +73 -0
- ledger/templates/ledger/view-character-overview.html +135 -0
- ledger/templates/ledger/view-character-planetary-overview.html +135 -0
- ledger/templates/ledger/view-character-planetary.html +73 -0
- ledger/templates/ledger/view-corporation-administration.html +42 -0
- ledger/templates/ledger/view-corporation-ledger.html +73 -0
- ledger/templates/ledger/view-corporation-overview.html +131 -0
- ledger/templatetags/ledger.py +3 -5
- ledger/tests/__init__.py +187 -0
- ledger/tests/test_admin.py +164 -68
- ledger/tests/test_auth_hook.py +31 -13
- ledger/tests/test_decarators.py +14 -79
- ledger/tests/test_discord_installed.py +0 -1
- ledger/tests/test_helpers/test_ledger_data.py +19 -0
- ledger/tests/test_managers/test_character_audit_manager.py +111 -69
- ledger/tests/test_managers/test_character_journal_manager.py +48 -208
- ledger/tests/test_managers/test_character_mining_manager.py +37 -16
- ledger/tests/test_managers/test_corporation_division_manager.py +66 -28
- ledger/tests/test_managers/test_corporation_journal_manager.py +39 -42
- ledger/tests/test_managers/test_general_manager.py +78 -18
- ledger/tests/test_managers/test_planetary_manager.py +73 -32
- ledger/tests/test_models/test_characteraudit.py +58 -74
- ledger/tests/test_models/test_characterminingledger.py +20 -26
- ledger/tests/test_models/test_characterwalletjournal.py +10 -33
- ledger/tests/test_models/test_corporationaudit.py +41 -35
- ledger/tests/test_models/test_corporationwalletjournal.py +35 -32
- ledger/tests/test_models/test_general.py +44 -11
- ledger/tests/test_models/test_planetary.py +14 -80
- ledger/tests/test_templatetags.py +2 -7
- ledger/tests/test_views/corporation/test_add_corp.py +16 -35
- ledger/tests/test_views/corporation/test_delete_corporation.py +66 -42
- ledger/tests/test_views/test_access.py +512 -545
- ledger/tests/test_views/test_add_ally.py +57 -46
- ledger/tests/test_views/test_add_char.py +21 -33
- ledger/tests/test_views/test_delete_character.py +24 -21
- ledger/tests/testdata/README_ESI_STUB.md +430 -0
- ledger/tests/testdata/esi_stub_openapi.py +511 -0
- ledger/tests/testdata/integrations/__init__.py +0 -0
- ledger/tests/testdata/{load_eveuniverse.py → integrations/eveuniverse.py} +0 -1
- ledger/tests/testdata/integrations/planetary.py +13 -0
- ledger/tests/testdata/json/factory.json +281 -0
- ledger/tests/testdata/json/inactive.json +281 -0
- ledger/tests/testdata/json/pins.json +175 -272
- ledger/tests/testdata/json/route.json +95 -528
- ledger/tests/testdata/test_esi_stub.py +468 -0
- ledger/tests/testdata/utils.py +601 -0
- ledger/thirdparty/charlink_hook.py +60 -30
- ledger/urls.py +0 -135
- ledger/views/alliance/add_ally.py +2 -4
- ledger/views/alliance/alliance_ledger.py +64 -147
- ledger/views/character/add_char.py +8 -10
- ledger/views/character/character_ledger.py +60 -126
- ledger/views/character/planetary.py +5 -98
- ledger/views/corporation/add_corp.py +10 -12
- ledger/views/corporation/corporation_ledger.py +65 -327
- ledger/views/index.py +92 -30
- aa_ledger-1.0.4.dist-info/RECORD +0 -236
- ledger/api/api_helper/planetary_helper.py +0 -107
- ledger/api/ledger/__init__.py +0 -7
- ledger/api/ledger/planetary.py +0 -231
- ledger/helpers/alliance.py +0 -317
- ledger/helpers/character.py +0 -251
- ledger/helpers/core.py +0 -665
- ledger/helpers/corporation.py +0 -427
- ledger/helpers/data_exporter.py +0 -452
- ledger/static/ledger/js/planetary-confirm.js +0 -66
- ledger/static/ledger/js/planetary.js +0 -143
- ledger/templates/ledger/admin.html +0 -43
- ledger/templates/ledger/allyledger/admin/alliance_administration.html +0 -46
- ledger/templates/ledger/allyledger/admin/alliance_overview.html +0 -108
- ledger/templates/ledger/allyledger/alliance_ledger.html +0 -86
- ledger/templates/ledger/bundles/character-ledger-bundles.html +0 -66
- ledger/templates/ledger/bundles/corporation-ledger-bundles.html +0 -75
- ledger/templates/ledger/bundles/ledger-bundles.html +0 -23
- ledger/templates/ledger/bundles/ledger-css.html +0 -3
- ledger/templates/ledger/bundles/planetary-bundles.html +0 -50
- ledger/templates/ledger/bundles/table-css.html +0 -3
- ledger/templates/ledger/charledger/admin/character_administration.html +0 -39
- ledger/templates/ledger/charledger/admin/character_overview.html +0 -106
- ledger/templates/ledger/charledger/character_ledger.html +0 -94
- ledger/templates/ledger/charledger/planetary/admin/planetary_overview.html +0 -123
- ledger/templates/ledger/charledger/planetary/planetary_ledger.html +0 -54
- ledger/templates/ledger/corpledger/admin/corporation_administration.html +0 -39
- ledger/templates/ledger/corpledger/admin/corporation_overview.html +0 -108
- ledger/templates/ledger/corpledger/corporation_ledger.html +0 -129
- ledger/templates/ledger/data-export.html +0 -78
- ledger/templates/ledger/error.html +0 -31
- ledger/templates/ledger/partials/form/error-message.html +0 -1
- ledger/templates/ledger/partials/information/daily.html +0 -56
- ledger/templates/ledger/partials/information/day.html +0 -48
- ledger/templates/ledger/partials/information/error.html +0 -8
- ledger/templates/ledger/partials/information/hourly.html +0 -53
- ledger/templates/ledger/partials/information/summary.html +0 -88
- ledger/templates/ledger/partials/information/view_character_content.html +0 -35
- ledger/templates/ledger/partials/modal/switchalarm_confirm.html +0 -39
- ledger/templates/ledger/partials/modal/view_extractor.html +0 -48
- ledger/templates/ledger/partials/modal/view_factory.html +0 -123
- ledger/templates/ledger/partials/table/char-ledger.html +0 -85
- ledger/templates/ledger/partials/table/corp-ledger.html +0 -66
- ledger/templates/ledger/partials/table/planetary.html +0 -18
- ledger/templates/ledger/partials/thirdparty/billboard.html +0 -22
- ledger/templates/ledger/partials/view/card.html +0 -160
- ledger/templates/ledger/permission.html +0 -2
- ledger/tests/test_helpers/test_billboard.py +0 -11
- ledger/tests/test_helpers/test_data_exporter.py +0 -207
- ledger/tests/test_tasks.py +0 -282
- ledger/tests/test_view_helpers/test_core.py +0 -47
- ledger/tests/test_views/corporation/test_corporation.py +0 -267
- ledger/tests/test_views/test_planetary.py +0 -137
- ledger/tests/testdata/esi_stub.py +0 -109
- ledger/tests/testdata/esi_stub_migration.py +0 -80
- ledger/tests/testdata/generate_characteraudit.py +0 -106
- ledger/tests/testdata/generate_corporationaudit.py +0 -74
- ledger/tests/testdata/generate_events.py +0 -31
- ledger/tests/testdata/generate_miningledger.py +0 -13
- ledger/tests/testdata/generate_planets.py +0 -48
- ledger/tests/testdata/generate_walletjournal.py +0 -42
- ledger/tests/testdata/json/czarno-pins.json +0 -240
- ledger/tests/testdata/json/czarno-routes.json +0 -165
- ledger/tests/testdata/json/pins2.json +0 -538
- {aa_ledger-1.0.4.dist-info → aa_ledger-2.0.0.dist-info}/WHEEL +0 -0
- {aa_ledger-1.0.4.dist-info → aa_ledger-2.0.0.dist-info}/licenses/LICENSE +0 -0
- /ledger/{tests/test_view_helpers → api/helpers}/__init__.py +0 -0
- /ledger/templates/ledger/bundles/{ally-administration-bundles.html → view-alliance-administration-js.html} +0 -0
- /ledger/templates/ledger/bundles/{char-administration-bundles.html → view-character-administration-js.html} +0 -0
- /ledger/templates/ledger/bundles/{corp-administration-bundles.html → view-corporation-administration-js.html} +0 -0
- /ledger/templates/ledger/partials/{administration → view-alliance-administration}/alliance.html +0 -0
- /ledger/tests/testdata/{esi.json → esi_test_data.json} +0 -0
- /ledger/tests/testdata/{allianceauth.json → integrations/allianceauth.json} +0 -0
- /ledger/tests/testdata/{load_allianceauth.py → integrations/allianceauth.py} +0 -0
- /ledger/tests/testdata/{eveentity.json → integrations/eveentity.json} +0 -0
- /ledger/tests/testdata/{load_eveentity.py → integrations/eveentity.py} +0 -0
- /ledger/tests/testdata/{eveuniverse.json → integrations/eveuniverse.json} +0 -0
- /ledger/tests/testdata/{planetary.json → integrations/planetary.json} +0 -0
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ESI OpenAPI Client Stub for Testing
|
|
3
|
+
|
|
4
|
+
This module provides a stub implementation for ESI OpenAPI clients that can be used
|
|
5
|
+
in tests to return predefined test data without making actual API calls.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Standard Library
|
|
9
|
+
import json
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Any
|
|
13
|
+
|
|
14
|
+
# Third Party
|
|
15
|
+
from pydantic import BaseModel, create_model
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _to_pydantic_model_instance(name: str, data: Any) -> Any:
|
|
19
|
+
"""
|
|
20
|
+
Recursively convert dicts/lists to Pydantic model instances.
|
|
21
|
+
"""
|
|
22
|
+
# Lists -> convert each item
|
|
23
|
+
if isinstance(data, list):
|
|
24
|
+
return [_to_pydantic_model_instance(name + "Item", v) for v in data]
|
|
25
|
+
|
|
26
|
+
# Helper: try to parse ISO datetime strings into datetime objects
|
|
27
|
+
def _try_parse_datetime(value: Any) -> Any:
|
|
28
|
+
if not isinstance(value, str):
|
|
29
|
+
return value
|
|
30
|
+
try:
|
|
31
|
+
v = value
|
|
32
|
+
# Accept trailing Z as UTC
|
|
33
|
+
if v.endswith("Z"):
|
|
34
|
+
v = v[:-1] + "+00:00"
|
|
35
|
+
return datetime.fromisoformat(v)
|
|
36
|
+
except Exception:
|
|
37
|
+
return value
|
|
38
|
+
|
|
39
|
+
# Dicts -> create a transient pydantic model class and instantiate it
|
|
40
|
+
if isinstance(data, dict):
|
|
41
|
+
fields: dict[str, tuple[type, Any]] = {}
|
|
42
|
+
values: dict[str, Any] = {}
|
|
43
|
+
for k, v in data.items():
|
|
44
|
+
# Use Any for field type; instantiate nested models recursively
|
|
45
|
+
fields[k] = (Any, ...)
|
|
46
|
+
# Recursively convert nested structures
|
|
47
|
+
val = _to_pydantic_model_instance(name + k.capitalize(), v)
|
|
48
|
+
# Try to convert ISO datetime-like strings to datetime objects
|
|
49
|
+
if isinstance(val, str):
|
|
50
|
+
val = _try_parse_datetime(val)
|
|
51
|
+
values[k] = val
|
|
52
|
+
|
|
53
|
+
Model = create_model(name, **fields, __base__=BaseModel)
|
|
54
|
+
return Model(**values)
|
|
55
|
+
|
|
56
|
+
# Primitives -> return as-is
|
|
57
|
+
return data
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _select_method_data(method_data: Any, kwargs: dict, endpoint=None):
|
|
61
|
+
"""Select appropriate test data entry from a mapping using endpoint param names.
|
|
62
|
+
|
|
63
|
+
- If method_data is a dict and endpoint.param_names are provided, try to match
|
|
64
|
+
kwargs values (as str or int) to the dict keys. If found, return that value.
|
|
65
|
+
- If only a single entry exists in the dict, return its value as a sensible default.
|
|
66
|
+
- Otherwise return method_data unchanged.
|
|
67
|
+
"""
|
|
68
|
+
if not endpoint or not isinstance(method_data, dict):
|
|
69
|
+
return method_data
|
|
70
|
+
|
|
71
|
+
# Drill down through param names in order, attempting to match kwargs
|
|
72
|
+
current = method_data
|
|
73
|
+
matched_any = False
|
|
74
|
+
for param_name in endpoint.param_names:
|
|
75
|
+
if not isinstance(current, dict):
|
|
76
|
+
break
|
|
77
|
+
if param_name in kwargs and kwargs[param_name] is not None:
|
|
78
|
+
key = str(kwargs[param_name])
|
|
79
|
+
# direct string key
|
|
80
|
+
if key in current:
|
|
81
|
+
current = current[key]
|
|
82
|
+
matched_any = True
|
|
83
|
+
continue
|
|
84
|
+
# try integer key match
|
|
85
|
+
try:
|
|
86
|
+
int_key = int(kwargs[param_name])
|
|
87
|
+
except Exception:
|
|
88
|
+
int_key = None
|
|
89
|
+
if int_key is not None and int_key in current:
|
|
90
|
+
current = current[int_key]
|
|
91
|
+
matched_any = True
|
|
92
|
+
continue
|
|
93
|
+
# if we couldn't match this param, stop drilling
|
|
94
|
+
break
|
|
95
|
+
|
|
96
|
+
if matched_any:
|
|
97
|
+
return current
|
|
98
|
+
|
|
99
|
+
# Fallback for POST-style methods where the body contains ids (common key 'body')
|
|
100
|
+
if (
|
|
101
|
+
"body" in kwargs
|
|
102
|
+
and kwargs["body"] is not None
|
|
103
|
+
and isinstance(kwargs["body"], (list, tuple))
|
|
104
|
+
):
|
|
105
|
+
key = str(kwargs["body"])
|
|
106
|
+
if key in method_data:
|
|
107
|
+
return method_data[key]
|
|
108
|
+
|
|
109
|
+
# Fallback: if single-entry dict, return its value
|
|
110
|
+
if isinstance(method_data, dict) and len(method_data) == 1:
|
|
111
|
+
return list(method_data.values())[0]
|
|
112
|
+
|
|
113
|
+
return method_data
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class MockResponse:
|
|
117
|
+
"""
|
|
118
|
+
Mock HTTP response object for testing.
|
|
119
|
+
|
|
120
|
+
Mimics the response object returned by ESI when return_response=True.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
def __init__(self, status_code: int = 200, headers: dict | None = None):
|
|
124
|
+
"""
|
|
125
|
+
Initialize mock response.
|
|
126
|
+
|
|
127
|
+
Attributes:
|
|
128
|
+
status_code (int): HTTP status code
|
|
129
|
+
headers (dict | None): Response headers
|
|
130
|
+
"""
|
|
131
|
+
self.status_code = status_code
|
|
132
|
+
self.headers = headers or {"X-Pages": 1}
|
|
133
|
+
self.text = ""
|
|
134
|
+
self.content = b""
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class EsiEndpoint:
|
|
138
|
+
"""
|
|
139
|
+
Definition of an ESI endpoint for stub configuration.
|
|
140
|
+
|
|
141
|
+
Defines which endpoints should be stubbed and with what side effects.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
def __init__(
|
|
145
|
+
self,
|
|
146
|
+
category: str,
|
|
147
|
+
method: str,
|
|
148
|
+
param_names: str | tuple[str, ...],
|
|
149
|
+
side_effect: Exception | None = None,
|
|
150
|
+
):
|
|
151
|
+
"""
|
|
152
|
+
Initialize an ESI endpoint definition.
|
|
153
|
+
|
|
154
|
+
Attributes:
|
|
155
|
+
category (str): ESI category name (e.g., "Character", "Skills")
|
|
156
|
+
method (str): ESI method name (e.g., "GetCharactersCharacterIdSkills")
|
|
157
|
+
param_names (str | tuple[str, ...]): Parameter name(s) used to look up test data
|
|
158
|
+
side_effect (Exception | None): Optional exception to raise when this endpoint is called
|
|
159
|
+
"""
|
|
160
|
+
self.category = category
|
|
161
|
+
self.method = method
|
|
162
|
+
# Allow tests to pass multiple param names as two positional strings
|
|
163
|
+
if isinstance(param_names, tuple):
|
|
164
|
+
params = list(param_names)
|
|
165
|
+
else:
|
|
166
|
+
params = [param_names]
|
|
167
|
+
|
|
168
|
+
# If side_effect is a string, tests likely passed a second param name
|
|
169
|
+
if isinstance(side_effect, str):
|
|
170
|
+
params.append(side_effect)
|
|
171
|
+
self.side_effect = None
|
|
172
|
+
else:
|
|
173
|
+
self.side_effect = side_effect
|
|
174
|
+
|
|
175
|
+
self.param_names = tuple(params)
|
|
176
|
+
|
|
177
|
+
def __repr__(self):
|
|
178
|
+
return f"EsiEndpoint({self.category}.{self.method})"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class EsiOperationStub:
|
|
182
|
+
"""
|
|
183
|
+
Stub for ESI operation that mimics the behavior of openapi_clients operations.
|
|
184
|
+
|
|
185
|
+
This class simulates the result() and results() methods that are called on
|
|
186
|
+
ESI operations in the actual implementation.
|
|
187
|
+
|
|
188
|
+
If a side_effect is configured, calling result() or results() will raise that exception
|
|
189
|
+
instead of returning test data.
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
def __init__(self, test_data: Any, side_effect: Exception | None = None):
|
|
193
|
+
"""
|
|
194
|
+
Initialize the operation stub with test data or side effect.
|
|
195
|
+
|
|
196
|
+
Attributes:
|
|
197
|
+
test_data (Any): The data to return when result() or results() is called
|
|
198
|
+
side_effect (Exception | None): Exception to raise when result() or results() is called
|
|
199
|
+
"""
|
|
200
|
+
self._test_data = test_data
|
|
201
|
+
self._side_effect = side_effect
|
|
202
|
+
|
|
203
|
+
def result(
|
|
204
|
+
self,
|
|
205
|
+
use_etag: bool = True, # not implemented yet
|
|
206
|
+
return_response: bool = False,
|
|
207
|
+
force_refresh: bool = False, # not implemented yet
|
|
208
|
+
use_cache: bool = True, # not implemented yet
|
|
209
|
+
**kwargs,
|
|
210
|
+
) -> Any:
|
|
211
|
+
"""
|
|
212
|
+
Simulate the result() method of an ESI operation.
|
|
213
|
+
|
|
214
|
+
Returns a single result (not a list) as an object with attributes.
|
|
215
|
+
When return_response=True, returns tuple of (data, response).
|
|
216
|
+
|
|
217
|
+
If a side_effect was configured, raises that exception instead.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
use_etag (bool) Whether to use ETag (ignored in stub)
|
|
221
|
+
return_response (bool): Whether to return response object
|
|
222
|
+
force_refresh (bool): Whether to force refresh (ignored in stub)
|
|
223
|
+
use_cache (bool): Whether to use cache (ignored in stub)
|
|
224
|
+
Returns:
|
|
225
|
+
Any: Test data as object, or tuple of (data, response) if return_response=True
|
|
226
|
+
Raises:
|
|
227
|
+
Exception: if side_effect was configured
|
|
228
|
+
"""
|
|
229
|
+
# If side_effect is configured, raise it
|
|
230
|
+
if self._side_effect is not None:
|
|
231
|
+
# Support both exception instances and lists of exceptions/values
|
|
232
|
+
if isinstance(self._side_effect, list):
|
|
233
|
+
# Pop from list for sequential side effects
|
|
234
|
+
if self._side_effect:
|
|
235
|
+
effect = self._side_effect.pop(0)
|
|
236
|
+
if isinstance(effect, Exception):
|
|
237
|
+
raise effect
|
|
238
|
+
# If not an exception, return it as data
|
|
239
|
+
return (
|
|
240
|
+
_to_pydantic_model_instance("SideEffect", effect)
|
|
241
|
+
if not return_response
|
|
242
|
+
else (
|
|
243
|
+
_to_pydantic_model_instance("SideEffect", effect),
|
|
244
|
+
MockResponse(),
|
|
245
|
+
)
|
|
246
|
+
)
|
|
247
|
+
elif isinstance(self._side_effect, Exception):
|
|
248
|
+
raise self._side_effect
|
|
249
|
+
|
|
250
|
+
# Convert dict to Pydantic model instance to mimic OpenAPI 3 behavior
|
|
251
|
+
data = _to_pydantic_model_instance("Result", self._test_data)
|
|
252
|
+
|
|
253
|
+
if return_response:
|
|
254
|
+
# Return tuple of (data, response)
|
|
255
|
+
response = MockResponse()
|
|
256
|
+
return (data, response)
|
|
257
|
+
|
|
258
|
+
return data
|
|
259
|
+
|
|
260
|
+
def results(
|
|
261
|
+
self,
|
|
262
|
+
use_etag: bool = True, # not implemented yet
|
|
263
|
+
return_response: bool = False,
|
|
264
|
+
force_refresh: bool = False, # not implemented yet
|
|
265
|
+
use_cache: bool = True, # not implemented yet
|
|
266
|
+
**kwargs,
|
|
267
|
+
) -> list[Any]:
|
|
268
|
+
"""
|
|
269
|
+
Simulate the results() method of an ESI operation.
|
|
270
|
+
|
|
271
|
+
Returns a list of results (paginated data) as objects with attributes.
|
|
272
|
+
When return_response=True, returns tuple of (data, response).
|
|
273
|
+
|
|
274
|
+
If a side_effect was configured, raises that exception instead.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
use_etag (bool): Whether to use ETag (ignored in stub)
|
|
278
|
+
return_response (bool): Whether to return response object
|
|
279
|
+
force_refresh (bool): Whether to force refresh (ignored in stub)
|
|
280
|
+
use_cache (bool): Whether to use cache (ignored in stub)
|
|
281
|
+
Returns:
|
|
282
|
+
list[Any]: Test data as list of objects, or tuple of (data, response) if return_response=True
|
|
283
|
+
Raises:
|
|
284
|
+
Exception: if side_effect was configured
|
|
285
|
+
"""
|
|
286
|
+
# If side_effect is configured, raise it
|
|
287
|
+
if self._side_effect is not None:
|
|
288
|
+
# Support both exception instances and lists of exceptions/values
|
|
289
|
+
if isinstance(self._side_effect, list):
|
|
290
|
+
# Pop from list for sequential side effects
|
|
291
|
+
if self._side_effect:
|
|
292
|
+
effect = self._side_effect.pop(0)
|
|
293
|
+
if isinstance(effect, Exception):
|
|
294
|
+
raise effect
|
|
295
|
+
# If not an exception, return it as data
|
|
296
|
+
data = _to_pydantic_model_instance("SideEffect", effect)
|
|
297
|
+
result_data = data if isinstance(data, list) else [data]
|
|
298
|
+
return (
|
|
299
|
+
(result_data, MockResponse())
|
|
300
|
+
if return_response
|
|
301
|
+
else result_data
|
|
302
|
+
)
|
|
303
|
+
elif isinstance(self._side_effect, Exception):
|
|
304
|
+
raise self._side_effect
|
|
305
|
+
|
|
306
|
+
# Convert to Pydantic model instances first
|
|
307
|
+
data = _to_pydantic_model_instance("Results", self._test_data)
|
|
308
|
+
|
|
309
|
+
# If test data is already a list, use it as is
|
|
310
|
+
if isinstance(data, list):
|
|
311
|
+
result_data = data
|
|
312
|
+
else:
|
|
313
|
+
# If single item, wrap in list
|
|
314
|
+
result_data = [data]
|
|
315
|
+
|
|
316
|
+
if return_response:
|
|
317
|
+
# Return tuple of (data, response)
|
|
318
|
+
response = MockResponse()
|
|
319
|
+
return (result_data, response)
|
|
320
|
+
|
|
321
|
+
return result_data
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
class EsiCategoryStub:
|
|
325
|
+
"""
|
|
326
|
+
Stub for an ESI category (e.g., Skills, Character, Wallet).
|
|
327
|
+
|
|
328
|
+
This class holds methods for a specific ESI category and returns
|
|
329
|
+
EsiOperationStub instances when methods are called.
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
def __init__(
|
|
333
|
+
self,
|
|
334
|
+
category_name: str,
|
|
335
|
+
test_data: dict[str, Any],
|
|
336
|
+
endpoints: dict[str, EsiEndpoint],
|
|
337
|
+
):
|
|
338
|
+
"""
|
|
339
|
+
Initialize the category stub.
|
|
340
|
+
|
|
341
|
+
Attributes:
|
|
342
|
+
category_name (str): Name of the ESI category
|
|
343
|
+
test_data (dict[str, Any]): Test data for methods in this category
|
|
344
|
+
endpoints (dict[str, EsiEndpoint]): Endpoint definitions for this category
|
|
345
|
+
"""
|
|
346
|
+
self._category_name = category_name
|
|
347
|
+
self._test_data = test_data
|
|
348
|
+
self._endpoints = endpoints
|
|
349
|
+
|
|
350
|
+
def __getattr__(self, method_name: str) -> callable:
|
|
351
|
+
"""
|
|
352
|
+
Return a callable that creates an EsiOperationStub when invoked.
|
|
353
|
+
|
|
354
|
+
Args:
|
|
355
|
+
method_name (str): Name of the ESI method
|
|
356
|
+
Returns:
|
|
357
|
+
callable: Callable that returns EsiOperationStub
|
|
358
|
+
Raises:
|
|
359
|
+
AttributeError: If method is not registered in endpoints
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
def operation_caller(**kwargs) -> EsiOperationStub:
|
|
363
|
+
"""
|
|
364
|
+
Create and return an operation stub with test data and optional side effect.
|
|
365
|
+
|
|
366
|
+
:return: Operation stub with test data
|
|
367
|
+
:rtype: EsiOperationStub
|
|
368
|
+
:raises AttributeError: If endpoints were provided and this method is not registered
|
|
369
|
+
"""
|
|
370
|
+
# Check if endpoint is registered
|
|
371
|
+
endpoint = self._endpoints.get(method_name)
|
|
372
|
+
|
|
373
|
+
# Only registered methods are allowed
|
|
374
|
+
if endpoint is None:
|
|
375
|
+
raise AttributeError(
|
|
376
|
+
f"Method '{self._category_name}.{method_name}' is not registered. "
|
|
377
|
+
f"Available methods: {list(self._endpoints.keys())}"
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# Look up test data for this method
|
|
381
|
+
method_data = self._test_data.get(method_name, {})
|
|
382
|
+
|
|
383
|
+
# print(f"DEBUG: EsiCategoryStub: method_data for {self._category_name}.{method_name}: {method_data}")
|
|
384
|
+
data = _select_method_data(method_data, kwargs, endpoint)
|
|
385
|
+
# print(f"DEBUG: EsiCategoryStub: selected data for {self._category_name}.{method_name}: {data}")
|
|
386
|
+
|
|
387
|
+
# Get side effect from endpoint if defined
|
|
388
|
+
side_effect = endpoint.side_effect if endpoint else None
|
|
389
|
+
|
|
390
|
+
return EsiOperationStub(test_data=data, side_effect=side_effect)
|
|
391
|
+
|
|
392
|
+
return operation_caller
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
class EsiClientStub:
|
|
396
|
+
"""
|
|
397
|
+
Stub for ESI OpenAPI client that mimics ESIClientProvider.client.
|
|
398
|
+
|
|
399
|
+
This class provides access to ESI categories and their methods,
|
|
400
|
+
returning test data instead of making real API calls.
|
|
401
|
+
"""
|
|
402
|
+
|
|
403
|
+
def __init__(
|
|
404
|
+
self,
|
|
405
|
+
test_data_config: dict[str, dict[str, Any]],
|
|
406
|
+
endpoints: list[EsiEndpoint],
|
|
407
|
+
):
|
|
408
|
+
"""
|
|
409
|
+
Initialize the ESI client stub.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
test_data_config (dict[str, dict[str, Any]]): Test data configuration
|
|
413
|
+
Format: {"CategoryName": {"MethodName": test_data}}
|
|
414
|
+
endpoints (list[EsiEndpoint]): List of endpoint definitions (REQUIRED)
|
|
415
|
+
Raises:
|
|
416
|
+
ValueError: If endpoints is None or empty
|
|
417
|
+
"""
|
|
418
|
+
if not endpoints:
|
|
419
|
+
raise ValueError(
|
|
420
|
+
"endpoints parameter is required and cannot be empty. "
|
|
421
|
+
"You must provide a list of EsiEndpoint definitions."
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
self._test_data_config = test_data_config
|
|
425
|
+
self._categories = {}
|
|
426
|
+
self._endpoints_by_category = {}
|
|
427
|
+
|
|
428
|
+
# Build endpoint lookup by category and method
|
|
429
|
+
for endpoint in endpoints:
|
|
430
|
+
if endpoint.category not in self._endpoints_by_category:
|
|
431
|
+
self._endpoints_by_category[endpoint.category] = {}
|
|
432
|
+
self._endpoints_by_category[endpoint.category][endpoint.method] = endpoint
|
|
433
|
+
|
|
434
|
+
# Create category stubs only for categories that have registered endpoints
|
|
435
|
+
for category_name in self._endpoints_by_category.keys():
|
|
436
|
+
methods_data = test_data_config.get(category_name, {})
|
|
437
|
+
category_endpoints = self._endpoints_by_category[category_name]
|
|
438
|
+
self._categories[category_name] = EsiCategoryStub(
|
|
439
|
+
category_name=category_name,
|
|
440
|
+
test_data=methods_data,
|
|
441
|
+
endpoints=category_endpoints,
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
def __getattr__(self, category_name: str) -> EsiCategoryStub:
|
|
445
|
+
"""
|
|
446
|
+
Return the category stub for the requested ESI category.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
category_name (str): Name of the ESI category
|
|
450
|
+
Returns:
|
|
451
|
+
EsiCategoryStub: The category stub
|
|
452
|
+
Raises:
|
|
453
|
+
AttributeError: If category is not registered in endpoints
|
|
454
|
+
"""
|
|
455
|
+
if category_name in self._categories:
|
|
456
|
+
return self._categories[category_name]
|
|
457
|
+
|
|
458
|
+
# Only registered categories are allowed
|
|
459
|
+
raise AttributeError(
|
|
460
|
+
f"Category '{category_name}' is not registered. "
|
|
461
|
+
f"Available categories: {list(self._categories.keys())}"
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def load_test_data_from_json(file_name: str = "esi_test_data.json") -> dict:
|
|
466
|
+
"""
|
|
467
|
+
Load test data from a JSON file in the testdata directory.
|
|
468
|
+
|
|
469
|
+
Args:
|
|
470
|
+
file_name (str): Name of the JSON file (default: "esi_test_data.json")
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
dict: Loaded test data
|
|
474
|
+
"""
|
|
475
|
+
file_path = Path(__file__).parent / file_name
|
|
476
|
+
|
|
477
|
+
if not file_path.exists():
|
|
478
|
+
return {}
|
|
479
|
+
|
|
480
|
+
with file_path.open("r", encoding="utf-8") as fp:
|
|
481
|
+
return json.load(fp)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def create_esi_client_stub(
|
|
485
|
+
test_data_config: dict[str, dict[str, Any]] | None = None,
|
|
486
|
+
endpoints: list[EsiEndpoint] | None = None,
|
|
487
|
+
) -> EsiClientStub:
|
|
488
|
+
"""
|
|
489
|
+
Create an ESI client stub with the provided test data configuration.
|
|
490
|
+
This function can be used in tests to provide a stub ESI client.
|
|
491
|
+
|
|
492
|
+
Args:
|
|
493
|
+
test_data_config (dict[str, dict[str, Any]] | None): Test data configuration, if None loads from JSON file
|
|
494
|
+
endpoints (list[EsiEndpoint] | None): List of endpoint definitions (REQUIRED)
|
|
495
|
+
|
|
496
|
+
Returns:
|
|
497
|
+
EsiClientStub: ESI client stub
|
|
498
|
+
|
|
499
|
+
Raises:
|
|
500
|
+
ValueError: If endpoints is None or empty
|
|
501
|
+
"""
|
|
502
|
+
if test_data_config is None:
|
|
503
|
+
test_data_config = load_test_data_from_json()
|
|
504
|
+
|
|
505
|
+
if not endpoints:
|
|
506
|
+
raise ValueError(
|
|
507
|
+
"endpoints parameter is required. "
|
|
508
|
+
"You must provide a list of EsiEndpoint definitions."
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
return EsiClientStub(test_data_config=test_data_config, endpoints=endpoints)
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Generate AllianceAuth test objects from allianceauth.json."""
|
|
2
|
+
|
|
3
|
+
# Standard Library
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _load_planetary_data():
|
|
9
|
+
with open(Path(__file__).parent / "planetary.json", encoding="utf-8") as fp:
|
|
10
|
+
return json.load(fp)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
_planetary_data = _load_planetary_data()
|