All checks were successful
Deploy Development / deploy (push) Successful in 47s
Test Suite / pytest-backend (push) Successful in 42s
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
Co-authored-by: Cursor <cursoragent@cursor.com>
248 lines
8.3 KiB
Python
248 lines
8.3 KiB
Python
"""AP0.7 — Actor directory, workspace data layer, tenant hardening tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from auth import AUTH_HEADER
|
|
from services.actors import create_actor
|
|
from tests.factories import provision_user_in_tenant
|
|
from tests.test_initiatives_actions import (
|
|
_auth,
|
|
_create_action,
|
|
_create_initiative,
|
|
_login,
|
|
)
|
|
|
|
|
|
def test_list_actors_tenant_scoped(client):
|
|
user = provision_user_in_tenant(tenant_role="member")
|
|
token = _login(client, user)
|
|
agent = create_actor(tenant_id=user["tenant_id"], actor_type="agent", name="Bot Alpha")
|
|
|
|
res = client.get("/api/actors", headers=_auth(token))
|
|
assert res.status_code == 200
|
|
body = res.json()
|
|
ids = {a["id"] for a in body}
|
|
assert user["actor_id"] in ids
|
|
assert agent["id"] in ids
|
|
types = {a["actor_type"] for a in body}
|
|
assert "human" in types
|
|
assert "agent" in types
|
|
for actor in body:
|
|
assert "user_id" not in actor
|
|
|
|
|
|
def test_list_actors_filter_by_type(client):
|
|
user = provision_user_in_tenant(tenant_role="admin")
|
|
token = _login(client, user)
|
|
create_actor(tenant_id=user["tenant_id"], actor_type="agent", name="Filter Agent")
|
|
|
|
res = client.get("/api/actors?actor_type=agent", headers=_auth(token))
|
|
assert res.status_code == 200
|
|
assert all(a["actor_type"] == "agent" for a in res.json())
|
|
|
|
|
|
def test_list_actors_excludes_inactive_by_default(client):
|
|
user = provision_user_in_tenant(tenant_role="admin")
|
|
token = _login(client, user)
|
|
inactive = create_actor(
|
|
tenant_id=user["tenant_id"], actor_type="external_system", name="Legacy"
|
|
)
|
|
conn = __import__("db").get_connection()
|
|
try:
|
|
with conn.cursor() as cur:
|
|
cur.execute("UPDATE actors SET is_active = FALSE WHERE id = %s", (inactive["id"],))
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
res = client.get("/api/actors", headers=_auth(token))
|
|
assert inactive["id"] not in {a["id"] for a in res.json()}
|
|
|
|
res_active = client.get("/api/actors?include_inactive=true", headers=_auth(token))
|
|
assert inactive["id"] in {a["id"] for a in res_active.json()}
|
|
|
|
|
|
def test_get_actor_detail(client):
|
|
user = provision_user_in_tenant()
|
|
token = _login(client, user)
|
|
agent = create_actor(tenant_id=user["tenant_id"], actor_type="working_group", name="Team A")
|
|
|
|
res = client.get(f"/api/actors/{agent['id']}", headers=_auth(token))
|
|
assert res.status_code == 200
|
|
assert res.json()["name"] == "Team A"
|
|
assert res.json()["actor_type"] == "working_group"
|
|
|
|
|
|
def test_cross_tenant_actor_directory_isolation(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)
|
|
agent_a = create_actor(tenant_id=user_a["tenant_id"], actor_type="agent", name="Tenant A Agent")
|
|
|
|
list_b = client.get("/api/actors", headers=_auth(token_b)).json()
|
|
assert agent_a["id"] not in {a["id"] for a in list_b}
|
|
assert user_a["actor_id"] not in {a["id"] for a in list_b}
|
|
|
|
detail = client.get(f"/api/actors/{agent_a['id']}", headers=_auth(token_b))
|
|
assert detail.status_code == 404
|
|
|
|
|
|
def test_member_has_actor_and_workspace_capabilities(client):
|
|
member = provision_user_in_tenant(tenant_role="member", portal_role="user")
|
|
token = _login(client, member)
|
|
ctx = client.get("/api/me/context", headers=_auth(token)).json()
|
|
assert "kairo.actor.read" in ctx["capabilities"]
|
|
assert "kairo.workspace.read" in ctx["capabilities"]
|
|
|
|
|
|
def test_workspace_summary(client):
|
|
user = provision_user_in_tenant(tenant_role="member")
|
|
token = _login(client, user)
|
|
initiative_id = _create_initiative(client, token).json()["id"]
|
|
|
|
open_action = _create_action(
|
|
client,
|
|
token,
|
|
initiative_id,
|
|
assigned_actor_ids=[user["actor_id"]],
|
|
).json()
|
|
blocked = _create_action(
|
|
client,
|
|
token,
|
|
initiative_id,
|
|
title="Blocked",
|
|
assigned_actor_ids=[user["actor_id"]],
|
|
).json()
|
|
client.patch(
|
|
f"/api/actions/{blocked['id']}",
|
|
json={"status": "blocked"},
|
|
headers=_auth(token),
|
|
)
|
|
done = _create_action(
|
|
client,
|
|
token,
|
|
initiative_id,
|
|
title="Done",
|
|
assigned_actor_ids=[user["actor_id"]],
|
|
).json()
|
|
client.patch(
|
|
f"/api/actions/{done['id']}",
|
|
json={"status": "done"},
|
|
headers=_auth(token),
|
|
)
|
|
|
|
res = client.get("/api/workspace/summary", headers=_auth(token))
|
|
assert res.status_code == 200
|
|
summary = res.json()
|
|
assert summary["open_actions_count"] >= 1
|
|
assert summary["blocked_actions_count"] >= 1
|
|
assert summary["active_initiatives_count"] >= 1
|
|
assert summary["done_actions_recent_count"] >= 1
|
|
assert open_action["id"] # used
|
|
|
|
|
|
def test_workspace_open_and_blocked_actions(client):
|
|
user = provision_user_in_tenant(tenant_role="member")
|
|
token = _login(client, user)
|
|
initiative_id = _create_initiative(client, token).json()["id"]
|
|
|
|
open_id = _create_action(
|
|
client,
|
|
token,
|
|
initiative_id,
|
|
title="Open",
|
|
assigned_actor_ids=[user["actor_id"]],
|
|
).json()["id"]
|
|
blocked_id = _create_action(
|
|
client,
|
|
token,
|
|
initiative_id,
|
|
title="Blocked",
|
|
assigned_actor_ids=[user["actor_id"]],
|
|
).json()["id"]
|
|
client.patch(
|
|
f"/api/actions/{blocked_id}",
|
|
json={"status": "blocked"},
|
|
headers=_auth(token),
|
|
)
|
|
|
|
open_res = client.get("/api/workspace/actions/open", headers=_auth(token))
|
|
blocked_res = client.get("/api/workspace/actions/blocked", headers=_auth(token))
|
|
assert open_res.status_code == 200
|
|
assert blocked_res.status_code == 200
|
|
assert open_id in {a["id"] for a in open_res.json()}
|
|
assert blocked_id in {a["id"] for a in blocked_res.json()}
|
|
assert blocked_id not in {a["id"] for a in open_res.json()}
|
|
|
|
|
|
def test_workspace_active_initiatives(client):
|
|
user = provision_user_in_tenant()
|
|
token = _login(client, user)
|
|
initiative_id = _create_initiative(client, token, title="Active One").json()["id"]
|
|
|
|
res = client.get("/api/workspace/initiatives/active?limit=5", headers=_auth(token))
|
|
assert res.status_code == 200
|
|
ids = {i["id"] for i in res.json()}
|
|
assert initiative_id in ids
|
|
|
|
|
|
def test_workspace_actor_workload(client):
|
|
user = provision_user_in_tenant(tenant_role="admin")
|
|
token = _login(client, user)
|
|
agent = create_actor(tenant_id=user["tenant_id"], actor_type="agent", name="Workload Bot")
|
|
initiative_id = _create_initiative(client, token).json()["id"]
|
|
_create_action(
|
|
client,
|
|
token,
|
|
initiative_id,
|
|
assigned_actor_ids=[agent["id"]],
|
|
)
|
|
|
|
res = client.get("/api/workspace/actors/workload", headers=_auth(token))
|
|
assert res.status_code == 200
|
|
workload = {row["actor_id"]: row for row in res.json()}
|
|
assert agent["id"] in workload
|
|
assert workload[agent["id"]]["open_actions"] >= 1
|
|
|
|
|
|
def test_cross_tenant_workspace_summary_isolation(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"]
|
|
_create_action(
|
|
client,
|
|
token_a,
|
|
initiative_id,
|
|
assigned_actor_ids=[user_a["actor_id"]],
|
|
)
|
|
|
|
summary_b = client.get("/api/workspace/summary", headers=_auth(token_b)).json()
|
|
assert summary_b["open_actions_count"] == 0
|
|
|
|
open_b = client.get("/api/workspace/actions/open", headers=_auth(token_b)).json()
|
|
assert open_b == []
|
|
|
|
|
|
def test_assign_action_to_tenant_actor(client):
|
|
user = provision_user_in_tenant(tenant_role="admin")
|
|
token = _login(client, user)
|
|
agent = create_actor(tenant_id=user["tenant_id"], actor_type="agent", name="Assignee")
|
|
initiative_id = _create_initiative(client, token).json()["id"]
|
|
action_id = _create_action(client, token, initiative_id).json()["id"]
|
|
|
|
res = client.put(
|
|
f"/api/actions/{action_id}/assignments",
|
|
json={"actor_ids": [agent["id"]]},
|
|
headers=_auth(token),
|
|
)
|
|
assert res.status_code == 200
|
|
assert res.json()["assigned_actor_ids"] == [agent["id"]]
|
|
|
|
|
|
def test_unauthenticated_workspace_rejected(client):
|
|
assert client.get("/api/workspace/summary").status_code == 401
|
|
assert client.get("/api/actors").status_code == 401
|