arthexis 0.1.8__py3-none-any.whl → 0.1.9__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.8.dist-info → arthexis-0.1.9.dist-info}/METADATA +42 -4
- arthexis-0.1.9.dist-info/RECORD +92 -0
- arthexis-0.1.9.dist-info/licenses/LICENSE +674 -0
- config/__init__.py +0 -1
- config/auth_app.py +0 -1
- config/celery.py +1 -2
- config/context_processors.py +1 -1
- config/offline.py +2 -0
- config/settings.py +133 -16
- config/urls.py +65 -6
- core/admin.py +1226 -191
- core/admin_history.py +50 -0
- core/admindocs.py +108 -1
- core/apps.py +158 -3
- core/backends.py +46 -4
- core/entity.py +62 -48
- core/fields.py +6 -1
- core/github_helper.py +25 -0
- core/github_issues.py +172 -0
- core/lcd_screen.py +1 -0
- core/liveupdate.py +25 -0
- core/log_paths.py +100 -0
- core/mailer.py +83 -0
- core/middleware.py +57 -0
- core/models.py +1071 -264
- core/notifications.py +11 -1
- core/public_wifi.py +227 -0
- core/release.py +27 -20
- core/sigil_builder.py +131 -0
- core/sigil_context.py +20 -0
- core/sigil_resolver.py +284 -0
- core/system.py +129 -10
- core/tasks.py +118 -19
- core/test_system_info.py +22 -0
- core/tests.py +358 -63
- core/tests_liveupdate.py +17 -0
- core/urls.py +2 -2
- core/user_data.py +329 -167
- core/views.py +383 -57
- core/widgets.py +51 -0
- core/workgroup_urls.py +7 -3
- core/workgroup_views.py +43 -6
- nodes/actions.py +0 -2
- nodes/admin.py +159 -284
- nodes/apps.py +9 -15
- nodes/backends.py +53 -0
- nodes/lcd.py +24 -10
- nodes/models.py +375 -178
- nodes/tasks.py +1 -5
- nodes/tests.py +524 -129
- nodes/utils.py +13 -2
- nodes/views.py +66 -23
- ocpp/admin.py +150 -61
- ocpp/apps.py +1 -1
- ocpp/consumers.py +432 -69
- ocpp/evcs.py +25 -8
- ocpp/models.py +408 -68
- ocpp/simulator.py +13 -6
- ocpp/store.py +258 -30
- ocpp/tasks.py +11 -7
- ocpp/test_export_import.py +8 -7
- ocpp/test_rfid.py +211 -16
- ocpp/tests.py +1198 -135
- ocpp/transactions_io.py +68 -22
- ocpp/urls.py +35 -2
- ocpp/views.py +654 -101
- pages/admin.py +173 -13
- pages/checks.py +0 -1
- pages/context_processors.py +19 -6
- pages/middleware.py +153 -0
- pages/models.py +37 -9
- pages/tests.py +759 -40
- pages/urls.py +3 -0
- pages/utils.py +0 -1
- pages/views.py +576 -25
- arthexis-0.1.8.dist-info/RECORD +0 -80
- arthexis-0.1.8.dist-info/licenses/LICENSE +0 -21
- config/workgroup_app.py +0 -7
- core/checks.py +0 -29
- {arthexis-0.1.8.dist-info → arthexis-0.1.9.dist-info}/WHEEL +0 -0
- {arthexis-0.1.8.dist-info → arthexis-0.1.9.dist-info}/top_level.txt +0 -0
core/tests.py
CHANGED
|
@@ -2,42 +2,56 @@ import os
|
|
|
2
2
|
|
|
3
3
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
|
4
4
|
import django
|
|
5
|
+
|
|
5
6
|
django.setup()
|
|
6
7
|
|
|
7
|
-
from django.test import Client, TestCase, RequestFactory
|
|
8
|
+
from django.test import Client, TestCase, RequestFactory, override_settings
|
|
8
9
|
from django.urls import reverse
|
|
9
10
|
from django.http import HttpRequest
|
|
10
11
|
import json
|
|
12
|
+
from decimal import Decimal
|
|
11
13
|
from unittest import mock
|
|
14
|
+
from unittest.mock import patch
|
|
12
15
|
from pathlib import Path
|
|
13
16
|
import subprocess
|
|
17
|
+
from glob import glob
|
|
18
|
+
from datetime import timedelta
|
|
19
|
+
import tempfile
|
|
14
20
|
|
|
15
21
|
from django.utils import timezone
|
|
22
|
+
from django.contrib.auth.models import Permission
|
|
16
23
|
from .models import (
|
|
17
24
|
User,
|
|
25
|
+
UserPhoneNumber,
|
|
18
26
|
EnergyAccount,
|
|
19
27
|
ElectricVehicle,
|
|
20
28
|
EnergyCredit,
|
|
21
|
-
Address,
|
|
22
29
|
Product,
|
|
23
|
-
Subscription,
|
|
24
30
|
Brand,
|
|
25
31
|
EVModel,
|
|
26
32
|
RFID,
|
|
27
|
-
FediverseProfile,
|
|
28
33
|
SecurityGroup,
|
|
29
34
|
Package,
|
|
30
35
|
PackageRelease,
|
|
36
|
+
ReleaseManager,
|
|
37
|
+
Todo,
|
|
38
|
+
PublicWifiAccess,
|
|
31
39
|
)
|
|
32
40
|
from django.contrib.admin.sites import AdminSite
|
|
33
|
-
from core.admin import
|
|
41
|
+
from core.admin import (
|
|
42
|
+
PackageReleaseAdmin,
|
|
43
|
+
PackageAdmin,
|
|
44
|
+
UserAdmin,
|
|
45
|
+
USER_PROFILE_INLINES,
|
|
46
|
+
)
|
|
34
47
|
from ocpp.models import Transaction, Charger
|
|
35
48
|
|
|
36
49
|
from django.core.exceptions import ValidationError
|
|
37
50
|
from django.core.management import call_command
|
|
38
51
|
from django.db import IntegrityError
|
|
39
52
|
from .backends import LocalhostAdminBackend
|
|
40
|
-
from core.views import
|
|
53
|
+
from core.views import _step_check_version, _step_promote_build, _step_publish
|
|
54
|
+
from core import public_wifi
|
|
41
55
|
|
|
42
56
|
|
|
43
57
|
class DefaultAdminTests(TestCase):
|
|
@@ -78,6 +92,205 @@ class DefaultAdminTests(TestCase):
|
|
|
78
92
|
)
|
|
79
93
|
|
|
80
94
|
|
|
95
|
+
class UserOperateAsTests(TestCase):
|
|
96
|
+
@classmethod
|
|
97
|
+
def setUpTestData(cls):
|
|
98
|
+
cls.permission = Permission.objects.get(codename="view_todo")
|
|
99
|
+
|
|
100
|
+
def test_staff_user_delegates_permissions(self):
|
|
101
|
+
delegate = User.objects.create_user(username="delegate", password="secret")
|
|
102
|
+
delegate.user_permissions.add(self.permission)
|
|
103
|
+
operator = User.objects.create_user(
|
|
104
|
+
username="operator", password="secret", is_staff=True
|
|
105
|
+
)
|
|
106
|
+
self.assertFalse(operator.has_perm("core.view_todo"))
|
|
107
|
+
operator.operate_as = delegate
|
|
108
|
+
operator.full_clean()
|
|
109
|
+
operator.save()
|
|
110
|
+
operator.refresh_from_db()
|
|
111
|
+
self.assertTrue(operator.has_perm("core.view_todo"))
|
|
112
|
+
|
|
113
|
+
def test_only_staff_may_operate_as(self):
|
|
114
|
+
delegate = User.objects.create_user(username="delegate", password="secret")
|
|
115
|
+
operator = User.objects.create_user(username="operator", password="secret")
|
|
116
|
+
operator.operate_as = delegate
|
|
117
|
+
with self.assertRaises(ValidationError):
|
|
118
|
+
operator.full_clean()
|
|
119
|
+
|
|
120
|
+
def test_non_superuser_cannot_operate_as_staff(self):
|
|
121
|
+
staff_delegate = User.objects.create_user(
|
|
122
|
+
username="delegate", password="secret", is_staff=True
|
|
123
|
+
)
|
|
124
|
+
operator = User.objects.create_user(
|
|
125
|
+
username="operator", password="secret", is_staff=True
|
|
126
|
+
)
|
|
127
|
+
operator.operate_as = staff_delegate
|
|
128
|
+
with self.assertRaises(ValidationError):
|
|
129
|
+
operator.full_clean()
|
|
130
|
+
|
|
131
|
+
def test_recursive_chain_and_cycle_detection(self):
|
|
132
|
+
base = User.objects.create_user(username="base", password="secret")
|
|
133
|
+
base.user_permissions.add(self.permission)
|
|
134
|
+
middle = User.objects.create_user(
|
|
135
|
+
username="middle", password="secret", is_staff=True
|
|
136
|
+
)
|
|
137
|
+
middle.operate_as = base
|
|
138
|
+
middle.full_clean()
|
|
139
|
+
middle.save()
|
|
140
|
+
top = User.objects.create_superuser(
|
|
141
|
+
username="top", email="top@example.com", password="secret"
|
|
142
|
+
)
|
|
143
|
+
top.operate_as = middle
|
|
144
|
+
top.full_clean()
|
|
145
|
+
top.save()
|
|
146
|
+
top.refresh_from_db()
|
|
147
|
+
self.assertTrue(top.has_perm("core.view_todo"))
|
|
148
|
+
|
|
149
|
+
first = User.objects.create_superuser(
|
|
150
|
+
username="first", email="first@example.com", password="secret"
|
|
151
|
+
)
|
|
152
|
+
second = User.objects.create_superuser(
|
|
153
|
+
username="second", email="second@example.com", password="secret"
|
|
154
|
+
)
|
|
155
|
+
first.operate_as = second
|
|
156
|
+
first.full_clean()
|
|
157
|
+
first.save()
|
|
158
|
+
second.operate_as = first
|
|
159
|
+
second.full_clean()
|
|
160
|
+
second.save()
|
|
161
|
+
self.assertFalse(first._check_operate_as_chain(lambda user: False))
|
|
162
|
+
|
|
163
|
+
def test_module_permissions_fall_back(self):
|
|
164
|
+
delegate = User.objects.create_user(username="helper", password="secret")
|
|
165
|
+
delegate.user_permissions.add(self.permission)
|
|
166
|
+
operator = User.objects.create_user(
|
|
167
|
+
username="mod", password="secret", is_staff=True
|
|
168
|
+
)
|
|
169
|
+
operator.operate_as = delegate
|
|
170
|
+
operator.full_clean()
|
|
171
|
+
operator.save()
|
|
172
|
+
self.assertTrue(operator.has_module_perms("core"))
|
|
173
|
+
|
|
174
|
+
def test_has_profile_via_delegate(self):
|
|
175
|
+
delegate = User.objects.create_user(
|
|
176
|
+
username="delegate", password="secret", is_staff=True
|
|
177
|
+
)
|
|
178
|
+
ReleaseManager.objects.create(user=delegate)
|
|
179
|
+
operator = User.objects.create_superuser(
|
|
180
|
+
username="operator",
|
|
181
|
+
email="operator@example.com",
|
|
182
|
+
password="secret",
|
|
183
|
+
)
|
|
184
|
+
operator.operate_as = delegate
|
|
185
|
+
operator.full_clean()
|
|
186
|
+
operator.save()
|
|
187
|
+
profile = operator.get_profile(ReleaseManager)
|
|
188
|
+
self.assertIsNotNone(profile)
|
|
189
|
+
self.assertEqual(profile.user, delegate)
|
|
190
|
+
self.assertTrue(operator.has_profile(ReleaseManager))
|
|
191
|
+
|
|
192
|
+
def test_has_profile_via_group_membership(self):
|
|
193
|
+
member = User.objects.create_user(username="member", password="secret")
|
|
194
|
+
group = SecurityGroup.objects.create(name="Managers")
|
|
195
|
+
group.user_set.add(member)
|
|
196
|
+
profile = ReleaseManager.objects.create(group=group)
|
|
197
|
+
self.assertEqual(member.get_profile(ReleaseManager), profile)
|
|
198
|
+
self.assertTrue(member.has_profile(ReleaseManager))
|
|
199
|
+
|
|
200
|
+
def test_release_manager_property_uses_delegate_profile(self):
|
|
201
|
+
delegate = User.objects.create_user(
|
|
202
|
+
username="delegate-property", password="secret", is_staff=True
|
|
203
|
+
)
|
|
204
|
+
profile = ReleaseManager.objects.create(user=delegate)
|
|
205
|
+
operator = User.objects.create_superuser(
|
|
206
|
+
username="operator-property",
|
|
207
|
+
email="operator-property@example.com",
|
|
208
|
+
password="secret",
|
|
209
|
+
)
|
|
210
|
+
operator.operate_as = delegate
|
|
211
|
+
operator.full_clean()
|
|
212
|
+
operator.save()
|
|
213
|
+
self.assertEqual(operator.release_manager, profile)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class UserPhoneNumberTests(TestCase):
|
|
217
|
+
def test_get_phone_numbers_by_priority(self):
|
|
218
|
+
user = User.objects.create_user(username="phone-user", password="secret")
|
|
219
|
+
later = UserPhoneNumber.objects.create(
|
|
220
|
+
user=user, number="+15555550101", priority=10
|
|
221
|
+
)
|
|
222
|
+
earlier = UserPhoneNumber.objects.create(
|
|
223
|
+
user=user, number="+15555550100", priority=1
|
|
224
|
+
)
|
|
225
|
+
immediate = UserPhoneNumber.objects.create(
|
|
226
|
+
user=user, number="+15555550099", priority=0
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
phones = user.get_phones_by_priority()
|
|
230
|
+
self.assertEqual(phones, [immediate, earlier, later])
|
|
231
|
+
|
|
232
|
+
def test_get_phone_numbers_by_priority_orders_by_id_when_equal(self):
|
|
233
|
+
user = User.objects.create_user(username="phone-order", password="secret")
|
|
234
|
+
first = UserPhoneNumber.objects.create(
|
|
235
|
+
user=user, number="+19995550000", priority=0
|
|
236
|
+
)
|
|
237
|
+
second = UserPhoneNumber.objects.create(
|
|
238
|
+
user=user, number="+19995550001", priority=0
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
phones = user.get_phones_by_priority()
|
|
242
|
+
self.assertEqual(phones, [first, second])
|
|
243
|
+
|
|
244
|
+
def test_get_phone_numbers_by_priority_alias(self):
|
|
245
|
+
user = User.objects.create_user(username="phone-alias", password="secret")
|
|
246
|
+
phone = UserPhoneNumber.objects.create(
|
|
247
|
+
user=user, number="+14445550000", priority=3
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
self.assertEqual(user.get_phone_numbers_by_priority(), [phone])
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
class ProfileValidationTests(TestCase):
|
|
254
|
+
def test_system_user_cannot_receive_profiles(self):
|
|
255
|
+
system_user = User.objects.get(username=User.SYSTEM_USERNAME)
|
|
256
|
+
profile = ReleaseManager(user=system_user)
|
|
257
|
+
with self.assertRaises(ValidationError) as exc:
|
|
258
|
+
profile.full_clean()
|
|
259
|
+
self.assertIn("user", exc.exception.error_dict)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class UserAdminInlineTests(TestCase):
|
|
263
|
+
def setUp(self):
|
|
264
|
+
self.site = AdminSite()
|
|
265
|
+
self.factory = RequestFactory()
|
|
266
|
+
self.admin = UserAdmin(User, self.site)
|
|
267
|
+
self.system_user = User.objects.get(username=User.SYSTEM_USERNAME)
|
|
268
|
+
self.superuser = User.objects.create_superuser(
|
|
269
|
+
username="inline-super",
|
|
270
|
+
email="inline-super@example.com",
|
|
271
|
+
password="secret",
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def test_profile_inlines_hidden_for_system_user(self):
|
|
275
|
+
request = self.factory.get("/")
|
|
276
|
+
request.user = self.superuser
|
|
277
|
+
system_inlines = self.admin.get_inline_instances(request, self.system_user)
|
|
278
|
+
system_profiles = [
|
|
279
|
+
inline
|
|
280
|
+
for inline in system_inlines
|
|
281
|
+
if inline.__class__ in USER_PROFILE_INLINES
|
|
282
|
+
]
|
|
283
|
+
self.assertFalse(system_profiles)
|
|
284
|
+
|
|
285
|
+
other_inlines = self.admin.get_inline_instances(request, self.superuser)
|
|
286
|
+
other_profiles = [
|
|
287
|
+
inline
|
|
288
|
+
for inline in other_inlines
|
|
289
|
+
if inline.__class__ in USER_PROFILE_INLINES
|
|
290
|
+
]
|
|
291
|
+
self.assertEqual(len(other_profiles), len(USER_PROFILE_INLINES))
|
|
292
|
+
|
|
293
|
+
|
|
81
294
|
class RFIDLoginTests(TestCase):
|
|
82
295
|
def setUp(self):
|
|
83
296
|
self.client = Client()
|
|
@@ -112,7 +325,7 @@ class RFIDBatchApiTests(TestCase):
|
|
|
112
325
|
self.client.force_login(self.user)
|
|
113
326
|
|
|
114
327
|
def test_export_rfids(self):
|
|
115
|
-
tag_black = RFID.objects.create(rfid="CARD999")
|
|
328
|
+
tag_black = RFID.objects.create(rfid="CARD999", custom_label="Main Tag")
|
|
116
329
|
tag_white = RFID.objects.create(rfid="CARD998", color=RFID.WHITE)
|
|
117
330
|
self.account.rfids.add(tag_black, tag_white)
|
|
118
331
|
response = self.client.get(reverse("rfid-batch"))
|
|
@@ -123,6 +336,7 @@ class RFIDBatchApiTests(TestCase):
|
|
|
123
336
|
"rfids": [
|
|
124
337
|
{
|
|
125
338
|
"rfid": "CARD999",
|
|
339
|
+
"custom_label": "Main Tag",
|
|
126
340
|
"energy_accounts": [self.account.id],
|
|
127
341
|
"allowed": True,
|
|
128
342
|
"color": "B",
|
|
@@ -141,6 +355,7 @@ class RFIDBatchApiTests(TestCase):
|
|
|
141
355
|
"rfids": [
|
|
142
356
|
{
|
|
143
357
|
"rfid": "CARD111",
|
|
358
|
+
"custom_label": "",
|
|
144
359
|
"energy_accounts": [],
|
|
145
360
|
"allowed": True,
|
|
146
361
|
"color": "W",
|
|
@@ -160,6 +375,7 @@ class RFIDBatchApiTests(TestCase):
|
|
|
160
375
|
"rfids": [
|
|
161
376
|
{
|
|
162
377
|
"rfid": "CARD112",
|
|
378
|
+
"custom_label": "",
|
|
163
379
|
"energy_accounts": [],
|
|
164
380
|
"allowed": True,
|
|
165
381
|
"color": "B",
|
|
@@ -174,6 +390,7 @@ class RFIDBatchApiTests(TestCase):
|
|
|
174
390
|
"rfids": [
|
|
175
391
|
{
|
|
176
392
|
"rfid": "A1B2C3D4",
|
|
393
|
+
"custom_label": "Imported Tag",
|
|
177
394
|
"energy_accounts": [self.account.id],
|
|
178
395
|
"allowed": True,
|
|
179
396
|
"color": "W",
|
|
@@ -191,6 +408,7 @@ class RFIDBatchApiTests(TestCase):
|
|
|
191
408
|
self.assertTrue(
|
|
192
409
|
RFID.objects.filter(
|
|
193
410
|
rfid="A1B2C3D4",
|
|
411
|
+
custom_label="Imported Tag",
|
|
194
412
|
energy_accounts=self.account,
|
|
195
413
|
color=RFID.WHITE,
|
|
196
414
|
released=True,
|
|
@@ -237,6 +455,11 @@ class RFIDValidationTests(TestCase):
|
|
|
237
455
|
found = RFID.get_account_by_rfid("abcd1234")
|
|
238
456
|
self.assertEqual(found, acc)
|
|
239
457
|
|
|
458
|
+
def test_custom_label_length(self):
|
|
459
|
+
tag = RFID(rfid="FACE1234", custom_label="x" * 33)
|
|
460
|
+
with self.assertRaises(ValidationError):
|
|
461
|
+
tag.full_clean()
|
|
462
|
+
|
|
240
463
|
|
|
241
464
|
class RFIDAssignmentTests(TestCase):
|
|
242
465
|
def setUp(self):
|
|
@@ -279,7 +502,9 @@ class EnergyAccountTests(TestCase):
|
|
|
279
502
|
|
|
280
503
|
def test_service_account_ignores_balance(self):
|
|
281
504
|
user = User.objects.create_user(username="service", password="x")
|
|
282
|
-
acc = EnergyAccount.objects.create(
|
|
505
|
+
acc = EnergyAccount.objects.create(
|
|
506
|
+
user=user, service_account=True, name="SERVICE"
|
|
507
|
+
)
|
|
283
508
|
self.assertTrue(acc.can_authorize())
|
|
284
509
|
|
|
285
510
|
def test_account_without_user(self):
|
|
@@ -331,7 +556,43 @@ class AddressTests(TestCase):
|
|
|
331
556
|
self.assertEqual(user.address, addr)
|
|
332
557
|
|
|
333
558
|
|
|
334
|
-
class
|
|
559
|
+
class PublicWifiUtilitiesTests(TestCase):
|
|
560
|
+
def setUp(self):
|
|
561
|
+
self.user = User.objects.create_user(username="wifi", password="pwd")
|
|
562
|
+
|
|
563
|
+
def test_grant_public_access_records_allowlist(self):
|
|
564
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
565
|
+
base = Path(tmp)
|
|
566
|
+
allow_file = base / "locks" / "public_wifi_allow.list"
|
|
567
|
+
with override_settings(BASE_DIR=base):
|
|
568
|
+
with patch("core.public_wifi._iptables_available", return_value=False):
|
|
569
|
+
public_wifi.grant_public_access(self.user, "AA:BB:CC:DD:EE:FF")
|
|
570
|
+
self.assertTrue(allow_file.exists())
|
|
571
|
+
content = allow_file.read_text()
|
|
572
|
+
self.assertIn("aa:bb:cc:dd:ee:ff", content)
|
|
573
|
+
self.assertTrue(
|
|
574
|
+
PublicWifiAccess.objects.filter(
|
|
575
|
+
user=self.user, mac_address="aa:bb:cc:dd:ee:ff"
|
|
576
|
+
).exists()
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
def test_revoke_public_access_for_user_updates_allowlist(self):
|
|
580
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
581
|
+
base = Path(tmp)
|
|
582
|
+
allow_file = base / "locks" / "public_wifi_allow.list"
|
|
583
|
+
with override_settings(BASE_DIR=base):
|
|
584
|
+
with patch("core.public_wifi._iptables_available", return_value=False):
|
|
585
|
+
access = public_wifi.grant_public_access(
|
|
586
|
+
self.user, "AA:BB:CC:DD:EE:FF"
|
|
587
|
+
)
|
|
588
|
+
public_wifi.revoke_public_access_for_user(self.user)
|
|
589
|
+
access.refresh_from_db()
|
|
590
|
+
self.assertIsNotNone(access.revoked_on)
|
|
591
|
+
if allow_file.exists():
|
|
592
|
+
self.assertNotIn("aa:bb:cc:dd:ee:ff", allow_file.read_text())
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
class LiveSubscriptionTests(TestCase):
|
|
335
596
|
def setUp(self):
|
|
336
597
|
self.client = Client()
|
|
337
598
|
self.user = User.objects.create_user(username="bob", password="pwd")
|
|
@@ -339,22 +600,41 @@ class SubscriptionTests(TestCase):
|
|
|
339
600
|
self.product = Product.objects.create(name="Gold", renewal_period=30)
|
|
340
601
|
self.client.force_login(self.user)
|
|
341
602
|
|
|
342
|
-
def
|
|
603
|
+
def test_create_and_list_live_subscription(self):
|
|
343
604
|
response = self.client.post(
|
|
344
|
-
reverse("add-subscription"),
|
|
605
|
+
reverse("add-live-subscription"),
|
|
345
606
|
data={"account_id": self.account.id, "product_id": self.product.id},
|
|
346
607
|
content_type="application/json",
|
|
347
608
|
)
|
|
348
609
|
self.assertEqual(response.status_code, 200)
|
|
349
|
-
self.
|
|
610
|
+
self.account.refresh_from_db()
|
|
611
|
+
self.assertEqual(
|
|
612
|
+
self.account.live_subscription_product,
|
|
613
|
+
self.product,
|
|
614
|
+
)
|
|
615
|
+
self.assertIsNotNone(self.account.live_subscription_start_date)
|
|
616
|
+
self.assertEqual(
|
|
617
|
+
self.account.live_subscription_start_date,
|
|
618
|
+
timezone.localdate(),
|
|
619
|
+
)
|
|
620
|
+
self.assertEqual(
|
|
621
|
+
self.account.live_subscription_next_renewal,
|
|
622
|
+
self.account.live_subscription_start_date
|
|
623
|
+
+ timedelta(days=self.product.renewal_period),
|
|
624
|
+
)
|
|
350
625
|
|
|
351
626
|
list_resp = self.client.get(
|
|
352
|
-
reverse("subscription-list"), {"account_id": self.account.id}
|
|
627
|
+
reverse("live-subscription-list"), {"account_id": self.account.id}
|
|
353
628
|
)
|
|
354
629
|
self.assertEqual(list_resp.status_code, 200)
|
|
355
630
|
data = list_resp.json()
|
|
356
|
-
self.assertEqual(len(data["
|
|
357
|
-
self.assertEqual(data["
|
|
631
|
+
self.assertEqual(len(data["live_subscriptions"]), 1)
|
|
632
|
+
self.assertEqual(data["live_subscriptions"][0]["product__name"], "Gold")
|
|
633
|
+
self.assertEqual(data["live_subscriptions"][0]["id"], self.account.id)
|
|
634
|
+
self.assertEqual(
|
|
635
|
+
data["live_subscriptions"][0]["next_renewal"],
|
|
636
|
+
str(self.account.live_subscription_next_renewal),
|
|
637
|
+
)
|
|
358
638
|
|
|
359
639
|
def test_product_list(self):
|
|
360
640
|
response = self.client.get(reverse("product-list"))
|
|
@@ -394,8 +674,8 @@ class EVBrandFixtureTests(TestCase):
|
|
|
394
674
|
def test_ev_brand_fixture_loads(self):
|
|
395
675
|
call_command(
|
|
396
676
|
"loaddata",
|
|
397
|
-
"core/fixtures/
|
|
398
|
-
"core/fixtures/
|
|
677
|
+
*sorted(glob("core/fixtures/ev_brands__*.json")),
|
|
678
|
+
*sorted(glob("core/fixtures/ev_models__*.json")),
|
|
399
679
|
verbosity=0,
|
|
400
680
|
)
|
|
401
681
|
porsche = Brand.objects.get(name="Porsche")
|
|
@@ -408,11 +688,14 @@ class EVBrandFixtureTests(TestCase):
|
|
|
408
688
|
)
|
|
409
689
|
self.assertTrue(EVModel.objects.filter(brand=porsche, name="Taycan").exists())
|
|
410
690
|
self.assertTrue(EVModel.objects.filter(brand=audi, name="e-tron GT").exists())
|
|
691
|
+
self.assertTrue(EVModel.objects.filter(brand=porsche, name="Macan").exists())
|
|
692
|
+
model3 = EVModel.objects.get(brand__name="Tesla", name="Model 3 RWD")
|
|
693
|
+
self.assertEqual(model3.est_battery_kwh, Decimal("57.50"))
|
|
411
694
|
|
|
412
695
|
def test_brand_from_vin(self):
|
|
413
696
|
call_command(
|
|
414
697
|
"loaddata",
|
|
415
|
-
"core/fixtures/
|
|
698
|
+
*sorted(glob("core/fixtures/ev_brands__*.json")),
|
|
416
699
|
verbosity=0,
|
|
417
700
|
)
|
|
418
701
|
self.assertEqual(Brand.from_vin("WP0ZZZ12345678901").name, "Porsche")
|
|
@@ -424,9 +707,9 @@ class RFIDFixtureTests(TestCase):
|
|
|
424
707
|
def test_fixture_assigns_gelectriic_rfid(self):
|
|
425
708
|
call_command(
|
|
426
709
|
"loaddata",
|
|
427
|
-
"core/fixtures/
|
|
428
|
-
"core/fixtures/
|
|
429
|
-
"core/fixtures/
|
|
710
|
+
"core/fixtures/users__arthexis.json",
|
|
711
|
+
"core/fixtures/energy_accounts__gelectriic.json",
|
|
712
|
+
"core/fixtures/rfids__ffffffff.json",
|
|
430
713
|
verbosity=0,
|
|
431
714
|
)
|
|
432
715
|
account = EnergyAccount.objects.get(name="GELECTRIIC")
|
|
@@ -458,37 +741,6 @@ class SecurityGroupTests(TestCase):
|
|
|
458
741
|
self.assertIn(user, child.user_set.all())
|
|
459
742
|
|
|
460
743
|
|
|
461
|
-
class FediverseProfileTests(TestCase):
|
|
462
|
-
def setUp(self):
|
|
463
|
-
self.user = User.objects.create_user(username="fed", password="secret")
|
|
464
|
-
|
|
465
|
-
@mock.patch("requests.get")
|
|
466
|
-
def test_connection_success_sets_verified(self, mock_get):
|
|
467
|
-
mock_get.return_value.ok = True
|
|
468
|
-
mock_get.return_value.raise_for_status.return_value = None
|
|
469
|
-
profile = FediverseProfile.objects.create(
|
|
470
|
-
user=self.user,
|
|
471
|
-
service=FediverseProfile.MASTODON,
|
|
472
|
-
host="example.com",
|
|
473
|
-
handle="fed",
|
|
474
|
-
access_token="tok",
|
|
475
|
-
)
|
|
476
|
-
self.assertTrue(profile.test_connection())
|
|
477
|
-
self.assertIsNotNone(profile.verified_on)
|
|
478
|
-
|
|
479
|
-
@mock.patch("requests.get", side_effect=Exception("boom"))
|
|
480
|
-
def test_connection_failure_raises(self, mock_get):
|
|
481
|
-
profile = FediverseProfile.objects.create(
|
|
482
|
-
user=self.user,
|
|
483
|
-
service=FediverseProfile.MASTODON,
|
|
484
|
-
host="example.com",
|
|
485
|
-
handle="fed",
|
|
486
|
-
)
|
|
487
|
-
with self.assertRaises(ValidationError):
|
|
488
|
-
profile.test_connection()
|
|
489
|
-
self.assertIsNone(profile.verified_on)
|
|
490
|
-
|
|
491
|
-
|
|
492
744
|
class ReleaseProcessTests(TestCase):
|
|
493
745
|
def setUp(self):
|
|
494
746
|
self.package = Package.objects.create(name="pkg")
|
|
@@ -499,14 +751,14 @@ class ReleaseProcessTests(TestCase):
|
|
|
499
751
|
@mock.patch("core.views.release_utils._git_clean", return_value=False)
|
|
500
752
|
def test_step_check_requires_clean_repo(self, git_clean):
|
|
501
753
|
with self.assertRaises(Exception):
|
|
502
|
-
|
|
754
|
+
_step_check_version(self.release, {}, Path("rel.log"))
|
|
503
755
|
|
|
504
756
|
@mock.patch("core.views.release_utils._git_clean", return_value=True)
|
|
505
757
|
@mock.patch("core.views.release_utils.network_available", return_value=False)
|
|
506
758
|
def test_step_check_keeps_repo_clean(self, network_available, git_clean):
|
|
507
759
|
version_path = Path("VERSION")
|
|
508
760
|
original = version_path.read_text(encoding="utf-8")
|
|
509
|
-
|
|
761
|
+
_step_check_version(self.release, {}, Path("rel.log"))
|
|
510
762
|
proc = subprocess.run(
|
|
511
763
|
["git", "status", "--porcelain", str(version_path)],
|
|
512
764
|
capture_output=True,
|
|
@@ -524,9 +776,7 @@ class ReleaseProcessTests(TestCase):
|
|
|
524
776
|
@mock.patch("core.views.subprocess.run")
|
|
525
777
|
@mock.patch("core.views.PackageRelease.dump_fixture")
|
|
526
778
|
@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
|
-
):
|
|
779
|
+
def test_promote_cleans_repo_on_failure(self, promote, dump_fixture, run):
|
|
530
780
|
with self.assertRaises(Exception):
|
|
531
781
|
_step_promote_build(self.release, {}, Path("rel.log"))
|
|
532
782
|
dump_fixture.assert_not_called()
|
|
@@ -688,12 +938,9 @@ class PackageReleaseAdminActionTests(TestCase):
|
|
|
688
938
|
mock_get.return_value.json.return_value = {
|
|
689
939
|
"releases": {"1.0.0": [], "1.1.0": []}
|
|
690
940
|
}
|
|
691
|
-
self.admin.refresh_from_pypi(
|
|
692
|
-
|
|
693
|
-
)
|
|
694
|
-
self.assertTrue(
|
|
695
|
-
PackageRelease.objects.filter(version="1.1.0").exists()
|
|
696
|
-
)
|
|
941
|
+
self.admin.refresh_from_pypi(self.request, PackageRelease.objects.none())
|
|
942
|
+
new_release = PackageRelease.objects.get(version="1.1.0")
|
|
943
|
+
self.assertEqual(new_release.revision, "")
|
|
697
944
|
dump.assert_called_once()
|
|
698
945
|
|
|
699
946
|
|
|
@@ -772,3 +1019,51 @@ class PackageReleaseChangelistTests(TestCase):
|
|
|
772
1019
|
)
|
|
773
1020
|
self.assertContains(response, refresh_url, html=False)
|
|
774
1021
|
|
|
1022
|
+
|
|
1023
|
+
class TodoDoneTests(TestCase):
|
|
1024
|
+
def setUp(self):
|
|
1025
|
+
self.client = Client()
|
|
1026
|
+
User.objects.create_superuser("admin", "admin@example.com", "pw")
|
|
1027
|
+
self.client.force_login(User.objects.get(username="admin"))
|
|
1028
|
+
|
|
1029
|
+
def test_mark_done_sets_timestamp(self):
|
|
1030
|
+
todo = Todo.objects.create(request="Task", is_seed_data=True)
|
|
1031
|
+
resp = self.client.post(reverse("todo-done", args=[todo.pk]))
|
|
1032
|
+
self.assertRedirects(resp, reverse("admin:index"))
|
|
1033
|
+
todo.refresh_from_db()
|
|
1034
|
+
self.assertIsNotNone(todo.done_on)
|
|
1035
|
+
self.assertFalse(todo.is_deleted)
|
|
1036
|
+
|
|
1037
|
+
|
|
1038
|
+
class TodoUrlValidationTests(TestCase):
|
|
1039
|
+
def test_relative_url_valid(self):
|
|
1040
|
+
todo = Todo(request="Task", url="/path")
|
|
1041
|
+
todo.full_clean() # should not raise
|
|
1042
|
+
|
|
1043
|
+
def test_absolute_url_invalid(self):
|
|
1044
|
+
todo = Todo(request="Task", url="https://example.com/path")
|
|
1045
|
+
with self.assertRaises(ValidationError):
|
|
1046
|
+
todo.full_clean()
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
class TodoUniqueTests(TestCase):
|
|
1050
|
+
def test_request_unique_case_insensitive(self):
|
|
1051
|
+
Todo.objects.create(request="Task")
|
|
1052
|
+
with self.assertRaises(IntegrityError):
|
|
1053
|
+
Todo.objects.create(request="task")
|
|
1054
|
+
|
|
1055
|
+
|
|
1056
|
+
class TodoAdminPermissionTests(TestCase):
|
|
1057
|
+
def setUp(self):
|
|
1058
|
+
self.client = Client()
|
|
1059
|
+
User.objects.create_superuser("admin", "admin@example.com", "pw")
|
|
1060
|
+
self.client.force_login(User.objects.get(username="admin"))
|
|
1061
|
+
|
|
1062
|
+
def test_add_view_disallowed(self):
|
|
1063
|
+
resp = self.client.get(reverse("admin:core_todo_add"))
|
|
1064
|
+
self.assertEqual(resp.status_code, 403)
|
|
1065
|
+
|
|
1066
|
+
def test_change_form_loads(self):
|
|
1067
|
+
todo = Todo.objects.create(request="Task")
|
|
1068
|
+
resp = self.client.get(reverse("admin:core_todo_change", args=[todo.pk]))
|
|
1069
|
+
self.assertEqual(resp.status_code, 200)
|
core/tests_liveupdate.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from django.test import RequestFactory, TestCase
|
|
2
|
+
from django.views.generic import TemplateView
|
|
3
|
+
|
|
4
|
+
from core.liveupdate import LiveUpdateMixin
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DummyView(LiveUpdateMixin, TemplateView):
|
|
8
|
+
template_name = "pages/base.html"
|
|
9
|
+
live_update_interval = 7
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LiveUpdateMixinTests(TestCase):
|
|
13
|
+
def test_mixin_sets_request_interval(self):
|
|
14
|
+
factory = RequestFactory()
|
|
15
|
+
request = factory.get("/")
|
|
16
|
+
DummyView.as_view()(request)
|
|
17
|
+
self.assertEqual(request.live_update_interval, 7)
|
core/urls.py
CHANGED
|
@@ -6,6 +6,6 @@ urlpatterns = [
|
|
|
6
6
|
path("rfid-login/", views.rfid_login, name="rfid-login"),
|
|
7
7
|
path("rfids/", views.rfid_batch, name="rfid-batch"),
|
|
8
8
|
path("products/", views.product_list, name="product-list"),
|
|
9
|
-
path("subscribe/", views.
|
|
10
|
-
path("list/", views.
|
|
9
|
+
path("live-subscribe/", views.add_live_subscription, name="add-live-subscription"),
|
|
10
|
+
path("live-list/", views.live_subscription_list, name="live-subscription-list"),
|
|
11
11
|
]
|