"""AP0.8 — Operating Model Extension I tests. Ausführung: im Backend-Container auf dem Pi nach Deploy (develop/main), siehe .gitea/workflows/test.yml und docs/DEPLOYMENT.md. """ from __future__ import annotations from tests.factories import provision_user_in_tenant from tests.test_initiatives_actions import ( _auth, _create_action, _create_initiative, _login, ) def _create_blocker(client, token, initiative_id, **kwargs): body = {"title": "Blocker Test", **kwargs} return client.post( f"/api/initiatives/{initiative_id}/blockers", json=body, headers=_auth(token), ) def _create_backlog(client, token, initiative_id, **kwargs): body = {"title": "Backlog Test", **kwargs} return client.post( f"/api/initiatives/{initiative_id}/backlog", json=body, headers=_auth(token), ) def _create_milestone(client, token, initiative_id, **kwargs): body = {"title": "Milestone Test", **kwargs} return client.post( f"/api/initiatives/{initiative_id}/milestones", json=body, headers=_auth(token), ) def test_attention_blocked_action(client): user = provision_user_in_tenant(tenant_role="member") token = _login(client, user) initiative_id = _create_initiative(client, token).json()["id"] action = _create_action( client, token, initiative_id, assigned_actor_ids=[user["actor_id"]] ).json() client.patch( f"/api/actions/{action['id']}", json={"status": "blocked"}, headers=_auth(token), ) res = client.get("/api/workspace/attention", headers=_auth(token)) assert res.status_code == 200 items = res.json() assert any(i["kind"] == "blocked_action" for i in items) blocked = next(i for i in items if i["kind"] == "blocked_action") assert blocked["reason_code"] == "action_blocked" assert blocked["data_source"] == "actions" assert blocked["scope_type"] == "action" def test_attention_unassigned_action(client): user = provision_user_in_tenant(tenant_role="member") token = _login(client, user) initiative_id = _create_initiative(client, token).json()["id"] _create_action(client, token, initiative_id, title="Unassigned") res = client.get("/api/workspace/attention", headers=_auth(token)) assert res.status_code == 200 assert any(i["kind"] == "unassigned_action" for i in res.json()) def test_attention_initiative_without_next_action(client): user = provision_user_in_tenant(tenant_role="member") token = _login(client, user) _create_initiative(client, token, title="Empty Initiative") res = client.get("/api/workspace/attention", headers=_auth(token)) assert res.status_code == 200 assert any(i["kind"] == "initiative_without_next_action" for i in res.json()) def test_attention_open_blocker(client): user = provision_user_in_tenant(tenant_role="member") token = _login(client, user) initiative_id = _create_initiative(client, token).json()["id"] _create_blocker(client, token, initiative_id, title="Dependency missing") res = client.get("/api/workspace/attention", headers=_auth(token)) assert res.status_code == 200 assert any(i["kind"] == "open_blocker" for i in res.json()) def test_attention_milestone_at_risk(client): user = provision_user_in_tenant(tenant_role="member") token = _login(client, user) initiative_id = _create_initiative(client, token).json()["id"] ms = _create_milestone(client, token, initiative_id).json() client.patch( f"/api/milestones/{ms['id']}", json={"status": "at_risk"}, headers=_auth(token), ) res = client.get("/api/workspace/attention", headers=_auth(token)) assert any(i["kind"] == "milestone_at_risk" for i in res.json()) def test_attention_severity_sorting(client): user = provision_user_in_tenant(tenant_role="member") token = _login(client, user) initiative_id = _create_initiative(client, token).json()["id"] action = _create_action(client, token, initiative_id).json() client.patch( f"/api/actions/{action['id']}", json={"status": "blocked"}, headers=_auth(token), ) _create_blocker(client, token, initiative_id) items = client.get("/api/workspace/attention", headers=_auth(token)).json() severities = [i["severity"] for i in items] order = {"critical": 0, "warning": 1, "info": 2} assert severities == sorted(severities, key=lambda s: order.get(s, 99)) def test_next_action_convert_backlog(client): user = provision_user_in_tenant(tenant_role="member") token = _login(client, user) initiative_id = _create_initiative(client, token).json()["id"] item = _create_backlog( client, token, initiative_id, status="accepted", title="Convert me" ).json() res = client.get("/api/workspace/next-actions?limit=10", headers=_auth(token)) assert res.status_code == 200 assert any( c["kind"] == "convert_backlog" and c["backlog_item_id"] == item["id"] for c in res.json() ) def test_next_action_limit(client): user = provision_user_in_tenant(tenant_role="member") token = _login(client, user) initiative_id = _create_initiative(client, token).json()["id"] for i in range(5): _create_backlog( client, token, initiative_id, status="accepted", title=f"Item {i}" ) res = client.get("/api/workspace/next-actions?limit=2", headers=_auth(token)) assert len(res.json()) <= 2 def test_blocker_crud(client): user = provision_user_in_tenant(tenant_role="member") token = _login(client, user) initiative_id = _create_initiative(client, token).json()["id"] action = _create_action(client, token, initiative_id).json() created = _create_blocker( client, token, initiative_id, title="API Block", action_id=action["id"], ) assert created.status_code == 201 blocker = created.json() assert blocker["initiative_id"] == initiative_id listed = client.get( f"/api/initiatives/{initiative_id}/blockers", headers=_auth(token) ) assert listed.status_code == 200 assert any(b["id"] == blocker["id"] for b in listed.json()) updated = client.patch( f"/api/blockers/{blocker['id']}", json={"status": "resolved"}, headers=_auth(token), ) assert updated.status_code == 200 assert updated.json()["status"] == "resolved" deleted = client.delete(f"/api/blockers/{blocker['id']}", headers=_auth(token)) assert deleted.status_code == 204 def test_blocker_cross_tenant(client): user_a = provision_user_in_tenant() user_b = provision_user_in_tenant() token_a = _login(client, user_a) token_b = _login(client, user_b) initiative_id = _create_initiative(client, token_a).json()["id"] blocker = _create_blocker(client, token_a, initiative_id).json() assert ( client.get(f"/api/blockers/{blocker['id']}", headers=_auth(token_b)).status_code == 404 ) def test_backlog_convert_to_action(client): user = provision_user_in_tenant(tenant_role="member") token = _login(client, user) initiative_id = _create_initiative(client, token).json()["id"] item = _create_backlog( client, token, initiative_id, status="accepted", title="Feature X" ).json() converted = client.post( f"/api/backlog/{item['id']}/convert-to-action", json={}, headers=_auth(token), ) assert converted.status_code == 201 body = converted.json() assert body["backlog_item"]["status"] == "converted" assert body["action"]["title"] == "Feature X" assert body["backlog_item"]["converted_action_id"] == body["action"]["id"] def test_backlog_crud(client): user = provision_user_in_tenant(tenant_role="member") token = _login(client, user) initiative_id = _create_initiative(client, token).json()["id"] created = _create_backlog(client, token, initiative_id, status="triaged") assert created.status_code == 201 item = created.json() patched = client.patch( f"/api/backlog/{item['id']}", json={"status": "accepted"}, headers=_auth(token), ) assert patched.status_code == 200 deleted = client.delete(f"/api/backlog/{item['id']}", headers=_auth(token)) assert deleted.status_code == 204 def test_backlog_sort_order(client): user = provision_user_in_tenant(tenant_role="member") token = _login(client, user) initiative_id = _create_initiative(client, token).json()["id"] first = _create_backlog(client, token, initiative_id, title="First").json() second = _create_backlog(client, token, initiative_id, title="Second").json() assert first["sort_order"] < second["sort_order"] patched = client.patch( f"/api/backlog/{second['id']}", json={"sort_order": 0}, headers=_auth(token), ) assert patched.status_code == 200 assert patched.json()["sort_order"] == 0 listed = client.get( f"/api/initiatives/{initiative_id}/backlog", headers=_auth(token), ).json() assert [row["title"] for row in listed[:2]] == ["Second", "First"] def test_milestone_crud(client): user = provision_user_in_tenant(tenant_role="member") token = _login(client, user) initiative_id = _create_initiative(client, token).json()["id"] created = _create_milestone( client, token, initiative_id, status="active", target_date="2026-12-31" ) assert created.status_code == 201 ms = created.json() assert ms["target_date"] == "2026-12-31" deleted = client.delete(f"/api/milestones/{ms['id']}", headers=_auth(token)) assert deleted.status_code == 204 def test_member_has_ap08_capabilities(client): member = provision_user_in_tenant(tenant_role="member") token = _login(client, member) ctx = client.get("/api/me/context", headers=_auth(token)).json() caps = ctx["capabilities"] for key in ( "kairo.attention.read", "kairo.blocker.read", "kairo.blocker.manage", "kairo.backlog.read", "kairo.backlog.manage", "kairo.milestone.read", "kairo.milestone.manage", ): assert key in caps