Model/tests/domain/tasks/test_subtasks.py
Khalim Conn-Kowlessar 8a3c0f3e77 A recorded batch failure carries structured details onto the sub_task outputs 🟥
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-07 11:49:56 +00:00

134 lines
3.2 KiB
Python

from uuid import uuid4
import pytest
from domain.tasks.subtasks import SubTask, SubTaskFailure, SubTaskStatus
def test_create_subtask_starts_waiting() -> None:
# arrange
task_id = uuid4()
# act
st = SubTask.create(task_id=task_id, inputs={"foo": "bar"})
# assert
assert st.task_id == task_id
assert st.status is SubTaskStatus.WAITING
assert st.inputs == {"foo": "bar"}
assert st.outputs is None
assert st.job_started is None
assert st.job_completed is None
def test_start_transitions_to_in_progress_and_sets_cloud_logs_url() -> None:
# arrange
st = SubTask.create(task_id=uuid4())
# act
st.start(cloud_logs_url="https://example/log")
# assert
assert st.status is SubTaskStatus.IN_PROGRESS
assert st.cloud_logs_url == "https://example/log"
assert st.job_started is not None
def test_start_is_idempotent_from_in_progress() -> None:
# arrange
st = SubTask.create(task_id=uuid4())
st.start()
first_start = st.job_started
# act
st.start(cloud_logs_url="https://other")
# assert
assert st.status is SubTaskStatus.IN_PROGRESS
assert st.job_started == first_start # not overwritten
assert st.cloud_logs_url == "https://other"
def test_start_rejects_from_complete() -> None:
# arrange
st = SubTask.create(task_id=uuid4())
st.complete()
# act / assert
with pytest.raises(ValueError):
st.start()
def test_start_restarts_a_failed_subtask_for_a_rerun() -> None:
# arrange
st = SubTask.create(task_id=uuid4())
st.start()
st.fail(RuntimeError("degenerate prediction"))
# act
st.start()
# assert
assert st.status is SubTaskStatus.IN_PROGRESS
assert st.job_completed is None
def test_complete_marks_outputs_and_job_completed() -> None:
# arrange
st = SubTask.create(task_id=uuid4())
st.start()
# act
st.complete({"uprn": "123"})
# assert
assert st.status is SubTaskStatus.COMPLETE
assert st.outputs == {"result": {"uprn": "123"}}
assert st.job_completed is not None
def test_complete_without_result_leaves_outputs_unset() -> None:
# arrange
st = SubTask.create(task_id=uuid4())
# act
st.complete()
# assert
assert st.outputs is None
def test_fail_with_subtask_failure_records_structured_details() -> None:
# arrange
st = SubTask.create(task_id=uuid4())
st.start()
# act
st.fail(
SubTaskFailure(
"1 of 2 properties failed",
details={
"succeeded": 1,
"failed": [{"property_id": 9, "error": "degenerate prediction"}],
},
)
)
# assert
assert st.status is SubTaskStatus.FAILED
assert st.outputs == {
"error": "1 of 2 properties failed",
"succeeded": 1,
"failed": [{"property_id": 9, "error": "degenerate prediction"}],
}
def test_fail_records_error_in_outputs() -> None:
# arrange
st = SubTask.create(task_id=uuid4())
err = RuntimeError("boom")
# act
st.fail(err)
# assert
assert st.status is SubTaskStatus.FAILED
assert st.outputs == {"error": "boom"}
assert st.job_completed is not None