Kairo-Jinkendo/backend/tests/test_ap08_operating_model.py
Lars fe3bc4f057
All checks were successful
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Successful in 58s
Test Suite / lint-backend (push) Successful in 2s
Test Suite / compose-smoke (push) Has been skipped
Test Suite / k6 /api/health Baseline (push) Successful in 18s
Test Suite / playwright-smoke (push) Successful in 12s
AP0.8: Operating Model Extension I (Attention, Blocker, Backlog, Milestone)
Schema 007, regelbasierte Attention/NextAction im Data Layer, CRUD fuer Blocker/Backlog/Meilensteine, Workspace-Widget und Initiative-Detail-Sektionen. 25 Capabilities. Tests fuer Remote-Pytest auf Pi angepasst (conftest Session-Guard).

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

280 lines
9.3 KiB
Python

"""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_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