aa-killtracker 0.17.0__py3-none-any.whl → 1.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_killtracker-0.17.0.dist-info → aa_killtracker-1.0.0.dist-info}/METADATA +7 -8
- {aa_killtracker-0.17.0.dist-info → aa_killtracker-1.0.0.dist-info}/RECORD +36 -29
- killtracker/__init__.py +1 -1
- killtracker/admin.py +13 -8
- killtracker/app_settings.py +20 -10
- killtracker/apps.py +2 -4
- killtracker/core/discord.py +162 -0
- killtracker/core/helpers.py +13 -0
- killtracker/core/{discord_messages.py → trackers.py} +74 -59
- killtracker/core/workers.py +46 -0
- killtracker/core/{killmails.py → zkb.py} +97 -72
- killtracker/forms.py +1 -1
- killtracker/managers.py +3 -3
- killtracker/models/trackers.py +7 -10
- killtracker/models/webhooks.py +60 -128
- killtracker/providers.py +1 -1
- killtracker/signals.py +31 -0
- killtracker/tasks.py +141 -92
- 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} +28 -8
- killtracker/tests/core/{test_discord_messages_2.py → test_tracker_2.py} +11 -11
- killtracker/tests/core/test_workers.py +49 -0
- killtracker/tests/core/{test_killmails.py → test_zkb.py} +109 -52
- killtracker/tests/models/test_killmails.py +0 -2
- killtracker/tests/models/test_trackers_1.py +24 -24
- killtracker/tests/models/test_trackers_2.py +6 -5
- killtracker/tests/models/test_webhooks.py +63 -0
- killtracker/tests/test_integration.py +25 -12
- killtracker/tests/test_tasks.py +161 -92
- 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 +44 -0
- killtracker/exceptions.py +0 -32
- killtracker/tests/models/test_webhook.py +0 -150
- killtracker/tests/test_exceptions.py +0 -12
- {aa_killtracker-0.17.0.dist-info → aa_killtracker-1.0.0.dist-info}/WHEEL +0 -0
- {aa_killtracker-0.17.0.dist-info → aa_killtracker-1.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -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,6 +88,23 @@ 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)
|
94
|
+
|
95
|
+
|
96
|
+
class TestDiscordMessageFromKillmail(NoSocketsTestCase):
|
97
|
+
@classmethod
|
98
|
+
def setUpClass(cls):
|
99
|
+
super().setUpClass()
|
100
|
+
load_eveuniverse()
|
101
|
+
load_eve_entities()
|
102
|
+
|
103
|
+
def test_should_create_from_killmail(self):
|
104
|
+
# given
|
105
|
+
tracker = TrackerFactory()
|
106
|
+
killmail = KillmailFactory()
|
107
|
+
# when
|
108
|
+
m = create_discord_message_from_killmail(tracker, killmail)
|
109
|
+
# then
|
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):
|
@@ -51,8 +51,8 @@ if "discord" in app_labels():
|
|
51
51
|
self.assertTrue(
|
52
52
|
mock_import_discord_user.return_value.objects.group_to_role.called
|
53
53
|
)
|
54
|
-
self.assertEqual(self.webhook_1.
|
55
|
-
message = json.loads(self.webhook_1.
|
54
|
+
self.assertEqual(self.webhook_1._main_queue.size(), 1)
|
55
|
+
message = json.loads(self.webhook_1._main_queue.dequeue())
|
56
56
|
self.assertIn(f"<@&{self.group_1.pk}>", message["content"])
|
57
57
|
|
58
58
|
def test_can_ping_multiple_groups(self, mock_import_discord_user):
|
@@ -70,8 +70,8 @@ if "discord" in app_labels():
|
|
70
70
|
self.assertTrue(
|
71
71
|
mock_import_discord_user.return_value.objects.group_to_role.called
|
72
72
|
)
|
73
|
-
self.assertEqual(self.webhook_1.
|
74
|
-
message = json.loads(self.webhook_1.
|
73
|
+
self.assertEqual(self.webhook_1._main_queue.size(), 1)
|
74
|
+
message = json.loads(self.webhook_1._main_queue.dequeue())
|
75
75
|
self.assertIn(f"<@&{self.group_1.pk}>", message["content"])
|
76
76
|
self.assertIn(f"<@&{self.group_2.pk}>", message["content"])
|
77
77
|
|
@@ -91,8 +91,8 @@ if "discord" in app_labels():
|
|
91
91
|
self.assertTrue(
|
92
92
|
mock_import_discord_user.return_value.objects.group_to_role.called
|
93
93
|
)
|
94
|
-
self.assertEqual(self.webhook_1.
|
95
|
-
message = json.loads(self.webhook_1.
|
94
|
+
self.assertEqual(self.webhook_1._main_queue.size(), 1)
|
95
|
+
message = json.loads(self.webhook_1._main_queue.dequeue())
|
96
96
|
self.assertIn(f"<@&{self.group_1.pk}>", message["content"])
|
97
97
|
self.assertIn("@here", message["content"])
|
98
98
|
|
@@ -110,6 +110,6 @@ if "discord" in app_labels():
|
|
110
110
|
self.assertTrue(
|
111
111
|
mock_import_discord_user.return_value.objects.group_to_role.called
|
112
112
|
)
|
113
|
-
self.assertEqual(self.webhook_1.
|
114
|
-
message = json.loads(self.webhook_1.
|
113
|
+
self.assertEqual(self.webhook_1._main_queue.size(), 1)
|
114
|
+
message = json.loads(self.webhook_1._main_queue.dequeue())
|
115
115
|
self.assertNotIn(f"<@&{self.group_1.pk}>", message["content"])
|
@@ -0,0 +1,49 @@
|
|
1
|
+
from unittest.mock import patch
|
2
|
+
|
3
|
+
from django.test import TestCase
|
4
|
+
|
5
|
+
from killtracker.core import workers
|
6
|
+
from killtracker.tests.utils import CacheFake
|
7
|
+
|
8
|
+
MODULE_PATH = "killtracker.core.workers"
|
9
|
+
|
10
|
+
|
11
|
+
class TaskFake:
|
12
|
+
class RequestFake:
|
13
|
+
def __init__(self, hostname: str):
|
14
|
+
self.hostname = hostname
|
15
|
+
|
16
|
+
def __init__(self, hostname: str):
|
17
|
+
self.request = self.RequestFake(hostname)
|
18
|
+
|
19
|
+
|
20
|
+
class TestWorker(TestCase):
|
21
|
+
def test_should_report_false_when_not_set(self):
|
22
|
+
with patch(MODULE_PATH + ".cache", new_callable=CacheFake):
|
23
|
+
got = workers.is_shutting_down("dummy")
|
24
|
+
self.assertFalse(got)
|
25
|
+
|
26
|
+
def test_should_report_true_when_set(self):
|
27
|
+
with patch(MODULE_PATH + ".cache", new_callable=CacheFake):
|
28
|
+
workers.state_set("alpha")
|
29
|
+
got = workers.is_shutting_down(TaskFake("alpha"))
|
30
|
+
self.assertTrue(got)
|
31
|
+
|
32
|
+
def test_should_report_false_when_other_worker_is_shutting_down(self):
|
33
|
+
with patch(MODULE_PATH + ".cache", new_callable=CacheFake):
|
34
|
+
workers.state_set("alpha")
|
35
|
+
got = workers.is_shutting_down(TaskFake("bravo"))
|
36
|
+
self.assertFalse(got)
|
37
|
+
|
38
|
+
def test_should_report_false_when_task_not_valid(self):
|
39
|
+
with patch(MODULE_PATH + ".cache", new_callable=CacheFake):
|
40
|
+
workers.state_set("alpha")
|
41
|
+
got = workers.is_shutting_down("invalid")
|
42
|
+
self.assertFalse(got)
|
43
|
+
|
44
|
+
def test_should_report_false_when_worker_shutdown_has_reset(self):
|
45
|
+
with patch(MODULE_PATH + ".cache", new_callable=CacheFake):
|
46
|
+
workers.state_set("alpha")
|
47
|
+
workers.state_reset("alpha")
|
48
|
+
got = workers.is_shutting_down(TaskFake("alpha"))
|
49
|
+
self.assertFalse(got)
|