Kairo-Jinkendo/backend/tests/test_ap22c_sequential_horizon.py
Lars 753f178b0c
Some checks failed
Deploy Development / deploy (push) Successful in 48s
Test Suite / pytest-backend (push) Failing after 3m23s
Test Suite / k6 /api/health Baseline (push) Has been skipped
Test Suite / playwright-smoke (push) Has been skipped
Test Suite / lint-backend (push) Successful in 3s
Test Suite / compose-smoke (push) Has been skipped
docs: Archetyp-Vollspecs und Methodenkern vorlaeufig freigeben.
Decision-Lock, Spec-D-Methoden und C1-Massstab-Vollspecs als Zielbild; Steering-Registry/Compat und Horizon-Tests an die Normierung anbinden. Implementierungsausreichendheit bleibt bewusst offen.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-26 15:02:04 +02:00

189 lines
5.8 KiB
Python

"""AP2.2c — A2 sequential_dependency: Gate-Horizont + Agile-Komposition."""
from __future__ import annotations
from tests.factories import provision_user_in_tenant
from tests.test_initiatives_actions import _auth, _create_initiative, _login
def _gate_ids(client, token, initiative_id):
roadmap = client.get(
f"/api/initiatives/{initiative_id}/roadmap/items",
headers=_auth(token),
)
assert roadmap.status_code == 200
items = roadmap.json()
active = next(i for i in items if i.get("status") == "active")
planned = next(i for i in items if i.get("title", "").startswith("G2"))
return active["id"], planned["id"]
def test_sequential_next_action_only_in_active_gate_horizon(client):
user = provision_user_in_tenant(tenant_role="admin")
token = _login(client, user)
created = _create_initiative(
client,
token,
title="Horizon Test",
archetype_key="initiative.linear_project",
)
initiative_id = created.json()["id"]
g1_id, g2_id = _gate_ids(client, token, initiative_id)
off_horizon = client.post(
f"/api/initiatives/{initiative_id}/actions",
json={
"title": "Außerhalb Horizont",
"status": "open",
"roadmap_item_id": g2_id,
"sort_order": 0,
},
headers=_auth(token),
)
on_horizon = client.post(
f"/api/initiatives/{initiative_id}/actions",
json={
"title": "Im aktiven Gate",
"status": "open",
"roadmap_item_id": g1_id,
"sort_order": 1,
},
headers=_auth(token),
)
assert off_horizon.status_code == 201 and on_horizon.status_code == 201
snap = client.get(
f"/api/initiatives/{initiative_id}/steering-snapshot",
headers=_auth(token),
)
assert snap.status_code == 200
next_actions = snap.json().get("next_actions") or []
assert next_actions
next_ids = {n.get("action_id") for n in next_actions}
assert off_horizon.json()["id"] not in next_ids
assert on_horizon.json()["id"] in next_ids
def test_agile_on_linear_delegates_primary_without_active_sprint(client):
user = provision_user_in_tenant(tenant_role="admin")
token = _login(client, user)
created = _create_initiative(
client,
token,
title="Agile Primary",
archetype_key="initiative.linear_project",
)
initiative_id = created.json()["id"]
g1_id, _ = _gate_ids(client, token, initiative_id)
action = client.post(
f"/api/initiatives/{initiative_id}/actions",
json={
"title": "Gate Action",
"status": "open",
"roadmap_item_id": g1_id,
},
headers=_auth(token),
)
assert action.status_code == 201
snap = client.get(
f"/api/initiatives/{initiative_id}/steering-snapshot",
headers=_auth(token),
)
assert snap.status_code == 200
assert snap.json().get("active_work_cycle") is None
next_actions = snap.json().get("next_actions") or []
assert next_actions[0]["action_id"] == action.json()["id"]
assert next_actions[0]["reason_code"] in (
"execution_ready",
"execution_critical_path",
)
def test_agile_on_linear_sprint_intersects_gate_horizon(client):
user = provision_user_in_tenant(tenant_role="admin")
token = _login(client, user)
created = _create_initiative(
client,
token,
title="Sprint Horizon",
archetype_key="initiative.linear_project",
)
initiative_id = created.json()["id"]
g1_id, _ = _gate_ids(client, token, initiative_id)
cycle = client.post(
f"/api/initiatives/{initiative_id}/work-cycles",
json={"title": "Montage-Woche", "status": "active"},
headers=_auth(token),
)
assert cycle.status_code == 201
cycle_id = cycle.json()["id"]
outside_sprint = client.post(
f"/api/initiatives/{initiative_id}/actions",
json={
"title": "Nur Gate",
"status": "open",
"roadmap_item_id": g1_id,
},
headers=_auth(token),
)
in_sprint = client.post(
f"/api/initiatives/{initiative_id}/actions",
json={
"title": "Im Sprint",
"status": "ready",
"roadmap_item_id": g1_id,
"work_cycle_id": cycle_id,
},
headers=_auth(token),
)
assert outside_sprint.status_code == 201 and in_sprint.status_code == 201
snap = client.get(
f"/api/initiatives/{initiative_id}/steering-snapshot",
headers=_auth(token),
)
assert snap.status_code == 200
assert snap.json().get("active_work_cycle")
next_actions = snap.json().get("next_actions") or []
assert next_actions
assert next_actions[0]["action_id"] == in_sprint.json()["id"]
assert next_actions[0]["reason_code"] == "work_cycle_ready"
def test_operating_context_active_composition_modifier_with_sprint(client):
user = provision_user_in_tenant(tenant_role="member")
token = _login(client, user)
created = _create_initiative(
client,
token,
title="Composition Flag",
archetype_key="initiative.linear_project",
)
initiative_id = created.json()["id"]
ctx_before = client.get(
f"/api/initiatives/{initiative_id}/operating-context",
headers=_auth(token),
)
assert ctx_before.json().get("active_composition_modifier") is None
client.post(
f"/api/initiatives/{initiative_id}/work-cycles",
json={"title": "S1", "status": "active"},
headers=_auth(token),
)
ctx_after = client.get(
f"/api/initiatives/{initiative_id}/operating-context",
headers=_auth(token),
)
assert ctx_after.json().get("active_composition_modifier") == "agile_iteration"