Some checks failed
Deploy Development / deploy (push) Successful in 45s
Test Suite / pytest-backend (push) Failing after 3m33s
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
list_compatible_methods nur Primary-Methoden; Agile-Graph auf Sprint-Scope; Tests ohne Starter-Action-Kollision; TenantContext-Factory fuer Kernel-Tests. Co-authored-by: Cursor <cursoragent@cursor.com>
140 lines
4.2 KiB
Python
140 lines
4.2 KiB
Python
"""Test data factories (PostgreSQL required)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
|
|
from auth import hash_password
|
|
from db import get_connection
|
|
from services.actors import create_actor
|
|
|
|
|
|
def create_tenant(*, slug: str | None = None, name: str = "Test Tenant") -> str:
|
|
slug = slug or f"t-{uuid.uuid4().hex[:10]}"
|
|
conn = get_connection()
|
|
try:
|
|
with conn.cursor() as cur:
|
|
cur.execute(
|
|
"INSERT INTO tenants (slug, name) VALUES (%s, %s) RETURNING id",
|
|
(slug, name),
|
|
)
|
|
tenant_id = str(cur.fetchone()[0])
|
|
conn.commit()
|
|
return tenant_id
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def create_user(
|
|
*,
|
|
email: str | None = None,
|
|
password: str = "test-password-123",
|
|
display_name: str = "Test User",
|
|
portal_role: str = "user",
|
|
) -> dict:
|
|
email = email or f"user-{uuid.uuid4().hex[:8]}@example.com"
|
|
conn = get_connection()
|
|
try:
|
|
with conn.cursor() as cur:
|
|
cur.execute(
|
|
"""
|
|
INSERT INTO users (email, password_hash, display_name, portal_role)
|
|
VALUES (%s, %s, %s, %s)
|
|
RETURNING id, email, display_name, portal_role
|
|
""",
|
|
(email, hash_password(password), display_name, portal_role),
|
|
)
|
|
row = cur.fetchone()
|
|
conn.commit()
|
|
return {
|
|
"id": str(row[0]),
|
|
"email": row[1],
|
|
"display_name": row[2],
|
|
"portal_role": row[3],
|
|
"password": password,
|
|
}
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def add_membership(*, tenant_id: str, user_id: str, tenant_role: str = "member") -> None:
|
|
conn = get_connection()
|
|
try:
|
|
with conn.cursor() as cur:
|
|
cur.execute(
|
|
"""
|
|
INSERT INTO tenant_memberships (tenant_id, user_id, tenant_role)
|
|
VALUES (%s, %s, %s)
|
|
ON CONFLICT (tenant_id, user_id) DO UPDATE SET tenant_role = EXCLUDED.tenant_role
|
|
""",
|
|
(tenant_id, user_id, tenant_role),
|
|
)
|
|
conn.commit()
|
|
finally:
|
|
conn.close()
|
|
|
|
|
|
def tenant_context_from_user(user: dict):
|
|
"""Build TenantContext for direct service/kernel calls in tests."""
|
|
from psycopg2.extras import RealDictCursor
|
|
|
|
from rights_registry import load_grants_for_roles
|
|
from tenant_context import TenantContext
|
|
|
|
conn = get_connection()
|
|
try:
|
|
with conn.cursor(cursor_factory=RealDictCursor) as cur:
|
|
cur.execute(
|
|
"""
|
|
SELECT t.id, t.slug, t.name, tm.tenant_role
|
|
FROM tenant_memberships tm
|
|
JOIN tenants t ON t.id = tm.tenant_id
|
|
WHERE tm.user_id = %s AND tm.tenant_id = %s
|
|
""",
|
|
(user["id"], user["tenant_id"]),
|
|
)
|
|
membership = cur.fetchone()
|
|
if not membership:
|
|
raise ValueError("Test user has no tenant membership")
|
|
finally:
|
|
conn.close()
|
|
|
|
tenant_role = membership["tenant_role"]
|
|
caps = frozenset(
|
|
load_grants_for_roles(
|
|
portal_role=user["portal_role"],
|
|
tenant_role=tenant_role,
|
|
)
|
|
)
|
|
return TenantContext(
|
|
user_id=user["id"],
|
|
email=user["email"],
|
|
display_name=user["display_name"],
|
|
portal_role=user["portal_role"],
|
|
tenant_id=str(membership["id"]),
|
|
tenant_slug=membership["slug"],
|
|
tenant_name=membership["name"],
|
|
tenant_role=tenant_role,
|
|
actor_id=user.get("actor_id"),
|
|
actor_type="human" if user.get("actor_id") else None,
|
|
session_token="test-session",
|
|
capabilities=caps,
|
|
)
|
|
|
|
|
|
def provision_user_in_tenant(
|
|
*,
|
|
tenant_role: str = "member",
|
|
portal_role: str = "user",
|
|
) -> dict:
|
|
tenant_id = create_tenant()
|
|
user = create_user(portal_role=portal_role)
|
|
add_membership(tenant_id=tenant_id, user_id=user["id"], tenant_role=tenant_role)
|
|
actor = create_actor(
|
|
tenant_id=tenant_id,
|
|
actor_type="human",
|
|
name=user["display_name"],
|
|
user_id=user["id"],
|
|
)
|
|
return {**user, "tenant_id": tenant_id, "actor_id": actor["id"]}
|