05/06/2026 Initial commit
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user