Close the remaining places where an extension point was hardcoded, and expose a stable public import surface for every app. - files: register_storage_backend(...) / STORAGE_BACKENDS[name]["CLASS"]; unknown backends now fail loudly instead of silently using local - files: PipelineStepRegistry + @pipeline_step; PIPELINE_EXECUTOR setting - billing: INVOICE_PDF_BUILDER setting - security: TWO_FACTOR_SERVICE / TWO_FACTOR_RECOVERY_SERVICE settings - all app packages expose lazy public exports (PEP 562); infrasynth.shared re-exports its primitives eagerly - README documents the per-module extension-point table - tests/test_extensibility.py pins each hook plus the public surface
194 lines
6.3 KiB
Python
194 lines
6.3 KiB
Python
"""Every module must be extensible without editing the kit.
|
|
|
|
These tests pin the extension points that let a pip-installed consumer add
|
|
storage backends, pipeline steps, an invoice builder, or a 2FA method purely
|
|
through settings/registries, and confirm the public import surface.
|
|
"""
|
|
|
|
import io
|
|
|
|
import pytest
|
|
from django.core.exceptions import ImproperlyConfigured
|
|
|
|
pytestmark = pytest.mark.django_db
|
|
|
|
|
|
# --- fakes referenced by dotted path from settings --------------------------
|
|
|
|
|
|
class FakeStorage:
|
|
def __init__(self, config):
|
|
self.config = config
|
|
self.saved = {}
|
|
|
|
def save(self, name, content):
|
|
self.saved[name] = content.read()
|
|
return name
|
|
|
|
def open(self, name, mode="rb"):
|
|
return io.BytesIO(self.saved[name])
|
|
|
|
def exists(self, name):
|
|
return name in self.saved
|
|
|
|
def url(self, name):
|
|
return f"/fake/{name}"
|
|
|
|
def delete(self, name):
|
|
self.saved.pop(name, None)
|
|
|
|
def generate_signed_url(self, name, expiry_seconds=3600):
|
|
return f"/fake/{name}?signed=1"
|
|
|
|
|
|
class RecordingExecutor:
|
|
def execute(self, execution):
|
|
execution.status = "completed"
|
|
execution.save(update_fields=["status"])
|
|
return execution
|
|
|
|
|
|
def fake_invoice_builder(invoice):
|
|
return b"%PDF-1.4 custom-builder"
|
|
|
|
|
|
class FakeTwoFactorService:
|
|
def generate_secret(self):
|
|
return "FAKESECRET"
|
|
|
|
|
|
class TestPublicImportSurface:
|
|
def test_app_packages_expose_public_names(self):
|
|
from infrasynth.audit import ModelChangeLog
|
|
from infrasynth.billing import EntitlementService, Plan
|
|
from infrasynth.features import FeatureService
|
|
from infrasynth.files import FileService, get_storage_backend
|
|
from infrasynth.gates import GatePermission
|
|
from infrasynth.notifications import NotificationService
|
|
from infrasynth.scheduler import TaskService
|
|
from infrasynth.security import AuthorizationService, HybridPermission
|
|
from infrasynth.shared import Result
|
|
from infrasynth.tenancy import TenantManager
|
|
from infrasynth.webhooks import EventRegistry
|
|
from infrasynth.workflows import WorkflowEngine
|
|
|
|
assert all(
|
|
x is not None
|
|
for x in (
|
|
ModelChangeLog,
|
|
EntitlementService,
|
|
Plan,
|
|
FeatureService,
|
|
FileService,
|
|
get_storage_backend,
|
|
NotificationService,
|
|
TaskService,
|
|
AuthorizationService,
|
|
HybridPermission,
|
|
Result,
|
|
TenantManager,
|
|
EventRegistry,
|
|
WorkflowEngine,
|
|
GatePermission,
|
|
)
|
|
)
|
|
|
|
def test_unknown_attribute_raises(self):
|
|
import infrasynth.security as security
|
|
|
|
with pytest.raises(AttributeError):
|
|
security.ThisDoesNotExist
|
|
|
|
|
|
class TestStorageExtension:
|
|
def test_custom_backend_via_settings_class(self, settings):
|
|
settings.INFRASYNTH_FILES = {
|
|
**settings.INFRASYNTH_FILES,
|
|
"STORAGE_BACKENDS": {
|
|
**settings.INFRASYNTH_FILES.get("STORAGE_BACKENDS", {}),
|
|
"fake": {"CLASS": "tests.test_extensibility.FakeStorage", "REGION": "x"},
|
|
},
|
|
}
|
|
storage = get_backend("fake")
|
|
assert isinstance(storage, FakeStorage)
|
|
assert storage.config.get("REGION") == "x"
|
|
|
|
def test_custom_backend_via_registry(self):
|
|
from infrasynth.files.storage import register_storage_backend
|
|
|
|
register_storage_backend("factory-made", FakeStorage)
|
|
assert isinstance(get_backend("factory-made"), FakeStorage)
|
|
|
|
def test_unknown_backend_raises(self):
|
|
from infrasynth.files.storage import get_storage_backend
|
|
|
|
with pytest.raises(ImproperlyConfigured):
|
|
get_storage_backend("does-not-exist")
|
|
|
|
|
|
class TestPipelineExtension:
|
|
def test_registered_step_is_used(self, settings, user, media_root):
|
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
|
|
|
from infrasynth.files.models import PipelineExecution, ProcessingPipeline
|
|
from infrasynth.files.processing import PipelineExecutor, pipeline_step
|
|
from infrasynth.files.services import FileService
|
|
|
|
calls = []
|
|
|
|
@pipeline_step("test.tag")
|
|
def tag_step(data, mime_type, params):
|
|
calls.append(params)
|
|
return data, mime_type
|
|
|
|
uploaded = FileService().upload(
|
|
SimpleUploadedFile("a.txt", b"hello", content_type="text/plain"),
|
|
filename="a.txt",
|
|
user=user,
|
|
)
|
|
pipeline = ProcessingPipeline.objects.create(
|
|
name="Tag", slug="tag", steps=[{"type": "test.tag", "params": {"k": "v"}}]
|
|
)
|
|
execution = PipelineExecution.objects.create(file=uploaded, pipeline=pipeline)
|
|
PipelineExecutor().execute(execution)
|
|
|
|
execution.refresh_from_db()
|
|
assert execution.status == PipelineExecution.Status.COMPLETED
|
|
assert calls == [{"k": "v"}]
|
|
|
|
def test_executor_is_swappable(self, settings):
|
|
from infrasynth.files.processing import get_pipeline_executor
|
|
|
|
settings.INFRASYNTH_FILES = {
|
|
**settings.INFRASYNTH_FILES,
|
|
"PIPELINE_EXECUTOR": "tests.test_extensibility.RecordingExecutor",
|
|
}
|
|
assert isinstance(get_pipeline_executor(), RecordingExecutor)
|
|
|
|
|
|
class TestInvoiceBuilderExtension:
|
|
def test_builder_is_swappable(self, settings):
|
|
from infrasynth.billing.invoice_generator import get_invoice_pdf_builder
|
|
|
|
settings.INFRASYNTH_BILLING = {
|
|
**settings.INFRASYNTH_BILLING,
|
|
"INVOICE_PDF_BUILDER": "tests.test_extensibility.fake_invoice_builder",
|
|
}
|
|
assert get_invoice_pdf_builder()(object()) == b"%PDF-1.4 custom-builder"
|
|
|
|
|
|
class TestTwoFactorExtension:
|
|
def test_service_is_swappable(self, settings):
|
|
from infrasynth.security.two_factor.services import get_two_factor_service
|
|
|
|
settings.INFRASYNTH_SECURITY = {
|
|
**settings.INFRASYNTH_SECURITY,
|
|
"TWO_FACTOR_SERVICE": "tests.test_extensibility.FakeTwoFactorService",
|
|
}
|
|
assert get_two_factor_service().generate_secret() == "FAKESECRET"
|
|
|
|
|
|
def get_backend(name):
|
|
from infrasynth.files.storage import get_storage_backend
|
|
|
|
return get_storage_backend(name)
|