""" Forms behavior — the genuine capability behind the `forms` probe. Proves the Pydantic binding exposes the same schema / validate / submit role contract as the Django adapter: subclassing `mizanForm` auto-registers `{name}.schema`, `{name}.validate`, `{name}.submit` with the matching `_meta["form_role"]`, the schema role emits typed field definitions, validate returns structured field errors, and submit validates then runs `on_submit_success` / `on_submit_failure`. """ from __future__ import annotations import pytest from mizan_core.registry import clear_registry, get_function from mizan_fastapi.forms import ( FormConfig, FormSubmitFail, FormSubmitPass, FormValidation, build_form_schema, get_forms, mizanForm, ) @pytest.fixture(autouse=True) def _clean(): clear_registry() yield clear_registry() def _make_contact_form(): class ContactForm(mizanForm): mizan = FormConfig(name="contact", title="Contact Us", submit_label="Send") name: str email: str message: str = "" def on_submit_success(self, request) -> dict: return {"sent": True, "to": self.email} return ContactForm def test_subclassing_registers_three_role_functions(): _make_contact_form() for role in ("schema", "validate", "submit"): fn = get_function(f"contact.{role}") assert fn is not None, f"contact.{role} not registered" assert fn._meta["form"] is True assert fn._meta["form_name"] == "contact" assert fn._meta["form_role"] == role def test_schema_role_emits_field_definitions(): form_cls = _make_contact_form() SchemaFn = get_function("contact.schema") schema = SchemaFn(request=None).call(None) assert schema.name == "contact" assert schema.title == "Contact Us" assert schema.submit_label == "Send" field_names = {f.name for f in schema.fields} assert field_names == {"name", "email", "message"} # `message` has a default → not required; `name`/`email` required by_name = {f.name: f for f in schema.fields} assert by_name["name"].required is True assert by_name["message"].required is False def test_build_form_schema_maps_types(): class TypedForm(mizanForm): mizan = FormConfig(name="typed") count: int ratio: float active: bool label: str schema = build_form_schema(TypedForm) by_name = {f.name: f for f in schema.fields} assert by_name["count"].type == "number" assert by_name["ratio"].type == "number" assert by_name["active"].type == "checkbox" assert by_name["label"].type == "text" def test_validate_role_passes_clean_data(): _make_contact_form() ValidateFn = get_function("contact.validate") ValidateInput = ValidateFn.Input out = ValidateFn(request=None).call(ValidateInput(data={"name": "Ryth", "email": "r@x.com"})) assert isinstance(out, FormValidation) assert out.errors == [] def test_validate_role_reports_field_errors(): _make_contact_form() ValidateFn = get_function("contact.validate") ValidateInput = ValidateFn.Input out = ValidateFn(request=None).call(ValidateInput(data={"email": "r@x.com"})) # missing 'name' error_fields = {e.field for e in out.errors} assert "name" in error_fields def test_submit_role_runs_on_submit_success(): _make_contact_form() SubmitFn = get_function("contact.submit") SubmitInput = SubmitFn.Input result = SubmitFn(request=None).call( SubmitInput(data={"name": "Ryth", "email": "ryth@example.com", "message": "hi"}) ) assert isinstance(result, FormSubmitPass) assert result.success is True assert result.data == {"sent": True, "to": "ryth@example.com"} def test_submit_role_returns_fail_on_invalid(): captured = {} class GuardedForm(mizanForm): mizan = FormConfig(name="guarded") name: str def on_submit_failure(self, request, errors) -> None: captured["errors"] = errors SubmitFn = get_function("guarded.submit") SubmitInput = SubmitFn.Input result = SubmitFn(request=None).call(SubmitInput(data={})) # missing required 'name' assert isinstance(result, FormSubmitFail) assert result.success is False assert any(e.field == "name" for e in result.errors.errors) # on_submit_failure hook fired with the validation assert "errors" in captured def test_get_forms_groups_by_form_name(): _make_contact_form() forms = get_forms() assert set(forms.keys()) == {"contact"} assert len(forms["contact"]) == 3