refactor(cleanup): remove dead GuestUser/GuestInvitation code and fix test imports

Deleted:
- app/models/guest_user.py
- app/models/guest_invitation.py
- app/routes/guest_auth.py (was orphaned, not imported)
- tests/test_guest_auth.py (tested removed guest auth system)

Modified:
- app/models/__init__.py: removed stale GuestUser/GuestInvitation comments
- app/services/entity_permission_service.py: updated guest permission comment
- tests/test_permission_system_live.py: replaced GuestUser with User+UserTenant(role=guest),
  changed principal_type from "guest" to "user", switched guest test from
  /api/v1/guest/login to regular /api/v1/auth/login endpoint

Frontend: no guest components found, nothing to clean up.
Alembic migrations: historical migrations referencing guest_users/guest_invitations
  tables are left intact (they document DB history).
This commit is contained in:
Agent Zero
2026-08-06 11:46:00 +02:00
parent a0c7a80381
commit 8060505baa
7 changed files with 14 additions and 376 deletions
-75
View File
@@ -1,75 +0,0 @@
"""Tests for guest authentication — login, logout, tenant isolation.
Security-critical: Guest users must only access their assigned tenant.
Cross-tenant enumeration must be prevented.
"""
from __future__ import annotations
import pytest
from httpx import AsyncClient
from tests.conftest import ORIGIN_HEADER, seed_tenant_and_users, login_client
@pytest.mark.asyncio
class TestGuestAuth:
"""Guest authentication and authorization tests."""
async def test_guest_login_requires_tenant_slug(self, client: AsyncClient, db_session):
"""Guest login without tenant_slug should return 422 (validation error)."""
resp = await client.post(
"/api/v1/guest/login",
json={"email": "guest@test.de", "password": "TestPass123!"},
headers=ORIGIN_HEADER,
)
# Pydantic validation error — tenant_slug is required
assert resp.status_code == 422
async def test_guest_login_invalid_tenant_returns_401(self, client: AsyncClient, db_session):
"""Guest login with non-existent tenant_slug returns 401."""
resp = await client.post(
"/api/v1/guest/login",
json={"email": "guest@test.de", "password": "TestPass123!", "tenant_slug": "nonexistent"},
headers=ORIGIN_HEADER,
)
assert resp.status_code == 401
async def test_guest_login_invalid_credentials(self, client: AsyncClient, db_session):
"""Guest login with valid tenant but wrong password returns 401."""
# Seed data
seed = await seed_tenant_and_users(db_session)
resp = await client.post(
"/api/v1/guest/login",
json={"email": "nobody@test.de", "password": "WrongPass!", "tenant_slug": "tenant-a"},
headers=ORIGIN_HEADER,
)
assert resp.status_code == 401
async def test_guest_login_valid_email_format_validation(self, client: AsyncClient, db_session):
"""Guest login with invalid email format returns 422."""
resp = await client.post(
"/api/v1/guest/login",
json={"email": "not-an-email", "password": "TestPass123!", "tenant_slug": "tenant-a"},
headers=ORIGIN_HEADER,
)
assert resp.status_code == 422
async def test_guest_login_missing_password(self, client: AsyncClient, db_session):
"""Guest login with empty password returns 401."""
resp = await client.post(
"/api/v1/guest/login",
json={"email": "guest@test.de", "password": "", "tenant_slug": "tenant-a"},
headers=ORIGIN_HEADER,
)
assert resp.status_code in (401, 422)
async def test_guest_logout_without_session(self, client: AsyncClient, db_session):
"""Guest logout without active session returns 200 (idempotent)."""
resp = await client.post(
"/api/v1/guest/logout",
headers=ORIGIN_HEADER,
)
# Guest logout is idempotent — returns 200 even without session
assert resp.status_code in (200, 401, 403, 400)
+12 -25
View File
@@ -47,7 +47,6 @@ from app.models.role import Role
from app.models.tenant import Tenant
from app.models.user import User, UserTenant
from app.models.entity_permission import EntityPermission
from app.models.guest_user import GuestUser
from app.models.group import Group, UserGroup
from app.plugins.registry import reset_registry_for_testing # noqa: F401
from app.services.plugin_service import reset_plugin_service_for_testing # noqa: F401
@@ -262,13 +261,13 @@ async def seed_full_data(db: AsyncSession) -> dict[str, Any]:
is_active=True,
preferences={},
)
# Guest user in tenant A
guest_a = GuestUser(
# Guest user in tenant A (now a regular User with role=guest)
guest_a = User(
email="guest@tenanta.com",
name="Guest A",
password_hash=hash_password("TestPass123!"),
tenant_id=tenant_a.id,
status="active",
is_active=True,
preferences={},
)
db.add_all([admin_a, editor_a, viewer_a, admin_b, orphan_user, guest_a])
await db.flush()
@@ -280,7 +279,9 @@ async def seed_full_data(db: AsyncSession) -> dict[str, Any]:
ut4 = UserTenant(user_id=admin_b.id, tenant_id=tenant_b.id, is_default=True, role="admin")
# Admin A is also member of tenant B (multi-tenant)
ut5 = UserTenant(user_id=admin_a.id, tenant_id=tenant_b.id, is_default=False, role="admin")
db.add_all([ut1, ut2, ut3, ut4, ut5])
# Guest tenant membership with role=guest
ut6 = UserTenant(user_id=guest_a.id, tenant_id=tenant_a.id, is_default=True, role="guest")
db.add_all([ut1, ut2, ut3, ut4, ut5, ut6])
await db.flush()
# Custom role with limited permissions in tenant A
@@ -368,7 +369,7 @@ async def seed_full_data(db: AsyncSession) -> dict[str, Any]:
tenant_id=tenant_a.id,
entity_type="contact",
entity_id=contact_a1.id,
principal_type="guest",
principal_type="user",
principal_id=guest_a.id,
permission_level="read",
created_by=admin_a.id,
@@ -862,26 +863,12 @@ async def test_scenario_guest_access(client: AsyncClient, db_session: AsyncSessi
"""Guest user can only see entities shared with them."""
seed = await seed_full_data(db_session)
# Guest login
resp = await client.post(
"/api/v1/guest/login",
json={
"email": "guest@tenanta.com",
"password": "TestPass123!",
"tenant_slug": "tenant-a",
},
headers=ORIGIN_HEADER,
)
assert resp.status_code == 200, f"Guest login failed: {resp.status_code} {resp.text}"
# Guest login (now uses regular auth endpoint)
await login(client, "guest@tenanta.com", tenant_slug="tenant-a")
# Guest should be able to access shared contact (contact_a1 shared with guest)
# Note: guest endpoints may differ from regular contact endpoints
# Test guest-specific contact access if available
# If no guest contact endpoint exists, verify guest session is valid
guest_session = resp.json()
assert guest_session.get("guest_user_id") or guest_session.get("user_id"), (
f"Guest session should have user ID: {guest_session}"
)
resp = await client.get("/api/v1/contacts")
assert resp.status_code == 200, f"Guest contacts access failed: {resp.status_code} {resp.text}"
@pytest.mark.asyncio