82 lines
2.8 KiB
Python
82 lines
2.8 KiB
Python
"""
|
|
tests/test_tenancy_isolation.py
|
|
Verifies that tenant data isolation rules are enforced —
|
|
one tenant's data must never be accessible from another tenant's session.
|
|
"""
|
|
|
|
import pytest
|
|
from app.extensions import db, bcrypt as _bcrypt
|
|
from app.models.platform import Tenant, Plan
|
|
from app.models.salon import User, Customer, Location
|
|
|
|
|
|
def _create_tenant_with_user(app, slug, email, customer_name):
|
|
with app.app_context():
|
|
plan = Plan.query.first()
|
|
tenant = Tenant(
|
|
slug=slug,
|
|
name=slug,
|
|
owner_email=email,
|
|
plan_id=plan.id,
|
|
status="active",
|
|
)
|
|
db.session.add(tenant)
|
|
db.session.flush()
|
|
|
|
loc = Location(
|
|
tenant_id=tenant.id, name="Main", is_primary=True, is_active=True
|
|
)
|
|
db.session.add(loc)
|
|
|
|
user = User(
|
|
tenant_id=tenant.id,
|
|
email=email,
|
|
password_hash=_bcrypt.generate_password_hash("IsolationPass1").decode("utf-8"),
|
|
role="tenant_admin",
|
|
is_active=True,
|
|
)
|
|
db.session.add(user)
|
|
db.session.flush()
|
|
|
|
customer = Customer(
|
|
tenant_id=tenant.id,
|
|
name=customer_name,
|
|
phone=f"555-{tenant.id:04d}",
|
|
)
|
|
db.session.add(customer)
|
|
db.session.commit()
|
|
return tenant.id, customer.id
|
|
|
|
|
|
class TestTenancyIsolation:
|
|
def test_customer_belongs_to_own_tenant(self, tenant_app):
|
|
"""Querying customers filtered by tenant_id returns only that tenant's data."""
|
|
t1_id, c1_id = _create_tenant_with_user(
|
|
tenant_app, "iso-tenant-1", "owner1@iso.com", "Alice"
|
|
)
|
|
t2_id, c2_id = _create_tenant_with_user(
|
|
tenant_app, "iso-tenant-2", "owner2@iso.com", "Bob"
|
|
)
|
|
with tenant_app.app_context():
|
|
t1_customers = Customer.query.filter_by(tenant_id=t1_id).all()
|
|
t2_customers = Customer.query.filter_by(tenant_id=t2_id).all()
|
|
|
|
t1_ids = {c.id for c in t1_customers}
|
|
t2_ids = {c.id for c in t2_customers}
|
|
|
|
assert c1_id in t1_ids
|
|
assert c2_id not in t1_ids
|
|
assert c2_id in t2_ids
|
|
assert c1_id not in t2_ids
|
|
|
|
def test_no_cross_tenant_customer_names(self, tenant_app):
|
|
"""Alice should not appear in tenant 2's query results."""
|
|
with tenant_app.app_context():
|
|
t1 = Tenant.query.filter_by(slug="iso-tenant-1").first()
|
|
t2 = Tenant.query.filter_by(slug="iso-tenant-2").first()
|
|
if t1 and t2:
|
|
t1_names = {c.name for c in Customer.query.filter_by(tenant_id=t1.id).all()}
|
|
t2_names = {c.name for c in Customer.query.filter_by(tenant_id=t2.id).all()}
|
|
assert "Alice" not in t2_names
|
|
assert "Bob" not in t1_names
|