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.

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")