From 28cdd03eb76127185ff8801400a1227f8c836706 Mon Sep 17 00:00:00 2001 From: bramjanssen Date: Thu, 12 Mar 2026 14:11:49 +0100 Subject: [PATCH] feat: integrated deleting a job --- app/database/models/processing_job.py | 15 +++++++++ app/routers/unit_jobs.py | 46 +++++++++++++++++++++++++++ app/services/processing.py | 20 ++++++++++++ tests/routers/test_unit_jobs.py | 38 ++++++++++++++++++++++ tests/services/test_processing.py | 42 ++++++++++++++++++++++++ 5 files changed, 161 insertions(+) diff --git a/app/database/models/processing_job.py b/app/database/models/processing_job.py index dd42f53..3611b8e 100644 --- a/app/database/models/processing_job.py +++ b/app/database/models/processing_job.py @@ -103,6 +103,21 @@ def get_job_by_user_id( ) +def remove_job_by_id(database: Session, job_id: int, user_id: str) -> bool: + logger.info(f"Removing processing job with ID {job_id} for user {user_id}") + job = get_job_by_user_id(database, job_id, user_id) + if job: + database.delete(job) + database.commit() + return True + else: + logger.warning( + f"Could not remove processing job with ID {job_id} for user {user_id} as it could not" + " be found in the database" + ) + return False + + def update_job_status_by_id( database: Session, job_id: int, status: ProcessingStatusEnum ): diff --git a/app/routers/unit_jobs.py b/app/routers/unit_jobs.py index d70102b..6c07127 100644 --- a/app/routers/unit_jobs.py +++ b/app/routers/unit_jobs.py @@ -21,6 +21,7 @@ ) from app.services.processing import ( create_processing_job, + delete_processing_job, get_processing_job_by_user_id, get_processing_job_results, ) @@ -222,3 +223,48 @@ async def get_job_results( raise InternalException( message="An error occurred while retrieving processing job results." ) + + +@router.delete( + "/unit_jobs/{job_id}", + tags=["Unit Jobs"], + responses={ + JobNotFoundException.http_status: { + "description": "Job not found", + "model": ErrorResponse, + "content": { + "application/json": { + "example": get_dispatcher_error_response( + JobNotFoundException(), "request-id" + ) + } + }, + }, + InternalException.http_status: { + "description": "Internal server error", + "model": ErrorResponse, + "content": { + "application/json": { + "example": get_dispatcher_error_response( + InternalException(), "request-id" + ) + } + }, + }, + }, +) +async def delete_job( + job_id: int, db: Session = Depends(get_db), token: str = Depends(oauth2_scheme) +) -> None: + try: + job = await get_processing_job_by_user_id(token, db, job_id) + if not job: + raise JobNotFoundException() + await delete_processing_job(token, db, job_id) + except DispatcherException as de: + raise de + except Exception as e: + logger.error(f"Error deleting processing job {job_id}: {e}") + raise InternalException( + message="An error occurred while deleting the processing job." + ) diff --git a/app/services/processing.py b/app/services/processing.py index 92b3cf4..11c3a53 100644 --- a/app/services/processing.py +++ b/app/services/processing.py @@ -8,6 +8,7 @@ ProcessingJobRecord, get_job_by_user_id, get_jobs_by_user_id, + remove_job_by_id, save_job_to_db, update_job_status_by_id, ) @@ -228,3 +229,22 @@ async def retrieve_service_parameters( user_token=user_token, details=payload.service, ) + + +async def delete_processing_job( + token: str, + database: Session, + job_id: int, +) -> None: + user = get_current_user_id(token) + logger.info(f"Deleting processing job with ID {job_id} for user {user}") + + record = get_job_by_user_id(database, job_id, user) + if not record: + return + + # @TODO - Cancel job on the platform as well when supported by the platform + # platform = get_processing_platform(record.label) + # await platform.cancel_job(user_token=token, job_id=record.platform_job_id) + + remove_job_by_id(database, record.id, user) diff --git a/tests/routers/test_unit_jobs.py b/tests/routers/test_unit_jobs.py index de4a585..ef38fb3 100644 --- a/tests/routers/test_unit_jobs.py +++ b/tests/routers/test_unit_jobs.py @@ -137,3 +137,41 @@ def test_unit_jobs_get_job_results_500(mock_get_processing_job_results, client): assert "An error occurred while retrieving processing job results." in r.json().get( "message", "" ) + + +@patch("app.routers.unit_jobs.delete_processing_job") +@patch("app.routers.unit_jobs.get_processing_job_by_user_id") +def test_unit_jobs_delete_job_200( + mock_get_processing_job, + mock_delete_processing_job, + client, + fake_processing_job, +): + + mock_get_processing_job.return_value = fake_processing_job + mock_delete_processing_job.return_value = None + + r = client.delete("/unit_jobs/1") + assert r.status_code == 200 + + +@patch("app.routers.unit_jobs.get_processing_job_by_user_id") +def test_unit_jobs_delete_job_results_404(mock_get_processing_job, client): + + mock_get_processing_job.return_value = None + + r = client.delete("/unit_jobs/1") + assert r.status_code == status.HTTP_404_NOT_FOUND + assert "The requested job was not found." in r.json().get("message", "") + + +@patch("app.routers.unit_jobs.get_processing_job_by_user_id") +def test_unit_jobs_delete_job_500(mock_get_processing_job, client): + + mock_get_processing_job.side_effect = RuntimeError("Database connection lost") + + r = client.delete("/unit_jobs/1") + assert r.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR + assert "An error occurred while deleting the processing job." in r.json().get( + "message", "" + ) diff --git a/tests/services/test_processing.py b/tests/services/test_processing.py index dda49b0..81815db 100644 --- a/tests/services/test_processing.py +++ b/tests/services/test_processing.py @@ -14,6 +14,7 @@ from app.services.processing import ( create_processing_job, create_synchronous_job, + delete_processing_job, get_processing_job_results, get_job_status, get_processing_job_by_user_id, @@ -555,3 +556,44 @@ async def test_retrieve_service_parameters_success( details=fake_param_request.service, ) assert result == fake_parameter_result + + +@pytest.mark.asyncio +@patch("app.services.processing.remove_job_by_id") +@patch("app.services.processing.get_job_by_user_id") +@patch("app.services.processing.get_current_user_id") +async def test_delete_processing_job_deletes_record_when_found( + mock_current_user, + mock_get_job, + mock_remove_job, + fake_db_session, + fake_processing_job_record, +): + mock_current_user.return_value = "foobar" + mock_get_job.return_value = fake_processing_job_record + + await delete_processing_job("foobar-token", fake_db_session, 1) + + mock_get_job.assert_called_once_with(fake_db_session, 1, "foobar") + mock_remove_job.assert_called_once_with( + fake_db_session, fake_processing_job_record.id, "foobar" + ) + + +@pytest.mark.asyncio +@patch("app.services.processing.remove_job_by_id") +@patch("app.services.processing.get_job_by_user_id") +@patch("app.services.processing.get_current_user_id") +async def test_delete_processing_job_noop_when_not_found( + mock_current_user, + mock_get_job, + mock_remove_job, + fake_db_session, +): + mock_current_user.return_value = "foobar" + mock_get_job.return_value = None + + await delete_processing_job("foobar-token", fake_db_session, 1) + + mock_get_job.assert_called_once_with(fake_db_session, 1, "foobar") + mock_remove_job.assert_not_called()