aa-killtracker 0.18.0a2__py3-none-any.whl → 1.0.0a1__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_killtracker-0.18.0a2.dist-info → aa_killtracker-1.0.0a1.dist-info}/METADATA +7 -7
- {aa_killtracker-0.18.0a2.dist-info → aa_killtracker-1.0.0a1.dist-info}/RECORD +33 -30
- killtracker/__init__.py +1 -1
- killtracker/admin.py +1 -1
- killtracker/app_settings.py +14 -7
- killtracker/core/discord.py +162 -0
- killtracker/core/helpers.py +13 -0
- killtracker/core/{discord_messages.py → trackers.py} +16 -75
- killtracker/core/{worker_shutdown.py → workers.py} +3 -4
- killtracker/core/{killmails.py → zkb.py} +32 -40
- killtracker/managers.py +1 -1
- killtracker/models/trackers.py +4 -5
- killtracker/models/webhooks.py +4 -53
- killtracker/signals.py +4 -4
- killtracker/tasks.py +47 -49
- killtracker/tests/core/test_discord.py +184 -0
- killtracker/tests/core/test_helpers.py +23 -0
- killtracker/tests/core/{test_discord_messages_1.py → test_tracker_1.py} +12 -39
- killtracker/tests/core/{test_discord_messages_2.py → test_tracker_2.py} +3 -3
- killtracker/tests/core/test_workers.py +49 -0
- killtracker/tests/core/{test_killmails.py → test_zkb.py} +58 -46
- killtracker/tests/models/test_killmails.py +0 -2
- killtracker/tests/models/test_trackers_1.py +1 -1
- killtracker/tests/models/test_trackers_2.py +2 -2
- killtracker/tests/models/test_webhooks.py +63 -0
- killtracker/tests/test_integration.py +26 -13
- killtracker/tests/test_tasks.py +68 -58
- killtracker/tests/test_utils.py +39 -0
- killtracker/tests/testdata/factories.py +1 -1
- killtracker/tests/testdata/helpers.py +1 -1
- killtracker/tests/utils.py +28 -0
- killtracker/exceptions.py +0 -32
- killtracker/tests/core/test_worker_shutdown.py +0 -34
- killtracker/tests/models/test_webhook.py +0 -164
- killtracker/tests/test_exceptions.py +0 -12
- {aa_killtracker-0.18.0a2.dist-info → aa_killtracker-1.0.0a1.dist-info}/WHEEL +0 -0
- {aa_killtracker-0.18.0a2.dist-info → aa_killtracker-1.0.0a1.dist-info}/licenses/LICENSE +0 -0
killtracker/managers.py
CHANGED
@@ -15,7 +15,7 @@ from app_utils.logging import LoggerAddTag
|
|
15
15
|
|
16
16
|
from killtracker import __title__
|
17
17
|
from killtracker.app_settings import KILLTRACKER_PURGE_KILLMAILS_AFTER_DAYS
|
18
|
-
from killtracker.core.
|
18
|
+
from killtracker.core.zkb import Killmail, _KillmailCharacter
|
19
19
|
|
20
20
|
logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
21
21
|
|
killtracker/models/trackers.py
CHANGED
@@ -28,11 +28,10 @@ from app_utils.logging import LoggerAddTag
|
|
28
28
|
from killtracker import __title__
|
29
29
|
from killtracker.app_settings import KILLTRACKER_KILLMAIL_MAX_AGE_FOR_TRACKER
|
30
30
|
from killtracker.constants import EveCategoryId, EveGroupId
|
31
|
-
from killtracker.core.
|
32
|
-
from killtracker.core.
|
31
|
+
from killtracker.core.trackers import create_discord_message_from_killmail
|
32
|
+
from killtracker.core.zkb import Killmail
|
33
33
|
from killtracker.managers import TrackerManager
|
34
|
-
|
35
|
-
from .webhooks import Webhook
|
34
|
+
from killtracker.models.webhooks import Webhook
|
36
35
|
|
37
36
|
logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
38
37
|
|
@@ -886,5 +885,5 @@ class Tracker(models.Model):
|
|
886
885
|
|
887
886
|
Returns the new queue size.
|
888
887
|
"""
|
889
|
-
message =
|
888
|
+
message = create_discord_message_from_killmail(self, killmail, intro_text)
|
890
889
|
return self.webhook.enqueue_message(message)
|
killtracker/models/webhooks.py
CHANGED
@@ -2,10 +2,8 @@
|
|
2
2
|
|
3
3
|
from typing import Optional
|
4
4
|
|
5
|
-
import dhooks_lite
|
6
5
|
from simple_mq import SimpleMQ
|
7
6
|
|
8
|
-
from django.core.cache import cache
|
9
7
|
from django.db import models
|
10
8
|
from django.utils.translation import gettext_lazy as _
|
11
9
|
|
@@ -14,10 +12,9 @@ from app_utils.allianceauth import get_redis_client
|
|
14
12
|
from app_utils.logging import LoggerAddTag
|
15
13
|
from app_utils.urls import static_file_absolute_url
|
16
14
|
|
17
|
-
from killtracker import
|
15
|
+
from killtracker import __title__
|
18
16
|
from killtracker.app_settings import KILLTRACKER_WEBHOOK_SET_AVATAR
|
19
|
-
from killtracker.core.
|
20
|
-
from killtracker.exceptions import WebhookTooManyRequests
|
17
|
+
from killtracker.core.discord import DiscordMessage, send_message_to_webhook
|
21
18
|
from killtracker.managers import WebhookManager
|
22
19
|
|
23
20
|
logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
@@ -26,8 +23,6 @@ logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
|
26
23
|
class Webhook(models.Model):
|
27
24
|
"""A webhook to receive messages"""
|
28
25
|
|
29
|
-
HTTP_TOO_MANY_REQUESTS = 429
|
30
|
-
|
31
26
|
class WebhookType(models.IntegerChoices):
|
32
27
|
"""A webhook type."""
|
33
28
|
|
@@ -167,53 +162,9 @@ class Webhook(models.Model):
|
|
167
162
|
|
168
163
|
return q.clear()
|
169
164
|
|
170
|
-
def send_message(self, message: DiscordMessage) ->
|
165
|
+
def send_message(self, message: DiscordMessage) -> int:
|
171
166
|
"""Send a message to the webhook."""
|
172
|
-
|
173
|
-
if timeout:
|
174
|
-
raise WebhookTooManyRequests(timeout)
|
175
|
-
|
176
|
-
hook = dhooks_lite.Webhook(
|
177
|
-
url=self.url,
|
178
|
-
user_agent=dhooks_lite.UserAgent(
|
179
|
-
name=APP_NAME, url=HOMEPAGE_URL, version=__version__
|
180
|
-
),
|
181
|
-
)
|
182
|
-
response = hook.execute(
|
183
|
-
content=message.content,
|
184
|
-
embeds=message.embeds,
|
185
|
-
username=message.username,
|
186
|
-
avatar_url=message.avatar_url,
|
187
|
-
wait_for_response=True,
|
188
|
-
max_retries=0, # we will handle retries ourselves
|
189
|
-
)
|
190
|
-
logger.debug(
|
191
|
-
"%s: Response from Discord for creating message from killmail %d: %s %s %s",
|
192
|
-
self,
|
193
|
-
message.killmail_id,
|
194
|
-
response.status_code,
|
195
|
-
response.headers,
|
196
|
-
response.content,
|
197
|
-
)
|
198
|
-
if response.status_code == self.HTTP_TOO_MANY_REQUESTS:
|
199
|
-
logger.error(
|
200
|
-
"%s: Received too many requests error from API: %s",
|
201
|
-
self,
|
202
|
-
response.content,
|
203
|
-
)
|
204
|
-
try:
|
205
|
-
retry_after = int(response.headers["Retry-After"]) + 2
|
206
|
-
except (ValueError, KeyError):
|
207
|
-
retry_after = WebhookTooManyRequests.DEFAULT_RESET_AFTER
|
208
|
-
cache.set(
|
209
|
-
key=self._blocked_cache_key(), value="BLOCKED", timeout=retry_after
|
210
|
-
)
|
211
|
-
raise WebhookTooManyRequests(retry_after)
|
212
|
-
|
213
|
-
return response
|
214
|
-
|
215
|
-
def _blocked_cache_key(self) -> str:
|
216
|
-
return f"{__title__}_webhook_{self.pk}_blocked"
|
167
|
+
return send_message_to_webhook(name=self.name, url=self.url, message=message)
|
217
168
|
|
218
169
|
@staticmethod
|
219
170
|
def create_message_link(name: str, url: str) -> str:
|
killtracker/signals.py
CHANGED
@@ -8,24 +8,24 @@ from allianceauth.services.hooks import get_extension_logger
|
|
8
8
|
from app_utils.logging import LoggerAddTag
|
9
9
|
|
10
10
|
from killtracker import __title__
|
11
|
-
from killtracker.core import
|
11
|
+
from killtracker.core import workers
|
12
12
|
|
13
13
|
logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
14
14
|
|
15
15
|
|
16
16
|
@signals.worker_ready.connect
|
17
17
|
def worker_ready_handler(sender, **kwargs):
|
18
|
-
|
18
|
+
workers.state_reset(sender.hostname)
|
19
19
|
logger.debug("worker_ready: %s", sender.hostname)
|
20
20
|
|
21
21
|
|
22
22
|
@signals.worker_shutting_down.connect
|
23
23
|
def worker_shutting_down_handler(sender, **kwargs):
|
24
|
-
|
24
|
+
workers.state_set(sender)
|
25
25
|
logger.debug("worker_shutting_down: %s", sender)
|
26
26
|
|
27
27
|
|
28
28
|
@signals.worker_shutdown.connect
|
29
29
|
def worker_shutdown_handler(sender, **kwargs):
|
30
|
-
|
30
|
+
workers.state_reset(sender.hostname)
|
31
31
|
logger.debug("worker_shutdown: %s", sender.hostname)
|
killtracker/tasks.py
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
import time
|
4
4
|
|
5
|
-
import dhooks_lite
|
6
5
|
from celery import Task, chain, shared_task
|
7
6
|
|
8
7
|
from django.db import IntegrityError
|
@@ -21,20 +20,20 @@ from killtracker.app_settings import (
|
|
21
20
|
KILLTRACKER_GENERATE_MESSAGE_MAX_RETRIES,
|
22
21
|
KILLTRACKER_GENERATE_MESSAGE_RETRY_COUNTDOWN,
|
23
22
|
KILLTRACKER_MAX_KILLMAILS_PER_RUN,
|
23
|
+
KILLTRACKER_MAX_MESSAGES_SENT_PER_RUN,
|
24
24
|
KILLTRACKER_PURGE_KILLMAILS_AFTER_DAYS,
|
25
25
|
KILLTRACKER_RUN_TIMEOUT,
|
26
26
|
KILLTRACKER_STORING_KILLMAILS_ENABLED,
|
27
27
|
KILLTRACKER_TASK_OBJECTS_CACHE_TIMEOUT,
|
28
28
|
KILLTRACKER_TASKS_TIMEOUT,
|
29
29
|
)
|
30
|
-
from killtracker.core import
|
31
|
-
from killtracker.core.
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
ZKBTooManyRequestsError,
|
30
|
+
from killtracker.core import workers
|
31
|
+
from killtracker.core.discord import (
|
32
|
+
DiscordMessage,
|
33
|
+
HTTPError,
|
34
|
+
WebhookRateLimitExhausted,
|
36
35
|
)
|
37
|
-
from killtracker.
|
36
|
+
from killtracker.core.zkb import Killmail, KillmailDoesNotExist, ZKBTooManyRequestsError
|
38
37
|
from killtracker.models import EveKillmail, Tracker, Webhook
|
39
38
|
|
40
39
|
logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
@@ -42,7 +41,8 @@ logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
|
42
41
|
|
43
42
|
@shared_task(bind=True, base=QueueOnce, timeout=KILLTRACKER_TASKS_TIMEOUT)
|
44
43
|
def run_killtracker(self: Task) -> int:
|
45
|
-
"""
|
44
|
+
"""Fetches and processes new killmails from ZKB API
|
45
|
+
and returns how many killmails were processed.
|
46
46
|
|
47
47
|
This is the main periodic task for running Killtracker.
|
48
48
|
"""
|
@@ -64,7 +64,7 @@ def run_killtracker(self: Task) -> int:
|
|
64
64
|
if is_timed_out():
|
65
65
|
break
|
66
66
|
|
67
|
-
if
|
67
|
+
if workers.is_shutting_down(self):
|
68
68
|
logger.debug("Aborting due to worker shutdown")
|
69
69
|
break
|
70
70
|
|
@@ -203,54 +203,51 @@ def delete_stale_killmails() -> None:
|
|
203
203
|
|
204
204
|
|
205
205
|
@shared_task(
|
206
|
-
bind=True,
|
207
|
-
base=QueueOnce, # celery_once locks stay intact during retries
|
208
|
-
timeout=KILLTRACKER_TASKS_TIMEOUT,
|
209
|
-
retry_backoff=False,
|
210
|
-
max_retries=None,
|
206
|
+
bind=True, base=QueueOnce, timeout=KILLTRACKER_TASKS_TIMEOUT, max_retries=None
|
211
207
|
)
|
212
208
|
def send_messages_to_webhook(self: Task, webhook_pk: int) -> None:
|
213
|
-
"""Sends
|
209
|
+
"""Sends queued messages to a webhook.
|
214
210
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
211
|
+
Note: This task will retry after processing a set number of messages
|
212
|
+
to avoid running potentially forever.
|
213
|
+
"""
|
214
|
+
|
215
|
+
webhook: Webhook = Webhook.objects.get(pk=webhook_pk)
|
219
216
|
if not webhook.is_enabled:
|
220
217
|
logger.info("%s: Webhook is disabled - aborting", webhook)
|
221
218
|
return
|
222
219
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
220
|
+
for _ in range(KILLTRACKER_MAX_MESSAGES_SENT_PER_RUN):
|
221
|
+
if workers.is_shutting_down(self):
|
222
|
+
logger.debug("Aborting due to worker shutdown")
|
223
|
+
return
|
227
224
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
logger.warning(
|
233
|
-
"%s: Too many requests for webhook. Blocked for %s seconds. Aborting.",
|
234
|
-
webhook,
|
235
|
-
ex.retry_after,
|
236
|
-
)
|
237
|
-
return
|
225
|
+
message = webhook.dequeue_message()
|
226
|
+
if not message:
|
227
|
+
logger.debug("%s: No more messages to send for webhook", webhook)
|
228
|
+
break
|
238
229
|
|
239
|
-
if not response.status_ok:
|
240
|
-
webhook.enqueue_message(message, is_error=True)
|
241
|
-
logger.warning(
|
242
|
-
"%s: Failed to send message for Killmail %d to webhook, will retry. "
|
243
|
-
"HTTP status code: %d, response: %s",
|
244
|
-
webhook,
|
245
|
-
message.killmail_id,
|
246
|
-
response.status_code,
|
247
|
-
response.content,
|
248
|
-
)
|
249
|
-
else:
|
250
230
|
try:
|
251
|
-
message_id =
|
252
|
-
|
253
|
-
|
231
|
+
message_id = webhook.send_message(message)
|
232
|
+
|
233
|
+
except WebhookRateLimitExhausted as ex:
|
234
|
+
webhook.enqueue_message(message)
|
235
|
+
logger.warning(
|
236
|
+
"%s: Webhook temporarily blocked. Retrying at %s.", webhook, ex.retry_at
|
237
|
+
)
|
238
|
+
raise self.retry(eta=ex.retry_at)
|
239
|
+
|
240
|
+
except HTTPError as ex:
|
241
|
+
webhook.enqueue_message(message, is_error=True)
|
242
|
+
logger.warning(
|
243
|
+
"%s: Failed to send message for Killmail %d to webhook, will retry. "
|
244
|
+
"HTTP status code: %d",
|
245
|
+
webhook,
|
246
|
+
message.killmail_id,
|
247
|
+
ex.status_code,
|
248
|
+
)
|
249
|
+
continue
|
250
|
+
|
254
251
|
logger.info(
|
255
252
|
"%s: Discord message %s created for killmail %d",
|
256
253
|
webhook,
|
@@ -258,7 +255,8 @@ def send_messages_to_webhook(self: Task, webhook_pk: int) -> None:
|
|
258
255
|
message.killmail_id,
|
259
256
|
)
|
260
257
|
|
261
|
-
|
258
|
+
if webhook.messages_queued() > 0:
|
259
|
+
raise self.retry(countdown=KILLTRACKER_DISCORD_SEND_DELAY)
|
262
260
|
|
263
261
|
|
264
262
|
@shared_task(timeout=KILLTRACKER_TASKS_TIMEOUT)
|
@@ -0,0 +1,184 @@
|
|
1
|
+
import datetime as dt
|
2
|
+
from unittest.mock import patch
|
3
|
+
|
4
|
+
import dhooks_lite
|
5
|
+
import requests_mock
|
6
|
+
|
7
|
+
from django.utils.timezone import now
|
8
|
+
|
9
|
+
from app_utils.testing import NoSocketsTestCase
|
10
|
+
|
11
|
+
from killtracker.core.discord import (
|
12
|
+
DiscordMessage,
|
13
|
+
HTTPError,
|
14
|
+
WebhookRateLimitExhausted,
|
15
|
+
_make_key_last_request,
|
16
|
+
_make_key_retry_at,
|
17
|
+
send_message_to_webhook,
|
18
|
+
)
|
19
|
+
from killtracker.tests.utils import CacheFake
|
20
|
+
|
21
|
+
MODULE_PATH = "killtracker.core.discord"
|
22
|
+
|
23
|
+
|
24
|
+
class TestDiscordMessage(NoSocketsTestCase):
|
25
|
+
def test_can_create(self):
|
26
|
+
o = DiscordMessage(content="content")
|
27
|
+
self.assertEqual(o.content, "content")
|
28
|
+
|
29
|
+
def test_should_raise_exception_when_invalid(self):
|
30
|
+
with self.assertRaises(ValueError):
|
31
|
+
DiscordMessage(username="user")
|
32
|
+
|
33
|
+
def test_can_convert_to_and_from_json_1(self):
|
34
|
+
o1 = DiscordMessage(
|
35
|
+
content="content",
|
36
|
+
)
|
37
|
+
s = o1.to_json()
|
38
|
+
o2 = DiscordMessage.from_json(s)
|
39
|
+
self.assertEqual(o1, o2)
|
40
|
+
|
41
|
+
def test_can_convert_to_and_from_json_2(self):
|
42
|
+
o1 = DiscordMessage(
|
43
|
+
avatar_url="avatar_url",
|
44
|
+
content="content",
|
45
|
+
embeds=[dhooks_lite.Embed(description="description")],
|
46
|
+
killmail_id=42,
|
47
|
+
username="username",
|
48
|
+
)
|
49
|
+
s = o1.to_json()
|
50
|
+
o2 = DiscordMessage.from_json(s)
|
51
|
+
self.assertEqual(o1, o2)
|
52
|
+
|
53
|
+
|
54
|
+
@requests_mock.Mocker()
|
55
|
+
@patch(MODULE_PATH + ".cache", new_callable=CacheFake)
|
56
|
+
class TestWebhookSendMessage(NoSocketsTestCase):
|
57
|
+
def setUp(self) -> None:
|
58
|
+
self.name = "webhook"
|
59
|
+
self.message = DiscordMessage(content="Test message")
|
60
|
+
self.url = "https://webhook.example.com/1234"
|
61
|
+
self.message_api = {
|
62
|
+
"name": "test webhook",
|
63
|
+
"type": 1,
|
64
|
+
"channel_id": "199737254929760256",
|
65
|
+
"token": "3d89bb7572e0fb30d8128367b3b1b44fecd1726de135cbe28a41f8b2f777c372ba2939e72279b94526ff5d1bd4358d65cf11",
|
66
|
+
"avatar": None,
|
67
|
+
"guild_id": "199737254929760256",
|
68
|
+
"id": "223704706495545344",
|
69
|
+
"application_id": None,
|
70
|
+
"user": {
|
71
|
+
"username": "test",
|
72
|
+
"discriminator": "7479",
|
73
|
+
"id": "190320984123768832",
|
74
|
+
"avatar": "b004ec1740a63ca06ae2e14c5cee11f3",
|
75
|
+
"public_flags": 131328,
|
76
|
+
},
|
77
|
+
}
|
78
|
+
|
79
|
+
def test_when_send_ok_returns_true(self, requests_mocker, mock_cache):
|
80
|
+
# given
|
81
|
+
requests_mocker.register_uri(
|
82
|
+
"POST", self.url, status_code=200, json=self.message_api
|
83
|
+
)
|
84
|
+
# when
|
85
|
+
got = send_message_to_webhook(
|
86
|
+
name=self.name, url=self.url, message=self.message
|
87
|
+
)
|
88
|
+
# then
|
89
|
+
self.assertEqual(got, 223704706495545344)
|
90
|
+
self.assertTrue(requests_mocker.called)
|
91
|
+
|
92
|
+
def test_should_ignore_invalid_key_for_last_request(
|
93
|
+
self, requests_mocker, mock_cache
|
94
|
+
):
|
95
|
+
# given
|
96
|
+
mock_cache.set(_make_key_last_request(self.url), "invalid")
|
97
|
+
requests_mocker.register_uri(
|
98
|
+
"POST", self.url, status_code=200, json=self.message_api
|
99
|
+
)
|
100
|
+
# when
|
101
|
+
got = send_message_to_webhook(
|
102
|
+
name=self.name, url=self.url, message=self.message
|
103
|
+
)
|
104
|
+
# then
|
105
|
+
self.assertEqual(got, 223704706495545344)
|
106
|
+
self.assertTrue(requests_mocker.called)
|
107
|
+
|
108
|
+
def test_should_ignore_invalid_key_for_retry_at(self, requests_mocker, mock_cache):
|
109
|
+
# given
|
110
|
+
mock_cache.set(_make_key_retry_at(self.url), "invalid")
|
111
|
+
requests_mocker.register_uri(
|
112
|
+
"POST", self.url, status_code=200, json=self.message_api
|
113
|
+
)
|
114
|
+
# when
|
115
|
+
got = send_message_to_webhook(
|
116
|
+
name=self.name, url=self.url, message=self.message
|
117
|
+
)
|
118
|
+
# then
|
119
|
+
self.assertEqual(got, 223704706495545344)
|
120
|
+
self.assertTrue(requests_mocker.called)
|
121
|
+
|
122
|
+
def test_when_send_not_ok_raise_error(self, requests_mocker, mock_cache):
|
123
|
+
# given
|
124
|
+
requests_mocker.register_uri("POST", self.url, status_code=404)
|
125
|
+
# when
|
126
|
+
with self.assertRaises(HTTPError) as ctx:
|
127
|
+
send_message_to_webhook(name=self.name, url=self.url, message=self.message)
|
128
|
+
# then
|
129
|
+
self.assertEqual(ctx.exception.status_code, 404)
|
130
|
+
self.assertTrue(requests_mocker.called)
|
131
|
+
|
132
|
+
def test_raise_too_many_requests_when_received_from_api(
|
133
|
+
self, requests_mocker, mock_cache
|
134
|
+
):
|
135
|
+
# given
|
136
|
+
requests_mocker.register_uri(
|
137
|
+
"POST",
|
138
|
+
self.url,
|
139
|
+
status_code=429,
|
140
|
+
json={
|
141
|
+
"global": False,
|
142
|
+
"message": "You are being rate limited.",
|
143
|
+
"retry_after": 2000,
|
144
|
+
},
|
145
|
+
headers={
|
146
|
+
"x-ratelimit-remaining": "5",
|
147
|
+
"x-ratelimit-reset-after": "60",
|
148
|
+
"Retry-After": "2000",
|
149
|
+
},
|
150
|
+
)
|
151
|
+
# when/then
|
152
|
+
with self.assertRaises(WebhookRateLimitExhausted) as ctx:
|
153
|
+
send_message_to_webhook(name=self.name, url=self.url, message=self.message)
|
154
|
+
|
155
|
+
self.assertTrue(ctx.exception.retry_at)
|
156
|
+
|
157
|
+
def test_too_many_requests_no_retry_value(self, requests_mocker, mock_cache):
|
158
|
+
# given
|
159
|
+
requests_mocker.register_uri(
|
160
|
+
"POST",
|
161
|
+
self.url,
|
162
|
+
status_code=429,
|
163
|
+
headers={
|
164
|
+
"x-ratelimit-remaining": "5",
|
165
|
+
"x-ratelimit-reset-after": "60",
|
166
|
+
},
|
167
|
+
)
|
168
|
+
# when/then
|
169
|
+
with self.assertRaises(WebhookRateLimitExhausted) as ctx:
|
170
|
+
send_message_to_webhook(name=self.name, url=self.url, message=self.message)
|
171
|
+
|
172
|
+
self.assertTrue(ctx.exception.retry_at)
|
173
|
+
|
174
|
+
def test_should_reraise_exception_when_not_expired(
|
175
|
+
self, requests_mocker, mock_cache
|
176
|
+
):
|
177
|
+
# given
|
178
|
+
key = _make_key_retry_at(self.url)
|
179
|
+
mock_cache.set(key, now() + dt.timedelta(hours=1))
|
180
|
+
# when
|
181
|
+
with self.assertRaises(WebhookRateLimitExhausted) as ctx:
|
182
|
+
send_message_to_webhook(name=self.name, url=self.url, message=self.message)
|
183
|
+
# then
|
184
|
+
self.assertTrue(ctx.exception.retry_at)
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import datetime as dt
|
2
|
+
from typing import Any, NamedTuple, Optional
|
3
|
+
from unittest import TestCase
|
4
|
+
|
5
|
+
from killtracker.core.helpers import datetime_or_none
|
6
|
+
|
7
|
+
|
8
|
+
class TestDatetimeOrNone(TestCase):
|
9
|
+
def test_should_return_value(self):
|
10
|
+
class Case(NamedTuple):
|
11
|
+
value: Any
|
12
|
+
want: Optional[dt.datetime]
|
13
|
+
|
14
|
+
now = dt.datetime.now()
|
15
|
+
cases = [
|
16
|
+
Case(now, now),
|
17
|
+
Case("abc", None),
|
18
|
+
Case(42, None),
|
19
|
+
Case(None, None),
|
20
|
+
]
|
21
|
+
for tc in cases:
|
22
|
+
got = datetime_or_none(tc.value)
|
23
|
+
self.assertIs(got, tc.want)
|
@@ -2,7 +2,10 @@ import dhooks_lite
|
|
2
2
|
|
3
3
|
from app_utils.testing import NoSocketsTestCase
|
4
4
|
|
5
|
-
from killtracker.core import
|
5
|
+
from killtracker.core.trackers import (
|
6
|
+
_create_embed,
|
7
|
+
create_discord_message_from_killmail,
|
8
|
+
)
|
6
9
|
from killtracker.tests.testdata.factories import (
|
7
10
|
EveEntityVariant,
|
8
11
|
KillmailFactory,
|
@@ -25,7 +28,7 @@ class TestCreateEmbed(NoSocketsTestCase):
|
|
25
28
|
tracker = TrackerFactory()
|
26
29
|
killmail = KillmailFactory()
|
27
30
|
# when
|
28
|
-
embed =
|
31
|
+
embed = _create_embed(tracker, killmail)
|
29
32
|
# then
|
30
33
|
self.assertIsInstance(embed, dhooks_lite.Embed)
|
31
34
|
|
@@ -34,7 +37,7 @@ class TestCreateEmbed(NoSocketsTestCase):
|
|
34
37
|
tracker = TrackerFactory()
|
35
38
|
killmail = KillmailFactory(zkb__total_value=None)
|
36
39
|
# when
|
37
|
-
embed =
|
40
|
+
embed = _create_embed(tracker, killmail)
|
38
41
|
# then
|
39
42
|
self.assertIsInstance(embed, dhooks_lite.Embed)
|
40
43
|
|
@@ -43,7 +46,7 @@ class TestCreateEmbed(NoSocketsTestCase):
|
|
43
46
|
tracker = TrackerFactory()
|
44
47
|
killmail = KillmailFactory(victim__alliance_id=None)
|
45
48
|
# when
|
46
|
-
embed =
|
49
|
+
embed = _create_embed(tracker, killmail)
|
47
50
|
# then
|
48
51
|
self.assertIsInstance(embed, dhooks_lite.Embed)
|
49
52
|
|
@@ -54,7 +57,7 @@ class TestCreateEmbed(NoSocketsTestCase):
|
|
54
57
|
victim__alliance_id=None, victim__corporation_id=None
|
55
58
|
)
|
56
59
|
# when
|
57
|
-
embed =
|
60
|
+
embed = _create_embed(tracker, killmail)
|
58
61
|
# then
|
59
62
|
self.assertIsInstance(embed, dhooks_lite.Embed)
|
60
63
|
|
@@ -64,7 +67,7 @@ class TestCreateEmbed(NoSocketsTestCase):
|
|
64
67
|
killmail = KillmailFactory()
|
65
68
|
killmail.attackers.remove(killmail.attacker_final_blow())
|
66
69
|
# when
|
67
|
-
embed =
|
70
|
+
embed = _create_embed(tracker, killmail)
|
68
71
|
# then
|
69
72
|
self.assertIsInstance(embed, dhooks_lite.Embed)
|
70
73
|
|
@@ -73,7 +76,7 @@ class TestCreateEmbed(NoSocketsTestCase):
|
|
73
76
|
tracker = TrackerFactory()
|
74
77
|
killmail = KillmailFactory().clone_with_tracker_info(tracker.pk)
|
75
78
|
# when
|
76
|
-
embed =
|
79
|
+
embed = _create_embed(tracker, killmail)
|
77
80
|
# then
|
78
81
|
self.assertIsInstance(embed, dhooks_lite.Embed)
|
79
82
|
|
@@ -85,41 +88,11 @@ class TestCreateEmbed(NoSocketsTestCase):
|
|
85
88
|
tracker.pk, jumps=3, distance=3.5, matching_ship_type_ids=[ship_type.id]
|
86
89
|
)
|
87
90
|
# when
|
88
|
-
embed =
|
91
|
+
embed = _create_embed(tracker, killmail)
|
89
92
|
# then
|
90
93
|
self.assertIsInstance(embed, dhooks_lite.Embed)
|
91
94
|
|
92
95
|
|
93
|
-
class TestDiscordMessage(NoSocketsTestCase):
|
94
|
-
def test_can_create(self):
|
95
|
-
o = discord_messages.DiscordMessage(content="content")
|
96
|
-
self.assertEqual(o.content, "content")
|
97
|
-
|
98
|
-
def test_should_raise_exception_when_invalid(self):
|
99
|
-
with self.assertRaises(ValueError):
|
100
|
-
discord_messages.DiscordMessage(username="user")
|
101
|
-
|
102
|
-
def test_can_convert_to_and_from_json_1(self):
|
103
|
-
o1 = discord_messages.DiscordMessage(
|
104
|
-
content="content",
|
105
|
-
)
|
106
|
-
s = o1.to_json()
|
107
|
-
o2 = discord_messages.DiscordMessage.from_json(s)
|
108
|
-
self.assertEqual(o1, o2)
|
109
|
-
|
110
|
-
def test_can_convert_to_and_from_json_2(self):
|
111
|
-
o1 = discord_messages.DiscordMessage(
|
112
|
-
avatar_url="avatar_url",
|
113
|
-
content="content",
|
114
|
-
embeds=[dhooks_lite.Embed(description="description")],
|
115
|
-
killmail_id=42,
|
116
|
-
username="username",
|
117
|
-
)
|
118
|
-
s = o1.to_json()
|
119
|
-
o2 = discord_messages.DiscordMessage.from_json(s)
|
120
|
-
self.assertEqual(o1, o2)
|
121
|
-
|
122
|
-
|
123
96
|
class TestDiscordMessageFromKillmail(NoSocketsTestCase):
|
124
97
|
@classmethod
|
125
98
|
def setUpClass(cls):
|
@@ -132,6 +105,6 @@ class TestDiscordMessageFromKillmail(NoSocketsTestCase):
|
|
132
105
|
tracker = TrackerFactory()
|
133
106
|
killmail = KillmailFactory()
|
134
107
|
# when
|
135
|
-
m =
|
108
|
+
m = create_discord_message_from_killmail(tracker, killmail)
|
136
109
|
# then
|
137
110
|
self.assertIsInstance(m.embeds[0], dhooks_lite.Embed)
|
@@ -8,16 +8,16 @@ from django.test import TestCase
|
|
8
8
|
|
9
9
|
from app_utils.django import app_labels
|
10
10
|
|
11
|
-
from killtracker.core.
|
11
|
+
from killtracker.core.zkb import Killmail
|
12
12
|
from killtracker.models import Tracker
|
13
13
|
from killtracker.tests.testdata.factories import TrackerFactory
|
14
14
|
from killtracker.tests.testdata.helpers import LoadTestDataMixin, load_killmail
|
15
15
|
|
16
|
-
|
16
|
+
MODULE_PATH = "killtracker.core.trackers"
|
17
17
|
|
18
18
|
if "discord" in app_labels():
|
19
19
|
|
20
|
-
@patch(
|
20
|
+
@patch(MODULE_PATH + "._import_discord_user")
|
21
21
|
class TestGroupPings(LoadTestDataMixin, TestCase):
|
22
22
|
@classmethod
|
23
23
|
def setUpClass(cls):
|