infrasynth-backend-kit/tests/test_tenancy/test_managers.py
jcv-dev 551b42eab5 feat: production-hardening pass across the kit
Close the gaps between the documented contract (API-STANDARD, TENANCY,
ENTITLEMENTS) and the implementation, and remove committed build artifacts.

Security:
- verify + process inbound webhooks (HMAC/handler verify, size limit,
  timestamp tolerance, idempotency via InboundEvent.external_id)
- real 2FA login flow (pre-auth challenge; tokens only after verify/recovery)
- wire HybridPermission into security/audit views; add API-key rotate and
  users/<id>/permissions|roles endpoints
- tenant-scoped throttling on by default; webhook replay protection
- verify MercadoPago webhook signatures
- login brute-force guard, configurable password policy, real ALTCHA PoW

Correctness:
- apply verified billing webhooks idempotently (subscription/entitlement/
  invoice/PaymentTransaction); scheduled payment lifecycle jobs
- capture audit update diffs automatically; add audit retention purge
- working notification retries, per-channel rate limits, log retention
- pluggable virus scanner, upload-size limit, pipeline toggle
- feature rollout %/environment targeting; settings-driven registrations
- workflow guards (instance cap, route depth, self-assignment, clone on re-entry)
- wire every previously-dead INFRASYNTH_* setting; drop truly dead ones

Delivery:
- README + CHANGELOG; CI format check + coverage gate
- keep test media out of the tree; untrack .coverage, __pycache__,
  egg-info, docs/ and invoice artifacts
2026-09-24 10:41:21 -05:00

80 lines
3.1 KiB
Python

"""Tenant-scoped manager behaviour (TENANCY.md §4)."""
import pytest
from infrasynth.features.models import FeatureFlag
from infrasynth.scheduler.models import ScheduledTask
from infrasynth.tenancy.context import tenant_context
from infrasynth.tenancy.models import Tenant
pytestmark = pytest.mark.django_db
def _task(tenant, name):
return ScheduledTask.all_objects.create(
tenant=tenant,
name=name,
task_path="tests.helpers.noop_task",
schedule_type="manual",
)
class TestTenantManager:
def test_fail_closed_without_context(self, tenant):
_task(tenant, "a")
with tenant_context(None):
assert ScheduledTask.objects.count() == 0
assert list(ScheduledTask.objects.all()) == []
def test_scopes_to_current_tenant(self, tenant):
other = Tenant.objects.create(slug="other", name="Other")
_task(tenant, "a")
_task(other, "b")
with tenant_context(tenant):
assert list(ScheduledTask.objects.values_list("name", flat=True)) == ["a"]
with tenant_context(other):
assert list(ScheduledTask.objects.values_list("name", flat=True)) == ["b"]
def test_cross_tenant_get_raises_does_not_exist(self, tenant):
other = Tenant.objects.create(slug="other", name="Other")
_task(tenant, "a")
with tenant_context(other), pytest.raises(ScheduledTask.DoesNotExist):
ScheduledTask.objects.get(name="a")
def test_unsafe_all_sees_every_tenant(self, tenant):
other = Tenant.objects.create(slug="other", name="Other")
_task(tenant, "a")
_task(other, "b")
with tenant_context(None):
assert ScheduledTask.objects.unsafe_all().count() == 2
def test_save_auto_assigns_current_tenant(self, tenant):
with tenant_context(tenant):
task = ScheduledTask.objects.create(
name="auto",
task_path="tests.helpers.noop_task",
schedule_type="manual",
)
assert task.tenant_id == tenant.id
class TestGlobalOrTenantManager:
def test_exposes_global_plus_override_only(self, tenant):
other = Tenant.objects.create(slug="other", name="Other")
FeatureFlag.all_objects.create(slug="f", is_active=True, tenant=None)
FeatureFlag.all_objects.create(slug="f", is_active=False, tenant=tenant)
FeatureFlag.all_objects.create(slug="f", is_active=True, tenant=other)
with tenant_context(tenant):
assert FeatureFlag.objects.filter(slug="f").count() == 2 # global + own override
resolved = FeatureFlag.objects.resolve(slug="f")
assert resolved is not None and resolved.is_active is False
with tenant_context(other):
assert FeatureFlag.objects.filter(slug="f").count() == 2
def test_resolve_falls_back_to_global(self, tenant):
FeatureFlag.all_objects.create(slug="only_global", is_active=True, tenant=None)
with tenant_context(tenant):
resolved = FeatureFlag.objects.resolve(slug="only_global")
assert resolved is not None and resolved.tenant_id is None