diff --git a/tests/test_lifecycle.py b/tests/test_lifecycle.py new file mode 100644 index 0000000..3e84048 --- /dev/null +++ b/tests/test_lifecycle.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +import pytest + +from api.services.lifecycle import ( + has_active_task_status, + should_activate_parent_for_active_tasks, + should_activate_parent_for_task_start, + status_value, +) + + +@pytest.mark.parametrize("parent_status", ["proposed", "ready", "backlog"]) +def test_task_start_activates_planning_parent(parent_status): + assert should_activate_parent_for_task_start( + previous_task_status="todo", + new_task_status="in_progress", + parent_workstream_status=parent_status, + ) + + +@pytest.mark.parametrize("parent_status", ["active", "blocked", "finished", "archived"]) +def test_task_start_does_not_rewrite_non_planning_parent(parent_status): + assert not should_activate_parent_for_task_start( + previous_task_status="todo", + new_task_status="in_progress", + parent_workstream_status=parent_status, + ) + + +def test_task_start_requires_todo_to_in_progress_transition(): + assert not should_activate_parent_for_task_start( + previous_task_status="in_progress", + new_task_status="in_progress", + parent_workstream_status="ready", + ) + assert not should_activate_parent_for_task_start( + previous_task_status="todo", + new_task_status="done", + parent_workstream_status="ready", + ) + + +def test_has_active_task_status_ignores_terminal_and_todo_statuses(): + assert has_active_task_status(["todo", "done", "cancelled"]) is False + assert has_active_task_status(["todo", "blocked"]) is True + assert has_active_task_status(["in_progress"]) is True + + +def test_active_task_state_activates_planning_parent_for_renormalization(): + assert should_activate_parent_for_active_tasks( + parent_workstream_status="proposed", + task_statuses=["todo", "in_progress"], + ) + + +def test_active_task_state_does_not_unblock_blocked_parent(): + assert not should_activate_parent_for_active_tasks( + parent_workstream_status="blocked", + task_statuses=["in_progress"], + ) + + +def test_status_value_unwraps_enum_like_values(): + class Status: + value = "In_Progress" + + assert status_value(Status()) == "in_progress" diff --git a/workplans/STATE-WP-0047-lifecycle-assertions-and-renormalization.md b/workplans/STATE-WP-0047-lifecycle-assertions-and-renormalization.md index 9c693b0..f331102 100644 --- a/workplans/STATE-WP-0047-lifecycle-assertions-and-renormalization.md +++ b/workplans/STATE-WP-0047-lifecycle-assertions-and-renormalization.md @@ -174,7 +174,7 @@ pattern into metadata, detection, repair, and tests. ```task id: STATE-WP-0047-T07 -status: in_progress +status: done priority: high state_hub_task_id: "def5ce49-1938-4c45-807d-78ac15c995cb" ``` @@ -199,6 +199,10 @@ precedence over generic C-04 status drift. Progress 2026-05-23: added guide coverage so the drift-learning scaffold has a stable test anchor. +Result 2026-05-23: lifecycle coverage now spans direct helper tests, +task-start parent activation route tests, flow exit assertion tests, and +consistency repair/guide tests. + ## Acceptance Criteria - Starting task work deterministically activates the parent workstream.