arthexis 0.1.9__py3-none-any.whl → 0.1.10__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.
Potentially problematic release.
This version of arthexis might be problematic. Click here for more details.
- {arthexis-0.1.9.dist-info → arthexis-0.1.10.dist-info}/METADATA +63 -20
- {arthexis-0.1.9.dist-info → arthexis-0.1.10.dist-info}/RECORD +39 -36
- config/settings.py +221 -23
- config/urls.py +6 -0
- core/admin.py +401 -35
- core/apps.py +3 -0
- core/auto_upgrade.py +57 -0
- core/backends.py +77 -3
- core/fields.py +93 -0
- core/models.py +212 -7
- core/reference_utils.py +97 -0
- core/sigil_builder.py +16 -3
- core/system.py +157 -143
- core/tasks.py +151 -8
- core/test_system_info.py +37 -1
- core/tests.py +288 -12
- core/user_data.py +103 -8
- core/views.py +257 -15
- nodes/admin.py +12 -4
- nodes/backends.py +109 -17
- nodes/models.py +205 -2
- nodes/tests.py +370 -1
- nodes/views.py +140 -7
- ocpp/admin.py +63 -3
- ocpp/consumers.py +252 -41
- ocpp/evcs.py +6 -3
- ocpp/models.py +49 -7
- ocpp/simulator.py +62 -5
- ocpp/store.py +30 -0
- ocpp/tests.py +384 -8
- ocpp/views.py +101 -76
- pages/context_processors.py +20 -0
- pages/forms.py +131 -0
- pages/tests.py +434 -13
- pages/urls.py +1 -0
- pages/views.py +334 -92
- {arthexis-0.1.9.dist-info → arthexis-0.1.10.dist-info}/WHEEL +0 -0
- {arthexis-0.1.9.dist-info → arthexis-0.1.10.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.9.dist-info → arthexis-0.1.10.dist-info}/top_level.txt +0 -0
nodes/tests.py
CHANGED
|
@@ -9,6 +9,7 @@ from pathlib import Path
|
|
|
9
9
|
from types import SimpleNamespace
|
|
10
10
|
from unittest.mock import patch, call, MagicMock
|
|
11
11
|
from django.core import mail
|
|
12
|
+
from django.core.mail import EmailMessage
|
|
12
13
|
from django.core.management import call_command
|
|
13
14
|
import socket
|
|
14
15
|
import base64
|
|
@@ -18,6 +19,7 @@ from tempfile import TemporaryDirectory
|
|
|
18
19
|
import shutil
|
|
19
20
|
import stat
|
|
20
21
|
import time
|
|
22
|
+
from datetime import timedelta
|
|
21
23
|
|
|
22
24
|
from django.test import Client, TestCase, TransactionTestCase, override_settings
|
|
23
25
|
from django.urls import reverse
|
|
@@ -26,6 +28,7 @@ from django.contrib import admin
|
|
|
26
28
|
from django.contrib.sites.models import Site
|
|
27
29
|
from django_celery_beat.models import PeriodicTask
|
|
28
30
|
from django.conf import settings
|
|
31
|
+
from django.utils import timezone
|
|
29
32
|
from .actions import NodeAction
|
|
30
33
|
from selenium.common.exceptions import WebDriverException
|
|
31
34
|
from .utils import capture_screenshot
|
|
@@ -43,7 +46,7 @@ from .backends import OutboxEmailBackend
|
|
|
43
46
|
from .tasks import capture_node_screenshot, sample_clipboard
|
|
44
47
|
from cryptography.hazmat.primitives.asymmetric import rsa, padding
|
|
45
48
|
from cryptography.hazmat.primitives import serialization, hashes
|
|
46
|
-
from core.models import PackageRelease
|
|
49
|
+
from core.models import PackageRelease, SecurityGroup
|
|
47
50
|
|
|
48
51
|
|
|
49
52
|
class NodeTests(TestCase):
|
|
@@ -156,6 +159,118 @@ class NodeTests(TestCase):
|
|
|
156
159
|
node.refresh_from_db()
|
|
157
160
|
self.assertFalse(node.has_feature("clipboard-poll"))
|
|
158
161
|
|
|
162
|
+
def test_register_node_records_version_details(self):
|
|
163
|
+
url = reverse("register-node")
|
|
164
|
+
payload = {
|
|
165
|
+
"hostname": "versioned",
|
|
166
|
+
"address": "127.0.0.5",
|
|
167
|
+
"port": 8100,
|
|
168
|
+
"mac_address": "aa:bb:cc:dd:ee:10",
|
|
169
|
+
"installed_version": "2.0.1",
|
|
170
|
+
"installed_revision": "rev-abcdef",
|
|
171
|
+
}
|
|
172
|
+
response = self.client.post(
|
|
173
|
+
url, data=json.dumps(payload), content_type="application/json"
|
|
174
|
+
)
|
|
175
|
+
self.assertEqual(response.status_code, 200)
|
|
176
|
+
node = Node.objects.get(mac_address="aa:bb:cc:dd:ee:10")
|
|
177
|
+
self.assertEqual(node.installed_version, "2.0.1")
|
|
178
|
+
self.assertEqual(node.installed_revision, "rev-abcdef")
|
|
179
|
+
|
|
180
|
+
update_payload = {
|
|
181
|
+
**payload,
|
|
182
|
+
"installed_version": "2.1.0",
|
|
183
|
+
"installed_revision": "rev-fedcba",
|
|
184
|
+
}
|
|
185
|
+
second = self.client.post(
|
|
186
|
+
url, data=json.dumps(update_payload), content_type="application/json"
|
|
187
|
+
)
|
|
188
|
+
self.assertEqual(second.status_code, 200)
|
|
189
|
+
node.refresh_from_db()
|
|
190
|
+
self.assertEqual(node.installed_version, "2.1.0")
|
|
191
|
+
self.assertEqual(node.installed_revision, "rev-fedcba")
|
|
192
|
+
|
|
193
|
+
def test_register_node_update_triggers_notification(self):
|
|
194
|
+
node = Node.objects.create(
|
|
195
|
+
hostname="friend",
|
|
196
|
+
address="10.1.1.5",
|
|
197
|
+
port=8123,
|
|
198
|
+
mac_address="aa:bb:cc:dd:ee:01",
|
|
199
|
+
installed_version="1.0.0",
|
|
200
|
+
installed_revision="rev-old",
|
|
201
|
+
)
|
|
202
|
+
url = reverse("register-node")
|
|
203
|
+
payload = {
|
|
204
|
+
"hostname": "friend",
|
|
205
|
+
"address": "10.1.1.5",
|
|
206
|
+
"port": 8123,
|
|
207
|
+
"mac_address": "aa:bb:cc:dd:ee:01",
|
|
208
|
+
"installed_version": "2.0.0",
|
|
209
|
+
"installed_revision": "abcdef123456",
|
|
210
|
+
}
|
|
211
|
+
with patch("nodes.models.notify_async") as mock_notify:
|
|
212
|
+
response = self.client.post(
|
|
213
|
+
url, data=json.dumps(payload), content_type="application/json"
|
|
214
|
+
)
|
|
215
|
+
self.assertEqual(response.status_code, 200)
|
|
216
|
+
node.refresh_from_db()
|
|
217
|
+
self.assertEqual(node.installed_version, "2.0.0")
|
|
218
|
+
self.assertEqual(node.installed_revision, "abcdef123456")
|
|
219
|
+
mock_notify.assert_called_once()
|
|
220
|
+
subject, body = mock_notify.call_args[0]
|
|
221
|
+
self.assertEqual(subject, "UP friend")
|
|
222
|
+
self.assertEqual(body, "v2.0.0 r123456")
|
|
223
|
+
|
|
224
|
+
def test_register_node_update_without_version_change_still_notifies(self):
|
|
225
|
+
node = Node.objects.create(
|
|
226
|
+
hostname="friend",
|
|
227
|
+
address="10.1.1.5",
|
|
228
|
+
port=8123,
|
|
229
|
+
mac_address="aa:bb:cc:dd:ee:02",
|
|
230
|
+
installed_version="2.0.0",
|
|
231
|
+
installed_revision="abcdef123456",
|
|
232
|
+
)
|
|
233
|
+
url = reverse("register-node")
|
|
234
|
+
payload = {
|
|
235
|
+
"hostname": "friend",
|
|
236
|
+
"address": "10.1.1.5",
|
|
237
|
+
"port": 8123,
|
|
238
|
+
"mac_address": "aa:bb:cc:dd:ee:02",
|
|
239
|
+
"installed_version": "2.0.0",
|
|
240
|
+
"installed_revision": "abcdef123456",
|
|
241
|
+
}
|
|
242
|
+
with patch("nodes.models.notify_async") as mock_notify:
|
|
243
|
+
response = self.client.post(
|
|
244
|
+
url, data=json.dumps(payload), content_type="application/json"
|
|
245
|
+
)
|
|
246
|
+
self.assertEqual(response.status_code, 200)
|
|
247
|
+
node.refresh_from_db()
|
|
248
|
+
mock_notify.assert_called_once()
|
|
249
|
+
subject, body = mock_notify.call_args[0]
|
|
250
|
+
self.assertEqual(subject, "UP friend")
|
|
251
|
+
self.assertEqual(body, "v2.0.0 r123456")
|
|
252
|
+
|
|
253
|
+
def test_register_node_creation_triggers_notification(self):
|
|
254
|
+
url = reverse("register-node")
|
|
255
|
+
payload = {
|
|
256
|
+
"hostname": "newbie",
|
|
257
|
+
"address": "10.1.1.6",
|
|
258
|
+
"port": 8124,
|
|
259
|
+
"mac_address": "aa:bb:cc:dd:ee:03",
|
|
260
|
+
"installed_version": "3.0.0",
|
|
261
|
+
"installed_revision": "rev-1234567890",
|
|
262
|
+
}
|
|
263
|
+
with patch("nodes.models.notify_async") as mock_notify:
|
|
264
|
+
response = self.client.post(
|
|
265
|
+
url, data=json.dumps(payload), content_type="application/json"
|
|
266
|
+
)
|
|
267
|
+
self.assertEqual(response.status_code, 200)
|
|
268
|
+
self.assertTrue(Node.objects.filter(mac_address="aa:bb:cc:dd:ee:03").exists())
|
|
269
|
+
mock_notify.assert_called_once()
|
|
270
|
+
subject, body = mock_notify.call_args[0]
|
|
271
|
+
self.assertEqual(subject, "UP newbie")
|
|
272
|
+
self.assertEqual(body, "v3.0.0 r567890")
|
|
273
|
+
|
|
159
274
|
def test_register_node_sets_cors_headers(self):
|
|
160
275
|
payload = {
|
|
161
276
|
"hostname": "cors",
|
|
@@ -173,6 +288,74 @@ class NodeTests(TestCase):
|
|
|
173
288
|
self.assertEqual(response["Access-Control-Allow-Origin"], "http://example.com")
|
|
174
289
|
self.assertEqual(response["Access-Control-Allow-Credentials"], "true")
|
|
175
290
|
|
|
291
|
+
def test_register_node_requires_auth_without_signature(self):
|
|
292
|
+
self.client.logout()
|
|
293
|
+
payload = {
|
|
294
|
+
"hostname": "visitor",
|
|
295
|
+
"address": "127.0.0.1",
|
|
296
|
+
"port": 8000,
|
|
297
|
+
"mac_address": "aa:bb:cc:dd:ee:00",
|
|
298
|
+
}
|
|
299
|
+
response = self.client.post(
|
|
300
|
+
reverse("register-node"),
|
|
301
|
+
data=json.dumps(payload),
|
|
302
|
+
content_type="application/json",
|
|
303
|
+
HTTP_ORIGIN="http://example.com",
|
|
304
|
+
)
|
|
305
|
+
self.assertEqual(response.status_code, 401)
|
|
306
|
+
data = response.json()
|
|
307
|
+
self.assertEqual(data["detail"], "authentication required")
|
|
308
|
+
self.assertEqual(response["Access-Control-Allow-Origin"], "http://example.com")
|
|
309
|
+
|
|
310
|
+
def test_register_node_allows_preflight_without_authentication(self):
|
|
311
|
+
self.client.logout()
|
|
312
|
+
response = self.client.options(
|
|
313
|
+
reverse("register-node"), HTTP_ORIGIN="https://example.com"
|
|
314
|
+
)
|
|
315
|
+
self.assertEqual(response.status_code, 200)
|
|
316
|
+
self.assertEqual(response["Access-Control-Allow-Origin"], "https://example.com")
|
|
317
|
+
self.assertEqual(response["Access-Control-Allow-Credentials"], "true")
|
|
318
|
+
|
|
319
|
+
def test_register_node_accepts_signed_payload_without_login(self):
|
|
320
|
+
self.client.logout()
|
|
321
|
+
NodeFeature.objects.get_or_create(
|
|
322
|
+
slug="clipboard-poll", defaults={"display": "Clipboard Poll"}
|
|
323
|
+
)
|
|
324
|
+
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
|
325
|
+
public_bytes = private_key.public_key().public_bytes(
|
|
326
|
+
encoding=serialization.Encoding.PEM,
|
|
327
|
+
format=serialization.PublicFormat.SubjectPublicKeyInfo,
|
|
328
|
+
).decode()
|
|
329
|
+
token = "visitor-token"
|
|
330
|
+
signature = base64.b64encode(
|
|
331
|
+
private_key.sign(
|
|
332
|
+
token.encode(),
|
|
333
|
+
padding.PKCS1v15(),
|
|
334
|
+
hashes.SHA256(),
|
|
335
|
+
)
|
|
336
|
+
).decode()
|
|
337
|
+
payload = {
|
|
338
|
+
"hostname": "visitor",
|
|
339
|
+
"address": "127.0.0.1",
|
|
340
|
+
"port": 8000,
|
|
341
|
+
"mac_address": "aa:bb:cc:dd:ee:11",
|
|
342
|
+
"public_key": public_bytes,
|
|
343
|
+
"token": token,
|
|
344
|
+
"signature": signature,
|
|
345
|
+
"features": ["clipboard-poll"],
|
|
346
|
+
}
|
|
347
|
+
response = self.client.post(
|
|
348
|
+
reverse("register-node"),
|
|
349
|
+
data=json.dumps(payload),
|
|
350
|
+
content_type="application/json",
|
|
351
|
+
HTTP_ORIGIN="http://example.com",
|
|
352
|
+
)
|
|
353
|
+
self.assertEqual(response.status_code, 200)
|
|
354
|
+
self.assertEqual(response["Access-Control-Allow-Origin"], "http://example.com")
|
|
355
|
+
node = Node.objects.get(mac_address="aa:bb:cc:dd:ee:11")
|
|
356
|
+
self.assertEqual(node.public_key, public_bytes)
|
|
357
|
+
self.assertTrue(node.has_feature("clipboard-poll"))
|
|
358
|
+
|
|
176
359
|
def test_register_node_accepts_text_plain_payload(self):
|
|
177
360
|
payload = {
|
|
178
361
|
"hostname": "plain",
|
|
@@ -197,6 +380,26 @@ class NodeRegisterCurrentTests(TestCase):
|
|
|
197
380
|
self.client.force_login(self.user)
|
|
198
381
|
NodeRole.objects.get_or_create(name="Terminal")
|
|
199
382
|
|
|
383
|
+
def test_register_current_notifies_peers_on_start(self):
|
|
384
|
+
with TemporaryDirectory() as tmp:
|
|
385
|
+
base = Path(tmp)
|
|
386
|
+
with override_settings(BASE_DIR=base):
|
|
387
|
+
with (
|
|
388
|
+
patch(
|
|
389
|
+
"nodes.models.Node.get_current_mac",
|
|
390
|
+
return_value="00:ff:ee:dd:cc:bb",
|
|
391
|
+
),
|
|
392
|
+
patch("nodes.models.socket.gethostname", return_value="testhost"),
|
|
393
|
+
patch(
|
|
394
|
+
"nodes.models.socket.gethostbyname", return_value="127.0.0.1"
|
|
395
|
+
),
|
|
396
|
+
patch("nodes.models.revision.get_revision", return_value="rev"),
|
|
397
|
+
patch.object(Node, "ensure_keys"),
|
|
398
|
+
patch.object(Node, "notify_peers_of_update") as mock_notify,
|
|
399
|
+
):
|
|
400
|
+
Node.register_current()
|
|
401
|
+
mock_notify.assert_called_once()
|
|
402
|
+
|
|
200
403
|
def test_register_current_refreshes_lcd_feature(self):
|
|
201
404
|
NodeFeature.objects.get_or_create(
|
|
202
405
|
slug="lcd-screen", defaults={"display": "LCD Screen"}
|
|
@@ -262,6 +465,94 @@ class NodeRegisterCurrentTests(TestCase):
|
|
|
262
465
|
node.refresh_from_db()
|
|
263
466
|
self.assertTrue(node.has_feature("lcd-screen"))
|
|
264
467
|
|
|
468
|
+
def test_register_current_notifies_peers_on_version_upgrade(self):
|
|
469
|
+
remote = Node.objects.create(
|
|
470
|
+
hostname="remote",
|
|
471
|
+
address="10.0.0.2",
|
|
472
|
+
port=9100,
|
|
473
|
+
mac_address="aa:bb:cc:dd:ee:ff",
|
|
474
|
+
)
|
|
475
|
+
with TemporaryDirectory() as tmp:
|
|
476
|
+
base = Path(tmp)
|
|
477
|
+
(base / "VERSION").write_text("2.0.0")
|
|
478
|
+
with override_settings(BASE_DIR=base):
|
|
479
|
+
with (
|
|
480
|
+
patch(
|
|
481
|
+
"nodes.models.Node.get_current_mac",
|
|
482
|
+
return_value="00:ff:ee:dd:cc:bb",
|
|
483
|
+
),
|
|
484
|
+
patch("nodes.models.socket.gethostname", return_value="localnode"),
|
|
485
|
+
patch(
|
|
486
|
+
"nodes.models.socket.gethostbyname",
|
|
487
|
+
return_value="192.168.1.5",
|
|
488
|
+
),
|
|
489
|
+
patch("nodes.models.revision.get_revision", return_value="newrev"),
|
|
490
|
+
patch("requests.post") as mock_post,
|
|
491
|
+
):
|
|
492
|
+
Node.objects.create(
|
|
493
|
+
hostname="localnode",
|
|
494
|
+
address="192.168.1.5",
|
|
495
|
+
port=8000,
|
|
496
|
+
mac_address="00:ff:ee:dd:cc:bb",
|
|
497
|
+
installed_version="1.9.0",
|
|
498
|
+
installed_revision="oldrev",
|
|
499
|
+
)
|
|
500
|
+
mock_post.return_value = SimpleNamespace(
|
|
501
|
+
ok=True, status_code=200, text=""
|
|
502
|
+
)
|
|
503
|
+
node, created = Node.register_current()
|
|
504
|
+
self.assertFalse(created)
|
|
505
|
+
self.assertGreaterEqual(mock_post.call_count, 1)
|
|
506
|
+
args, kwargs = mock_post.call_args
|
|
507
|
+
self.assertIn(str(remote.port), args[0])
|
|
508
|
+
payload = json.loads(kwargs["data"])
|
|
509
|
+
self.assertEqual(payload["hostname"], "localnode")
|
|
510
|
+
self.assertEqual(payload["installed_version"], "2.0.0")
|
|
511
|
+
self.assertEqual(payload["installed_revision"], "newrev")
|
|
512
|
+
|
|
513
|
+
def test_register_current_notifies_peers_without_version_change(self):
|
|
514
|
+
Node.objects.create(
|
|
515
|
+
hostname="remote",
|
|
516
|
+
address="10.0.0.3",
|
|
517
|
+
port=9200,
|
|
518
|
+
mac_address="aa:bb:cc:dd:ee:11",
|
|
519
|
+
)
|
|
520
|
+
with TemporaryDirectory() as tmp:
|
|
521
|
+
base = Path(tmp)
|
|
522
|
+
(base / "VERSION").write_text("1.0.0")
|
|
523
|
+
with override_settings(BASE_DIR=base):
|
|
524
|
+
with (
|
|
525
|
+
patch(
|
|
526
|
+
"nodes.models.Node.get_current_mac",
|
|
527
|
+
return_value="00:ff:ee:dd:cc:cc",
|
|
528
|
+
),
|
|
529
|
+
patch("nodes.models.socket.gethostname", return_value="samever"),
|
|
530
|
+
patch(
|
|
531
|
+
"nodes.models.socket.gethostbyname",
|
|
532
|
+
return_value="192.168.1.6",
|
|
533
|
+
),
|
|
534
|
+
patch("nodes.models.revision.get_revision", return_value="rev1"),
|
|
535
|
+
patch("requests.post") as mock_post,
|
|
536
|
+
):
|
|
537
|
+
Node.objects.create(
|
|
538
|
+
hostname="samever",
|
|
539
|
+
address="192.168.1.6",
|
|
540
|
+
port=8000,
|
|
541
|
+
mac_address="00:ff:ee:dd:cc:cc",
|
|
542
|
+
installed_version="1.0.0",
|
|
543
|
+
installed_revision="rev1",
|
|
544
|
+
)
|
|
545
|
+
mock_post.return_value = SimpleNamespace(
|
|
546
|
+
ok=True, status_code=200, text=""
|
|
547
|
+
)
|
|
548
|
+
Node.register_current()
|
|
549
|
+
self.assertEqual(mock_post.call_count, 1)
|
|
550
|
+
args, kwargs = mock_post.call_args
|
|
551
|
+
self.assertIn("/nodes/register/", args[0])
|
|
552
|
+
payload = json.loads(kwargs["data"])
|
|
553
|
+
self.assertEqual(payload["installed_version"], "1.0.0")
|
|
554
|
+
self.assertEqual(payload.get("installed_revision"), "rev1")
|
|
555
|
+
|
|
265
556
|
@patch("nodes.views.capture_screenshot")
|
|
266
557
|
def test_capture_screenshot(self, mock_capture):
|
|
267
558
|
hostname = socket.gethostname()
|
|
@@ -399,6 +690,7 @@ class NodeRegisterCurrentTests(TestCase):
|
|
|
399
690
|
"body": "world",
|
|
400
691
|
"seen": [],
|
|
401
692
|
"sender": str(sender.uuid),
|
|
693
|
+
"origin": str(sender.uuid),
|
|
402
694
|
}
|
|
403
695
|
payload_json = json.dumps(payload, separators=(",", ":"), sort_keys=True)
|
|
404
696
|
signature = key.sign(payload_json.encode(), padding.PKCS1v15(), hashes.SHA256())
|
|
@@ -410,6 +702,8 @@ class NodeRegisterCurrentTests(TestCase):
|
|
|
410
702
|
)
|
|
411
703
|
self.assertEqual(resp.status_code, 200)
|
|
412
704
|
self.assertTrue(NetMessage.objects.filter(uuid=msg_id).exists())
|
|
705
|
+
message = NetMessage.objects.get(uuid=msg_id)
|
|
706
|
+
self.assertEqual(message.node_origin, sender)
|
|
413
707
|
|
|
414
708
|
def test_clipboard_polling_creates_task(self):
|
|
415
709
|
feature, _ = NodeFeature.objects.get_or_create(
|
|
@@ -446,6 +740,15 @@ class NodeRegisterCurrentTests(TestCase):
|
|
|
446
740
|
self.assertFalse(PeriodicTask.objects.filter(name=task_name).exists())
|
|
447
741
|
|
|
448
742
|
|
|
743
|
+
class CheckRegistrationReadyCommandTests(TestCase):
|
|
744
|
+
def test_command_completes_successfully(self):
|
|
745
|
+
NodeRole.objects.get_or_create(name="Terminal")
|
|
746
|
+
with TemporaryDirectory() as tmp:
|
|
747
|
+
base = Path(tmp)
|
|
748
|
+
with override_settings(BASE_DIR=base):
|
|
749
|
+
call_command("check_registration_ready")
|
|
750
|
+
|
|
751
|
+
|
|
449
752
|
class NodeAdminTests(TestCase):
|
|
450
753
|
|
|
451
754
|
def setUp(self):
|
|
@@ -727,6 +1030,11 @@ class NetMessagePropagationTests(TestCase):
|
|
|
727
1030
|
)
|
|
728
1031
|
)
|
|
729
1032
|
|
|
1033
|
+
def test_broadcast_sets_node_origin(self):
|
|
1034
|
+
with patch.object(Node, "get_local", return_value=self.local):
|
|
1035
|
+
msg = NetMessage.broadcast(subject="subject", body="body")
|
|
1036
|
+
self.assertEqual(msg.node_origin, self.local)
|
|
1037
|
+
|
|
730
1038
|
@patch("requests.post")
|
|
731
1039
|
@patch("core.notifications.notify")
|
|
732
1040
|
def test_propagate_forwards_to_three_and_notifies_local(
|
|
@@ -737,6 +1045,9 @@ class NetMessagePropagationTests(TestCase):
|
|
|
737
1045
|
msg.propagate(seen=[str(self.remotes[0].uuid)])
|
|
738
1046
|
mock_notify.assert_called_once_with("s", "b")
|
|
739
1047
|
self.assertEqual(mock_post.call_count, 3)
|
|
1048
|
+
for call_args in mock_post.call_args_list:
|
|
1049
|
+
payload = json.loads(call_args.kwargs["data"])
|
|
1050
|
+
self.assertEqual(payload.get("origin"), str(self.local.uuid))
|
|
740
1051
|
targets = {
|
|
741
1052
|
call.args[0].split("//")[1].split("/")[0]
|
|
742
1053
|
for call in mock_post.call_args_list
|
|
@@ -746,6 +1057,41 @@ class NetMessagePropagationTests(TestCase):
|
|
|
746
1057
|
self.assertEqual(msg.propagated_to.count(), 4)
|
|
747
1058
|
self.assertTrue(msg.complete)
|
|
748
1059
|
|
|
1060
|
+
@patch("requests.post")
|
|
1061
|
+
@patch("core.notifications.notify", return_value=True)
|
|
1062
|
+
def test_propagate_prunes_old_local_messages(self, mock_notify, mock_post):
|
|
1063
|
+
old_local = NetMessage.objects.create(
|
|
1064
|
+
subject="old local",
|
|
1065
|
+
body="body",
|
|
1066
|
+
reach=self.role,
|
|
1067
|
+
node_origin=self.local,
|
|
1068
|
+
)
|
|
1069
|
+
NetMessage.objects.filter(pk=old_local.pk).update(
|
|
1070
|
+
created=timezone.now() - timedelta(days=8)
|
|
1071
|
+
)
|
|
1072
|
+
old_remote = NetMessage.objects.create(
|
|
1073
|
+
subject="old remote",
|
|
1074
|
+
body="body",
|
|
1075
|
+
reach=self.role,
|
|
1076
|
+
node_origin=self.remotes[0],
|
|
1077
|
+
)
|
|
1078
|
+
NetMessage.objects.filter(pk=old_remote.pk).update(
|
|
1079
|
+
created=timezone.now() - timedelta(days=8)
|
|
1080
|
+
)
|
|
1081
|
+
msg = NetMessage.objects.create(
|
|
1082
|
+
subject="fresh",
|
|
1083
|
+
body="body",
|
|
1084
|
+
reach=self.role,
|
|
1085
|
+
node_origin=self.local,
|
|
1086
|
+
)
|
|
1087
|
+
with patch.object(Node, "get_local", return_value=self.local):
|
|
1088
|
+
msg.propagate()
|
|
1089
|
+
|
|
1090
|
+
mock_notify.assert_called_once_with("fresh", "body")
|
|
1091
|
+
self.assertFalse(NetMessage.objects.filter(pk=old_local.pk).exists())
|
|
1092
|
+
self.assertTrue(NetMessage.objects.filter(pk=old_remote.pk).exists())
|
|
1093
|
+
self.assertTrue(NetMessage.objects.filter(pk=msg.pk).exists())
|
|
1094
|
+
|
|
749
1095
|
|
|
750
1096
|
class NodeActionTests(TestCase):
|
|
751
1097
|
def setUp(self):
|
|
@@ -1034,6 +1380,29 @@ class EmailOutboxTests(TestCase):
|
|
|
1034
1380
|
self.assertEqual(email.subject, "sub")
|
|
1035
1381
|
self.assertEqual(email.to, ["to@example.com"])
|
|
1036
1382
|
|
|
1383
|
+
def test_string_representation_prefers_from_email(self):
|
|
1384
|
+
outbox = EmailOutbox.objects.create(
|
|
1385
|
+
host="smtp.example.com",
|
|
1386
|
+
port=587,
|
|
1387
|
+
username="mailer",
|
|
1388
|
+
password="secret",
|
|
1389
|
+
from_email="noreply@example.com",
|
|
1390
|
+
)
|
|
1391
|
+
|
|
1392
|
+
self.assertEqual(str(outbox), "noreply@example.com")
|
|
1393
|
+
|
|
1394
|
+
def test_string_representation_prefers_username_over_owner(self):
|
|
1395
|
+
group = SecurityGroup.objects.create(name="Operators")
|
|
1396
|
+
outbox = EmailOutbox.objects.create(
|
|
1397
|
+
group=group,
|
|
1398
|
+
host="smtp.example.com",
|
|
1399
|
+
port=587,
|
|
1400
|
+
username="mailer",
|
|
1401
|
+
password="secret",
|
|
1402
|
+
)
|
|
1403
|
+
|
|
1404
|
+
self.assertEqual(str(outbox), "mailer@smtp.example.com")
|
|
1405
|
+
|
|
1037
1406
|
|
|
1038
1407
|
class ClipboardTaskTests(TestCase):
|
|
1039
1408
|
@patch("nodes.tasks.pyperclip.paste")
|