arthexis 0.1.6__py3-none-any.whl → 0.1.8__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.
- {arthexis-0.1.6.dist-info → arthexis-0.1.8.dist-info}/METADATA +12 -8
- {arthexis-0.1.6.dist-info → arthexis-0.1.8.dist-info}/RECORD +36 -29
- config/celery.py +7 -0
- config/horologia_app.py +7 -0
- config/logging.py +8 -3
- config/settings.py +3 -2
- config/urls.py +9 -0
- config/workgroup_app.py +7 -0
- core/admin.py +192 -17
- core/admindocs.py +44 -0
- core/apps.py +2 -1
- core/checks.py +29 -0
- core/entity.py +29 -7
- core/models.py +124 -14
- core/release.py +29 -141
- core/system.py +2 -2
- core/test_system_info.py +21 -0
- core/tests.py +292 -1
- core/views.py +153 -134
- core/workgroup_urls.py +13 -0
- core/workgroup_views.py +57 -0
- nodes/admin.py +211 -0
- nodes/apps.py +1 -1
- nodes/models.py +103 -7
- nodes/tests.py +27 -0
- ocpp/apps.py +4 -3
- ocpp/models.py +1 -1
- ocpp/simulator.py +4 -0
- ocpp/tests.py +5 -1
- pages/admin.py +8 -3
- pages/apps.py +1 -1
- pages/tests.py +23 -4
- pages/views.py +22 -3
- {arthexis-0.1.6.dist-info → arthexis-0.1.8.dist-info}/WHEEL +0 -0
- {arthexis-0.1.6.dist-info → arthexis-0.1.8.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.6.dist-info → arthexis-0.1.8.dist-info}/top_level.txt +0 -0
core/tests.py
CHANGED
|
@@ -4,11 +4,13 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
|
|
4
4
|
import django
|
|
5
5
|
django.setup()
|
|
6
6
|
|
|
7
|
-
from django.test import Client, TestCase
|
|
7
|
+
from django.test import Client, TestCase, RequestFactory
|
|
8
8
|
from django.urls import reverse
|
|
9
9
|
from django.http import HttpRequest
|
|
10
10
|
import json
|
|
11
11
|
from unittest import mock
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
import subprocess
|
|
12
14
|
|
|
13
15
|
from django.utils import timezone
|
|
14
16
|
from .models import (
|
|
@@ -24,13 +26,18 @@ from .models import (
|
|
|
24
26
|
RFID,
|
|
25
27
|
FediverseProfile,
|
|
26
28
|
SecurityGroup,
|
|
29
|
+
Package,
|
|
30
|
+
PackageRelease,
|
|
27
31
|
)
|
|
32
|
+
from django.contrib.admin.sites import AdminSite
|
|
33
|
+
from core.admin import PackageReleaseAdmin, PackageAdmin
|
|
28
34
|
from ocpp.models import Transaction, Charger
|
|
29
35
|
|
|
30
36
|
from django.core.exceptions import ValidationError
|
|
31
37
|
from django.core.management import call_command
|
|
32
38
|
from django.db import IntegrityError
|
|
33
39
|
from .backends import LocalhostAdminBackend
|
|
40
|
+
from core.views import _step_check_pypi, _step_promote_build, _step_publish
|
|
34
41
|
|
|
35
42
|
|
|
36
43
|
class DefaultAdminTests(TestCase):
|
|
@@ -481,3 +488,287 @@ class FediverseProfileTests(TestCase):
|
|
|
481
488
|
profile.test_connection()
|
|
482
489
|
self.assertIsNone(profile.verified_on)
|
|
483
490
|
|
|
491
|
+
|
|
492
|
+
class ReleaseProcessTests(TestCase):
|
|
493
|
+
def setUp(self):
|
|
494
|
+
self.package = Package.objects.create(name="pkg")
|
|
495
|
+
self.release = PackageRelease.objects.create(
|
|
496
|
+
package=self.package, version="1.0.0"
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
@mock.patch("core.views.release_utils._git_clean", return_value=False)
|
|
500
|
+
def test_step_check_requires_clean_repo(self, git_clean):
|
|
501
|
+
with self.assertRaises(Exception):
|
|
502
|
+
_step_check_pypi(self.release, {}, Path("rel.log"))
|
|
503
|
+
|
|
504
|
+
@mock.patch("core.views.release_utils._git_clean", return_value=True)
|
|
505
|
+
@mock.patch("core.views.release_utils.network_available", return_value=False)
|
|
506
|
+
def test_step_check_keeps_repo_clean(self, network_available, git_clean):
|
|
507
|
+
version_path = Path("VERSION")
|
|
508
|
+
original = version_path.read_text(encoding="utf-8")
|
|
509
|
+
_step_check_pypi(self.release, {}, Path("rel.log"))
|
|
510
|
+
proc = subprocess.run(
|
|
511
|
+
["git", "status", "--porcelain", str(version_path)],
|
|
512
|
+
capture_output=True,
|
|
513
|
+
text=True,
|
|
514
|
+
)
|
|
515
|
+
self.assertFalse(proc.stdout.strip())
|
|
516
|
+
self.assertEqual(version_path.read_text(encoding="utf-8"), original)
|
|
517
|
+
|
|
518
|
+
@mock.patch("core.models.PackageRelease.dump_fixture")
|
|
519
|
+
def test_save_does_not_dump_fixture(self, dump):
|
|
520
|
+
self.release.pypi_url = "https://example.com"
|
|
521
|
+
self.release.save()
|
|
522
|
+
dump.assert_not_called()
|
|
523
|
+
|
|
524
|
+
@mock.patch("core.views.subprocess.run")
|
|
525
|
+
@mock.patch("core.views.PackageRelease.dump_fixture")
|
|
526
|
+
@mock.patch("core.views.release_utils.promote", side_effect=Exception("boom"))
|
|
527
|
+
def test_promote_cleans_repo_on_failure(
|
|
528
|
+
self, promote, dump_fixture, run
|
|
529
|
+
):
|
|
530
|
+
with self.assertRaises(Exception):
|
|
531
|
+
_step_promote_build(self.release, {}, Path("rel.log"))
|
|
532
|
+
dump_fixture.assert_not_called()
|
|
533
|
+
run.assert_any_call(["git", "reset", "--hard"], check=False)
|
|
534
|
+
run.assert_any_call(["git", "clean", "-fd"], check=False)
|
|
535
|
+
|
|
536
|
+
@mock.patch("core.views.subprocess.run")
|
|
537
|
+
@mock.patch("core.views.PackageRelease.dump_fixture")
|
|
538
|
+
@mock.patch("core.views.release_utils.promote")
|
|
539
|
+
def test_promote_rebases_and_pushes_main(self, promote, dump_fixture, run):
|
|
540
|
+
import subprocess as sp
|
|
541
|
+
|
|
542
|
+
def fake_run(cmd, check=True, capture_output=False, text=False):
|
|
543
|
+
if capture_output:
|
|
544
|
+
return sp.CompletedProcess(cmd, 0, stdout="", stderr="")
|
|
545
|
+
return sp.CompletedProcess(cmd, 0)
|
|
546
|
+
|
|
547
|
+
run.side_effect = fake_run
|
|
548
|
+
_step_promote_build(self.release, {}, Path("rel.log"))
|
|
549
|
+
run.assert_any_call(["git", "fetch", "origin", "main"], check=True)
|
|
550
|
+
run.assert_any_call(["git", "rebase", "origin/main"], check=True)
|
|
551
|
+
run.assert_any_call(["git", "push"], check=True)
|
|
552
|
+
|
|
553
|
+
@mock.patch("core.views.subprocess.run")
|
|
554
|
+
@mock.patch("core.views.PackageRelease.dump_fixture")
|
|
555
|
+
def test_promote_advances_version(self, dump_fixture, run):
|
|
556
|
+
import subprocess as sp
|
|
557
|
+
|
|
558
|
+
def fake_run(cmd, check=True, capture_output=False, text=False):
|
|
559
|
+
if capture_output:
|
|
560
|
+
return sp.CompletedProcess(cmd, 0, stdout="", stderr="")
|
|
561
|
+
return sp.CompletedProcess(cmd, 0)
|
|
562
|
+
|
|
563
|
+
run.side_effect = fake_run
|
|
564
|
+
|
|
565
|
+
version_path = Path("VERSION")
|
|
566
|
+
original = version_path.read_text(encoding="utf-8")
|
|
567
|
+
version_path.write_text("0.0.1\n", encoding="utf-8")
|
|
568
|
+
|
|
569
|
+
def fake_promote(*args, **kwargs):
|
|
570
|
+
version_path.write_text(self.release.version + "\n", encoding="utf-8")
|
|
571
|
+
|
|
572
|
+
with mock.patch("core.views.release_utils.promote", side_effect=fake_promote):
|
|
573
|
+
_step_promote_build(self.release, {}, Path("rel.log"))
|
|
574
|
+
|
|
575
|
+
self.assertEqual(
|
|
576
|
+
version_path.read_text(encoding="utf-8"),
|
|
577
|
+
self.release.version + "\n",
|
|
578
|
+
)
|
|
579
|
+
version_path.write_text(original, encoding="utf-8")
|
|
580
|
+
|
|
581
|
+
@mock.patch("core.views.PackageRelease.dump_fixture")
|
|
582
|
+
@mock.patch("core.views.release_utils.publish")
|
|
583
|
+
def test_publish_sets_pypi_url(self, publish, dump_fixture):
|
|
584
|
+
_step_publish(self.release, {}, Path("rel.log"))
|
|
585
|
+
self.release.refresh_from_db()
|
|
586
|
+
self.assertEqual(
|
|
587
|
+
self.release.pypi_url,
|
|
588
|
+
f"https://pypi.org/project/{self.package.name}/{self.release.version}/",
|
|
589
|
+
)
|
|
590
|
+
dump_fixture.assert_called_once()
|
|
591
|
+
|
|
592
|
+
@mock.patch("core.views.PackageRelease.dump_fixture")
|
|
593
|
+
@mock.patch("core.views.release_utils.publish", side_effect=Exception("boom"))
|
|
594
|
+
def test_publish_failure_keeps_url_blank(self, publish, dump_fixture):
|
|
595
|
+
with self.assertRaises(Exception):
|
|
596
|
+
_step_publish(self.release, {}, Path("rel.log"))
|
|
597
|
+
self.release.refresh_from_db()
|
|
598
|
+
self.assertEqual(self.release.pypi_url, "")
|
|
599
|
+
dump_fixture.assert_not_called()
|
|
600
|
+
|
|
601
|
+
def test_release_progress_uses_lockfile(self):
|
|
602
|
+
run = []
|
|
603
|
+
|
|
604
|
+
def step1(release, ctx, log_path):
|
|
605
|
+
run.append("step1")
|
|
606
|
+
|
|
607
|
+
def step2(release, ctx, log_path):
|
|
608
|
+
run.append("step2")
|
|
609
|
+
|
|
610
|
+
steps = [("One", step1), ("Two", step2)]
|
|
611
|
+
user = User.objects.create_superuser("admin", "admin@example.com", "pw")
|
|
612
|
+
url = reverse("release-progress", args=[self.release.pk, "publish"])
|
|
613
|
+
with mock.patch("core.views.PUBLISH_STEPS", steps):
|
|
614
|
+
self.client.force_login(user)
|
|
615
|
+
self.client.get(f"{url}?step=0")
|
|
616
|
+
self.assertEqual(run, ["step1"])
|
|
617
|
+
client2 = Client()
|
|
618
|
+
client2.force_login(user)
|
|
619
|
+
client2.get(f"{url}?step=1")
|
|
620
|
+
self.assertEqual(run, ["step1", "step2"])
|
|
621
|
+
lock_file = Path("locks") / f"release_publish_{self.release.pk}.json"
|
|
622
|
+
self.assertFalse(lock_file.exists())
|
|
623
|
+
|
|
624
|
+
def test_release_progress_restart(self):
|
|
625
|
+
run = []
|
|
626
|
+
|
|
627
|
+
def step_fail(release, ctx, log_path):
|
|
628
|
+
run.append("step")
|
|
629
|
+
raise Exception("boom")
|
|
630
|
+
|
|
631
|
+
steps = [("Fail", step_fail)]
|
|
632
|
+
user = User.objects.create_superuser("admin", "admin@example.com", "pw")
|
|
633
|
+
url = reverse("release-progress", args=[self.release.pk, "publish"])
|
|
634
|
+
count_file = Path("locks") / f"release_publish_{self.release.pk}.restarts"
|
|
635
|
+
if count_file.exists():
|
|
636
|
+
count_file.unlink()
|
|
637
|
+
with mock.patch("core.views.PUBLISH_STEPS", steps):
|
|
638
|
+
self.client.force_login(user)
|
|
639
|
+
self.assertFalse(count_file.exists())
|
|
640
|
+
self.client.get(f"{url}?step=0")
|
|
641
|
+
self.client.get(f"{url}?step=0")
|
|
642
|
+
self.assertEqual(run, ["step"])
|
|
643
|
+
self.assertFalse(count_file.exists())
|
|
644
|
+
self.client.get(f"{url}?restart=1")
|
|
645
|
+
self.assertTrue(count_file.exists())
|
|
646
|
+
self.assertEqual(count_file.read_text(), "1")
|
|
647
|
+
self.client.get(f"{url}?step=0")
|
|
648
|
+
self.assertEqual(run, ["step", "step"])
|
|
649
|
+
self.client.get(f"{url}?restart=1")
|
|
650
|
+
# Restart counter resets after running a step
|
|
651
|
+
self.assertEqual(count_file.read_text(), "1")
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
class PackageReleaseAdminActionTests(TestCase):
|
|
655
|
+
def setUp(self):
|
|
656
|
+
self.factory = RequestFactory()
|
|
657
|
+
self.site = AdminSite()
|
|
658
|
+
self.admin = PackageReleaseAdmin(PackageRelease, self.site)
|
|
659
|
+
self.admin.message_user = lambda *args, **kwargs: None
|
|
660
|
+
self.package = Package.objects.create(name="pkg")
|
|
661
|
+
self.release = PackageRelease.objects.create(
|
|
662
|
+
package=self.package,
|
|
663
|
+
version="1.0.0",
|
|
664
|
+
pypi_url="https://pypi.org/project/pkg/1.0.0/",
|
|
665
|
+
)
|
|
666
|
+
self.request = self.factory.get("/")
|
|
667
|
+
|
|
668
|
+
@mock.patch("core.admin.PackageRelease.dump_fixture")
|
|
669
|
+
@mock.patch("core.admin.requests.get")
|
|
670
|
+
def test_validate_deletes_missing_release(self, mock_get, dump):
|
|
671
|
+
mock_get.return_value.status_code = 404
|
|
672
|
+
self.admin.validate_releases(self.request, PackageRelease.objects.all())
|
|
673
|
+
self.assertEqual(PackageRelease.objects.count(), 0)
|
|
674
|
+
dump.assert_called_once()
|
|
675
|
+
|
|
676
|
+
@mock.patch("core.admin.PackageRelease.dump_fixture")
|
|
677
|
+
@mock.patch("core.admin.requests.get")
|
|
678
|
+
def test_validate_keeps_existing_release(self, mock_get, dump):
|
|
679
|
+
mock_get.return_value.status_code = 200
|
|
680
|
+
self.admin.validate_releases(self.request, PackageRelease.objects.all())
|
|
681
|
+
self.assertEqual(PackageRelease.objects.count(), 1)
|
|
682
|
+
dump.assert_not_called()
|
|
683
|
+
|
|
684
|
+
@mock.patch("core.admin.PackageRelease.dump_fixture")
|
|
685
|
+
@mock.patch("core.admin.requests.get")
|
|
686
|
+
def test_refresh_from_pypi_creates_releases(self, mock_get, dump):
|
|
687
|
+
mock_get.return_value.raise_for_status.return_value = None
|
|
688
|
+
mock_get.return_value.json.return_value = {
|
|
689
|
+
"releases": {"1.0.0": [], "1.1.0": []}
|
|
690
|
+
}
|
|
691
|
+
self.admin.refresh_from_pypi(
|
|
692
|
+
self.request, PackageRelease.objects.none()
|
|
693
|
+
)
|
|
694
|
+
self.assertTrue(
|
|
695
|
+
PackageRelease.objects.filter(version="1.1.0").exists()
|
|
696
|
+
)
|
|
697
|
+
dump.assert_called_once()
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
class PackageActiveTests(TestCase):
|
|
701
|
+
def test_only_one_active_package(self):
|
|
702
|
+
default = Package.objects.get(name="arthexis")
|
|
703
|
+
self.assertTrue(default.is_active)
|
|
704
|
+
other = Package.objects.create(name="pkg", is_active=True)
|
|
705
|
+
default.refresh_from_db()
|
|
706
|
+
other.refresh_from_db()
|
|
707
|
+
self.assertFalse(default.is_active)
|
|
708
|
+
self.assertTrue(other.is_active)
|
|
709
|
+
|
|
710
|
+
|
|
711
|
+
class PackageReleaseCurrentTests(TestCase):
|
|
712
|
+
def setUp(self):
|
|
713
|
+
self.package = Package.objects.get(name="arthexis")
|
|
714
|
+
self.version_path = Path("VERSION")
|
|
715
|
+
self.original = self.version_path.read_text()
|
|
716
|
+
self.version_path.write_text("1.0.0")
|
|
717
|
+
self.release = PackageRelease.objects.create(
|
|
718
|
+
package=self.package, version="1.0.0"
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
def tearDown(self):
|
|
722
|
+
self.version_path.write_text(self.original)
|
|
723
|
+
|
|
724
|
+
def test_is_current_true_when_version_matches_and_package_active(self):
|
|
725
|
+
self.assertTrue(self.release.is_current)
|
|
726
|
+
|
|
727
|
+
def test_is_current_false_when_package_inactive(self):
|
|
728
|
+
self.package.is_active = False
|
|
729
|
+
self.package.save()
|
|
730
|
+
self.assertFalse(self.release.is_current)
|
|
731
|
+
|
|
732
|
+
def test_is_current_false_when_version_differs(self):
|
|
733
|
+
self.release.version = "2.0.0"
|
|
734
|
+
self.release.save()
|
|
735
|
+
self.assertFalse(self.release.is_current)
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
class PackageAdminPrepareNextReleaseTests(TestCase):
|
|
739
|
+
def setUp(self):
|
|
740
|
+
self.factory = RequestFactory()
|
|
741
|
+
self.site = AdminSite()
|
|
742
|
+
self.admin = PackageAdmin(Package, self.site)
|
|
743
|
+
self.admin.message_user = lambda *args, **kwargs: None
|
|
744
|
+
self.package = Package.objects.get(name="arthexis")
|
|
745
|
+
|
|
746
|
+
def test_prepare_next_release_active_creates_release(self):
|
|
747
|
+
PackageRelease.all_objects.filter(package=self.package).delete()
|
|
748
|
+
request = self.factory.get("/admin/core/package/prepare-next-release/")
|
|
749
|
+
response = self.admin.prepare_next_release_active(request)
|
|
750
|
+
self.assertEqual(response.status_code, 302)
|
|
751
|
+
self.assertEqual(
|
|
752
|
+
PackageRelease.all_objects.filter(package=self.package).count(), 1
|
|
753
|
+
)
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
class PackageReleaseChangelistTests(TestCase):
|
|
757
|
+
def setUp(self):
|
|
758
|
+
self.client = Client()
|
|
759
|
+
User.objects.create_superuser("admin", "admin@example.com", "pw")
|
|
760
|
+
self.client.force_login(User.objects.get(username="admin"))
|
|
761
|
+
|
|
762
|
+
def test_prepare_next_release_button_present(self):
|
|
763
|
+
response = self.client.get(reverse("admin:core_packagerelease_changelist"))
|
|
764
|
+
self.assertContains(
|
|
765
|
+
response, reverse("admin:core_package_prepare_next_release"), html=False
|
|
766
|
+
)
|
|
767
|
+
|
|
768
|
+
def test_refresh_from_pypi_button_present(self):
|
|
769
|
+
response = self.client.get(reverse("admin:core_packagerelease_changelist"))
|
|
770
|
+
refresh_url = reverse(
|
|
771
|
+
"admin:core_packagerelease_actions", args=["refresh_from_pypi"]
|
|
772
|
+
)
|
|
773
|
+
self.assertContains(response, refresh_url, html=False)
|
|
774
|
+
|