diff --git a/arkindex_worker/worker.py b/arkindex_worker/worker.py index 6da26c3a6f3a7d46829d4ef5d60b6c2f66a17859..e00e4952ec20e694d916f914fe28599b51dc6592 100644 --- a/arkindex_worker/worker.py +++ b/arkindex_worker/worker.py @@ -266,8 +266,11 @@ class ElementsWorker(BaseWorker): **self.api_client.request("RetrieveElement", id=element_id) ) logger.info(f"Processing {element} ({i}/{count})") + + # Report start of process, run process, then report end of process self.update_activity(element, ActivityState.Started) self.process_element(element) + self.update_activity(element, ActivityState.Processed) except ErrorResponse as e: failed += 1 logger.warning( @@ -285,9 +288,6 @@ class ElementsWorker(BaseWorker): self.update_activity(element, ActivityState.Error) self.report.error(element_id, e) - # Report completion for this element - self.update_activity(element, ActivityState.Processed) - # Save report as local artifact self.report.save(os.path.join(self.work_dir, "ml_report.json")) diff --git a/tests/test_worker_activity.py b/tests/test_worker_activity.py new file mode 100644 index 0000000000000000000000000000000000000000..4498e5ff66c268cd65eee504351fefd59b4f8381 --- /dev/null +++ b/tests/test_worker_activity.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- +import json + +import pytest +from apistar.exceptions import ErrorResponse + +from arkindex_worker.worker import ActivityState, Element + +# Common API calls for all workers +BASE_API_CALLS = [ + "http://testserver/api/v1/user/", + "http://testserver/api/v1/workers/versions/12341234-1234-1234-1234-123412341234/", +] + + +def test_defaults(responses, mock_elements_worker): + """Test the default values from mocked calls""" + assert not mock_elements_worker.is_read_only + assert mock_elements_worker.features == { + "workers_activity": False, + "signup": False, + } + + assert len(responses.calls) == 2 + assert [call.request.url for call in responses.calls] == BASE_API_CALLS + + +def test_feature_disabled(responses, mock_elements_worker): + """Test disabled calls do not trigger any API calls""" + assert not mock_elements_worker.is_read_only + + out = mock_elements_worker.update_activity( + Element({"id": "1234-deadbeef"}), ActivityState.Processed + ) + + assert out is None + assert len(responses.calls) == 2 + assert [call.request.url for call in responses.calls] == BASE_API_CALLS + + +def test_readonly(responses, mock_elements_worker): + """Test readonly worker does not trigger any API calls""" + + # Setup the worker as read-only, but with workers_activity enabled + mock_elements_worker.worker_version_id = None + assert mock_elements_worker.is_read_only is True + mock_elements_worker.features["workers_activity"] = True + + out = mock_elements_worker.update_activity( + Element({"id": "1234-deadbeef"}), ActivityState.Processed + ) + + assert out is None + assert len(responses.calls) == 2 + assert [call.request.url for call in responses.calls] == BASE_API_CALLS + + +def test_update_call(responses, mock_elements_worker): + """Test an update call with feature enabled triggers an API call""" + responses.add( + responses.PUT, + "http://testserver/api/v1/workers/versions/12341234-1234-1234-1234-123412341234/activity/", + status=200, + json={ + "element_id": "1234-deadbeef", + "state": "processed", + }, + ) + + # Enable worker activity + mock_elements_worker.features["workers_activity"] = True + + out = mock_elements_worker.update_activity( + Element({"id": "1234-deadbeef"}), ActivityState.Processed + ) + + # Check the response received by worker + assert out == { + "element_id": "1234-deadbeef", + "state": "processed", + } + + assert len(responses.calls) == 3 + assert [call.request.url for call in responses.calls] == BASE_API_CALLS + [ + "http://testserver/api/v1/workers/versions/12341234-1234-1234-1234-123412341234/activity/", + ] + + # Check the request sent by worker + assert json.loads(responses.calls[2].request.body) == { + "element_id": "1234-deadbeef", + "state": "processed", + } + + +@pytest.mark.parametrize( + "process_exception, final_state", + [ + # Successful process_element + (None, "processed"), + # Failures in process_element + ( + ErrorResponse(title="bad gateway", status_code=502, content="Bad gateway"), + "error", + ), + (ValueError("Something bad"), "error"), + (Exception("Any error"), "error"), + ], +) +def test_run( + monkeypatch, mock_elements_worker, responses, process_exception, final_state +): + """Check the normal runtime sends 2 API calls to update activity""" + # Disable second configure call from run() + monkeypatch.setattr(mock_elements_worker, "configure", lambda: None) + + # Mock elements + monkeypatch.setattr( + mock_elements_worker, + "list_elements", + lambda: [ + "1234-deadbeef", + ], + ) + responses.add( + responses.GET, + "http://testserver/api/v1/element/1234-deadbeef/", + status=200, + json={ + "id": "1234-deadbeef", + "type": "page", + "name": "Test Page n°1", + }, + ) + + # Mock Update activity + responses.add( + responses.PUT, + "http://testserver/api/v1/workers/versions/12341234-1234-1234-1234-123412341234/activity/", + status=200, + json={ + "element_id": "1234-deadbeef", + "state": "started", + }, + ) + responses.add( + responses.PUT, + "http://testserver/api/v1/workers/versions/12341234-1234-1234-1234-123412341234/activity/", + status=200, + json={ + "element_id": "1234-deadbeef", + "state": final_state, + }, + ) + + # Enable worker activity + assert mock_elements_worker.is_read_only is False + mock_elements_worker.features["workers_activity"] = True + + # Mock exception in process_element + if process_exception: + + def _err(): + raise process_exception + + monkeypatch.setattr(mock_elements_worker, "process_element", _err) + + # The worker stops because all elements failed ! + with pytest.raises(SystemExit): + mock_elements_worker.run() + else: + # Simply run the process + mock_elements_worker.run() + + assert len(responses.calls) == 5 + assert [call.request.url for call in responses.calls] == BASE_API_CALLS + [ + "http://testserver/api/v1/element/1234-deadbeef/", + "http://testserver/api/v1/workers/versions/12341234-1234-1234-1234-123412341234/activity/", + "http://testserver/api/v1/workers/versions/12341234-1234-1234-1234-123412341234/activity/", + ] + + # Check the requests sent by worker + assert json.loads(responses.calls[3].request.body) == { + "element_id": "1234-deadbeef", + "state": "started", + } + assert json.loads(responses.calls[4].request.body) == { + "element_id": "1234-deadbeef", + "state": final_state, + }