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.
Files changed (39) hide show
  1. {aa_killtracker-0.17.0.dist-info → aa_killtracker-1.0.0.dist-info}/METADATA +7 -8
  2. {aa_killtracker-0.17.0.dist-info → aa_killtracker-1.0.0.dist-info}/RECORD +36 -29
  3. killtracker/__init__.py +1 -1
  4. killtracker/admin.py +13 -8
  5. killtracker/app_settings.py +20 -10
  6. killtracker/apps.py +2 -4
  7. killtracker/core/discord.py +162 -0
  8. killtracker/core/helpers.py +13 -0
  9. killtracker/core/{discord_messages.py → trackers.py} +74 -59
  10. killtracker/core/workers.py +46 -0
  11. killtracker/core/{killmails.py → zkb.py} +97 -72
  12. killtracker/forms.py +1 -1
  13. killtracker/managers.py +3 -3
  14. killtracker/models/trackers.py +7 -10
  15. killtracker/models/webhooks.py +60 -128
  16. killtracker/providers.py +1 -1
  17. killtracker/signals.py +31 -0
  18. killtracker/tasks.py +141 -92
  19. killtracker/tests/core/test_discord.py +184 -0
  20. killtracker/tests/core/test_helpers.py +23 -0
  21. killtracker/tests/core/{test_discord_messages_1.py → test_tracker_1.py} +28 -8
  22. killtracker/tests/core/{test_discord_messages_2.py → test_tracker_2.py} +11 -11
  23. killtracker/tests/core/test_workers.py +49 -0
  24. killtracker/tests/core/{test_killmails.py → test_zkb.py} +109 -52
  25. killtracker/tests/models/test_killmails.py +0 -2
  26. killtracker/tests/models/test_trackers_1.py +24 -24
  27. killtracker/tests/models/test_trackers_2.py +6 -5
  28. killtracker/tests/models/test_webhooks.py +63 -0
  29. killtracker/tests/test_integration.py +25 -12
  30. killtracker/tests/test_tasks.py +161 -92
  31. killtracker/tests/test_utils.py +39 -0
  32. killtracker/tests/testdata/factories.py +1 -1
  33. killtracker/tests/testdata/helpers.py +1 -1
  34. killtracker/tests/utils.py +44 -0
  35. killtracker/exceptions.py +0 -32
  36. killtracker/tests/models/test_webhook.py +0 -150
  37. killtracker/tests/test_exceptions.py +0 -12
  38. {aa_killtracker-0.17.0.dist-info → aa_killtracker-1.0.0.dist-info}/WHEEL +0 -0
  39. {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 discord_messages
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 = discord_messages.create_embed(tracker, killmail)
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 = discord_messages.create_embed(tracker, killmail)
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 = discord_messages.create_embed(tracker, killmail)
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 = discord_messages.create_embed(tracker, killmail)
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 = discord_messages.create_embed(tracker, killmail)
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 = discord_messages.create_embed(tracker, killmail)
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 = discord_messages.create_embed(tracker, killmail)
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.killmails import Killmail
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
- DISCORD_MESSAGES_PATH = "killtracker.core.discord_messages"
16
+ MODULE_PATH = "killtracker.core.trackers"
17
17
 
18
18
  if "discord" in app_labels():
19
19
 
20
- @patch(DISCORD_MESSAGES_PATH + "._import_discord_user")
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.main_queue.size(), 1)
55
- message = json.loads(self.webhook_1.main_queue.dequeue())
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.main_queue.size(), 1)
74
- message = json.loads(self.webhook_1.main_queue.dequeue())
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.main_queue.size(), 1)
95
- message = json.loads(self.webhook_1.main_queue.dequeue())
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.main_queue.size(), 1)
114
- message = json.loads(self.webhook_1.main_queue.dequeue())
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)