arthexis 0.1.15__py3-none-any.whl → 0.1.16__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.15.dist-info → arthexis-0.1.16.dist-info}/METADATA +1 -2
- {arthexis-0.1.15.dist-info → arthexis-0.1.16.dist-info}/RECORD +36 -35
- config/urls.py +5 -0
- core/admin.py +174 -7
- core/admindocs.py +44 -3
- core/apps.py +1 -1
- core/backends.py +44 -8
- core/github_issues.py +12 -7
- core/mailer.py +9 -5
- core/models.py +64 -23
- core/release.py +52 -0
- core/system.py +208 -1
- core/tasks.py +5 -1
- core/test_system_info.py +16 -0
- core/tests.py +207 -0
- core/views.py +221 -33
- nodes/admin.py +25 -1
- nodes/models.py +70 -4
- nodes/rfid_sync.py +15 -0
- nodes/tests.py +119 -0
- nodes/utils.py +3 -0
- ocpp/consumers.py +38 -0
- ocpp/models.py +19 -4
- ocpp/tasks.py +156 -2
- ocpp/test_rfid.py +44 -2
- ocpp/tests.py +111 -1
- pages/admin.py +126 -4
- pages/context_processors.py +20 -1
- pages/models.py +3 -1
- pages/module_defaults.py +156 -0
- pages/tests.py +215 -7
- pages/urls.py +1 -0
- pages/views.py +61 -4
- {arthexis-0.1.15.dist-info → arthexis-0.1.16.dist-info}/WHEEL +0 -0
- {arthexis-0.1.15.dist-info → arthexis-0.1.16.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.15.dist-info → arthexis-0.1.16.dist-info}/top_level.txt +0 -0
core/tests.py
CHANGED
|
@@ -10,6 +10,7 @@ from django.urls import reverse
|
|
|
10
10
|
from django.http import HttpRequest
|
|
11
11
|
import csv
|
|
12
12
|
import json
|
|
13
|
+
import importlib.util
|
|
13
14
|
from decimal import Decimal
|
|
14
15
|
from unittest import mock
|
|
15
16
|
from unittest.mock import patch
|
|
@@ -22,6 +23,7 @@ import tempfile
|
|
|
22
23
|
from urllib.parse import quote
|
|
23
24
|
|
|
24
25
|
from django.utils import timezone
|
|
26
|
+
from django.conf import settings
|
|
25
27
|
from django.contrib.auth.models import Permission
|
|
26
28
|
from django.contrib.messages import get_messages
|
|
27
29
|
from tablib import Dataset
|
|
@@ -51,6 +53,7 @@ from core.admin import (
|
|
|
51
53
|
USER_PROFILE_INLINES,
|
|
52
54
|
)
|
|
53
55
|
from ocpp.models import Transaction, Charger
|
|
56
|
+
from nodes.models import ContentSample
|
|
54
57
|
|
|
55
58
|
from django.core.exceptions import ValidationError
|
|
56
59
|
from django.core.management import call_command
|
|
@@ -365,6 +368,45 @@ class RFIDLoginTests(TestCase):
|
|
|
365
368
|
self.assertEqual(response.status_code, 401)
|
|
366
369
|
mock_run.assert_called_once()
|
|
367
370
|
|
|
371
|
+
@patch("core.backends.subprocess.Popen")
|
|
372
|
+
def test_rfid_login_post_command_runs_after_success(self, mock_popen):
|
|
373
|
+
tag = self.account.rfids.first()
|
|
374
|
+
tag.post_auth_command = "echo welcome"
|
|
375
|
+
tag.save(update_fields=["post_auth_command"])
|
|
376
|
+
|
|
377
|
+
response = self.client.post(
|
|
378
|
+
reverse("rfid-login"),
|
|
379
|
+
data={"rfid": "CARD123"},
|
|
380
|
+
content_type="application/json",
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
self.assertEqual(response.status_code, 200)
|
|
384
|
+
mock_popen.assert_called_once()
|
|
385
|
+
args, kwargs = mock_popen.call_args
|
|
386
|
+
self.assertEqual(args[0], "echo welcome")
|
|
387
|
+
self.assertTrue(kwargs.get("shell"))
|
|
388
|
+
env = kwargs.get("env", {})
|
|
389
|
+
self.assertEqual(env.get("RFID_VALUE"), "CARD123")
|
|
390
|
+
self.assertEqual(env.get("RFID_LABEL_ID"), str(tag.pk))
|
|
391
|
+
self.assertIs(kwargs.get("stdout"), subprocess.DEVNULL)
|
|
392
|
+
self.assertIs(kwargs.get("stderr"), subprocess.DEVNULL)
|
|
393
|
+
|
|
394
|
+
@patch("core.backends.subprocess.Popen")
|
|
395
|
+
def test_rfid_login_post_command_skipped_on_failure(self, mock_popen):
|
|
396
|
+
tag = self.account.rfids.first()
|
|
397
|
+
tag.post_auth_command = "echo welcome"
|
|
398
|
+
tag.allowed = False
|
|
399
|
+
tag.save(update_fields=["post_auth_command", "allowed"])
|
|
400
|
+
|
|
401
|
+
response = self.client.post(
|
|
402
|
+
reverse("rfid-login"),
|
|
403
|
+
data={"rfid": "CARD123"},
|
|
404
|
+
content_type="application/json",
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
self.assertEqual(response.status_code, 401)
|
|
408
|
+
mock_popen.assert_not_called()
|
|
409
|
+
|
|
368
410
|
|
|
369
411
|
class RFIDBatchApiTests(TestCase):
|
|
370
412
|
def setUp(self):
|
|
@@ -387,6 +429,8 @@ class RFIDBatchApiTests(TestCase):
|
|
|
387
429
|
"rfid": "CARD999",
|
|
388
430
|
"custom_label": "Main Tag",
|
|
389
431
|
"energy_accounts": [self.account.id],
|
|
432
|
+
"external_command": "",
|
|
433
|
+
"post_auth_command": "",
|
|
390
434
|
"allowed": True,
|
|
391
435
|
"color": "B",
|
|
392
436
|
"released": False,
|
|
@@ -406,6 +450,8 @@ class RFIDBatchApiTests(TestCase):
|
|
|
406
450
|
"rfid": "CARD111",
|
|
407
451
|
"custom_label": "",
|
|
408
452
|
"energy_accounts": [],
|
|
453
|
+
"external_command": "",
|
|
454
|
+
"post_auth_command": "",
|
|
409
455
|
"allowed": True,
|
|
410
456
|
"color": "W",
|
|
411
457
|
"released": False,
|
|
@@ -426,6 +472,8 @@ class RFIDBatchApiTests(TestCase):
|
|
|
426
472
|
"rfid": "CARD112",
|
|
427
473
|
"custom_label": "",
|
|
428
474
|
"energy_accounts": [],
|
|
475
|
+
"external_command": "",
|
|
476
|
+
"post_auth_command": "",
|
|
429
477
|
"allowed": True,
|
|
430
478
|
"color": "B",
|
|
431
479
|
"released": True,
|
|
@@ -441,6 +489,8 @@ class RFIDBatchApiTests(TestCase):
|
|
|
441
489
|
"rfid": "A1B2C3D4",
|
|
442
490
|
"custom_label": "Imported Tag",
|
|
443
491
|
"energy_accounts": [self.account.id],
|
|
492
|
+
"external_command": "echo pre",
|
|
493
|
+
"post_auth_command": "echo post",
|
|
444
494
|
"allowed": True,
|
|
445
495
|
"color": "W",
|
|
446
496
|
"released": True,
|
|
@@ -459,6 +509,8 @@ class RFIDBatchApiTests(TestCase):
|
|
|
459
509
|
rfid="A1B2C3D4",
|
|
460
510
|
custom_label="Imported Tag",
|
|
461
511
|
energy_accounts=self.account,
|
|
512
|
+
external_command="echo pre",
|
|
513
|
+
post_auth_command="echo post",
|
|
462
514
|
color=RFID.WHITE,
|
|
463
515
|
released=True,
|
|
464
516
|
).exists()
|
|
@@ -1552,6 +1604,71 @@ class ReleaseProcessTests(TestCase):
|
|
|
1552
1604
|
self.assertEqual(count_file.read_text(), "1")
|
|
1553
1605
|
|
|
1554
1606
|
|
|
1607
|
+
class PackageReleaseFixtureTests(TestCase):
|
|
1608
|
+
def setUp(self):
|
|
1609
|
+
self.base = Path("core/fixtures")
|
|
1610
|
+
self.existing_fixtures = {
|
|
1611
|
+
path: path.read_text(encoding="utf-8")
|
|
1612
|
+
for path in self.base.glob("releases__*.json")
|
|
1613
|
+
}
|
|
1614
|
+
self.addCleanup(self._restore_fixtures)
|
|
1615
|
+
|
|
1616
|
+
self.package = Package.objects.create(name="fixture-pkg")
|
|
1617
|
+
self.release_one = PackageRelease.objects.create(
|
|
1618
|
+
package=self.package,
|
|
1619
|
+
version="9.9.9",
|
|
1620
|
+
)
|
|
1621
|
+
self.release_two = PackageRelease.objects.create(
|
|
1622
|
+
package=self.package,
|
|
1623
|
+
version="9.9.10",
|
|
1624
|
+
)
|
|
1625
|
+
|
|
1626
|
+
def _restore_fixtures(self):
|
|
1627
|
+
current_paths = set(self.base.glob("releases__*.json"))
|
|
1628
|
+
for path in current_paths:
|
|
1629
|
+
if path not in self.existing_fixtures:
|
|
1630
|
+
path.unlink()
|
|
1631
|
+
for path, content in self.existing_fixtures.items():
|
|
1632
|
+
path.write_text(content, encoding="utf-8")
|
|
1633
|
+
|
|
1634
|
+
def test_dump_fixture_only_writes_changed_releases(self):
|
|
1635
|
+
PackageRelease.dump_fixture()
|
|
1636
|
+
target_one = self.base / "releases__packagerelease_9_9_9.json"
|
|
1637
|
+
target_two = self.base / "releases__packagerelease_9_9_10.json"
|
|
1638
|
+
|
|
1639
|
+
self.assertTrue(target_one.exists())
|
|
1640
|
+
self.assertTrue(target_two.exists())
|
|
1641
|
+
|
|
1642
|
+
self.release_two.changelog = "updated notes"
|
|
1643
|
+
self.release_two.save(update_fields=["changelog"])
|
|
1644
|
+
|
|
1645
|
+
original_write = Path.write_text
|
|
1646
|
+
written_paths: list[Path] = []
|
|
1647
|
+
|
|
1648
|
+
def tracking_write_text(path_obj, data, *args, **kwargs):
|
|
1649
|
+
written_paths.append(path_obj)
|
|
1650
|
+
return original_write(path_obj, data, *args, **kwargs)
|
|
1651
|
+
|
|
1652
|
+
with mock.patch("pathlib.Path.write_text", tracking_write_text):
|
|
1653
|
+
PackageRelease.dump_fixture()
|
|
1654
|
+
|
|
1655
|
+
written_set = set(written_paths)
|
|
1656
|
+
self.assertNotIn(target_one, written_set)
|
|
1657
|
+
self.assertIn(target_two, written_set)
|
|
1658
|
+
self.assertIn("updated notes", target_two.read_text(encoding="utf-8"))
|
|
1659
|
+
|
|
1660
|
+
def test_dump_fixture_removes_missing_release_files(self):
|
|
1661
|
+
PackageRelease.dump_fixture()
|
|
1662
|
+
target_two = self.base / "releases__packagerelease_9_9_10.json"
|
|
1663
|
+
self.assertTrue(target_two.exists())
|
|
1664
|
+
|
|
1665
|
+
self.release_two.delete()
|
|
1666
|
+
|
|
1667
|
+
PackageRelease.dump_fixture()
|
|
1668
|
+
|
|
1669
|
+
self.assertFalse(target_two.exists())
|
|
1670
|
+
|
|
1671
|
+
|
|
1555
1672
|
class ReleaseProgressSyncTests(TestCase):
|
|
1556
1673
|
def setUp(self):
|
|
1557
1674
|
self.client = Client()
|
|
@@ -2044,6 +2161,42 @@ class TodoDoneTests(TestCase):
|
|
|
2044
2161
|
todo.refresh_from_db()
|
|
2045
2162
|
self.assertIsNotNone(todo.done_on)
|
|
2046
2163
|
|
|
2164
|
+
def test_env_refresh_preserves_completed_fixture_todo(self):
|
|
2165
|
+
base_dir = Path(settings.BASE_DIR)
|
|
2166
|
+
fixture_path = base_dir / "core" / "fixtures" / "todo__validate_screen_system_reports.json"
|
|
2167
|
+
spec = importlib.util.spec_from_file_location(
|
|
2168
|
+
"env_refresh_todo", base_dir / "env-refresh.py"
|
|
2169
|
+
)
|
|
2170
|
+
env_refresh = importlib.util.module_from_spec(spec)
|
|
2171
|
+
spec.loader.exec_module(env_refresh)
|
|
2172
|
+
fixture_rel_path = str(fixture_path.relative_to(base_dir))
|
|
2173
|
+
env_refresh._fixture_files = lambda: [fixture_rel_path]
|
|
2174
|
+
|
|
2175
|
+
from django.core.management import call_command as django_call
|
|
2176
|
+
|
|
2177
|
+
def fake_call_command(name, *args, **kwargs):
|
|
2178
|
+
if name == "loaddata":
|
|
2179
|
+
return django_call(name, *args, **kwargs)
|
|
2180
|
+
return None
|
|
2181
|
+
|
|
2182
|
+
env_refresh.call_command = fake_call_command
|
|
2183
|
+
env_refresh.load_shared_user_fixtures = lambda *args, **kwargs: None
|
|
2184
|
+
env_refresh.load_user_fixtures = lambda *args, **kwargs: None
|
|
2185
|
+
env_refresh.generate_model_sigils = lambda: None
|
|
2186
|
+
|
|
2187
|
+
env_refresh.run_database_tasks()
|
|
2188
|
+
|
|
2189
|
+
todo = Todo.objects.get(request="Validate screen System Reports")
|
|
2190
|
+
self.assertTrue(todo.is_seed_data)
|
|
2191
|
+
todo.done_on = timezone.now()
|
|
2192
|
+
todo.save(update_fields=["done_on"])
|
|
2193
|
+
|
|
2194
|
+
env_refresh.run_database_tasks()
|
|
2195
|
+
|
|
2196
|
+
todo.refresh_from_db()
|
|
2197
|
+
self.assertIsNotNone(todo.done_on)
|
|
2198
|
+
self.assertTrue(todo.is_seed_data)
|
|
2199
|
+
|
|
2047
2200
|
|
|
2048
2201
|
class TodoFocusViewTests(TestCase):
|
|
2049
2202
|
def setUp(self):
|
|
@@ -2063,6 +2216,9 @@ class TodoFocusViewTests(TestCase):
|
|
|
2063
2216
|
self.assertContains(resp, f'src="{todo.url}"')
|
|
2064
2217
|
self.assertContains(resp, "Done")
|
|
2065
2218
|
self.assertContains(resp, "Back")
|
|
2219
|
+
self.assertContains(resp, "Take Snapshot")
|
|
2220
|
+
snapshot_url = reverse("todo-snapshot", args=[todo.pk])
|
|
2221
|
+
self.assertContains(resp, snapshot_url)
|
|
2066
2222
|
|
|
2067
2223
|
def test_focus_view_uses_admin_change_when_no_url(self):
|
|
2068
2224
|
todo = Todo.objects.create(request="Task")
|
|
@@ -2134,6 +2290,57 @@ class TodoFocusViewTests(TestCase):
|
|
|
2134
2290
|
self.assertRedirects(resp, next_url, target_status_code=200)
|
|
2135
2291
|
|
|
2136
2292
|
|
|
2293
|
+
class TodoSnapshotViewTests(TestCase):
|
|
2294
|
+
PNG_PIXEL = (
|
|
2295
|
+
"data:image/png;base64," "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVR42mP8/5+hHgAFgwJ/lSdX6QAAAABJRU5ErkJggg=="
|
|
2296
|
+
)
|
|
2297
|
+
|
|
2298
|
+
def setUp(self):
|
|
2299
|
+
self.user = User.objects.create_user(
|
|
2300
|
+
username="manager", password="secret", is_staff=True
|
|
2301
|
+
)
|
|
2302
|
+
self.client.force_login(self.user)
|
|
2303
|
+
self.todo = Todo.objects.create(
|
|
2304
|
+
request="QA release notes", request_details="Verify layout"
|
|
2305
|
+
)
|
|
2306
|
+
|
|
2307
|
+
def test_snapshot_creates_content_sample(self):
|
|
2308
|
+
tmpdir = tempfile.TemporaryDirectory()
|
|
2309
|
+
self.addCleanup(tmpdir.cleanup)
|
|
2310
|
+
with override_settings(LOG_DIR=Path(tmpdir.name)):
|
|
2311
|
+
response = self.client.post(
|
|
2312
|
+
reverse("todo-snapshot", args=[self.todo.pk]),
|
|
2313
|
+
data=json.dumps({"image": self.PNG_PIXEL}),
|
|
2314
|
+
content_type="application/json",
|
|
2315
|
+
)
|
|
2316
|
+
self.assertEqual(response.status_code, 200)
|
|
2317
|
+
payload = response.json()
|
|
2318
|
+
self.assertIn("sample", payload)
|
|
2319
|
+
self.assertEqual(ContentSample.objects.filter(kind=ContentSample.IMAGE).count(), 1)
|
|
2320
|
+
sample = ContentSample.objects.get(pk=payload["sample"])
|
|
2321
|
+
self.assertEqual(sample.method, "TODO_QA")
|
|
2322
|
+
self.assertEqual(sample.user, self.user)
|
|
2323
|
+
self.assertEqual(sample.content, "QA release notes — Verify layout")
|
|
2324
|
+
rel_path = Path(sample.path)
|
|
2325
|
+
self.assertTrue(rel_path.parts)
|
|
2326
|
+
self.assertEqual(rel_path.parts[0], "screenshots")
|
|
2327
|
+
self.assertTrue((Path(tmpdir.name) / rel_path).exists())
|
|
2328
|
+
|
|
2329
|
+
def test_snapshot_rejects_completed_todo(self):
|
|
2330
|
+
self.todo.done_on = timezone.now()
|
|
2331
|
+
self.todo.save(update_fields=["done_on"])
|
|
2332
|
+
tmpdir = tempfile.TemporaryDirectory()
|
|
2333
|
+
self.addCleanup(tmpdir.cleanup)
|
|
2334
|
+
with override_settings(LOG_DIR=Path(tmpdir.name)):
|
|
2335
|
+
response = self.client.post(
|
|
2336
|
+
reverse("todo-snapshot", args=[self.todo.pk]),
|
|
2337
|
+
data=json.dumps({"image": self.PNG_PIXEL}),
|
|
2338
|
+
content_type="application/json",
|
|
2339
|
+
)
|
|
2340
|
+
self.assertEqual(response.status_code, 400)
|
|
2341
|
+
self.assertEqual(ContentSample.objects.count(), 0)
|
|
2342
|
+
|
|
2343
|
+
|
|
2137
2344
|
class TodoUrlValidationTests(TestCase):
|
|
2138
2345
|
def test_relative_url_valid(self):
|
|
2139
2346
|
todo = Todo(request="Task", url="/path")
|