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.
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
+