From d015d3bc4585619f52b42448395d44403bce5e5a Mon Sep 17 00:00:00 2001
From: Erwan Rouchet <rouchet@teklia.com>
Date: Thu, 25 May 2023 13:11:03 +0200
Subject: [PATCH] Allow RetrieveWorkerRun on a local process

---
 arkindex/process/api.py                   |  14 ++-
 arkindex/process/tests/test_workerruns.py | 126 ++++++++++++++++++++++
 2 files changed, 136 insertions(+), 4 deletions(-)

diff --git a/arkindex/process/api.py b/arkindex/process/api.py
index bfd49b13bb..6606b187c4 100644
--- a/arkindex/process/api.py
+++ b/arkindex/process/api.py
@@ -1304,7 +1304,10 @@ class WorkerRunDetails(CorpusACLMixin, RetrieveUpdateDestroyAPIView):
     def get_queryset(self):
         # Use default DB to avoid a race condition checking process workflow
         return WorkerRun.objects \
-            .filter(process__corpus_id__isnull=False) \
+            .filter(
+                ~Q(process__corpus_id=None)
+                | Q(process__creator_id=self.user.id, process__mode=ProcessMode.Local)
+            ) \
             .using('default') \
             .select_related('version__worker__type', 'configuration', 'process__workflow', 'process__corpus', 'version__revision__repo')
 
@@ -1315,12 +1318,15 @@ class WorkerRunDetails(CorpusACLMixin, RetrieveUpdateDestroyAPIView):
         return context
 
     def check_object_permissions(self, request, worker_run):
-        if not self.has_admin_access(worker_run.process.corpus):
+        if worker_run.process.corpus_id and not self.has_admin_access(worker_run.process.corpus):
             raise PermissionDenied(detail='You do not have an admin access to the process project.')
 
         # Updating a worker run is not possible once the process is started
-        if request.method not in permissions.SAFE_METHODS and worker_run.process.workflow_id is not None:
-            raise ValidationError({'__all__': ["Cannot update a WorkerRun on a Process that has already started"]})
+        if request.method not in permissions.SAFE_METHODS:
+            if worker_run.process.workflow_id is not None:
+                raise ValidationError({'__all__': ["Cannot update a WorkerRun on a Process that has already started"]})
+            if worker_run.process.mode == ProcessMode.Local:
+                raise ValidationError({'__all__': ['Cannot update a WorkerRun on a local process']})
 
         super().check_object_permissions(request, worker_run)
 
diff --git a/arkindex/process/tests/test_workerruns.py b/arkindex/process/tests/test_workerruns.py
index 3e50fd4835..9931783c60 100644
--- a/arkindex/process/tests/test_workerruns.py
+++ b/arkindex/process/tests/test_workerruns.py
@@ -657,6 +657,78 @@ class TestWorkerRuns(FixtureAPITestCase):
             }
         ])
 
