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
80 lines
3.1 KiB
Python
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
|