feat: add cancel, pause, and resume flow control for scans
Some checks failed
Deploy / deploy (push) Failing after 18s
Some checks failed
Deploy / deploy (push) Failing after 18s
Users running large scans can now pause (keep partial results, resume
later), cancel (stop permanently, partial results preserved), or resume
a paused scan which races through cache hits before continuing.
Backend:
- Extend scans.status CHECK to include 'paused' and 'cancelled'
- Add _migrate_add_pause_cancel_status() table-recreation migration
- scan_processor: _running_tasks/_cancel_reasons registries,
cancel_scan_task/pause_scan_task/stop_scan_task helpers,
CancelledError handler in process_scan(), start_resume_processor()
- api_server: POST /scans/{id}/pause|cancel|resume endpoints with
rate limits (30/min pause+cancel, 10/min resume); list_scans now
accepts paused/cancelled as status filter values
Frontend:
- Scan.status type extended with 'paused' | 'cancelled'
- scanApi.pause/cancel/resume added
- StatusChip: amber PauseCircle chip for paused, grey Ban for cancelled
- ScanDetails: context-aware action row with inline-confirm for
Pause and Cancel; Resume button for paused scans
Tests: 129 total (58 new) across test_scan_control.py,
test_scan_processor_control.py, and additions to existing suites
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -245,6 +245,45 @@ class TestScanEndpoints:
|
||||
assert data["data"][0]["destination"] == "FRA"
|
||||
assert data["data"][0]["min_price"] == 50
|
||||
|
||||
def test_get_scan_paused_status(self, client: TestClient, create_test_scan):
|
||||
"""Test that GET /scans/{id} returns paused status correctly."""
|
||||
scan_id = create_test_scan(status='paused')
|
||||
response = client.get(f"/api/v1/scans/{scan_id}")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == "paused"
|
||||
|
||||
def test_get_scan_cancelled_status(self, client: TestClient, create_test_scan):
|
||||
"""Test that GET /scans/{id} returns cancelled status correctly."""
|
||||
scan_id = create_test_scan(status='cancelled')
|
||||
response = client.get(f"/api/v1/scans/{scan_id}")
|
||||
assert response.status_code == 200
|
||||
assert response.json()["status"] == "cancelled"
|
||||
|
||||
def test_list_scans_filter_paused(self, client: TestClient, create_test_scan):
|
||||
"""Test filtering scans by paused status."""
|
||||
create_test_scan(status='paused')
|
||||
create_test_scan(status='completed')
|
||||
create_test_scan(status='running')
|
||||
|
||||
response = client.get("/api/v1/scans?status=paused")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["data"]) == 1
|
||||
assert data["data"][0]["status"] == "paused"
|
||||
|
||||
def test_list_scans_filter_cancelled(self, client: TestClient, create_test_scan):
|
||||
"""Test filtering scans by cancelled status."""
|
||||
create_test_scan(status='cancelled')
|
||||
create_test_scan(status='pending')
|
||||
|
||||
response = client.get("/api/v1/scans?status=cancelled")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert len(data["data"]) == 1
|
||||
assert data["data"][0]["status"] == "cancelled"
|
||||
|
||||
|
||||
@pytest.mark.unit
|
||||
@pytest.mark.api
|
||||
|
||||
Reference in New Issue
Block a user