Skip to content

Commit a4c9794

Browse files
committed
Add more tests
1 parent c945e6f commit a4c9794

File tree

5 files changed

+213
-10
lines changed

5 files changed

+213
-10
lines changed

galahad/admin.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,11 @@ def rerun(modeladmin, request, queryset):
2828

2929

3030
def cancel(modeladmin, request, queryset):
31-
succeeded = queryset.succeeded().count()
32-
if succeeded:
33-
messages.warning(request, "Only failed tasks can be retried. %s tasks have been skipped" % succeeded)
34-
queryset.not_succeeded().cancel(request.user)
35-
messages.success(request, "Tasks have been successfully queued")
31+
not_scheduled = queryset.not_scheduled().count()
32+
if not_scheduled:
33+
messages.warning(request, "Only scheduled tasks can be canceled. %s tasks have been skipped" % not_scheduled)
34+
queryset.scheduled().cancel(request.user)
35+
messages.success(request, "Tasks have been successfully canceled")
3636

3737

3838
cancel.short_description = t('Cancel selected tasks')
@@ -47,6 +47,11 @@ def has_rerun_permission(self, request):
4747
codename = get_permission_codename('rerun', opts)
4848
return request.user.has_perm('%s.%s' % (opts.app_label, codename))
4949

50+
def has_cancel_permission(self, request):
51+
opts = self.opts
52+
codename = get_permission_codename('cancel', opts)
53+
return request.user.has_perm('%s.%s' % (opts.app_label, codename))
54+
5055
def pretty_stacktrace(self, obj):
5156
return format_html('<pre class="readonly collapse">{}<pre>', obj.stacktrace)
5257

@@ -57,7 +62,7 @@ def child_tasks(self, obj):
5762

5863
child_tasks.short_description = t('Child tasks')
5964

60-
actions = (rerun,)
65+
actions = (rerun, cancel)
6166