+    def test_retrieve_run_local(self):
+        """
+        A user can retrieve a run on their own local process
+        """
+        local_process = self.user.processes.get(mode=ProcessMode.Local)
+        run = local_process.worker_runs.create(version=self.version_1, parents=[])
+        self.client.force_login(self.user)
+
+        with self.assertNumQueries(5):
+            response = self.client.get(
+                reverse('api:worker-run-details', kwargs={'pk': str(run.id)}),
+            )
+            self.assertEqual(response.status_code, status.HTTP_200_OK)
+
+        self.assertDictEqual(response.json(), {
+            'id': str(run.id),
+            'worker_version': {
+                'id': str(self.version_1.id),
+                'configuration': {'test': 42},
+                'docker_image': str(self.version_1.docker_image.id),
+                'docker_image_iid': None,
+                'docker_image_name': f'my_repo.fake/workers/worker/reco:{self.version_1.id}',
+                'gpu_usage': 'disabled',
+                'model_usage': False,
+                'revision': {
+                    'id': str(self.version_1.revision.id),
+                    'author': 'Test user',
+                    'commit_url': 'http://my_repo.fake/workers/worker/commit/1337',
+                    'created': self.version_1.revision.created.isoformat().replace('+00:00', 'Z'),
+                    'hash': '1337',
+                    'message': 'My w0rk3r',
+                    'refs': []
+                },
+                'state': 'available',
+                'worker': {
+                    'id': str(self.worker_1.id),
+                    'name': 'Recognizer',
+                    'slug': 'reco',
+                    'type': 'recognizer'
+                }
+            },
+            'parents': [],
+            'model_version': None,
+            'configuration': None,
+            'process': {
+                'id': str(local_process.id),
+                'activity_state': 'disabled',
+                'corpus': None,
+                'mode': 'local',
+                'model_id': None,
+                'name': None,
+                'state': 'unscheduled',
+                'test_folder_id': None,
+                'train_folder_id': None,
+                'validation_folder_id': None,
+                'workflow': None
+            },
+        })
+
+    def test_retrieve_run_local_only_current_user(self):
+        """
+        A user cannot retrieve a run on another user's local process
+        """
+        run = WorkerRun.objects.filter(process__creator=self.superuser, process__mode=ProcessMode.Local).first()
+        self.client.force_login(self.user)
+
+        with self.assertNumQueries(3):
+            response = self.client.get(
+                reverse('api:worker-run-details', kwargs={'pk': str(run.id)}),
+            )
+            self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
+
     def test_update_run_requires_id_and_parents(self):
         self.client.force_login(self.user)
         with self.assertNumQueries(7):
@@ -710,6 +782,25 @@ class TestWorkerRuns(FixtureAPITestCase):
         self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
         self.assertEqual(response.json(), {'detail': 'You do not have an admin access to the process project.'})
 
+    def test_update_run_local(self):
+        """
+        A user cannot update a worker run on a local process
+        """
+        local_process = self.user.processes.get(mode=ProcessMode.Local)
+        run = local_process.worker_runs.create(version=self.version_1, parents=[])
+        self.client.force_login(self.user)
+
+        with self.assertNumQueries(3):
+            response = self.client.put(
+                reverse('api:worker-run-details', kwargs={'pk': str(run.id)}),
+                data={
+                    'parents': []
+                }
+            )
+            self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+        self.assertEqual(response.json(), {'__all__': ['Cannot update a WorkerRun on a local process']})
+
     def test_update_run_invalid_id(self):
         rev_2 = self.repo.revisions.create(
             hash='2',
@@ -1439,6 +1530,25 @@ class TestWorkerRuns(FixtureAPITestCase):
             )
         self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
 
+    def test_partial_update_run_local(self):
+        """
+        A user cannot update a worker run on a local process
+        """
+        local_process = self.user.processes.get(mode=ProcessMode.Local)
+        run = local_process.worker_runs.create(version=self.version_1, parents=[])
+        self.client.force_login(self.user)
+
+        with self.assertNumQueries(3):
+            response = self.client.patch(
+                reverse('api:worker-run-details', kwargs={'pk': str(run.id)}),
+                data={
+                    'parents': []
+                }
+            )
+            self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+        self.assertEqual(response.json(), {'__all__': ['Cannot update a WorkerRun on a local process']})
+
     def test_partial_update_run_inexistant_parent(self):
         self.client.force_login(self.user)
         with self.assertNumQueries(7):
@@ -2088,6 +2198,22 @@ class TestWorkerRuns(FixtureAPITestCase):
         with self.assertRaises(WorkerRun.DoesNotExist):
             self.run_1.refresh_from_db()
 
+    def test_delete_run_local(self):
+        """
+        A user cannot delete a worker run on a local process
+        """
+        local_process = self.user.processes.get(mode=ProcessMode.Local)
+        run = local_process.worker_runs.create(version=self.version_1, parents=[])
+        self.client.force_login(self.user)
+
+        with self.assertNumQueries(3):
+            response = self.client.delete(
+                reverse('api:worker-run-details', kwargs={'pk': str(run.id)}),
+            )
+            self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
+
+        self.assertEqual(response.json(), {'__all__': ['Cannot update a WorkerRun on a local process']})
+
     def test_delete_run(self):
         self.client.force_login(self.user)
         response = self.client.delete(
-- 
GitLab