All checks were successful
Deploy Development / deploy (push) Successful in 53s
Test Suite / pytest-backend (push) Successful in 1m42s
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 21s
Test Suite / playwright-smoke (push) Successful in 14s
parent_project_id und container_kind ermoeglichen verschachtelte Projektstruktur; Arbeitspakete bleiben an Blatt-Projekten gebunden. Co-authored-by: Cursor <cursoragent@cursor.com>
112 lines
3.5 KiB
Python
112 lines
3.5 KiB
Python
"""AP1.5c — Rekursive Projects (parent_project_id)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from tests.factories import provision_user_in_tenant
|
|
from tests.test_initiatives_actions import (
|
|
_auth,
|
|
_create_initiative,
|
|
_login,
|
|
)
|
|
|
|
|
|
def test_nested_projects_and_depth(client):
|
|
user = provision_user_in_tenant(tenant_role="member")
|
|
token = _login(client, user)
|
|
initiative_id = _create_initiative(client, token).json()["id"]
|
|
|
|
root = client.post(
|
|
f"/api/initiatives/{initiative_id}/projects",
|
|
json={"title": "Programm", "container_kind": "project"},
|
|
headers=_auth(token),
|
|
)
|
|
assert root.status_code == 201
|
|
root_id = root.json()["id"]
|
|
|
|
stream = client.post(
|
|
f"/api/initiatives/{initiative_id}/projects",
|
|
json={
|
|
"title": "Backend Stream",
|
|
"parent_project_id": root_id,
|
|
"container_kind": "stream",
|
|
},
|
|
headers=_auth(token),
|
|
)
|
|
assert stream.status_code == 201
|
|
stream_id = stream.json()["id"]
|
|
|
|
leaf = client.post(
|
|
f"/api/initiatives/{initiative_id}/projects",
|
|
json={"title": "API", "parent_project_id": stream_id},
|
|
headers=_auth(token),
|
|
)
|
|
assert leaf.status_code == 201
|
|
leaf_id = leaf.json()["id"]
|
|
|
|
listed = client.get(
|
|
f"/api/initiatives/{initiative_id}/projects",
|
|
headers=_auth(token),
|
|
)
|
|
assert listed.status_code == 200
|
|
items = {p["id"]: p for p in listed.json()}
|
|
assert items[stream_id]["parent_project_id"] == root_id
|
|
assert items[leaf_id]["depth"] == 3
|
|
assert items[leaf_id]["is_leaf"] is True
|
|
assert items[root_id]["has_children"] is True
|
|
|
|
|
|
def test_action_only_on_leaf_project(client):
|
|
user = provision_user_in_tenant(tenant_role="member")
|
|
token = _login(client, user)
|
|
initiative_id = _create_initiative(client, token).json()["id"]
|
|
|
|
parent = client.post(
|
|
f"/api/initiatives/{initiative_id}/projects",
|
|
json={"title": "Container"},
|
|
headers=_auth(token),
|
|
).json()
|
|
child = client.post(
|
|
f"/api/initiatives/{initiative_id}/projects",
|
|
json={"title": "Blatt", "parent_project_id": parent["id"]},
|
|
headers=_auth(token),
|
|
).json()
|
|
|
|
blocked = client.post(
|
|
f"/api/initiatives/{initiative_id}/actions",
|
|
json={"title": "Am Container", "project_id": parent["id"]},
|
|
headers=_auth(token),
|
|
)
|
|
assert blocked.status_code == 400
|
|
assert "Blatt" in blocked.json()["detail"]
|
|
|
|
ok = client.post(
|
|
f"/api/initiatives/{initiative_id}/actions",
|
|
json={"title": "Am Blatt", "project_id": child["id"]},
|
|
headers=_auth(token),
|
|
)
|
|
assert ok.status_code == 201
|
|
|
|
|
|
def test_cannot_delete_project_with_children(client):
|
|
user = provision_user_in_tenant(tenant_role="member")
|
|
token = _login(client, user)
|
|
initiative_id = _create_initiative(client, token).json()["id"]
|
|
|
|
parent = client.post(
|
|
f"/api/initiatives/{initiative_id}/projects",
|
|
json={"title": "Parent"},
|
|
headers=_auth(token),
|
|
).json()
|
|
child = client.post(
|
|
f"/api/initiatives/{initiative_id}/projects",
|
|
json={"title": "Child", "parent_project_id": parent["id"]},
|
|
headers=_auth(token),
|
|
).json()
|
|
|
|
resp = client.delete(f"/api/projects/{parent['id']}", headers=_auth(token))
|
|
assert resp.status_code == 400
|
|
|
|
client.delete(f"/api/projects/{child['id']}", headers=_auth(token))
|
|
ok = client.delete(f"/api/projects/{parent['id']}", headers=_auth(token))
|
|
assert ok.status_code == 204
|