Kairo-Jinkendo/backend/tests/factories.py
Lars 0443b734c1
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
fix(tests): pytest nach Kernel Spine und Starter-Kit bereinigen
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>
2026-07-26 16:15:32 +02:00

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"]}