6267
list_display = (
6368
'node_name',

galahad/models.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,6 @@ def get_next_nodes(cls, prev_node):
153153
if start.node_name == prev_node.node_name:
154154
yield end
155155

156-
def finish(self):
157-
self.completed = timezone.now()
158-
self.save(update_fields=['completed'])
159-
160156
@classmethod
161157
def get_url_namespace(cls):
162158
return cls.__name__.lower()
@@ -297,6 +293,9 @@ class TasksQuerySet(models.query.QuerySet):
297293
def scheduled(self):
298294
return self.filter(status=self.model.SCHEDULED)
299295

296+
def not_scheduled(self):
297+
return self.exclude(status=self.model.SCHEDULED)
298+
300299
def succeeded(self):
301300
return self.filter(status=self.model.SUCCEEDED)
302301

@@ -496,6 +495,7 @@ def enqueue(self, countdown=None, eta=None):
496495
'exception',
497496
'stacktrace',
498497
])
498+
print(self.status)
499499
transaction.on_commit(lambda: celery.task_wrapper.apply_async(
500500
args=(self.pk, self._process_id),
501501
countdown=countdown,

tests/conftest.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
def testdir():
1010
return pathlib.Path(os.path.dirname(os.path.abspath(__file__)))
1111

12+
1213
@pytest.fixture()
1314
def fixturedir(testdir):
1415
return testdir / 'fixtures'
@@ -22,6 +23,14 @@ def on_commit(f):
2223
monkeypatch.setattr('django.db.transaction.on_commit', on_commit)
2324

2425

26+
@pytest.fixture()
27+
def no_on_commit(instant_commit, monkeypatch):
28+
def on_commit(f):
29+
pass
30+
31+
monkeypatch.setattr('django.db.transaction.on_commit', on_commit)
32+
33+
2534
@pytest.yield_fixture(autouse=True)
2635
def fake_lock(monkeypatch):
2736
@contextmanager

tests/test_admin.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import pytest
2+
from django.contrib.auth.models import AnonymousUser
3+
from django.contrib.messages.storage.fallback import FallbackStorage
4+
from django.contrib.sessions.middleware import SessionMiddleware
5+
6+
from galahad import admin
7+
from galahad.models import Task
8+
from tests.testapp import models
9+
10+
11+
class TestTaskAdmin:
12+
13+
@pytest.fixture
14+
def post_request(self, rf):
15+
request = rf.post('/')
16+
17+
# adding session
18+
middleware = SessionMiddleware()
19+
middleware.process_request(request)
20+
request.session.save()
21+
22+
# adding messages
23+
messages = FallbackStorage(request)
24+
setattr(request, '_messages', messages)
25+
setattr(request, 'user', AnonymousUser())
26+
return request
27+
28+
def test_rerun(self, db, no_on_commit, post_request):
29+
process = models.SimpleProcess.objects.create()
30+
task = process.task_set.create(status=Task.FAILED, node_name='save_the_princess')
31+
admin.rerun(None, post_request, process.task_set.all())
32+
assert '1 tasks have been successfully queued' == str(post_request._messages._queued_messages[0])
33+
task.refresh_from_db()
34+
assert task.status == Task.SCHEDULED
35+
36+
def test_rerun__succeeded(self, db, no_on_commit, post_request):
37+
process = models.SimpleProcess.objects.create()
38+
task = process.task_set.create(status=Task.SUCCEEDED, node_name='save_the_princess')
39+
admin.rerun(None, post_request, process.task_set.all())
40+
assert 'Only failed tasks can be retried. 1 tasks have been skipped' == \
41+
str(post_request._messages._queued_messages[0])
42+
assert '0 tasks have been successfully queued' == \
43+
str(post_request._messages._queued_messages[1])
44+
task.refresh_from_db()
45+
assert task.status == Task.SUCCEEDED
46+
47+
def test_cancel(self, db, post_request):
48+
process = models.SimpleProcess.objects.create()
49+
task = process.task_set.create(node_name='save_the_princess')
50+
admin.cancel(None, post_request, process.task_set.all())
51+
assert "Tasks have been successfully canceled" == \
52+
str(post_request._messages._queued_messages[0])
53+
task.refresh_from_db()
54+
assert task.status == Task.CANCELED
55+
56+
def test_cancel__not_scheduled(self, db, post_request):
57+
process = models.SimpleProcess.objects.create()
58+
task = process.task_set.create(status=Task.SUCCEEDED, node_name='save_the_princess')
59+
admin.cancel(None, post_request, process.task_set.all())
60+
assert 'Only scheduled tasks can be canceled. 1 tasks have been skipped' == \
61+
str(post_request._messages._queued_messages[0])
62+
assert "Tasks have been successfully canceled" == \
63+
str(post_request._messages._queued_messages[1])
64+
task.refresh_from_db()
65+
assert task.status == Task.SUCCEEDED

tests/test_models.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from unittest.mock import patch
22

33
import pytest
4+
from django.contrib.auth import get_user_model
5+
from django.contrib.auth.models import AnonymousUser
46
from django.urls import reverse
57
from django.utils import timezone
68
from django.utils.safestring import SafeString
@@ -181,6 +183,86 @@ def test_get_instance_graph_svg(self, db, fixturedir):
181183
svg = process.get_instance_graph_svg()
182184
assert isinstance(svg, SafeString)
183185

186+
def test_cancel(self, db):
187+
process = models.SimpleProcess.objects.create()
188+
process.task_set.create()
189+
assert process.task_set.scheduled().exists()
190+
process.cancel()
191+
assert not process.task_set.scheduled().exists()
192+
assert process.task_set.latest().completed_by_user is None
193+
assert process.task_set.latest().completed
194+
195+
def test_cancel__with_user(self, db):
196+
process = models.SimpleProcess.objects.create()
197+
process.task_set.create()
198+
user = get_user_model().objects.create(
199+
200+
first_name='Peter',
201+
last_name='Parker',
202+
username='spidy',
203+
)
204+
process.cancel(user=user)
205+
assert process.task_set.latest().completed_by_user == user
206+
207+
def test_cancel__with_anonymous_user(self, db):
208+
process = models.SimpleProcess.objects.create()
209+
process.task_set.create()
210+
user = AnonymousUser()
211+
process.cancel(user=user)
212+
assert process.task_set.latest().completed_by_user is None
213+
214+
215+
class TestTaskQuerySet:
216+
217+
def test_scheduled(self, db):
218+
process = models.SimpleProcess.objects.create()
219+
task = process.task_set.create()
220+
process.task_set.create(status=Task.CANCELED)
221+
assert process.task_set.scheduled().get() == task
222+
223+
def test_succeeded(self, db):
224+
process = models.SimpleProcess.objects.create()
225+
task = process.task_set.create(status=Task.SUCCEEDED)
226+
process.task_set.create(status=Task.CANCELED)
227+
assert process.task_set.succeeded().get() == task
228+
229+
def test_not_succeeded(self, db):
230+
process = models.SimpleProcess.objects.create()
231+
task = process.task_set.create()
232+
process.task_set.create(status=Task.SUCCEEDED)
233+
assert process.task_set.not_succeeded().get() == task
234+
235+
def test_failed(self, db):
236+
process = models.SimpleProcess.objects.create()
237+
task = process.task_set.create(status=Task.FAILED)
238+
process.task_set.create(status=Task.CANCELED)
239+
assert process.task_set.failed().get() == task
240+
241+
def test_canceled(self, db):
242+
process = models.SimpleProcess.objects.create()
243+
task = process.task_set.create(status=Task.CANCELED)
244+
process.task_set.create()
245+
assert process.task_set.canceled().get() == task
246+
247+
def test_cancel__with_user(self, db):
248+
process = models.SimpleProcess.objects.create()
249+
process.task_set.create()
250+
user = get_user_model().objects.create(
251+
252+
first_name='Peter',
253+
last_name='Parker',
254+
username='spidy',
255+
)
256+
process.task_set.cancel(user=user)
257+
assert process.task_set.latest().completed_by_user == user
258+
259+
def test_cancel__with_anonymous_user(self, db):
260+
process = models.SimpleProcess.objects.create()
261+
process.task_set.create()
262+
user = AnonymousUser()
263+
process.task_set.cancel(user=user)
264+
assert process.task_set.latest().completed_by_user is None
265+
184266

185267
class TestTask:
186268

@@ -239,3 +321,45 @@ def test_fail(self, db):
239321
assert 'Traceback (most recent call last):\n' in task.stacktrace
240322
assert ' raise IOError("nope")\n' in task.stacktrace
241323
assert 'OSError: nope\n' in task.stacktrace
324+
325+
def test_cancel(self, db):
326+
process = models.SimpleProcess.objects.create()
327+
task = process.task_set.create()
328+
task.cancel()
329+
assert task.status == task.CANCELED
330+
assert task.completed_by_user is None
331+
assert task.completed
332+
333+
def test_cancel__with_user(self, db):
334+
process = models.SimpleProcess.objects.create()
335+
task = process.task_set.create()
336+
user = get_user_model().objects.create(
337+
338+
first_name='Peter',
339+
last_name='Parker',
340+
username='spidy',
341+
)
342+
task.cancel(user=user)
343+
assert task.completed_by_user == user
344+
345+
def test_cancel__with_anonymous_user(self, db):
346+
process = models.SimpleProcess.objects.create()
347+
task = process.task_set.create()
348+
user = AnonymousUser()
349+
task.cancel(user=user)
350+
assert task.completed_by_user is None
351+
352+
def test_save__modified_date(self, db):
353+
process = models.SimpleProcess.objects.create()
354+
task = process.task_set.create()
355+
modified_date = task.modified
356+
task.save(update_fields=[])
357+
task.refresh_from_db()
358+
assert task.modified > modified_date
359+
360+
def test_save__no_update_fields(self, db):
361+
process = models.SimpleProcess.objects.create()
362+
task = process.task_set.create()
363+
with pytest.raises(ValueError) as e:
364+
task.save()
365+
assert "You need to provide explicit 'update_fields' to avoid race conditions." in str(e)

0 commit comments

Comments
 (0)