|
|
@@ -1,7 +1,14 @@
|
|
|
+import uuid
|
|
|
+
|
|
|
+from django_rq import get_queue
|
|
|
+from django_rq.workers import get_worker
|
|
|
from django.urls import reverse
|
|
|
from django.utils import timezone
|
|
|
+from rq.job import Job as RQ_Job, JobStatus
|
|
|
+from rq.registry import FailedJobRegistry, StartedJobRegistry
|
|
|
|
|
|
-from utilities.testing import APITestCase, APIViewTestCases
|
|
|
+from users.models import Token, User
|
|
|
+from utilities.testing import APITestCase, APIViewTestCases, TestCase
|
|
|
from ..models import *
|
|
|
|
|
|
|
|
|
@@ -91,3 +98,164 @@ class DataFileTest(
|
|
|
),
|
|
|
)
|
|
|
DataFile.objects.bulk_create(data_files)
|
|
|
+
|
|
|
+
|
|
|
+class BackgroundTaskTestCase(TestCase):
|
|
|
+ user_permissions = ()
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def dummy_job_default():
|
|
|
+ return "Job finished"
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def dummy_job_failing():
|
|
|
+ raise Exception("Job failed")
|
|
|
+
|
|
|
+ def setUp(self):
|
|
|
+ """
|
|
|
+ Create a user and token for API calls.
|
|
|
+ """
|
|
|
+ # Create the test user and assign permissions
|
|
|
+ self.user = User.objects.create_user(username='testuser')
|
|
|
+ self.user.is_staff = True
|
|
|
+ self.user.is_active = True
|
|
|
+ self.user.save()
|
|
|
+ self.token = Token.objects.create(user=self.user)
|
|
|
+ self.header = {'HTTP_AUTHORIZATION': f'Token {self.token.key}'}
|
|
|
+
|
|
|
+ # Clear all queues prior to running each test
|
|
|
+ get_queue('default').connection.flushall()
|
|
|
+ get_queue('high').connection.flushall()
|
|
|
+ get_queue('low').connection.flushall()
|
|
|
+
|
|
|
+ def test_background_queue_list(self):
|
|
|
+ url = reverse('core-api:rqqueue-list')
|
|
|
+
|
|
|
+ # Attempt to load view without permission
|
|
|
+ self.user.is_staff = False
|
|
|
+ self.user.save()
|
|
|
+ response = self.client.get(url, **self.header)
|
|
|
+ self.assertEqual(response.status_code, 403)
|
|
|
+
|
|
|
+ # Load view with permission
|
|
|
+ self.user.is_staff = True
|
|
|
+ self.user.save()
|
|
|
+ response = self.client.get(url, **self.header)
|
|
|
+ self.assertEqual(response.status_code, 200)
|
|
|
+ self.assertIn('default', str(response.content))
|
|
|
+ self.assertIn('high', str(response.content))
|
|
|
+ self.assertIn('low', str(response.content))
|
|
|
+
|
|
|
+ def test_background_queue(self):
|
|
|
+ response = self.client.get(reverse('core-api:rqqueue-detail', args=['default']), **self.header)
|
|
|
+ self.assertEqual(response.status_code, 200)
|
|
|
+ self.assertIn('default', str(response.content))
|
|
|
+ self.assertIn('oldest_job_timestamp', str(response.content))
|
|
|
+ self.assertIn('scheduled_jobs', str(response.content))
|
|
|
+
|
|
|
+ def test_background_task_list(self):
|
|
|
+ queue = get_queue('default')
|
|
|
+ queue.enqueue(self.dummy_job_default)
|
|
|
+
|
|
|
+ response = self.client.get(reverse('core-api:rqtask-list'), **self.header)
|
|
|
+ self.assertEqual(response.status_code, 200)
|
|
|
+ self.assertIn('origin', str(response.content))
|
|
|
+ self.assertIn('core.tests.test_api.BackgroundTaskTestCase.dummy_job_default()', str(response.content))
|
|
|
+
|
|
|
+ def test_background_task(self):
|
|
|
+ queue = get_queue('default')
|
|
|
+ job = queue.enqueue(self.dummy_job_default)
|
|
|
+
|
|
|
+ response = self.client.get(reverse('core-api:rqtask-detail', args=[job.id]), **self.header)
|
|
|
+ self.assertEqual(response.status_code, 200)
|
|
|
+ self.assertIn(str(job.id), str(response.content))
|
|
|
+ self.assertIn('origin', str(response.content))
|
|
|
+ self.assertIn('meta', str(response.content))
|
|
|
+ self.assertIn('kwargs', str(response.content))
|
|
|
+
|
|
|
+ def test_background_task_delete(self):
|
|
|
+ queue = get_queue('default')
|
|
|
+ job = queue.enqueue(self.dummy_job_default)
|
|
|
+
|
|
|
+ response = self.client.post(reverse('core-api:rqtask-delete', args=[job.id]), **self.header)
|
|
|
+ self.assertEqual(response.status_code, 200)
|
|
|
+ self.assertFalse(RQ_Job.exists(job.id, connection=queue.connection))
|
|
|
+ queue = get_queue('default')
|
|
|
+ self.assertNotIn(job.id, queue.job_ids)
|
|
|
+
|
|
|
+ def test_background_task_requeue(self):
|
|
|
+ queue = get_queue('default')
|
|
|
+
|
|
|
+ # Enqueue & run a job that will fail
|
|
|
+ job = queue.enqueue(self.dummy_job_failing)
|
|
|
+ worker = get_worker('default')
|
|
|
+ worker.work(burst=True)
|
|
|
+ self.assertTrue(job.is_failed)
|
|
|
+
|
|
|
+ # Re-enqueue the failed job and check that its status has been reset
|
|
|
+ response = self.client.post(reverse('core-api:rqtask-requeue', args=[job.id]), **self.header)
|
|
|
+ self.assertEqual(response.status_code, 200)
|
|
|
+ job = RQ_Job.fetch(job.id, queue.connection)
|
|
|
+ self.assertFalse(job.is_failed)
|
|
|
+
|
|
|
+ def test_background_task_enqueue(self):
|
|
|
+ queue = get_queue('default')
|
|
|
+
|
|
|
+ # Enqueue some jobs that each depends on its predecessor
|
|
|
+ job = previous_job = None
|
|
|
+ for _ in range(0, 3):
|
|
|
+ job = queue.enqueue(self.dummy_job_default, depends_on=previous_job)
|
|
|
+ previous_job = job
|
|
|
+
|
|
|
+ # Check that the last job to be enqueued has a status of deferred
|
|
|
+ self.assertIsNotNone(job)
|
|
|
+ self.assertEqual(job.get_status(), JobStatus.DEFERRED)
|
|
|
+ self.assertIsNone(job.enqueued_at)
|
|
|
+
|
|
|
+ # Force-enqueue the deferred job
|
|
|
+ response = self.client.post(reverse('core-api:rqtask-enqueue', args=[job.id]), **self.header)
|
|
|
+ self.assertEqual(response.status_code, 200)
|
|
|
+
|
|
|
+ # Check that job's status is updated correctly
|
|
|
+ job = queue.fetch_job(job.id)
|
|
|
+ self.assertEqual(job.get_status(), JobStatus.QUEUED)
|
|
|
+ self.assertIsNotNone(job.enqueued_at)
|
|
|
+
|
|
|
+ def test_background_task_stop(self):
|
|
|
+ queue = get_queue('default')
|
|
|
+
|
|
|
+ worker = get_worker('default')
|
|
|
+ job = queue.enqueue(self.dummy_job_default)
|
|
|
+ worker.prepare_job_execution(job)
|
|
|
+
|
|
|
+ self.assertEqual(job.get_status(), JobStatus.STARTED)
|
|
|
+ response = self.client.post(reverse('core-api:rqtask-stop', args=[job.id]), **self.header)
|
|
|
+ self.assertEqual(response.status_code, 200)
|
|
|
+ worker.monitor_work_horse(job, queue) # Sets the job as Failed and removes from Started
|
|
|
+ started_job_registry = StartedJobRegistry(queue.name, connection=queue.connection)
|
|
|
+ self.assertEqual(len(started_job_registry), 0)
|
|
|
+
|
|
|
+ canceled_job_registry = FailedJobRegistry(queue.name, connection=queue.connection)
|
|
|
+ self.assertEqual(len(canceled_job_registry), 1)
|
|
|
+ self.assertIn(job.id, canceled_job_registry)
|
|
|
+
|
|
|
+ def test_worker_list(self):
|
|
|
+ worker1 = get_worker('default', name=uuid.uuid4().hex)
|
|
|
+ worker1.register_birth()
|
|
|
+
|
|
|
+ worker2 = get_worker('high')
|
|
|
+ worker2.register_birth()
|
|
|
+
|
|
|
+ response = self.client.get(reverse('core-api:rqworker-list'), **self.header)
|
|
|
+ self.assertEqual(response.status_code, 200)
|
|
|
+ self.assertIn(str(worker1.name), str(response.content))
|
|
|
+
|
|
|
+ def test_worker(self):
|
|
|
+ worker1 = get_worker('default', name=uuid.uuid4().hex)
|
|
|
+ worker1.register_birth()
|
|
|
+
|
|
|
+ response = self.client.get(reverse('core-api:rqworker-detail', args=[worker1.name]), **self.header)
|
|
|
+ self.assertEqual(response.status_code, 200)
|
|
|
+ self.assertIn(str(worker1.name), str(response.content))
|
|
|
+ self.assertIn('birth_date', str(response.content))
|
|
|
+ self.assertIn('total_working_time', str(response.content))
|