Add end-to-end cancellation path test and verify no regressions (task-cancellation 9.1-9.2)
- test_cancellation_full_path_queued_immediate_and_dispatched_collaborative exercises the full public-API cancellation path: immediate cancel of a queued task, collaborative cancel of a dispatched task surfaced through lease renewal and a cancelled terminal report, and visibility of the cancelled status via both the get and list endpoints. - Full backend suite (869 passed, 50 skipped) and cloud-console frontend suite (27 passed) + typecheck show no regressions; the only failures are 4 pre-existing live-LLM integration tests unrelated to this change.
This commit is contained in:
@@ -63,5 +63,5 @@
|
||||
|
||||
## 9. End-to-end verification
|
||||
|
||||
- [ ] 9.1 Run the full test suite (`uv run pytest` at repo root, plus `cloud-console` frontend tests) and confirm no regressions in existing task-scheduler, host-agent-protocol, platform-sdk, or workflow-orchestration tests.
|
||||
- [ ] 9.2 Manually or via an integration test, exercise the full path: submit a task, cancel a `queued` task (immediate), submit and dispatch another task, cancel it mid-execution, and confirm it reaches `cancelled` within one lease-renewal cycle with `cancelled` visible in both the public API and the Cloud Console.
|
||||
- [x] 9.1 Run the full test suite (`uv run pytest` at repo root, plus `cloud-console` frontend tests) and confirm no regressions in existing task-scheduler, host-agent-protocol, platform-sdk, or workflow-orchestration tests. (869 passed, 50 skipped, 4 pre-existing failures unrelated to this change — `test_verifier_against_real_llm`, `test_reflector_against_real_llm`, `test_real_anthropic_ai_planner_selects_a_tool`, `test_real_anthropic_semantic_enrichment_returns_schema_valid_scene` all require live Anthropic API network access and fail the same way on `master`. `cloud-console`: 27 tests passed, `vue-tsc --noEmit` typecheck clean.)
|
||||
- [x] 9.2 Manually or via an integration test, exercise the full path: submit a task, cancel a `queued` task (immediate), submit and dispatch another task, cancel it mid-execution, and confirm it reaches `cancelled` within one lease-renewal cycle with `cancelled` visible in both the public API and the Cloud Console. (Added `test_cancellation_full_path_queued_immediate_and_dispatched_collaborative` in `tests/test_cloud_sdk_api.py`: submits and immediately cancels a queued task via `POST /v1/tasks/{id}/cancel` (200, `cancelled`); submits, dispatches, and cancels a second task mid-execution (202, pending); drives one lease renewal confirming `cancel_requested=True` is surfaced; reports a `cancelled` terminal result as the Host Agent would; and confirms the task shows `status="cancelled"` via both `GET /v1/tasks/{id}` and `GET /v1/tasks?status=cancelled`. The Cloud Console reads task status through this same public API and its `cancelled` rendering is covered by the Task 6.4 `taskCancellation.ts` unit tests, so this repository-to-API round trip is the full path exercised at the automated-test layer; no manual browser session was run.)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -620,6 +620,69 @@ def test_cancel_scope_rejected_before_reaching_scheduler(tmp_path, monkeypatch)
|
||||
assert called is False
|
||||
|
||||
|
||||
def test_cancellation_full_path_queued_immediate_and_dispatched_collaborative(
|
||||
tmp_path,
|
||||
) -> None:
|
||||
"""End-to-end exercise of the cancellation path (task-cancellation 9.2):
|
||||
a queued task is cancelled immediately; a dispatched task's cancellation
|
||||
is only recorded until the Host Agent's next lease renewal surfaces it
|
||||
and reports back a cancelled terminal result, after which the task is
|
||||
visible as cancelled via the public API's get and list endpoints."""
|
||||
app, pool, scheduler, _ = _build_app(tmp_path)
|
||||
pool.sync_host_devices(
|
||||
"host-a",
|
||||
[Device(id="device-a", driver_type="wda", status="idle")], # type: ignore[arg-type]
|
||||
)
|
||||
client = _client_for(app)
|
||||
|
||||
queued_task_id = scheduler.submit(goal="cancel me while queued")
|
||||
queued_cancel = client.post(f"/v1/tasks/{queued_task_id}/cancel")
|
||||
assert queued_cancel.status_code == 200, queued_cancel.text
|
||||
assert queued_cancel.json() == {"task_id": queued_task_id, "status": "cancelled"}
|
||||
|
||||
dispatched_task_id = scheduler.submit(goal="cancel me mid-execution")
|
||||
scheduler.assign()
|
||||
dispatched_cancel = client.post(f"/v1/tasks/{dispatched_task_id}/cancel")
|
||||
assert dispatched_cancel.status_code == 202, dispatched_cancel.text
|
||||
assert dispatched_cancel.json() == {
|
||||
"task_id": dispatched_task_id,
|
||||
"status": "assigned",
|
||||
}
|
||||
|
||||
task = scheduler.store.get_task(dispatched_task_id)
|
||||
renewed_at = datetime.now(UTC)
|
||||
renewal = scheduler.store.renew_lease(
|
||||
task_id=dispatched_task_id,
|
||||
attempt=task.attempt_count,
|
||||
lease_id=task.lease_id or "",
|
||||
host_id=task.assigned_host_id or "",
|
||||
lease_expires_at=renewed_at + timedelta(seconds=30),
|
||||
now=renewed_at,
|
||||
)
|
||||
assert renewal.status == "renewed", renewal
|
||||
assert renewal.cancel_requested is True
|
||||
|
||||
scheduler.store.record_task_result(
|
||||
task_id=dispatched_task_id,
|
||||
attempt=task.attempt_count,
|
||||
lease_id=task.lease_id or "",
|
||||
host_id=task.assigned_host_id or "",
|
||||
status="cancelled",
|
||||
failure_reason="cancellation requested by control plane",
|
||||
terminal_result=None,
|
||||
completed_at=datetime.now(UTC),
|
||||
)
|
||||
|
||||
status_resp = client.get(f"/v1/tasks/{dispatched_task_id}")
|
||||
assert status_resp.status_code == 200, status_resp.text
|
||||
assert status_resp.json()["status"] == "cancelled"
|
||||
|
||||
list_resp = client.get("/v1/tasks", params={"status": "cancelled"})
|
||||
assert list_resp.status_code == 200, list_resp.text
|
||||
cancelled_ids = {t["id"] for t in list_resp.json()["items"]}
|
||||
assert {queued_task_id, dispatched_task_id} <= cancelled_ids
|
||||
|
||||
|
||||
def test_plugin_admin_scope_is_checked_before_registration(
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
|
||||
Reference in New Issue
Block a user