arthexis 0.1.9__py3-none-any.whl → 0.1.11__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.11.dist-info}/METADATA +76 -23
- arthexis-0.1.11.dist-info/RECORD +99 -0
- config/context_processors.py +1 -0
- config/settings.py +245 -26
- config/urls.py +11 -4
- core/admin.py +585 -57
- core/apps.py +29 -1
- core/auto_upgrade.py +57 -0
- core/backends.py +115 -3
- core/environment.py +23 -5
- core/fields.py +93 -0
- core/mailer.py +3 -1
- core/models.py +482 -38
- core/reference_utils.py +108 -0
- core/sigil_builder.py +23 -5
- core/sigil_resolver.py +35 -4
- core/system.py +400 -140
- core/tasks.py +151 -8
- core/temp_passwords.py +181 -0
- core/test_system_info.py +97 -1
- core/tests.py +393 -15
- core/user_data.py +154 -16
- core/views.py +499 -20
- nodes/admin.py +149 -6
- nodes/backends.py +125 -18
- nodes/dns.py +203 -0
- nodes/models.py +498 -9
- nodes/tests.py +682 -3
- nodes/views.py +154 -7
- ocpp/admin.py +63 -3
- ocpp/consumers.py +255 -41
- ocpp/evcs.py +6 -3
- ocpp/models.py +52 -7
- ocpp/reference_utils.py +42 -0
- ocpp/simulator.py +62 -5
- ocpp/store.py +30 -0
- ocpp/test_rfid.py +169 -7
- ocpp/tests.py +414 -8
- ocpp/views.py +109 -76
- pages/admin.py +9 -1
- pages/context_processors.py +24 -4
- pages/defaults.py +14 -0
- pages/forms.py +131 -0
- pages/models.py +53 -14
- pages/tests.py +450 -14
- pages/urls.py +4 -0
- pages/views.py +419 -110
- arthexis-0.1.9.dist-info/RECORD +0 -92
- {arthexis-0.1.9.dist-info → arthexis-0.1.11.dist-info}/WHEEL +0 -0
- {arthexis-0.1.9.dist-info → arthexis-0.1.11.dist-info}/licenses/LICENSE +0 -0
- {arthexis-0.1.9.dist-info → arthexis-0.1.11.dist-info}/top_level.txt +0 -0
core/tests.py
CHANGED
|
@@ -15,11 +15,13 @@ from unittest.mock import patch
|
|
|
15
15
|
from pathlib import Path
|
|
16
16
|
import subprocess
|
|
17
17
|
from glob import glob
|
|
18
|
-
from datetime import timedelta
|
|
18
|
+
from datetime import datetime, timedelta, timezone as datetime_timezone
|
|
19
19
|
import tempfile
|
|
20
|
+
from urllib.parse import quote
|
|
20
21
|
|
|
21
22
|
from django.utils import timezone
|
|
22
23
|
from django.contrib.auth.models import Permission
|
|
24
|
+
from django.contrib.messages import get_messages
|
|
23
25
|
from .models import (
|
|
24
26
|
User,
|
|
25
27
|
UserPhoneNumber,
|
|
@@ -51,6 +53,7 @@ from django.core.management import call_command
|
|
|
51
53
|
from django.db import IntegrityError
|
|
52
54
|
from .backends import LocalhostAdminBackend
|
|
53
55
|
from core.views import _step_check_version, _step_promote_build, _step_publish
|
|
56
|
+
from core import views as core_views
|
|
54
57
|
from core import public_wifi
|
|
55
58
|
|
|
56
59
|
|
|
@@ -828,15 +831,21 @@ class ReleaseProcessTests(TestCase):
|
|
|
828
831
|
)
|
|
829
832
|
version_path.write_text(original, encoding="utf-8")
|
|
830
833
|
|
|
834
|
+
@mock.patch("core.views.timezone.now")
|
|
831
835
|
@mock.patch("core.views.PackageRelease.dump_fixture")
|
|
832
836
|
@mock.patch("core.views.release_utils.publish")
|
|
833
|
-
def test_publish_sets_pypi_url(self, publish, dump_fixture):
|
|
837
|
+
def test_publish_sets_pypi_url(self, publish, dump_fixture, now):
|
|
838
|
+
now.return_value = datetime(2025, 3, 4, 5, 6, tzinfo=datetime_timezone.utc)
|
|
834
839
|
_step_publish(self.release, {}, Path("rel.log"))
|
|
835
840
|
self.release.refresh_from_db()
|
|
836
841
|
self.assertEqual(
|
|
837
842
|
self.release.pypi_url,
|
|
838
843
|
f"https://pypi.org/project/{self.package.name}/{self.release.version}/",
|
|
839
844
|
)
|
|
845
|
+
self.assertEqual(
|
|
846
|
+
self.release.release_on,
|
|
847
|
+
datetime(2025, 3, 4, 5, 6, tzinfo=datetime_timezone.utc),
|
|
848
|
+
)
|
|
840
849
|
dump_fixture.assert_called_once()
|
|
841
850
|
|
|
842
851
|
@mock.patch("core.views.PackageRelease.dump_fixture")
|
|
@@ -846,8 +855,33 @@ class ReleaseProcessTests(TestCase):
|
|
|
846
855
|
_step_publish(self.release, {}, Path("rel.log"))
|
|
847
856
|
self.release.refresh_from_db()
|
|
848
857
|
self.assertEqual(self.release.pypi_url, "")
|
|
858
|
+
self.assertIsNone(self.release.release_on)
|
|
849
859
|
dump_fixture.assert_not_called()
|
|
850
860
|
|
|
861
|
+
def test_new_todo_does_not_reset_pending_flow(self):
|
|
862
|
+
user = User.objects.create_superuser("admin", "admin@example.com", "pw")
|
|
863
|
+
url = reverse("release-progress", args=[self.release.pk, "publish"])
|
|
864
|
+
Todo.objects.create(request="Initial checklist item")
|
|
865
|
+
steps = [("Confirm release TODO completion", core_views._step_check_todos)]
|
|
866
|
+
with mock.patch("core.views.PUBLISH_STEPS", steps):
|
|
867
|
+
self.client.force_login(user)
|
|
868
|
+
response = self.client.get(url)
|
|
869
|
+
self.assertTrue(response.context["has_pending_todos"])
|
|
870
|
+
self.client.get(f"{url}?ack_todos=1")
|
|
871
|
+
self.client.get(f"{url}?start=1")
|
|
872
|
+
self.client.get(f"{url}?step=0")
|
|
873
|
+
Todo.objects.create(request="Follow-up checklist item")
|
|
874
|
+
response = self.client.get(url)
|
|
875
|
+
self.assertEqual(
|
|
876
|
+
Todo.objects.filter(is_deleted=False, done_on__isnull=True).count(),
|
|
877
|
+
1,
|
|
878
|
+
)
|
|
879
|
+
self.assertIsNone(response.context["todos"])
|
|
880
|
+
self.assertFalse(response.context["has_pending_todos"])
|
|
881
|
+
session = self.client.session
|
|
882
|
+
ctx = session.get(f"release_publish_{self.release.pk}")
|
|
883
|
+
self.assertTrue(ctx.get("todos_ack"))
|
|
884
|
+
|
|
851
885
|
def test_release_progress_uses_lockfile(self):
|
|
852
886
|
run = []
|
|
853
887
|
|
|
@@ -901,6 +935,139 @@ class ReleaseProcessTests(TestCase):
|
|
|
901
935
|
self.assertEqual(count_file.read_text(), "1")
|
|
902
936
|
|
|
903
937
|
|
|
938
|
+
class ReleaseProgressSyncTests(TestCase):
|
|
939
|
+
def setUp(self):
|
|
940
|
+
self.client = Client()
|
|
941
|
+
self.user = User.objects.create_superuser("admin", "admin@example.com", "pw")
|
|
942
|
+
self.client.force_login(self.user)
|
|
943
|
+
self.package = Package.objects.get(name="arthexis")
|
|
944
|
+
self.version_path = Path("VERSION")
|
|
945
|
+
self.original_version = self.version_path.read_text(encoding="utf-8")
|
|
946
|
+
self.version_path.write_text("1.2.3", encoding="utf-8")
|
|
947
|
+
|
|
948
|
+
def tearDown(self):
|
|
949
|
+
self.version_path.write_text(self.original_version, encoding="utf-8")
|
|
950
|
+
|
|
951
|
+
@mock.patch("core.views.PackageRelease.dump_fixture")
|
|
952
|
+
@mock.patch("core.views.revision.get_revision", return_value="abc123")
|
|
953
|
+
def test_unpublished_release_syncs_version_and_revision(
|
|
954
|
+
self, get_revision, dump_fixture
|
|
955
|
+
):
|
|
956
|
+
release = PackageRelease.objects.create(
|
|
957
|
+
package=self.package,
|
|
958
|
+
version="1.0.0",
|
|
959
|
+
)
|
|
960
|
+
release.revision = "oldrev"
|
|
961
|
+
release.save(update_fields=["revision"])
|
|
962
|
+
|
|
963
|
+
url = reverse("release-progress", args=[release.pk, "publish"])
|
|
964
|
+
response = self.client.get(url)
|
|
965
|
+
|
|
966
|
+
self.assertEqual(response.status_code, 200)
|
|
967
|
+
release.refresh_from_db()
|
|
968
|
+
self.assertEqual(release.version, "1.2.4")
|
|
969
|
+
self.assertEqual(release.revision, "abc123")
|
|
970
|
+
dump_fixture.assert_called_once()
|
|
971
|
+
|
|
972
|
+
def test_published_release_not_current_returns_404(self):
|
|
973
|
+
release = PackageRelease.objects.create(
|
|
974
|
+
package=self.package,
|
|
975
|
+
version="1.2.4",
|
|
976
|
+
pypi_url="https://example.com",
|
|
977
|
+
)
|
|
978
|
+
|
|
979
|
+
url = reverse("release-progress", args=[release.pk, "publish"])
|
|
980
|
+
response = self.client.get(url)
|
|
981
|
+
|
|
982
|
+
self.assertEqual(response.status_code, 404)
|
|
983
|
+
|
|
984
|
+
|
|
985
|
+
class ReleaseProgressFixtureVisibilityTests(TestCase):
|
|
986
|
+
def setUp(self):
|
|
987
|
+
self.client = Client()
|
|
988
|
+
self.user = User.objects.create_superuser(
|
|
989
|
+
"fixture-check", "fixture@example.com", "pw"
|
|
990
|
+
)
|
|
991
|
+
self.client.force_login(self.user)
|
|
992
|
+
current_version = Path("VERSION").read_text(encoding="utf-8").strip()
|
|
993
|
+
package = Package.objects.filter(is_active=True).first()
|
|
994
|
+
if package is None:
|
|
995
|
+
package = Package.objects.create(name="fixturepkg", is_active=True)
|
|
996
|
+
try:
|
|
997
|
+
self.release = PackageRelease.objects.get(
|
|
998
|
+
package=package, version=current_version
|
|
999
|
+
)
|
|
1000
|
+
except PackageRelease.DoesNotExist:
|
|
1001
|
+
self.release = PackageRelease.objects.create(
|
|
1002
|
+
package=package, version=current_version
|
|
1003
|
+
)
|
|
1004
|
+
self.session_key = f"release_publish_{self.release.pk}"
|
|
1005
|
+
self.log_name = f"{self.release.package.name}-{self.release.version}.log"
|
|
1006
|
+
self.lock_path = Path("locks") / f"{self.session_key}.json"
|
|
1007
|
+
self.restart_path = Path("locks") / f"{self.session_key}.restarts"
|
|
1008
|
+
self.log_path = Path("logs") / self.log_name
|
|
1009
|
+
for path in (self.lock_path, self.restart_path, self.log_path):
|
|
1010
|
+
if path.exists():
|
|
1011
|
+
path.unlink()
|
|
1012
|
+
try:
|
|
1013
|
+
self.fixture_step_index = next(
|
|
1014
|
+
idx
|
|
1015
|
+
for idx, (name, _) in enumerate(core_views.PUBLISH_STEPS)
|
|
1016
|
+
if name == core_views.FIXTURE_REVIEW_STEP_NAME
|
|
1017
|
+
)
|
|
1018
|
+
except StopIteration: # pragma: no cover - defensive guard
|
|
1019
|
+
self.fail("Fixture review step not configured in publish steps")
|
|
1020
|
+
self.url = reverse("release-progress", args=[self.release.pk, "publish"])
|
|
1021
|
+
|
|
1022
|
+
def tearDown(self):
|
|
1023
|
+
session = self.client.session
|
|
1024
|
+
if self.session_key in session:
|
|
1025
|
+
session.pop(self.session_key)
|
|
1026
|
+
session.save()
|
|
1027
|
+
for path in (self.lock_path, self.restart_path, self.log_path):
|
|
1028
|
+
if path.exists():
|
|
1029
|
+
path.unlink()
|
|
1030
|
+
super().tearDown()
|
|
1031
|
+
|
|
1032
|
+
def _set_session(self, step: int, fixtures: list[dict]):
|
|
1033
|
+
session = self.client.session
|
|
1034
|
+
session[self.session_key] = {
|
|
1035
|
+
"step": step,
|
|
1036
|
+
"fixtures": fixtures,
|
|
1037
|
+
"log": self.log_name,
|
|
1038
|
+
"started": True,
|
|
1039
|
+
}
|
|
1040
|
+
session.save()
|
|
1041
|
+
|
|
1042
|
+
def test_fixture_summary_visible_until_migration_step(self):
|
|
1043
|
+
fixtures = [
|
|
1044
|
+
{
|
|
1045
|
+
"path": "core/fixtures/example.json",
|
|
1046
|
+
"count": 2,
|
|
1047
|
+
"models": ["core.Model"],
|
|
1048
|
+
}
|
|
1049
|
+
]
|
|
1050
|
+
self._set_session(self.fixture_step_index, fixtures)
|
|
1051
|
+
response = self.client.get(self.url)
|
|
1052
|
+
self.assertEqual(response.status_code, 200)
|
|
1053
|
+
self.assertEqual(response.context["fixtures"], fixtures)
|
|
1054
|
+
self.assertContains(response, "Fixture changes")
|
|
1055
|
+
|
|
1056
|
+
def test_fixture_summary_hidden_after_migration_step(self):
|
|
1057
|
+
fixtures = [
|
|
1058
|
+
{
|
|
1059
|
+
"path": "core/fixtures/example.json",
|
|
1060
|
+
"count": 2,
|
|
1061
|
+
"models": ["core.Model"],
|
|
1062
|
+
}
|
|
1063
|
+
]
|
|
1064
|
+
self._set_session(self.fixture_step_index + 1, fixtures)
|
|
1065
|
+
response = self.client.get(self.url)
|
|
1066
|
+
self.assertEqual(response.status_code, 200)
|
|
1067
|
+
self.assertIsNone(response.context["fixtures"])
|
|
1068
|
+
self.assertNotContains(response, "Fixture changes")
|
|
1069
|
+
|
|
1070
|
+
|
|
904
1071
|
class PackageReleaseAdminActionTests(TestCase):
|
|
905
1072
|
def setUp(self):
|
|
906
1073
|
self.factory = RequestFactory()
|
|
@@ -908,6 +1075,8 @@ class PackageReleaseAdminActionTests(TestCase):
|
|
|
908
1075
|
self.admin = PackageReleaseAdmin(PackageRelease, self.site)
|
|
909
1076
|
self.admin.message_user = lambda *args, **kwargs: None
|
|
910
1077
|
self.package = Package.objects.create(name="pkg")
|
|
1078
|
+
self.package.is_active = True
|
|
1079
|
+
self.package.save(update_fields=["is_active"])
|
|
911
1080
|
self.release = PackageRelease.objects.create(
|
|
912
1081
|
package=self.package,
|
|
913
1082
|
version="1.0.0",
|
|
@@ -936,11 +1105,64 @@ class PackageReleaseAdminActionTests(TestCase):
|
|
|
936
1105
|
def test_refresh_from_pypi_creates_releases(self, mock_get, dump):
|
|
937
1106
|
mock_get.return_value.raise_for_status.return_value = None
|
|
938
1107
|
mock_get.return_value.json.return_value = {
|
|
939
|
-
"releases": {
|
|
1108
|
+
"releases": {
|
|
1109
|
+
"1.0.0": [
|
|
1110
|
+
{"upload_time_iso_8601": "2024-01-01T12:30:00.000000Z"}
|
|
1111
|
+
],
|
|
1112
|
+
"1.1.0": [
|
|
1113
|
+
{"upload_time_iso_8601": "2024-02-02T15:45:00.000000Z"}
|
|
1114
|
+
],
|
|
1115
|
+
}
|
|
940
1116
|
}
|
|
941
1117
|
self.admin.refresh_from_pypi(self.request, PackageRelease.objects.none())
|
|
942
1118
|
new_release = PackageRelease.objects.get(version="1.1.0")
|
|
943
1119
|
self.assertEqual(new_release.revision, "")
|
|
1120
|
+
self.assertEqual(
|
|
1121
|
+
new_release.release_on,
|
|
1122
|
+
datetime(2024, 2, 2, 15, 45, tzinfo=datetime_timezone.utc),
|
|
1123
|
+
)
|
|
1124
|
+
dump.assert_called_once()
|
|
1125
|
+
|
|
1126
|
+
@mock.patch("core.admin.PackageRelease.dump_fixture")
|
|
1127
|
+
@mock.patch("core.admin.requests.get")
|
|
1128
|
+
def test_refresh_from_pypi_updates_release_date(self, mock_get, dump):
|
|
1129
|
+
self.release.release_on = None
|
|
1130
|
+
self.release.save(update_fields=["release_on"])
|
|
1131
|
+
mock_get.return_value.raise_for_status.return_value = None
|
|
1132
|
+
mock_get.return_value.json.return_value = {
|
|
1133
|
+
"releases": {
|
|
1134
|
+
"1.0.0": [
|
|
1135
|
+
{"upload_time_iso_8601": "2024-01-01T12:30:00.000000Z"}
|
|
1136
|
+
]
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
self.admin.refresh_from_pypi(self.request, PackageRelease.objects.none())
|
|
1140
|
+
self.release.refresh_from_db()
|
|
1141
|
+
self.assertEqual(
|
|
1142
|
+
self.release.release_on,
|
|
1143
|
+
datetime(2024, 1, 1, 12, 30, tzinfo=datetime_timezone.utc),
|
|
1144
|
+
)
|
|
1145
|
+
dump.assert_called_once()
|
|
1146
|
+
|
|
1147
|
+
@mock.patch("core.admin.PackageRelease.dump_fixture")
|
|
1148
|
+
@mock.patch("core.admin.requests.get")
|
|
1149
|
+
def test_refresh_from_pypi_restores_deleted_release(self, mock_get, dump):
|
|
1150
|
+
self.release.is_deleted = True
|
|
1151
|
+
self.release.save(update_fields=["is_deleted"])
|
|
1152
|
+
mock_get.return_value.raise_for_status.return_value = None
|
|
1153
|
+
mock_get.return_value.json.return_value = {
|
|
1154
|
+
"releases": {
|
|
1155
|
+
"1.0.0": [
|
|
1156
|
+
{"upload_time_iso_8601": "2024-01-01T12:30:00.000000Z"}
|
|
1157
|
+
]
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
self.admin.refresh_from_pypi(self.request, PackageRelease.objects.none())
|
|
1162
|
+
|
|
1163
|
+
self.assertTrue(
|
|
1164
|
+
PackageRelease.objects.filter(version="1.0.0").exists()
|
|
1165
|
+
)
|
|
944
1166
|
dump.assert_called_once()
|
|
945
1167
|
|
|
946
1168
|
|
|
@@ -982,6 +1204,40 @@ class PackageReleaseCurrentTests(TestCase):
|
|
|
982
1204
|
self.assertFalse(self.release.is_current)
|
|
983
1205
|
|
|
984
1206
|
|
|
1207
|
+
class PackageReleaseChangelistTests(TestCase):
|
|
1208
|
+
def setUp(self):
|
|
1209
|
+
self.client = Client()
|
|
1210
|
+
User.objects.create_superuser("admin", "admin@example.com", "pw")
|
|
1211
|
+
self.client.force_login(User.objects.get(username="admin"))
|
|
1212
|
+
|
|
1213
|
+
def test_prepare_next_release_button_present(self):
|
|
1214
|
+
response = self.client.get(reverse("admin:core_packagerelease_changelist"))
|
|
1215
|
+
prepare_url = reverse(
|
|
1216
|
+
"admin:core_packagerelease_actions", args=["prepare_next_release"]
|
|
1217
|
+
)
|
|
1218
|
+
self.assertContains(response, prepare_url, html=False)
|
|
1219
|
+
|
|
1220
|
+
def test_refresh_from_pypi_button_present(self):
|
|
1221
|
+
response = self.client.get(reverse("admin:core_packagerelease_changelist"))
|
|
1222
|
+
refresh_url = reverse(
|
|
1223
|
+
"admin:core_packagerelease_actions", args=["refresh_from_pypi"]
|
|
1224
|
+
)
|
|
1225
|
+
self.assertContains(response, refresh_url, html=False)
|
|
1226
|
+
|
|
1227
|
+
def test_prepare_next_release_action_creates_release(self):
|
|
1228
|
+
package = Package.objects.get(name="arthexis")
|
|
1229
|
+
PackageRelease.all_objects.filter(package=package).delete()
|
|
1230
|
+
response = self.client.post(
|
|
1231
|
+
reverse(
|
|
1232
|
+
"admin:core_packagerelease_actions", args=["prepare_next_release"]
|
|
1233
|
+
)
|
|
1234
|
+
)
|
|
1235
|
+
self.assertEqual(response.status_code, 302)
|
|
1236
|
+
self.assertTrue(
|
|
1237
|
+
PackageRelease.all_objects.filter(package=package).exists()
|
|
1238
|
+
)
|
|
1239
|
+
|
|
1240
|
+
|
|
985
1241
|
class PackageAdminPrepareNextReleaseTests(TestCase):
|
|
986
1242
|
def setUp(self):
|
|
987
1243
|
self.factory = RequestFactory()
|
|
@@ -1000,24 +1256,18 @@ class PackageAdminPrepareNextReleaseTests(TestCase):
|
|
|
1000
1256
|
)
|
|
1001
1257
|
|
|
1002
1258
|
|
|
1003
|
-
class
|
|
1259
|
+
class PackageAdminChangeViewTests(TestCase):
|
|
1004
1260
|
def setUp(self):
|
|
1005
1261
|
self.client = Client()
|
|
1006
1262
|
User.objects.create_superuser("admin", "admin@example.com", "pw")
|
|
1007
1263
|
self.client.force_login(User.objects.get(username="admin"))
|
|
1264
|
+
self.package = Package.objects.get(name="arthexis")
|
|
1008
1265
|
|
|
1009
|
-
def
|
|
1010
|
-
response = self.client.get(
|
|
1011
|
-
|
|
1012
|
-
response, reverse("admin:core_package_prepare_next_release"), html=False
|
|
1013
|
-
)
|
|
1014
|
-
|
|
1015
|
-
def test_refresh_from_pypi_button_present(self):
|
|
1016
|
-
response = self.client.get(reverse("admin:core_packagerelease_changelist"))
|
|
1017
|
-
refresh_url = reverse(
|
|
1018
|
-
"admin:core_packagerelease_actions", args=["refresh_from_pypi"]
|
|
1266
|
+
def test_prepare_next_release_button_visible_on_change_view(self):
|
|
1267
|
+
response = self.client.get(
|
|
1268
|
+
reverse("admin:core_package_change", args=[self.package.pk])
|
|
1019
1269
|
)
|
|
1020
|
-
self.assertContains(response,
|
|
1270
|
+
self.assertContains(response, "Prepare next Release")
|
|
1021
1271
|
|
|
1022
1272
|
|
|
1023
1273
|
class TodoDoneTests(TestCase):
|
|
@@ -1034,6 +1284,134 @@ class TodoDoneTests(TestCase):
|
|
|
1034
1284
|
self.assertIsNotNone(todo.done_on)
|
|
1035
1285
|
self.assertFalse(todo.is_deleted)
|
|
1036
1286
|
|
|
1287
|
+
def test_mark_done_condition_failure_shows_message(self):
|
|
1288
|
+
todo = Todo.objects.create(
|
|
1289
|
+
request="Task",
|
|
1290
|
+
on_done_condition="1 = 0",
|
|
1291
|
+
)
|
|
1292
|
+
resp = self.client.post(reverse("todo-done", args=[todo.pk]))
|
|
1293
|
+
self.assertRedirects(resp, reverse("admin:index"))
|
|
1294
|
+
messages = [m.message for m in get_messages(resp.wsgi_request)]
|
|
1295
|
+
self.assertTrue(messages)
|
|
1296
|
+
self.assertIn("1 = 0", messages[0])
|
|
1297
|
+
todo.refresh_from_db()
|
|
1298
|
+
self.assertIsNone(todo.done_on)
|
|
1299
|
+
|
|
1300
|
+
def test_mark_done_condition_invalid_expression(self):
|
|
1301
|
+
todo = Todo.objects.create(
|
|
1302
|
+
request="Task",
|
|
1303
|
+
on_done_condition="1; SELECT 1",
|
|
1304
|
+
)
|
|
1305
|
+
resp = self.client.post(reverse("todo-done", args=[todo.pk]))
|
|
1306
|
+
self.assertRedirects(resp, reverse("admin:index"))
|
|
1307
|
+
messages = [m.message for m in get_messages(resp.wsgi_request)]
|
|
1308
|
+
self.assertTrue(messages)
|
|
1309
|
+
self.assertIn("Semicolons", messages[0])
|
|
1310
|
+
todo.refresh_from_db()
|
|
1311
|
+
self.assertIsNone(todo.done_on)
|
|
1312
|
+
|
|
1313
|
+
def test_mark_done_condition_resolves_sigils(self):
|
|
1314
|
+
todo = Todo.objects.create(
|
|
1315
|
+
request="Task",
|
|
1316
|
+
on_done_condition="[TEST]",
|
|
1317
|
+
)
|
|
1318
|
+
with mock.patch.object(Todo, "resolve_sigils", return_value="1 = 1") as resolver:
|
|
1319
|
+
resp = self.client.post(reverse("todo-done", args=[todo.pk]))
|
|
1320
|
+
self.assertRedirects(resp, reverse("admin:index"))
|
|
1321
|
+
resolver.assert_called_once_with("on_done_condition")
|
|
1322
|
+
todo.refresh_from_db()
|
|
1323
|
+
self.assertIsNotNone(todo.done_on)
|
|
1324
|
+
|
|
1325
|
+
def test_mark_done_respects_next_parameter(self):
|
|
1326
|
+
todo = Todo.objects.create(request="Task")
|
|
1327
|
+
next_url = reverse("admin:index") + "?section=todos"
|
|
1328
|
+
resp = self.client.post(
|
|
1329
|
+
reverse("todo-done", args=[todo.pk]),
|
|
1330
|
+
{"next": next_url},
|
|
1331
|
+
)
|
|
1332
|
+
self.assertRedirects(resp, next_url, target_status_code=200)
|
|
1333
|
+
todo.refresh_from_db()
|
|
1334
|
+
self.assertIsNotNone(todo.done_on)
|
|
1335
|
+
|
|
1336
|
+
def test_mark_done_rejects_external_next(self):
|
|
1337
|
+
todo = Todo.objects.create(request="Task")
|
|
1338
|
+
resp = self.client.post(
|
|
1339
|
+
reverse("todo-done", args=[todo.pk]),
|
|
1340
|
+
{"next": "https://example.com/"},
|
|
1341
|
+
)
|
|
1342
|
+
self.assertRedirects(resp, reverse("admin:index"))
|
|
1343
|
+
todo.refresh_from_db()
|
|
1344
|
+
self.assertIsNotNone(todo.done_on)
|
|
1345
|
+
|
|
1346
|
+
|
|
1347
|
+
class TodoFocusViewTests(TestCase):
|
|
1348
|
+
def setUp(self):
|
|
1349
|
+
self.client = Client()
|
|
1350
|
+
User.objects.create_superuser("admin", "admin@example.com", "pw")
|
|
1351
|
+
self.client.force_login(User.objects.get(username="admin"))
|
|
1352
|
+
|
|
1353
|
+
def test_focus_view_renders_requested_page(self):
|
|
1354
|
+
todo = Todo.objects.create(request="Task", url="/docs/")
|
|
1355
|
+
next_url = reverse("admin:index")
|
|
1356
|
+
resp = self.client.get(
|
|
1357
|
+
f"{reverse('todo-focus', args=[todo.pk])}?next={quote(next_url)}"
|
|
1358
|
+
)
|
|
1359
|
+
self.assertEqual(resp.status_code, 200)
|
|
1360
|
+
self.assertContains(resp, todo.request)
|
|
1361
|
+
self.assertEqual(resp["X-Frame-Options"], "SAMEORIGIN")
|
|
1362
|
+
self.assertContains(resp, f'src="{todo.url}"')
|
|
1363
|
+
self.assertContains(resp, "Done")
|
|
1364
|
+
self.assertContains(resp, "Back")
|
|
1365
|
+
|
|
1366
|
+
def test_focus_view_uses_admin_change_when_no_url(self):
|
|
1367
|
+
todo = Todo.objects.create(request="Task")
|
|
1368
|
+
resp = self.client.get(reverse("todo-focus", args=[todo.pk]))
|
|
1369
|
+
change_url = reverse("admin:core_todo_change", args=[todo.pk])
|
|
1370
|
+
self.assertContains(resp, f'src="{change_url}"')
|
|
1371
|
+
|
|
1372
|
+
def test_focus_view_sanitizes_loopback_absolute_url(self):
|
|
1373
|
+
todo = Todo.objects.create(
|
|
1374
|
+
request="Task",
|
|
1375
|
+
url="http://127.0.0.1:8000/docs/?section=chart",
|
|
1376
|
+
)
|
|
1377
|
+
resp = self.client.get(reverse("todo-focus", args=[todo.pk]))
|
|
1378
|
+
self.assertContains(resp, 'src="/docs/?section=chart"')
|
|
1379
|
+
|
|
1380
|
+
def test_focus_view_rejects_external_absolute_url(self):
|
|
1381
|
+
todo = Todo.objects.create(
|
|
1382
|
+
request="Task",
|
|
1383
|
+
url="https://outside.invalid/external/",
|
|
1384
|
+
)
|
|
1385
|
+
resp = self.client.get(reverse("todo-focus", args=[todo.pk]))
|
|
1386
|
+
change_url = reverse("admin:core_todo_change", args=[todo.pk])
|
|
1387
|
+
self.assertContains(resp, f'src="{change_url}"')
|
|
1388
|
+
|
|
1389
|
+
def test_focus_view_avoids_recursive_focus_url(self):
|
|
1390
|
+
todo = Todo.objects.create(request="Task")
|
|
1391
|
+
focus_url = reverse("todo-focus", args=[todo.pk])
|
|
1392
|
+
Todo.objects.filter(pk=todo.pk).update(url=focus_url)
|
|
1393
|
+
resp = self.client.get(reverse("todo-focus", args=[todo.pk]))
|
|
1394
|
+
change_url = reverse("admin:core_todo_change", args=[todo.pk])
|
|
1395
|
+
self.assertContains(resp, f'src="{change_url}"')
|
|
1396
|
+
|
|
1397
|
+
def test_focus_view_avoids_recursive_focus_absolute_url(self):
|
|
1398
|
+
todo = Todo.objects.create(request="Task")
|
|
1399
|
+
focus_url = reverse("todo-focus", args=[todo.pk])
|
|
1400
|
+
Todo.objects.filter(pk=todo.pk).update(url=f"http://testserver{focus_url}")
|
|
1401
|
+
resp = self.client.get(reverse("todo-focus", args=[todo.pk]))
|
|
1402
|
+
change_url = reverse("admin:core_todo_change", args=[todo.pk])
|
|
1403
|
+
self.assertContains(resp, f'src="{change_url}"')
|
|
1404
|
+
|
|
1405
|
+
def test_focus_view_redirects_if_todo_completed(self):
|
|
1406
|
+
todo = Todo.objects.create(request="Task")
|
|
1407
|
+
todo.done_on = timezone.now()
|
|
1408
|
+
todo.save(update_fields=["done_on"])
|
|
1409
|
+
next_url = reverse("admin:index")
|
|
1410
|
+
resp = self.client.get(
|
|
1411
|
+
f"{reverse('todo-focus', args=[todo.pk])}?next={quote(next_url)}"
|
|
1412
|
+
)
|
|
1413
|
+
self.assertRedirects(resp, next_url, target_status_code=200)
|
|
1414
|
+
|
|
1037
1415
|
|
|
1038
1416
|
class TodoUrlValidationTests(TestCase):
|
|
1039
1417
|
def test_relative_url_valid(self):
|