aa-intel-tool 2.10.0__py3-none-any.whl → 2.11.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_intel_tool/__init__.py +3 -2
- aa_intel_tool/apps.py +5 -4
- aa_intel_tool/auth_hooks.py +2 -2
- aa_intel_tool/constants.py +9 -3
- aa_intel_tool/exceptions.py +1 -1
- aa_intel_tool/locale/cs_CZ/LC_MESSAGES/django.po +13 -17
- aa_intel_tool/locale/de/LC_MESSAGES/django.mo +0 -0
- aa_intel_tool/locale/de/LC_MESSAGES/django.po +21 -21
- aa_intel_tool/locale/django.pot +14 -18
- aa_intel_tool/locale/es/LC_MESSAGES/django.po +17 -17
- aa_intel_tool/locale/fr_FR/LC_MESSAGES/django.po +19 -17
- aa_intel_tool/locale/it_IT/LC_MESSAGES/django.po +13 -17
- aa_intel_tool/locale/ja/LC_MESSAGES/django.mo +0 -0
- aa_intel_tool/locale/ja/LC_MESSAGES/django.po +48 -48
- aa_intel_tool/locale/ko_KR/LC_MESSAGES/django.po +19 -17
- aa_intel_tool/locale/nl_NL/LC_MESSAGES/django.po +13 -17
- aa_intel_tool/locale/pl_PL/LC_MESSAGES/django.po +13 -17
- aa_intel_tool/locale/ru/LC_MESSAGES/django.po +19 -17
- aa_intel_tool/locale/sk/LC_MESSAGES/django.po +13 -17
- aa_intel_tool/locale/uk/LC_MESSAGES/django.mo +0 -0
- aa_intel_tool/locale/uk/LC_MESSAGES/django.po +23 -23
- aa_intel_tool/locale/zh_Hans/LC_MESSAGES/django.po +13 -17
- aa_intel_tool/parser/module/chatlist.py +3 -3
- aa_intel_tool/parser/module/dscan.py +4 -5
- aa_intel_tool/static/aa_intel_tool/css/aa-intel-tool.css +13 -16
- aa_intel_tool/static/aa_intel_tool/css/aa-intel-tool.min.css +1 -1
- aa_intel_tool/static/aa_intel_tool/css/aa-intel-tool.min.css.map +1 -1
- aa_intel_tool/static/aa_intel_tool/javascript/aa-intel-tool-chatscan.js +171 -282
- aa_intel_tool/static/aa_intel_tool/javascript/aa-intel-tool-chatscan.min.js +1 -1
- aa_intel_tool/static/aa_intel_tool/javascript/aa-intel-tool-chatscan.min.js.map +1 -1
- aa_intel_tool/static/aa_intel_tool/javascript/aa-intel-tool-dscan.js +251 -572
- aa_intel_tool/static/aa_intel_tool/javascript/aa-intel-tool-dscan.min.js +1 -1
- aa_intel_tool/static/aa_intel_tool/javascript/aa-intel-tool-dscan.min.js.map +1 -1
- aa_intel_tool/static/aa_intel_tool/javascript/aa-intel-tool-fleetcomposition.js +163 -237
- aa_intel_tool/static/aa_intel_tool/javascript/aa-intel-tool-fleetcomposition.min.js +1 -1
- aa_intel_tool/static/aa_intel_tool/javascript/aa-intel-tool-fleetcomposition.min.js.map +1 -1
- aa_intel_tool/static/aa_intel_tool/javascript/aa-intel-tool-scan-result-common.js +18 -0
- aa_intel_tool/static/aa_intel_tool/javascript/aa-intel-tool-scan-result-common.min.js +1 -1
- aa_intel_tool/static/aa_intel_tool/javascript/aa-intel-tool-scan-result-common.min.js.map +1 -1
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/2.3.4/dataTables.bootstrap5.css +610 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/2.3.4/dataTables.bootstrap5.js +123 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/2.3.4/dataTables.bootstrap5.min.css +8 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/2.3.4/dataTables.bootstrap5.min.css.map +1 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/2.3.4/dataTables.bootstrap5.min.js +6 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/2.3.4/dataTables.bootstrap5.min.js.map +1 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/2.3.4/datatables.min.js +10 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/2.3.4/datatables.min.js.map +1 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/ColumnControl/1.1.1/css/columnControl.bootstrap5.css +516 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/ColumnControl/1.1.1/css/columnControl.bootstrap5.min.css +2 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/ColumnControl/1.1.1/css/columnControl.bootstrap5.min.css.map +1 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/ColumnControl/1.1.1/js/columnControl.bootstrap5.js +73 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/ColumnControl/1.1.1/js/columnControl.bootstrap5.min.js +6 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/ColumnControl/1.1.1/js/columnControl.bootstrap5.min.js.map +1 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/ColumnControl/1.1.1/js/dataTables.columnControl.js +3091 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/ColumnControl/1.1.1/js/dataTables.columnControl.min.js +10 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/ColumnControl/1.1.1/js/dataTables.columnControl.min.js.map +1 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/FixedHeader/4.0.4/css/fixedHeader.bootstrap5.css +20 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/FixedHeader/4.0.4/css/fixedHeader.bootstrap5.min.css +2 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/FixedHeader/4.0.4/css/fixedHeader.bootstrap5.min.css.map +1 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/FixedHeader/4.0.4/js/dataTables.fixedHeader.js +1203 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/FixedHeader/4.0.4/js/dataTables.fixedHeader.min.js +6 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/FixedHeader/4.0.4/js/dataTables.fixedHeader.min.js.map +1 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/FixedHeader/4.0.4/js/fixedHeader.bootstrap5.js +59 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/FixedHeader/4.0.4/js/fixedHeader.bootstrap5.min.js +6 -0
- aa_intel_tool/static/aa_intel_tool/libs/DataTables/Extensions/FixedHeader/4.0.4/js/fixedHeader.bootstrap5.min.js.map +1 -0
- aa_intel_tool/templates/aa_intel_tool/base.html +30 -0
- aa_intel_tool/templates/aa_intel_tool/bundles/datatables-2-css.html +11 -0
- aa_intel_tool/templates/aa_intel_tool/bundles/datatables-2-js.html +14 -0
- aa_intel_tool/templates/aa_intel_tool/partials/scan/chatlist/alliances.html +1 -1
- aa_intel_tool/templates/aa_intel_tool/partials/scan/chatlist/corporations.html +1 -1
- aa_intel_tool/templates/aa_intel_tool/partials/scan/chatlist/pilots.html +1 -1
- aa_intel_tool/templates/aa_intel_tool/partials/scan/dscan/interesting-on-grid/items.html +1 -1
- aa_intel_tool/templates/aa_intel_tool/partials/scan/dscan/ships-breakdown/ship-classes.html +1 -1
- aa_intel_tool/templates/aa_intel_tool/partials/scan/dscan/ships-breakdown/ship-types.html +1 -1
- aa_intel_tool/templates/aa_intel_tool/partials/scan/fleetcomp/fleet-details/pilots.html +1 -1
- aa_intel_tool/templates/aa_intel_tool/views/scan/chatlist.html +2 -2
- aa_intel_tool/templates/aa_intel_tool/views/scan/dscan.html +2 -2
- aa_intel_tool/templates/aa_intel_tool/views/scan/fleetcomp.html +2 -2
- aa_intel_tool/tests/__init__.py +38 -0
- aa_intel_tool/tests/test_access.py +2 -2
- aa_intel_tool/tests/test_admin.py +4 -3
- aa_intel_tool/tests/test_app_settings.py +3 -2
- aa_intel_tool/tests/test_auth_hooks.py +2 -2
- aa_intel_tool/tests/test_helper_data_structures.py +2 -4
- aa_intel_tool/tests/test_helper_eve_character.py +5 -7
- aa_intel_tool/tests/test_models.py +3 -3
- aa_intel_tool/tests/test_parser_general.py +48 -34
- aa_intel_tool/tests/test_parser_helper_db.py +2 -4
- aa_intel_tool/tests/test_parser_module_chatlist.py +2 -2
- aa_intel_tool/tests/test_parser_module_dscan.py +828 -0
- aa_intel_tool/tests/test_parser_module_feetcomp.py +230 -0
- aa_intel_tool/tests/test_tasks.py +56 -0
- aa_intel_tool/tests/test_views_ajax.py +72 -0
- aa_intel_tool/tests/test_views_general.py +240 -0
- aa_intel_tool/urls.py +1 -1
- aa_intel_tool/views/general.py +13 -5
- {aa_intel_tool-2.10.0.dist-info → aa_intel_tool-2.11.0.dist-info}/METADATA +9 -7
- aa_intel_tool-2.11.0.dist-info/RECORD +169 -0
- aa_intel_tool-2.10.0.dist-info/RECORD +0 -136
- {aa_intel_tool-2.10.0.dist-info → aa_intel_tool-2.11.0.dist-info}/WHEEL +0 -0
- {aa_intel_tool-2.10.0.dist-info → aa_intel_tool-2.11.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# Standard Library
|
|
2
|
+
from unittest.mock import MagicMock, patch
|
|
3
|
+
|
|
4
|
+
# AA Intel Tool
|
|
5
|
+
from aa_intel_tool.exceptions import ParserError
|
|
6
|
+
from aa_intel_tool.parser.module.fleetcomp import get_fleet_composition, parse
|
|
7
|
+
from aa_intel_tool.tests import BaseTestCase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class TestParse(BaseTestCase):
|
|
11
|
+
def test_raises_error_when_fleetcomp_module_is_disabled(self):
|
|
12
|
+
"""
|
|
13
|
+
Test that ParserError is raised when the fleet composition module is disabled.
|
|
14
|
+
|
|
15
|
+
:return:
|
|
16
|
+
:rtype:
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
with patch(
|
|
20
|
+
"aa_intel_tool.app_settings.AppSettings.INTELTOOL_ENABLE_MODULE_FLEETCOMP",
|
|
21
|
+
False,
|
|
22
|
+
):
|
|
23
|
+
with self.assertRaises(ParserError):
|
|
24
|
+
parse(scan_data=[])
|
|
25
|
+
|
|
26
|
+
def test_returns_empty_data_for_empty_scan(self):
|
|
27
|
+
"""
|
|
28
|
+
Test that an empty scan returns empty parsed data.
|
|
29
|
+
|
|
30
|
+
:return:
|
|
31
|
+
:rtype:
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
with patch(
|
|
35
|
+
"aa_intel_tool.app_settings.AppSettings.INTELTOOL_ENABLE_MODULE_FLEETCOMP",
|
|
36
|
+
True,
|
|
37
|
+
):
|
|
38
|
+
with patch(
|
|
39
|
+
"aa_intel_tool.parser.module.fleetcomp.safe_scan_to_db"
|
|
40
|
+
) as mock_safe_scan:
|
|
41
|
+
mock_safe_scan.return_value = {}
|
|
42
|
+
|
|
43
|
+
result = parse(scan_data=[])
|
|
44
|
+
|
|
45
|
+
self.assertEqual(result, {})
|
|
46
|
+
|
|
47
|
+
def test_parses_valid_scan_data_correctly(self):
|
|
48
|
+
"""
|
|
49
|
+
Test that valid scan data is parsed correctly.
|
|
50
|
+
|
|
51
|
+
:return:
|
|
52
|
+
:rtype:
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
scan_data = [
|
|
56
|
+
"Rounon Dax\tJita\tOmen\tCruiser\tFleet Commander (Boss)\t5 - 5 - 5",
|
|
57
|
+
"Arodem Artemis\tPerimeter\tLeopard\tShuttle\tSquad Member\t0 - 0 - 5\tWing 1 / Squad 1",
|
|
58
|
+
]
|
|
59
|
+
with patch(
|
|
60
|
+
"aa_intel_tool.app_settings.AppSettings.INTELTOOL_ENABLE_MODULE_FLEETCOMP",
|
|
61
|
+
True,
|
|
62
|
+
):
|
|
63
|
+
with patch(
|
|
64
|
+
"aa_intel_tool.parser.module.fleetcomp.EveEntity.objects.fetch_by_names_esi"
|
|
65
|
+
) as mock_fetch:
|
|
66
|
+
mock_qs = MagicMock()
|
|
67
|
+
mock_qs.filter.return_value = mock_qs
|
|
68
|
+
mock_qs.__iter__.return_value = iter([])
|
|
69
|
+
mock_fetch.return_value = mock_qs
|
|
70
|
+
with patch(
|
|
71
|
+
"aa_intel_tool.parser.module.fleetcomp.safe_scan_to_db"
|
|
72
|
+
) as mock_safe_scan:
|
|
73
|
+
mock_safe_scan.return_value = {"parsed": "data"}
|
|
74
|
+
result = parse(scan_data=scan_data)
|
|
75
|
+
self.assertEqual(result, {"parsed": "data"})
|
|
76
|
+
|
|
77
|
+
def tst_handles_duplicate_pilot_entries_in_scan(self):
|
|
78
|
+
scan_data = [
|
|
79
|
+
"Arodem Artemis\tPerimeter\tLeopard\tShuttle\tSquad Member\t0 - 0 - 5\tWing 1 / Squad 1",
|
|
80
|
+
"Arodem Artemis\tPerimeter\tLeopard\tShuttle\tSquad Member\t0 - 0 - 5\tWing 1 / Squad 1",
|
|
81
|
+
]
|
|
82
|
+
with patch(
|
|
83
|
+
"aa_intel_tool.app_settings.AppSettings.INTELTOOL_ENABLE_MODULE_FLEETCOMP",
|
|
84
|
+
True,
|
|
85
|
+
):
|
|
86
|
+
with patch(
|
|
87
|
+
"aa_intel_tool.parser.module.fleetcomp.safe_scan_to_db"
|
|
88
|
+
) as mock_safe_scan:
|
|
89
|
+
mock_safe_scan.return_value = {"parsed": "data"}
|
|
90
|
+
result = parse(scan_data=scan_data)
|
|
91
|
+
self.assertEqual(result, {"parsed": "data"})
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class TestGetFleetComposition(BaseTestCase):
|
|
95
|
+
"""
|
|
96
|
+
Testing the get_fleet_composition function
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
def test_returns_correct_fleet_composition_for_valid_data(self):
|
|
100
|
+
"""
|
|
101
|
+
Test that the fleet composition is returned correctly for valid data.
|
|
102
|
+
|
|
103
|
+
:return:
|
|
104
|
+
:rtype:
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
pilots = {
|
|
108
|
+
"Pilot1": {"ship": "Omen"},
|
|
109
|
+
"Pilot2": {"ship": "Zealot"},
|
|
110
|
+
}
|
|
111
|
+
ships = {
|
|
112
|
+
"class": {"Omen": {"count": 1}, "Zealot": {"count": 1}},
|
|
113
|
+
"type": {"Cruiser": {"count": 1}, "Heavy Assault Cruiser": {"count": 1}},
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
ship1 = MagicMock()
|
|
117
|
+
ship1.id = 1
|
|
118
|
+
ship1.name = "Omen"
|
|
119
|
+
ship1.eve_group__id = 10
|
|
120
|
+
ship1.eve_group__name = "Cruiser"
|
|
121
|
+
ship1.mass = 1000
|
|
122
|
+
|
|
123
|
+
ship2 = MagicMock()
|
|
124
|
+
ship2.id = 2
|
|
125
|
+
ship2.name = "Zealot"
|
|
126
|
+
ship2.eve_group__id = 20
|
|
127
|
+
ship2.eve_group__name = "Heavy Assault Cruiser"
|
|
128
|
+
ship2.mass = 500
|
|
129
|
+
|
|
130
|
+
with (
|
|
131
|
+
patch(
|
|
132
|
+
"aa_intel_tool.parser.module.fleetcomp.EveEntity.objects.fetch_by_names_esi"
|
|
133
|
+
) as mock_fetch,
|
|
134
|
+
patch(
|
|
135
|
+
"aa_intel_tool.parser.module.fleetcomp.EveType.objects.bulk_get_or_create_esi"
|
|
136
|
+
) as mock_bulk,
|
|
137
|
+
patch(
|
|
138
|
+
"aa_intel_tool.parser.module.fleetcomp._get_character_info"
|
|
139
|
+
) as mock_get_character_info,
|
|
140
|
+
):
|
|
141
|
+
mock_fetch.return_value.filter.return_value.values_list.return_value = [
|
|
142
|
+
1,
|
|
143
|
+
2,
|
|
144
|
+
]
|
|
145
|
+
mock_bulk.return_value.values_list.return_value = [ship1, ship2]
|
|
146
|
+
mock_get_character_info.return_value = [
|
|
147
|
+
MagicMock(
|
|
148
|
+
character_name="Pilot1",
|
|
149
|
+
character_id=1001,
|
|
150
|
+
portrait_url_32="portrait1.png",
|
|
151
|
+
),
|
|
152
|
+
MagicMock(
|
|
153
|
+
character_name="Pilot2",
|
|
154
|
+
character_id=1002,
|
|
155
|
+
portrait_url_32="portrait2.png",
|
|
156
|
+
),
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
result = get_fleet_composition(pilots=pilots, ships=ships)
|
|
160
|
+
|
|
161
|
+
self.assertEqual(len(result["classes"]), 2)
|
|
162
|
+
self.assertEqual(len(result["types"]), 2)
|
|
163
|
+
self.assertEqual(len(result["pilots"]), 2)
|
|
164
|
+
|
|
165
|
+
def test_handles_empty_pilots_and_ships(self):
|
|
166
|
+
"""
|
|
167
|
+
Test that empty pilots and ships return empty fleet composition.
|
|
168
|
+
:return:
|
|
169
|
+
:rtype:
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
pilots = {}
|
|
173
|
+
ships = {"class": {}, "type": {}}
|
|
174
|
+
|
|
175
|
+
with (
|
|
176
|
+
patch(
|
|
177
|
+
"aa_intel_tool.parser.module.fleetcomp.EveEntity.objects.fetch_by_names_esi"
|
|
178
|
+
) as mock_fetch,
|
|
179
|
+
patch(
|
|
180
|
+
"aa_intel_tool.parser.module.fleetcomp.EveType.objects.bulk_get_or_create_esi"
|
|
181
|
+
) as mock_bulk,
|
|
182
|
+
patch(
|
|
183
|
+
"aa_intel_tool.parser.module.fleetcomp._get_character_info"
|
|
184
|
+
) as mock_get_character_info,
|
|
185
|
+
):
|
|
186
|
+
mock_fetch.return_value.filter.return_value.values_list.return_value = []
|
|
187
|
+
mock_bulk.return_value.values_list.return_value = []
|
|
188
|
+
mock_get_character_info.return_value = []
|
|
189
|
+
|
|
190
|
+
result = get_fleet_composition(pilots=pilots, ships=ships)
|
|
191
|
+
|
|
192
|
+
self.assertEqual(result["classes"], [])
|
|
193
|
+
self.assertEqual(result["types"], [])
|
|
194
|
+
self.assertEqual(result["pilots"], [])
|
|
195
|
+
|
|
196
|
+
def test_raises_error_for_missing_ship_class_in_pilots(self):
|
|
197
|
+
"""
|
|
198
|
+
Test that an error is raised when a ship class in pilots is missing.
|
|
199
|
+
|
|
200
|
+
:return:
|
|
201
|
+
:rtype:
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
pilots = {"Pilot1": {"ship": "Unknown"}}
|
|
205
|
+
ships = {"class": {}, "type": {}}
|
|
206
|
+
mock_ship_class_details = []
|
|
207
|
+
|
|
208
|
+
with (
|
|
209
|
+
patch(
|
|
210
|
+
"aa_intel_tool.parser.module.fleetcomp.EveEntity.objects.fetch_by_names_esi"
|
|
211
|
+
) as mock_fetch,
|
|
212
|
+
patch(
|
|
213
|
+
"aa_intel_tool.parser.module.fleetcomp.EveType.objects.bulk_get_or_create_esi"
|
|
214
|
+
) as mock_bulk,
|
|
215
|
+
patch(
|
|
216
|
+
"aa_intel_tool.parser.module.fleetcomp._get_character_info"
|
|
217
|
+
) as mock_get_character_info,
|
|
218
|
+
):
|
|
219
|
+
mock_fetch.return_value.filter.return_value.values_list.return_value = []
|
|
220
|
+
mock_bulk.return_value.values_list.return_value = mock_ship_class_details
|
|
221
|
+
mock_get_character_info.return_value = [
|
|
222
|
+
MagicMock(
|
|
223
|
+
character_name="Pilot1",
|
|
224
|
+
character_id=1001,
|
|
225
|
+
portrait_url_32="portrait1.png",
|
|
226
|
+
)
|
|
227
|
+
]
|
|
228
|
+
|
|
229
|
+
with self.assertRaises(StopIteration):
|
|
230
|
+
get_fleet_composition(pilots=pilots, ships=ships)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for the tasks in aa_intel_tool.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Standard Library
|
|
6
|
+
from datetime import timedelta
|
|
7
|
+
from unittest.mock import patch
|
|
8
|
+
|
|
9
|
+
# Django
|
|
10
|
+
from django.utils.timezone import now
|
|
11
|
+
|
|
12
|
+
# AA Intel Tool
|
|
13
|
+
from aa_intel_tool.tasks import housekeeping
|
|
14
|
+
from aa_intel_tool.tests import BaseTestCase
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestHousekeeping(BaseTestCase):
|
|
18
|
+
"""
|
|
19
|
+
Tests for the housekeeping task
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def test_removes_scans_older_than_retention_time(self):
|
|
23
|
+
"""
|
|
24
|
+
Test that scans older than the retention time are removed.
|
|
25
|
+
|
|
26
|
+
:return:
|
|
27
|
+
:rtype:
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
with (
|
|
31
|
+
patch("aa_intel_tool.tasks.AppSettings.INTELTOOL_SCAN_RETENTION_TIME", 7),
|
|
32
|
+
patch("aa_intel_tool.tasks.Scan.objects.filter") as mock_filter,
|
|
33
|
+
):
|
|
34
|
+
housekeeping()
|
|
35
|
+
mock_filter.assert_called_once()
|
|
36
|
+
called_kwargs = mock_filter.call_args.kwargs
|
|
37
|
+
created_lte = called_kwargs.get("created__lte")
|
|
38
|
+
expected = now() - timedelta(days=7)
|
|
39
|
+
|
|
40
|
+
self.assertTrue(abs(created_lte - expected) < timedelta(seconds=1))
|
|
41
|
+
mock_filter.return_value.delete.assert_called_once()
|
|
42
|
+
|
|
43
|
+
def test_does_not_remove_scans_when_retention_time_is_zero(self):
|
|
44
|
+
"""
|
|
45
|
+
Test that no scans are removed when retention time is set to zero.
|
|
46
|
+
|
|
47
|
+
:return:
|
|
48
|
+
:rtype:
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
with (
|
|
52
|
+
patch("aa_intel_tool.tasks.AppSettings.INTELTOOL_SCAN_RETENTION_TIME", 0),
|
|
53
|
+
patch("aa_intel_tool.tasks.Scan.objects.filter") as mock_filter,
|
|
54
|
+
):
|
|
55
|
+
housekeeping()
|
|
56
|
+
mock_filter.assert_not_called()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for the AJAX views in aa_intel_tool.views.ajax.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Standard Library
|
|
6
|
+
from http import HTTPStatus
|
|
7
|
+
from unittest.mock import Mock, patch
|
|
8
|
+
|
|
9
|
+
# Django
|
|
10
|
+
from django.test import RequestFactory
|
|
11
|
+
from django.urls import reverse
|
|
12
|
+
|
|
13
|
+
# AA Intel Tool
|
|
14
|
+
from aa_intel_tool.models import ScanData
|
|
15
|
+
from aa_intel_tool.tests import BaseTestCase
|
|
16
|
+
from aa_intel_tool.views.ajax import get_scan_data
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestGetScanData(BaseTestCase):
|
|
20
|
+
"""
|
|
21
|
+
Tests for the get_scan_data AJAX view
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def test_returns_processed_data_for_valid_scan_hash_and_section(self):
|
|
25
|
+
"""
|
|
26
|
+
Testing return of processed data for valid scan hash and section
|
|
27
|
+
|
|
28
|
+
:return:
|
|
29
|
+
:rtype:
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
rf = RequestFactory()
|
|
33
|
+
request = rf.get(
|
|
34
|
+
reverse(
|
|
35
|
+
"aa_intel_tool:ajax_get_scan_data",
|
|
36
|
+
kwargs={"scan_hash": "valid-hash", "scan_section": "valid-section"},
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
with patch("aa_intel_tool.views.ajax.ScanData.objects.filter") as mock_filter:
|
|
41
|
+
mock_scan_data = Mock()
|
|
42
|
+
mock_scan_data.processed_data = {"key": "value"}
|
|
43
|
+
mock_filter.return_value.get.return_value = mock_scan_data
|
|
44
|
+
|
|
45
|
+
response = get_scan_data(request, "valid-hash", "valid-section")
|
|
46
|
+
|
|
47
|
+
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
48
|
+
self.assertJSONEqual(response.content, {"key": "value"})
|
|
49
|
+
|
|
50
|
+
def test_returns_empty_dict_when_scan_data_does_not_exist(self):
|
|
51
|
+
"""
|
|
52
|
+
Testing return of empty dict when scan data does not exist
|
|
53
|
+
|
|
54
|
+
:return:
|
|
55
|
+
:rtype:
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
rf = RequestFactory()
|
|
59
|
+
request = rf.get(
|
|
60
|
+
reverse(
|
|
61
|
+
"aa_intel_tool:ajax_get_scan_data",
|
|
62
|
+
kwargs={"scan_hash": "invalid-hash", "scan_section": "invalid-section"},
|
|
63
|
+
)
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
with patch("aa_intel_tool.views.ajax.ScanData.objects.filter") as mock_filter:
|
|
67
|
+
mock_filter.return_value.get.side_effect = ScanData.DoesNotExist
|
|
68
|
+
|
|
69
|
+
response = get_scan_data(request, "invalid-hash", "invalid-section")
|
|
70
|
+
|
|
71
|
+
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
72
|
+
self.assertJSONEqual(response.content, {})
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for the general views of the AA Intel Tool application.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Standard Library
|
|
6
|
+
from http import HTTPStatus
|
|
7
|
+
from unittest.mock import Mock, patch
|
|
8
|
+
|
|
9
|
+
# Django
|
|
10
|
+
from django.contrib.messages import get_messages
|
|
11
|
+
from django.contrib.messages.storage.fallback import FallbackStorage
|
|
12
|
+
from django.contrib.sessions.middleware import SessionMiddleware
|
|
13
|
+
from django.test import RequestFactory
|
|
14
|
+
|
|
15
|
+
# AA Intel Tool
|
|
16
|
+
from aa_intel_tool.exceptions import ParserError
|
|
17
|
+
from aa_intel_tool.models import Scan
|
|
18
|
+
from aa_intel_tool.tests import BaseTestCase
|
|
19
|
+
from aa_intel_tool.views import general as general_view
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TestViewIndex(BaseTestCase):
|
|
23
|
+
"""
|
|
24
|
+
Tests for the index view
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def test_redirects_to_scan_view_on_valid_form_submission_direct_call(self):
|
|
28
|
+
"""
|
|
29
|
+
Testing redirection to scan view on valid form submission
|
|
30
|
+
|
|
31
|
+
:return:
|
|
32
|
+
:rtype:
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
form_data = {"eve_intel": "valid intel data"}
|
|
36
|
+
rf = RequestFactory()
|
|
37
|
+
request = rf.post("/intel/", data=form_data)
|
|
38
|
+
|
|
39
|
+
with (
|
|
40
|
+
patch("aa_intel_tool.views.general.IntelForm") as mock_form,
|
|
41
|
+
patch("aa_intel_tool.views.general.parse_intel") as mock_parse,
|
|
42
|
+
):
|
|
43
|
+
mock_form.return_value.is_valid.return_value = True
|
|
44
|
+
mock_form.return_value.cleaned_data = form_data
|
|
45
|
+
mock_parse.return_value = "valid_scan_hash"
|
|
46
|
+
|
|
47
|
+
response = general_view.index(request)
|
|
48
|
+
|
|
49
|
+
self.assertEqual(response.status_code, HTTPStatus.FOUND)
|
|
50
|
+
self.assertEqual(response["Location"], "/intel/scan/valid_scan_hash/")
|
|
51
|
+
|
|
52
|
+
def test_shows_error_message_on_parser_error_direct_call(self):
|
|
53
|
+
"""
|
|
54
|
+
Testing error message on parser error during form submission
|
|
55
|
+
|
|
56
|
+
:return:
|
|
57
|
+
:rtype:
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
form_data = {"eve_intel": "invalid intel data"}
|
|
61
|
+
rf = RequestFactory()
|
|
62
|
+
request = rf.post("/intel/", data=form_data)
|
|
63
|
+
|
|
64
|
+
middleware = SessionMiddleware(lambda req: None)
|
|
65
|
+
middleware.process_request(request)
|
|
66
|
+
request.session.save()
|
|
67
|
+
request._messages = FallbackStorage(request)
|
|
68
|
+
|
|
69
|
+
with (
|
|
70
|
+
patch("aa_intel_tool.views.general.IntelForm") as mock_form,
|
|
71
|
+
patch("aa_intel_tool.views.general.parse_intel") as mock_parse,
|
|
72
|
+
):
|
|
73
|
+
mock_form.return_value.is_valid.return_value = True
|
|
74
|
+
mock_form.return_value.cleaned_data = form_data
|
|
75
|
+
mock_parse.side_effect = ParserError("Parsing failed")
|
|
76
|
+
|
|
77
|
+
response = general_view.index(request)
|
|
78
|
+
|
|
79
|
+
self.assertEqual(response.status_code, HTTPStatus.FOUND)
|
|
80
|
+
|
|
81
|
+
messages = [str(m) for m in get_messages(request)]
|
|
82
|
+
|
|
83
|
+
# ensure the general parse-failure message is present
|
|
84
|
+
self.assertTrue(
|
|
85
|
+
any("The provided data could not be parsed." in m for m in messages),
|
|
86
|
+
f"messages were: {messages}",
|
|
87
|
+
)
|
|
88
|
+
# ensure the exception detail is included somewhere
|
|
89
|
+
self.assertTrue(
|
|
90
|
+
any("Parsing failed" in m for m in messages),
|
|
91
|
+
f"messages were: {messages}",
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def test_shows_error_message_on_unexpected_exception_direct_call(self):
|
|
95
|
+
"""
|
|
96
|
+
Testing error message on unexpected exception during form submission
|
|
97
|
+
|
|
98
|
+
:return:
|
|
99
|
+
:rtype:
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
form_data = {"eve_intel": "unexpected error data"}
|
|
103
|
+
rf = RequestFactory()
|
|
104
|
+
request = rf.post("/intel/", data=form_data)
|
|
105
|
+
|
|
106
|
+
middleware = SessionMiddleware(lambda req: None)
|
|
107
|
+
middleware.process_request(request)
|
|
108
|
+
request.session.save()
|
|
109
|
+
request._messages = FallbackStorage(request)
|
|
110
|
+
|
|
111
|
+
with (
|
|
112
|
+
patch("aa_intel_tool.views.general.IntelForm") as mock_form,
|
|
113
|
+
patch("aa_intel_tool.views.general.parse_intel") as mock_parse,
|
|
114
|
+
):
|
|
115
|
+
mock_form.return_value.is_valid.return_value = True
|
|
116
|
+
mock_form.return_value.cleaned_data = form_data
|
|
117
|
+
mock_parse.side_effect = Exception("Unexpected error")
|
|
118
|
+
|
|
119
|
+
response = general_view.index(request)
|
|
120
|
+
|
|
121
|
+
self.assertEqual(response.status_code, HTTPStatus.FOUND)
|
|
122
|
+
|
|
123
|
+
messages = [str(m) for m in get_messages(request)]
|
|
124
|
+
|
|
125
|
+
self.assertTrue(
|
|
126
|
+
any(
|
|
127
|
+
"Something unexpected happened" in m or "(System Error)" in m
|
|
128
|
+
for m in messages
|
|
129
|
+
),
|
|
130
|
+
f"messages were: {messages}",
|
|
131
|
+
)
|
|
132
|
+
self.assertTrue(
|
|
133
|
+
any("Unexpected error" in m for m in messages),
|
|
134
|
+
f"messages were: {messages}",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
def test_renders_blank_form_on_get_request_direct_call(self):
|
|
138
|
+
"""
|
|
139
|
+
Testing rendering of blank form on GET request
|
|
140
|
+
|
|
141
|
+
:return:
|
|
142
|
+
:rtype:
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
rf = RequestFactory()
|
|
146
|
+
request = rf.get("/intel/")
|
|
147
|
+
|
|
148
|
+
response = general_view.index(request)
|
|
149
|
+
|
|
150
|
+
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
151
|
+
self.assertIn("<form", response.content.decode("utf-8"))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class TestViewScan(BaseTestCase):
|
|
155
|
+
"""
|
|
156
|
+
Tests for the scan view
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
def test_shows_error_message_when_scan_does_not_exist(self):
|
|
160
|
+
"""
|
|
161
|
+
Testing error message when scan does not exist
|
|
162
|
+
|
|
163
|
+
:return:
|
|
164
|
+
:rtype:
|
|
165
|
+
"""
|
|
166
|
+
|
|
167
|
+
rf = RequestFactory()
|
|
168
|
+
request = rf.get("/intel/scan/nonexistent-scan-hash/")
|
|
169
|
+
|
|
170
|
+
middleware = SessionMiddleware(lambda req: None)
|
|
171
|
+
middleware.process_request(request)
|
|
172
|
+
request.session.save()
|
|
173
|
+
|
|
174
|
+
request._messages = FallbackStorage(request)
|
|
175
|
+
with patch("aa_intel_tool.views.general.Scan.objects.exclude") as mock_exclude:
|
|
176
|
+
mock_exclude.return_value.get.side_effect = Scan.DoesNotExist
|
|
177
|
+
|
|
178
|
+
response = general_view.scan(request, scan_hash="nonexistent-scan-hash")
|
|
179
|
+
|
|
180
|
+
self.assertEqual(response.status_code, HTTPStatus.FOUND)
|
|
181
|
+
|
|
182
|
+
messages = [str(m) for m in get_messages(request)]
|
|
183
|
+
|
|
184
|
+
self.assertTrue(
|
|
185
|
+
any(
|
|
186
|
+
"The scan you were looking for could not be found." in m
|
|
187
|
+
for m in messages
|
|
188
|
+
),
|
|
189
|
+
f"messages were: {messages}",
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
def test_renders_correct_template_for_supported_scan_type(self):
|
|
193
|
+
"""
|
|
194
|
+
Testing rendering of correct template for supported scan type
|
|
195
|
+
|
|
196
|
+
:return:
|
|
197
|
+
:rtype:
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
rf = RequestFactory()
|
|
201
|
+
request = rf.get("/intel/scan/supported-scan-hash/")
|
|
202
|
+
|
|
203
|
+
middleware = SessionMiddleware(lambda req: None)
|
|
204
|
+
middleware.process_request(request)
|
|
205
|
+
request.session.save()
|
|
206
|
+
request._messages = FallbackStorage(request)
|
|
207
|
+
|
|
208
|
+
with patch("aa_intel_tool.views.general.Scan.objects.exclude") as mock_exclude:
|
|
209
|
+
mock_scan = Mock()
|
|
210
|
+
mock_scan.scan_type = "supported_type"
|
|
211
|
+
mock_scan.created = "2023-01-01"
|
|
212
|
+
mock_scan.raw_data = "raw intel data"
|
|
213
|
+
mock_exclude.return_value.get.return_value = mock_scan
|
|
214
|
+
|
|
215
|
+
# dummy response that mimics a rendered template response
|
|
216
|
+
dummy_response = Mock()
|
|
217
|
+
dummy_response.status_code = HTTPStatus.OK
|
|
218
|
+
dummy_response.templates = [
|
|
219
|
+
type("T", (), {"name": "supported_template.html"})()
|
|
220
|
+
]
|
|
221
|
+
|
|
222
|
+
with (
|
|
223
|
+
patch(
|
|
224
|
+
"aa_intel_tool.views.general.SUPPORTED_INTEL_TYPES",
|
|
225
|
+
{
|
|
226
|
+
"supported_type": {
|
|
227
|
+
"name": "Supported Type",
|
|
228
|
+
"template": "supported_template.html",
|
|
229
|
+
}
|
|
230
|
+
},
|
|
231
|
+
),
|
|
232
|
+
patch(
|
|
233
|
+
"aa_intel_tool.views.general.render", return_value=dummy_response
|
|
234
|
+
) as mock_render,
|
|
235
|
+
):
|
|
236
|
+
response = general_view.scan(request, scan_hash="supported-scan-hash")
|
|
237
|
+
|
|
238
|
+
self.assertEqual(response.status_code, HTTPStatus.OK)
|
|
239
|
+
self.assertTemplateUsed(response, "supported_template.html")
|
|
240
|
+
mock_render.assert_called_once()
|
aa_intel_tool/urls.py
CHANGED
|
@@ -9,7 +9,7 @@ from django.urls import include, path
|
|
|
9
9
|
from aa_intel_tool.constants import INTERNAL_URL_PREFIX
|
|
10
10
|
from aa_intel_tool.views import ajax, general
|
|
11
11
|
|
|
12
|
-
app_name: str = "aa_intel_tool"
|
|
12
|
+
app_name: str = "aa_intel_tool" # pylint: disable=invalid-name
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
app_urls = [
|
aa_intel_tool/views/general.py
CHANGED
|
@@ -50,16 +50,24 @@ def index(request: WSGIRequest) -> HttpResponse:
|
|
|
50
50
|
# Catching our own parser exceptions
|
|
51
51
|
except ParserError as exc:
|
|
52
52
|
exception_caught = True
|
|
53
|
-
|
|
54
|
-
messages.error(
|
|
53
|
+
|
|
54
|
+
messages.error(
|
|
55
|
+
request=request,
|
|
56
|
+
message=_("The provided data could not be parsed. ({exc})").format(
|
|
57
|
+
exc=exc
|
|
58
|
+
),
|
|
59
|
+
)
|
|
55
60
|
|
|
56
61
|
# Catching every other exception we can't think of (hopefully)
|
|
57
62
|
except Exception as exc: # pylint: disable=broad-exception-caught
|
|
58
63
|
exception_caught = True
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
|
|
65
|
+
messages.error(
|
|
66
|
+
request=request,
|
|
67
|
+
message=_(
|
|
68
|
+
"(System Error) Something unexpected happened. ({exc})"
|
|
69
|
+
).format(exc=exc),
|
|
61
70
|
)
|
|
62
|
-
messages.error(request=request, message=errormessage)
|
|
63
71
|
|
|
64
72
|
if exception_caught:
|
|
65
73
|
return redirect(to="aa_intel_tool:intel_tool_index")
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aa-intel-tool
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.11.0
|
|
4
4
|
Summary: A simple parser for D-Scans and more for Alliance Auth
|
|
5
5
|
Project-URL: Changelog, https://github.com/ppfeufer/aa-intel-tool/blob/master/CHANGELOG.md
|
|
6
|
+
Project-URL: Codecov, https://codecov.io/gh/ppfeufer/aa-intel-tool
|
|
7
|
+
Project-URL: Discord, https://discord.gg/fjnHAmk
|
|
6
8
|
Project-URL: Documentation, https://github.com/ppfeufer/aa-intel-tool/blob/master/README.md
|
|
7
|
-
Project-URL:
|
|
8
|
-
Project-URL:
|
|
9
|
-
Project-URL: Source, https://github.com/ppfeufer/aa-intel-tool.git
|
|
9
|
+
Project-URL: Donation, https://ko-fi.com/ppfeufer
|
|
10
|
+
Project-URL: GitHub, https://github.com/ppfeufer/aa-intel-tool
|
|
10
11
|
Project-URL: Tracker, https://github.com/ppfeufer/aa-intel-tool/issues
|
|
12
|
+
Project-URL: Translation, https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-intel-tool/
|
|
11
13
|
Author-email: Peter Pfeufer <develop@ppfeufer.de>
|
|
12
14
|
License: GNU GENERAL PUBLIC LICENSE
|
|
13
15
|
Version 3, 29 June 2007
|
|
@@ -699,7 +701,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
699
701
|
Classifier: Programming Language :: Python :: 3.13
|
|
700
702
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
701
703
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
702
|
-
Requires-Python:
|
|
704
|
+
Requires-Python: <3.14,>=3.10
|
|
703
705
|
Requires-Dist: allianceauth-app-utils>=1.19.1
|
|
704
706
|
Requires-Dist: allianceauth<5,>=4.10
|
|
705
707
|
Requires-Dist: django-eveuniverse>=1.3
|
|
@@ -976,7 +978,7 @@ Please make sure to read the [Contribution Guidelines].\
|
|
|
976
978
|
[badge: license]: https://img.shields.io/github/license/ppfeufer/aa-intel-tool "License"
|
|
977
979
|
[badge: pre-commit]: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white "pre-commit"
|
|
978
980
|
[badge: pre-commit.ci status]: https://results.pre-commit.ci/badge/github/ppfeufer/aa-intel-tool/master.svg "pre-commit.ci status"
|
|
979
|
-
[badge: support discord]: https://img.shields.io/discord/
|
|
981
|
+
[badge: support discord]: https://img.shields.io/discord/399006117012832262?label=discord "Support Discord"
|
|
980
982
|
[badge: supported django versions]: https://img.shields.io/pypi/djversions/aa-intel-tool?label=django "Supported Django Versions"
|
|
981
983
|
[badge: supported python versions]: https://img.shields.io/pypi/pyversions/aa-intel-tool "Supported Python Versions"
|
|
982
984
|
[badge: translation status]: https://weblate.ppfeufer.de/widget/alliance-auth-apps/aa-intel-tool/svg-badge.svg "Translation Status"
|
|
@@ -991,5 +993,5 @@ Please make sure to read the [Contribution Guidelines].\
|
|
|
991
993
|
[image: fleet composition module]: https://raw.githubusercontent.com/ppfeufer/aa-intel-tool/master/docs/images/presentation/fleet-composition.jpg "Fleet Composition Module"
|
|
992
994
|
[ppfeufer on ko-fi]: https://ko-fi.com/ppfeufer "Buy Me a Coffee!"
|
|
993
995
|
[pre-commit.ci status]: https://results.pre-commit.ci/latest/github/ppfeufer/aa-intel-tool/master "pre-commit.ci"
|
|
994
|
-
[support discord]: https://discord.gg/
|
|
996
|
+
[support discord]: https://discord.gg/fjnHAmk "Alliance Auth Support Discord"
|
|
995
997
|
[weblate engage]: https://weblate.ppfeufer.de/engage/alliance-auth-apps/ "Weblate Translations"
|