41 lines
1.5 KiB
Python
41 lines
1.5 KiB
Python
"""
|
|
Tests for the tenant routing layer (app/tenancy/routing.py).
|
|
|
|
The crown-jewel invariant of the multi-tenant design is data isolation: a
|
|
request bound to tenant A must never touch tenant B's database. Full isolation
|
|
proof requires two live tenant DBs and belongs in an integration suite (see
|
|
tests/README.md). What we CAN verify cheaply and deterministically here is the
|
|
other half of the contract:
|
|
|
|
"The routing layer is completely inert until a tenant is resolved."
|
|
|
|
If routing were NOT inert when MULTI_TENANT_ENABLED=false, the existing
|
|
single-tenant deployment would break. This test locks that guarantee in.
|
|
"""
|
|
|
|
from flask import g
|
|
|
|
|
|
def test_routing_is_inert_without_a_resolved_tenant(app):
|
|
"""With no g.tenant_engine set, db.session binds to the default engine."""
|
|
from app import db
|
|
with app.test_request_context('/'):
|
|
# Multi-tenancy disabled → the resolver never sets g.tenant_engine.
|
|
assert g.get('tenant_engine') is None
|
|
# …so get_bind() must fall through to the app's default engine.
|
|
assert db.session.get_bind() is db.engine
|
|
|
|
|
|
def test_resolved_tenant_engine_wins(app, monkeypatch):
|
|
"""When g.tenant_engine IS set, get_bind() returns it, not the default."""
|
|
from app import db
|
|
|
|
class _SentinelEngine:
|
|
"""Stand-in object; get_bind should return it verbatim when present."""
|
|
|
|
sentinel = _SentinelEngine()
|
|
|
|
with app.test_request_context('/'):
|
|
g.tenant_engine = sentinel
|
|
assert db.session.get_bind() is sentinel
|