|
5 | 5 | classes from introspected Django model metadata. |
6 | 6 | """ |
7 | 7 |
|
| 8 | +import pytest |
8 | 9 | from tortoise import models as tortoise_models |
9 | 10 |
|
10 | | -from django_tortoise.generator import generate_tortoise_model |
11 | | -from django_tortoise.introspection import ModelInfo, introspect_model |
| 11 | +from django_tortoise import generator |
| 12 | +from django_tortoise.generator import generate_tortoise_model, generate_tortoise_model_full |
| 13 | +from django_tortoise.introspection import FieldInfo, ModelInfo, introspect_model |
| 14 | + |
| 15 | + |
| 16 | +def _make_field_info(**overrides) -> FieldInfo: |
| 17 | + """Helper to create FieldInfo with sensible defaults.""" |
| 18 | + defaults = dict( |
| 19 | + name="test_field", |
| 20 | + internal_type="CharField", |
| 21 | + column="test_field", |
| 22 | + primary_key=False, |
| 23 | + null=False, |
| 24 | + unique=False, |
| 25 | + has_default=False, |
| 26 | + default=None, |
| 27 | + max_length=100, |
| 28 | + max_digits=None, |
| 29 | + decimal_places=None, |
| 30 | + db_index=False, |
| 31 | + choices=None, |
| 32 | + enum_type=None, |
| 33 | + is_relation=False, |
| 34 | + related_model=None, |
| 35 | + related_model_label=None, |
| 36 | + on_delete=None, |
| 37 | + related_name=None, |
| 38 | + is_self_referential=False, |
| 39 | + many_to_many=False, |
| 40 | + through_model=None, |
| 41 | + through_db_table=None, |
| 42 | + is_auto_field=False, |
| 43 | + django_field=None, |
| 44 | + ) |
| 45 | + defaults.update(overrides) |
| 46 | + return FieldInfo(**defaults) |
12 | 47 |
|
13 | 48 |
|
14 | 49 | class TestGenerateBasicModel: |
@@ -256,3 +291,51 @@ def test_unique_together_with_all_data_fields(self): |
256 | 291 | assert tortoise_model is not None |
257 | 292 | assert hasattr(tortoise_model.Meta, "unique_together") |
258 | 293 | assert ("a", "b") in tortoise_model.Meta.unique_together |
| 294 | + |
| 295 | + |
| 296 | +class TestModelCreationErrorContext: |
| 297 | + """Errors during model creation include model/field context.""" |
| 298 | + |
| 299 | + @pytest.fixture() |
| 300 | + def failing_model_info(self): |
| 301 | + fi = _make_field_info( |
| 302 | + name="id", |
| 303 | + internal_type="AutoField", |
| 304 | + column="id", |
| 305 | + primary_key=True, |
| 306 | + is_auto_field=True, |
| 307 | + max_length=None, |
| 308 | + ) |
| 309 | + return ModelInfo( |
| 310 | + model_class=type("MyModel", (), {}), |
| 311 | + app_label="myapp", |
| 312 | + model_name="mymodel", |
| 313 | + db_table="myapp_mymodel", |
| 314 | + fields=[fi], |
| 315 | + unique_together=[], |
| 316 | + is_abstract=False, |
| 317 | + is_proxy=False, |
| 318 | + is_managed=True, |
| 319 | + pk_name="id", |
| 320 | + ) |
| 321 | + |
| 322 | + @pytest.fixture() |
| 323 | + def patch_failing_model(self, monkeypatch): |
| 324 | + """Replace tortoise_models.Model with a class whose metaclass raises.""" |
| 325 | + |
| 326 | + class FailingModel: |
| 327 | + def __init_subclass__(cls, **kwargs): |
| 328 | + raise RuntimeError("simulated metaclass error") |
| 329 | + |
| 330 | + fake_module = type("FakeModule", (), {"Model": FailingModel})() |
| 331 | + monkeypatch.setattr(generator, "tortoise_models", fake_module) |
| 332 | + |
| 333 | + def test_error_includes_model_context(self, failing_model_info, patch_failing_model): |
| 334 | + """When type() raises during model creation, the error includes model context.""" |
| 335 | + with pytest.raises(RuntimeError, match=r"myapp\.mymodel"): |
| 336 | + generate_tortoise_model(failing_model_info) |
| 337 | + |
| 338 | + def test_error_includes_model_context_full(self, failing_model_info, patch_failing_model): |
| 339 | + """generate_tortoise_model_full() also includes model context on error.""" |
| 340 | + with pytest.raises(RuntimeError, match=r"myapp\.mymodel"): |
| 341 | + generate_tortoise_model_full(failing_model_info) |
0 commit comments