Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions rq_dashboard/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
Worker,
requeue_job,
)
from rq.exceptions import NoSuchJobError
from rq.exceptions import DeserializationError, NoSuchJobError
from rq.job import Job
from rq.registry import (
DeferredJobRegistry,
Expand Down Expand Up @@ -68,6 +68,27 @@ class Config:

config: Config = Config()


@blueprint.errorhandler(NoSuchJobError)
def handle_no_such_job(exc):
"""Return 404 so missing/expired jobs do not result in 500."""
return make_response(
json.dumps(dict(error="job_not_found")),
404,
{"Content-Type": "application/json", "Cache-Control": "no-store"},
)


@blueprint.errorhandler(DeserializationError)
def handle_deserialization_error(exc):
"""Return 409 so corrupt job data does not result in 500."""
return make_response(
json.dumps(dict(error="job_deserialization_failed")),
409,
{"Content-Type": "application/json", "Cache-Control": "no-store"},
)


# @blueprint.before_app_first_request
def setup_rq_connection(current_app):
# we need to do It here instead of cli, since It may be embeded
Expand Down Expand Up @@ -420,14 +441,8 @@ def job_view(instance_number, job_id):
@check_delete_enable
@jsonify
def delete_job_view(job_id, registry=None):
try:
job = Job.fetch(job_id, connection=current_app.redis_conn)
job.delete()
except NoSuchJobError:
if registry:
registry.remove(job_id)
return dict(status="ERROR")

job = Job.fetch(job_id, connection=current_app.redis_conn)
job.delete()
return dict(status="OK")


Expand Down
30 changes: 30 additions & 0 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import json
import time
import unittest
from unittest.mock import patch

import redis
from rq.exceptions import DeserializationError, NoSuchJobError
from rq import Queue, Worker

from rq_dashboard.cli import make_flask_app
Expand Down Expand Up @@ -80,6 +82,34 @@ def some_work():
response_del = self.client.post(job_del_url)
self.assertEqual(response_del.status_code, HTTP_OK)

def test_delete_nonexistent_job_returns_404(self):
"""Delete missing job returns 404 with job_not_found (not 500 or 200 ERROR)."""
response = self.client.post('/job/nonexistent-job-id/delete')
self.assertEqual(response.status_code, 404)
data = json.loads(response.data.decode('utf8'))
self.assertEqual(data, {'error': 'job_not_found'})

def test_view_nonexistent_job_returns_404(self):
"""HTML job view for missing job returns 404."""
response = self.client.get('/0/view/job/nonexistent-job-id')
self.assertEqual(response.status_code, 404)

def test_job_info_nonexistent_returns_404(self):
"""JSON job info for missing job returns 404 with job_not_found."""
response = self.client.get('/0/data/job/nonexistent-job-id.json')
self.assertEqual(response.status_code, 404)
data = json.loads(response.data.decode('utf8'))
self.assertEqual(data, {'error': 'job_not_found'})

def test_job_info_deserialization_error_returns_409(self):
"""Corrupt job data returns 409 with job_deserialization_failed."""
with patch('rq_dashboard.web.Job') as mock_job:
mock_job.fetch.side_effect = DeserializationError()
response = self.client.get('/0/data/job/any-id.json')
self.assertEqual(response.status_code, 409)
data = json.loads(response.data.decode('utf8'))
self.assertEqual(data, {'error': 'job_deserialization_failed'})

def test_registry_jobs_list(self):
for registry_name in REGISTRY_NAMES:
response = self.client.get(f'/0/data/jobs/default/{registry_name}/8/asc/1.json')
Expand Down