Skip to content

Commit 821086a

Browse files
committed
feat: add tests for task, task cancellation, fix sender tests
1 parent b34ff90 commit 821086a

3 files changed

Lines changed: 140 additions & 2 deletions

File tree

backend/utils/tests/test_cancel.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import subprocess
2+
from unittest.mock import call, patch
3+
4+
import pytest
5+
from fastapi.testclient import TestClient
6+
7+
from backend.config.paths import get_tmp_thread_files
8+
from backend.main import app
9+
10+
client = TestClient(app)
11+
12+
TASK_UID = "abc123"
13+
14+
15+
@pytest.fixture
16+
def mock_cancel_deps():
17+
"""Patch all three side-effects of the cancel endpoint."""
18+
with (
19+
patch("backend.main.celery_app") as mock_celery,
20+
patch("backend.main.subprocess.run") as mock_subproc,
21+
patch("backend.main.shutil.rmtree") as mock_rmtree,
22+
):
23+
yield {
24+
"celery": mock_celery,
25+
"subprocess": mock_subproc,
26+
"rmtree": mock_rmtree,
27+
}
28+
29+
30+
def test_cancel_returns_200(mock_cancel_deps):
31+
response = client.post(f"/api/cancel/{TASK_UID}")
32+
assert response.status_code == 200
33+
34+
35+
def test_cancel_response_body(mock_cancel_deps):
36+
response = client.post(f"/api/cancel/{TASK_UID}")
37+
data = response.json()
38+
assert data["status"] == "cancelled"
39+
assert data["task_uid"] == TASK_UID
40+
41+
42+
def test_cancel_revokes_celery_task(mock_cancel_deps):
43+
client.post(f"/api/cancel/{TASK_UID}")
44+
mock_cancel_deps["celery"].control.revoke.assert_called_once_with(TASK_UID)
45+
46+
47+
def test_cancel_stops_docker_container(mock_cancel_deps):
48+
client.post(f"/api/cancel/{TASK_UID}")
49+
mock_cancel_deps["subprocess"].assert_called_once_with(
50+
["docker", "stop", f"pysymbench-{TASK_UID}"],
51+
check=False,
52+
capture_output=True,
53+
timeout=30,
54+
)
55+
56+
57+
def test_cancel_removes_all_tmp_dirs(mock_cancel_deps):
58+
client.post(f"/api/cancel/{TASK_UID}")
59+
expected_paths = get_tmp_thread_files(TASK_UID)
60+
calls = [call(p, ignore_errors=True) for p in expected_paths]
61+
mock_cancel_deps["rmtree"].assert_has_calls(calls, any_order=True)
62+
assert mock_cancel_deps["rmtree"].call_count == len(expected_paths)
63+
64+
65+
def test_cancel_still_succeeds_when_docker_stop_fails(mock_cancel_deps):
66+
"""docker stop returning non-zero exit (container not running) must not raise."""
67+
mock_cancel_deps["subprocess"].return_value = subprocess.CompletedProcess(
68+
args=[], returncode=1
69+
)
70+
response = client.post(f"/api/cancel/{TASK_UID}")
71+
assert response.status_code == 200
72+
assert response.json()["status"] == "cancelled"
73+
74+
75+
def test_cancel_different_uids_use_correct_container_name(mock_cancel_deps):
76+
for uid in ["uid-one", "uid-two"]:
77+
mock_cancel_deps["subprocess"].reset_mock()
78+
client.post(f"/api/cancel/{uid}")
79+
args = mock_cancel_deps["subprocess"].call_args[0][0]
80+
assert args[-1] == f"pysymbench-{uid}"

backend/utils/tests/test_result_sender.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ def test_send_folder_by_email_success(
7474
assert "file2.txt" in file_list
7575
assert "subfolder/file3.txt" in file_list
7676

77-
mock_smtp.assert_called_once_with("smtp.gmail.com", 587, timeout=30)
77+
mock_smtp.assert_called_once_with("smtp.gmail.com", 587, timeout=15)
7878
mock_server_instance.ehlo.assert_called()
7979
mock_server_instance.starttls.assert_called_once()
8080
mock_server_instance.login.assert_called_once_with(
@@ -240,4 +240,4 @@ def test_email_content_structure(
240240

241241
attachment = sent_msg.get_payload()[1]
242242
assert attachment.get_content_type() == "application/zip"
243-
assert attachment.get_filename() == "results"
243+
assert attachment.get_filename() == "results.zip"

backend/utils/tests/test_task.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
from unittest.mock import MagicMock
2+
3+
import pytest
4+
from fastapi.testclient import TestClient
5+
6+
from backend.main import app
7+
from backend.utils import task as task_module
8+
9+
client = TestClient(app)
10+
11+
TASK_UID = "abc123"
12+
13+
14+
@pytest.fixture
15+
def mock_task_deps(monkeypatch):
16+
mock_run = MagicMock()
17+
mock_send = MagicMock()
18+
mock_reset = MagicMock()
19+
mock_get_files = MagicMock(return_value=["/tmp/fake/uploads", "/tmp/fake/results"])
20+
21+
monkeypatch.setattr(task_module, "run_pipeline", mock_run)
22+
monkeypatch.setattr(task_module, "send_folder_by_email", mock_send)
23+
monkeypatch.setattr(task_module, "reset_dirs", mock_reset)
24+
monkeypatch.setattr(task_module, "get_tmp_thread_files", mock_get_files)
25+
26+
return {
27+
"run_pipeline": mock_run,
28+
"send_email": mock_send,
29+
"reset_dirs": mock_reset,
30+
"get_tmp_thread_files": mock_get_files,
31+
}
32+
33+
34+
def test_task_cleanup_runs_on_success(mock_task_deps):
35+
task_module.process_and_cleanup_task.run(TASK_UID, "a@b.com", "exp", "model.onnx")
36+
mock_task_deps["reset_dirs"].assert_called_once()
37+
38+
39+
def test_task_cleanup_runs_on_pipeline_failure(mock_task_deps):
40+
mock_task_deps["run_pipeline"].side_effect = RuntimeError("docker died")
41+
task_module.process_and_cleanup_task.run(TASK_UID, "a@b.com", "exp", "model.onnx")
42+
mock_task_deps["reset_dirs"].assert_called_once()
43+
44+
45+
def test_task_no_email_on_pipeline_failure(mock_task_deps):
46+
mock_task_deps["run_pipeline"].side_effect = RuntimeError("docker died")
47+
task_module.process_and_cleanup_task.run(TASK_UID, "a@b.com", "exp", "model.onnx")
48+
mock_task_deps["send_email"].assert_not_called()
49+
50+
51+
def test_task_sends_email_on_success(mock_task_deps):
52+
task_module.process_and_cleanup_task.run(TASK_UID, "a@b.com", "exp", "model.onnx")
53+
mock_task_deps["send_email"].assert_called_once()
54+
55+
56+
def test_task_cleanup_receives_correct_uid(mock_task_deps):
57+
task_module.process_and_cleanup_task.run(TASK_UID, "a@b.com", "exp", "model.onnx")
58+
mock_task_deps["get_tmp_thread_files"].assert_called_once_with(TASK_UID)

0 commit comments

Comments
 (0)