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:
@@ -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)
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user