from unittest.mock import Mock import jsonschema from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models from servala.core.crd import generate_custom_form_class from servala.core.crd.forms import DEFAULT_FIELD_CONFIGS from servala.core.forms import ServiceDefinitionAdminForm from servala.core.models import ControlPlaneCRD def test_custom_model_form_class_returns_class_when_form_config_exists(): crd = Mock(spec=ControlPlaneCRD) service_def = Mock() service_def.form_config = { "fieldsets": [ { "title": "General", "fields": [ { "type": "text", "label": "Name", "controlplane_field_mapping": "display_name", "required": True, } ], } ] } crd.service_definition = service_def class TestModel(models.Model): display_name = models.CharField(max_length=100) class Meta: app_label = "test" crd.django_model = TestModel result = generate_custom_form_class( crd.service_definition.form_config, crd.django_model ) assert result is not None assert hasattr(result, "form_config") def test_form_config_schema_validates_minimal_config(): form = ServiceDefinitionAdminForm() schema = form.form_config_schema minimal_config = { "fieldsets": [ { "fields": [ { "type": "text", "label": "Service Name", "controlplane_field_mapping": "spec.serviceName", } ] } ] } jsonschema.validate(instance=minimal_config, schema=schema) def test_form_config_schema_validates_config_with_null_integers(): form = ServiceDefinitionAdminForm() schema = form.form_config_schema config_with_nulls = { "fieldsets": [ { "fields": [ { "type": "text", "label": "Service Name", "controlplane_field_mapping": "spec.serviceName", "max_length": None, "required": False, }, { "type": "textarea", "label": "Description", "controlplane_field_mapping": "spec.description", "rows": None, "max_length": None, }, { "type": "number", "label": "Port", "controlplane_field_mapping": "spec.port", "min_value": None, "max_value": None, }, { "type": "array", "label": "Tags", "controlplane_field_mapping": "spec.tags", "min_values": None, "max_values": None, }, ] } ] } jsonschema.validate(instance=config_with_nulls, schema=schema) def test_form_config_schema_validates_full_config(): form = ServiceDefinitionAdminForm() schema = form.form_config_schema full_config = { "fieldsets": [ { "title": "Service Configuration", "fields": [ { "type": "text", "label": "Service Name", "controlplane_field_mapping": "spec.serviceName", "help_text": "Enter a unique service name", "required": True, "max_length": 100, }, { "type": "email", "label": "Admin Email", "controlplane_field_mapping": "spec.adminEmail", "help_text": "Contact email for service administrator", "required": True, "max_length": 255, }, { "type": "textarea", "label": "Description", "controlplane_field_mapping": "spec.description", "help_text": "Describe the service purpose", "required": False, "rows": 5, "max_length": 500, }, { "type": "number", "label": "Port", "controlplane_field_mapping": "spec.port", "help_text": "Service port number", "required": True, "min_value": 1, "max_value": 65535, }, { "type": "choice", "label": "Environment", "controlplane_field_mapping": "spec.environment", "help_text": "Deployment environment", "required": True, "choices": [ ["dev", "Development"], ["staging", "Staging"], ["prod", "Production"], ], }, { "type": "checkbox", "label": "Enable Monitoring", "controlplane_field_mapping": "spec.monitoring.enabled", "help_text": "Enable service monitoring", "required": False, }, { "type": "array", "label": "Tags", "controlplane_field_mapping": "spec.tags", "help_text": "Service tags for organization", "required": False, "min_values": 0, "max_values": 10, }, ], } ] } jsonschema.validate(instance=full_config, schema=schema) def test_choice_field_uses_custom_choices_from_form_config(): """Test that choice fields use custom choices when provided in form_config""" class TestModel(models.Model): display_name = models.CharField(max_length=100) environment = models.CharField( max_length=20, choices=[ ("dev", "Development"), ("staging", "Staging"), ("prod", "Production"), ("test", "Testing"), ], ) class Meta: app_label = "test" form_config = { "fieldsets": [ { "title": "General", "fields": [ { "type": "text", "label": "Name", "controlplane_field_mapping": "display_name", "required": True, }, { "type": "choice", "label": "Environment", "controlplane_field_mapping": "environment", "required": True, "choices": [["dev", "Development"], ["prod", "Production"]], }, ], } ] } form_class = generate_custom_form_class(form_config, TestModel) form = form_class() environment_field = form.fields["environment"] assert list(environment_field.choices) == [ ("dev", "Development"), ("prod", "Production"), ] assert hasattr(environment_field, "_controlplane_choices") assert len(environment_field._controlplane_choices) == 5 # 4 choices + empty choice def test_choice_field_uses_control_plane_choices_when_no_custom_choices(): class TestModel(models.Model): display_name = models.CharField(max_length=100) environment = models.CharField( max_length=20, choices=[ ("dev", "Development"), ("staging", "Staging"), ("prod", "Production"), ], ) class Meta: app_label = "test" form_config = { "fieldsets": [ { "title": "General", "fields": [ { "type": "text", "label": "Name", "controlplane_field_mapping": "display_name", "required": True, }, { "type": "choice", "label": "Environment", "controlplane_field_mapping": "environment", "required": True, }, ], } ] } form_class = generate_custom_form_class(form_config, TestModel) form = form_class() environment_field = form.fields["environment"] choices_list = list(environment_field.choices) assert len(choices_list) == 4 # 3 choices + empty choice assert ("dev", "Development") in choices_list def test_choice_field_validates_against_control_plane_choices(): class TestModel(models.Model): display_name = models.CharField(max_length=100) environment = models.CharField( max_length=20, choices=[ ("dev", "Development"), ("staging", "Staging"), ("prod", "Production"), ], ) class Meta: app_label = "test" form_config = { "fieldsets": [ { "title": "General", "fields": [ { "type": "text", "label": "Name", "controlplane_field_mapping": "display_name", "required": True, }, { "type": "choice", "label": "Environment", "controlplane_field_mapping": "environment", "required": True, "choices": [["dev", "Development"], ["prod", "Production"]], }, ], } ] } form_class = generate_custom_form_class(form_config, TestModel) form = form_class(data={"display_name": "test-service", "environment": "dev"}) form.fields["context"].required = False # Skip context validation assert form.is_valid(), f"Form should be valid but has errors: {form.errors}" form = form_class(data={"display_name": "test-service", "environment": "prod"}) form.fields["context"].required = False # Skip context validation assert form.is_valid(), f"Form should be valid but has errors: {form.errors}" form = form_class(data={"display_name": "test-service", "environment": "invalid"}) form.fields["context"].required = False # Skip context validation assert not form.is_valid() assert "environment" in form.errors def test_admin_form_validates_choice_values_against_schema(): form = ServiceDefinitionAdminForm() mock_crd = Mock() mock_crd.resource_schema = { "properties": { "spec": { "properties": { "environment": { "type": "string", "enum": ["dev", "staging", "prod"], } } } } } valid_form_config = { "fieldsets": [ { "fields": [ { "type": "text", "label": "Name", "controlplane_field_mapping": "display_name", }, { "type": "choice", "label": "Environment", "controlplane_field_mapping": "spec.environment", "choices": [["dev", "Development"], ["prod", "Production"]], }, ] } ] } spec_schema = mock_crd.resource_schema["properties"]["spec"] errors = [] for field in valid_form_config["fieldsets"][0]["fields"]: if field.get("type") == "choice": form._validate_choice_field( field, field["controlplane_field_mapping"], spec_schema, "spec", errors ) assert len(errors) == 0, f"Expected no errors but got: {errors}" invalid_form_config = { "fieldsets": [ { "fields": [ { "type": "text", "label": "Name", "controlplane_field_mapping": "display_name", }, { "type": "choice", "label": "Environment", "controlplane_field_mapping": "spec.environment", "choices": [ ["dev", "Development"], ["invalid", "Invalid Environment"], ], }, ] } ] } errors = [] for field in invalid_form_config["fieldsets"][0]["fields"]: if field.get("type") == "choice": form._validate_choice_field( field, field["controlplane_field_mapping"], spec_schema, "spec", errors ) assert len(errors) > 0, "Expected validation errors but got none" error_message = str(errors[0]) assert "invalid" in error_message.lower() assert "Environment" in error_message def test_number_field_min_max_sets_widget_attributes(): class TestModel(models.Model): display_name = models.CharField(max_length=100) port = models.IntegerField() replica_count = models.IntegerField() class Meta: app_label = "test" form_config = { "fieldsets": [ { "title": "General", "fields": [ { "type": "text", "label": "Name", "controlplane_field_mapping": "display_name", "required": True, }, { "type": "number", "label": "Port", "controlplane_field_mapping": "port", "required": True, "min_value": 1, "max_value": 65535, }, { "type": "number", "label": "Replicas", "controlplane_field_mapping": "replica_count", "required": True, "min_value": 1, "max_value": 10, }, ], } ] } form_class = generate_custom_form_class(form_config, TestModel) form = form_class() port_field = form.fields["port"] assert port_field.widget.attrs.get("min") == 1 assert port_field.widget.attrs.get("max") == 65535 replica_field = form.fields["replica_count"] assert replica_field.widget.attrs.get("min") == 1 assert replica_field.widget.attrs.get("max") == 10 port_validators = port_field.validators assert any( isinstance(v, MinValueValidator) and v.limit_value == 1 for v in port_validators ) assert any( isinstance(v, MaxValueValidator) and v.limit_value == 65535 for v in port_validators ) def test_default_value_for_all_field_types(): class TestModel(models.Model): display_name = models.CharField(max_length=100) description = models.TextField() port = models.IntegerField() environment = models.CharField( max_length=20, choices=[ ("dev", "Development"), ("staging", "Staging"), ("prod", "Production"), ], ) monitoring_enabled = models.BooleanField() tags = models.JSONField() class Meta: app_label = "test" form_config = { "fieldsets": [ { "fields": [ { "type": "text", "label": "Name", "controlplane_field_mapping": "display_name", "default_value": "default-name", }, { "type": "textarea", "label": "Description", "controlplane_field_mapping": "description", "default_value": "Default description text", }, { "type": "number", "label": "Port", "controlplane_field_mapping": "port", "default_value": "8080", }, { "type": "choice", "label": "Environment", "controlplane_field_mapping": "environment", "default_value": "dev", }, { "type": "checkbox", "label": "Enable Monitoring", "controlplane_field_mapping": "monitoring_enabled", "default_value": "true", }, { "type": "array", "label": "Tags", "controlplane_field_mapping": "tags", "default_value": "tag1,tag2,tag3", }, ], } ] } form_class = generate_custom_form_class(form_config, TestModel) form = form_class() assert form.fields["display_name"].initial == "default-name" assert form.fields["description"].initial == "Default description text" assert form.fields["port"].initial == "8080" assert form.fields["environment"].initial == "dev" assert form.fields["monitoring_enabled"].initial == "true" assert form.fields["tags"].initial == "tag1,tag2,tag3" def test_default_value_not_override_existing_instance(): class TestModel(models.Model): display_name = models.CharField(max_length=100) port = models.IntegerField() class Meta: app_label = "test" form_config = { "fieldsets": [ { "fields": [ { "type": "text", "label": "Name", "controlplane_field_mapping": "display_name", "default_value": "default-name", }, { "type": "number", "label": "Port", "controlplane_field_mapping": "port", "default_value": "8080", }, ], } ] } instance = TestModel(display_name="existing-name", port=3000) form_class = generate_custom_form_class(form_config, TestModel) form = form_class(instance=instance) assert form.initial["display_name"] == "existing-name" assert form.initial["port"] == 3000 def test_form_config_coerces_string_numbers_to_integers(): form = ServiceDefinitionAdminForm() schema = form.form_config_schema config_with_string_numbers = { "fieldsets": [ { "fields": [ { "type": "text", "label": "Service Name", "controlplane_field_mapping": "spec.serviceName", "max_length": "64", # String instead of integer "required": True, }, { "type": "textarea", "label": "Description", "controlplane_field_mapping": "spec.description", "rows": "5", # String instead of integer "max_length": "500", # String instead of integer }, { "type": "number", "label": "Port", "controlplane_field_mapping": "spec.port", "min_value": "1", # String instead of integer "max_value": "65535", # String instead of integer }, { "type": "array", "label": "Tags", "controlplane_field_mapping": "spec.tags", "min_values": "0", # String instead of integer "max_values": "10", # String instead of integer }, ] } ] } normalized_config = form._normalize_form_config_types(config_with_string_numbers) fields = normalized_config["fieldsets"][0]["fields"] assert fields[0]["max_length"] == 64 assert isinstance(fields[0]["max_length"], int) assert fields[1]["rows"] == 5 assert isinstance(fields[1]["rows"], int) assert fields[1]["max_length"] == 500 assert isinstance(fields[1]["max_length"], int) assert fields[2]["min_value"] == 1 assert isinstance(fields[2]["min_value"], int) assert fields[2]["max_value"] == 65535 assert isinstance(fields[2]["max_value"], int) assert fields[3]["min_values"] == 0 assert isinstance(fields[3]["min_values"], int) assert fields[3]["max_values"] == 10 assert isinstance(fields[3]["max_values"], int) jsonschema.validate(instance=normalized_config, schema=schema) def test_form_config_handles_float_numbers(): form = ServiceDefinitionAdminForm() config_with_floats = { "fieldsets": [ { "fields": [ { "type": "number", "label": "Price", "controlplane_field_mapping": "spec.price", "min_value": "0.01", # String float "max_value": "999.99", # String float }, ] } ] } normalized_config = form._normalize_form_config_types(config_with_floats) field = normalized_config["fieldsets"][0]["fields"][0] assert field["min_value"] == 0.01 assert isinstance(field["min_value"], float) assert field["max_value"] == 999.99 assert isinstance(field["max_value"], float) def test_form_config_handles_empty_string_as_none(): form = ServiceDefinitionAdminForm() config_with_empty_strings = { "fieldsets": [ { "fields": [ { "type": "text", "label": "Name", "controlplane_field_mapping": "display_name", "max_length": "", # Empty string }, ] } ] } normalized_config = form._normalize_form_config_types(config_with_empty_strings) field = normalized_config["fieldsets"][0]["fields"][0] assert field["max_length"] is None def test_single_element_choices_are_normalized(): form = ServiceDefinitionAdminForm() mock_crd = Mock() mock_crd.resource_schema = { "properties": { "spec": { "properties": { "version": { "type": "string", "enum": ["6.2", "7.0", "7.2"], } } } } } config_with_single_choices = { "fieldsets": [ { "fields": [ { "type": "text", "label": "Name", "controlplane_field_mapping": "display_name", }, { "type": "choice", "label": "Version", "controlplane_field_mapping": "spec.version", "choices": [["6.2"]], # Single element - should be transformed }, ] } ] } spec_schema = mock_crd.resource_schema["properties"]["spec"] errors = [] for field in config_with_single_choices["fieldsets"][0]["fields"]: if field.get("type") == "choice": form._validate_choice_field( field, field["controlplane_field_mapping"], spec_schema, "spec", errors ) assert len(errors) == 0, f"Expected no errors but got: {errors}" version_field = config_with_single_choices["fieldsets"][0]["fields"][1] assert version_field["choices"] == [["6.2", "6.2"]] def test_two_element_choices_work_correctly(): form = ServiceDefinitionAdminForm() mock_crd = Mock() mock_crd.resource_schema = { "properties": { "spec": { "properties": { "version": { "type": "string", "enum": ["6.2", "7.0"], } } } } } config_with_proper_choices = { "fieldsets": [ { "fields": [ { "type": "choice", "label": "Version", "controlplane_field_mapping": "spec.version", "choices": [["6.2", "Version 6.2"], ["7.0", "Version 7.0"]], }, ] } ] } spec_schema = mock_crd.resource_schema["properties"]["spec"] errors = [] for field in config_with_proper_choices["fieldsets"][0]["fields"]: assert field["type"] == "choice" form._validate_choice_field( field, field["controlplane_field_mapping"], spec_schema, "spec", errors ) assert len(errors) == 0, f"Expected no errors but got: {errors}" version_field = config_with_proper_choices["fieldsets"][0]["fields"][0] assert version_field["choices"] == [["6.2", "Version 6.2"], ["7.0", "Version 7.0"]] def test_empty_choices_fail_validation(): form = ServiceDefinitionAdminForm() config_with_empty_choice = { "fieldsets": [ { "fields": [ { "type": "choice", "label": "Version", "controlplane_field_mapping": "spec.version", "choices": [[]], # Empty choice - invalid }, ] } ] } errors = [] for field in config_with_empty_choice["fieldsets"][0]["fields"]: assert field["type"] == "choice" form._validate_choice_field( field, field["controlplane_field_mapping"], {}, "spec", errors ) assert len(errors) > 0 assert "must have 1 or 2 elements" in str(errors[0]) def test_three_plus_element_choices_fail_validation(): form = ServiceDefinitionAdminForm() config_with_long_choice = { "fieldsets": [ { "fields": [ { "type": "choice", "label": "Version", "controlplane_field_mapping": "spec.version", "choices": [ ["6.2", "Version 6.2", "Extra"] ], # 3 elements - invalid }, ] } ] } errors = [] for field in config_with_long_choice["fieldsets"][0]["fields"]: assert field["type"] == "choice" form._validate_choice_field( field, field["controlplane_field_mapping"], {}, "spec", errors ) assert len(errors) > 0 assert "must have 1 or 2 elements" in str(errors[0]) def test_field_with_default_config_only_needs_mapping(): class TestModel(models.Model): display_name = models.CharField(max_length=100) class Meta: app_label = "test" minimal_config = { "fieldsets": [ { "fields": [ { "controlplane_field_mapping": "display_name", }, ] } ] } form_class = generate_custom_form_class(minimal_config, TestModel) form = form_class() name_field = form.fields["display_name"] assert name_field.label == DEFAULT_FIELD_CONFIGS["display_name"]["label"] assert name_field.help_text == DEFAULT_FIELD_CONFIGS["display_name"]["help_text"] assert name_field.required == DEFAULT_FIELD_CONFIGS["display_name"]["required"] def test_field_with_default_config_can_override_defaults(): class TestModel(models.Model): display_name = models.CharField(max_length=100) class Meta: app_label = "test" override_config = { "fieldsets": [ { "fields": [ { "controlplane_field_mapping": "display_name", "label": "Custom Name Label", "required": False, }, ] } ] } form_class = generate_custom_form_class(override_config, TestModel) form = form_class() name_field = form.fields["display_name"] assert name_field.label == "Custom Name Label" assert name_field.required is False assert name_field.help_text == DEFAULT_FIELD_CONFIGS["display_name"]["help_text"] def test_empty_values_dont_override_default_configs(): class TestModel(models.Model): display_name = models.CharField(max_length=100) class Meta: app_label = "test" admin_form_config = { "fieldsets": [ { "fields": [ { "controlplane_field_mapping": "display_name", "type": "", "label": "", "help_text": None, "max_length": None, "required": False, }, ] } ] } form_class = generate_custom_form_class(admin_form_config, TestModel) form = form_class() name_field = form.fields["display_name"] assert name_field.label == DEFAULT_FIELD_CONFIGS["display_name"]["label"] assert name_field.help_text == DEFAULT_FIELD_CONFIGS["display_name"]["help_text"] assert name_field.max_length == DEFAULT_FIELD_CONFIGS["display_name"]["max_length"] assert name_field.required is False # Was overridden by explicit False def test_number_field_validates_min_max_values(): class TestModel(models.Model): display_name = models.CharField(max_length=100) port = models.IntegerField() class Meta: app_label = "test" form_config = { "fieldsets": [ { "title": "General", "fields": [ { "type": "text", "label": "Name", "controlplane_field_mapping": "display_name", "required": True, }, { "type": "number", "label": "Port", "controlplane_field_mapping": "port", "required": True, "min_value": "1", "max_value": "65535", }, ], } ] } form_class = generate_custom_form_class(form_config, TestModel) # Test value below minimum fails validation form = form_class(data={"display_name": "test-service", "port": 0}) form.fields["context"].required = False assert not form.is_valid() assert "port" in form.errors # Test value above maximum fails validation form = form_class(data={"display_name": "test-service", "port": 65536}) form.fields["context"].required = False assert not form.is_valid() assert "port" in form.errors # Test valid value passes validation form = form_class(data={"display_name": "test-service", "port": 8080}) form.fields["context"].required = False assert form.is_valid(), f"Form should be valid but has errors: {form.errors}" def test_number_field_with_addon_text_roundtrip(): class TestModel(models.Model): display_name = models.CharField(max_length=100) disk_size = models.IntegerField() class Meta: app_label = "test" form_config = { "fieldsets": [ { "fields": [ { "type": "text", "label": "Name", "controlplane_field_mapping": "display_name", "required": True, }, { "type": "number", "label": "Disk Size", "controlplane_field_mapping": "disk_size", "addon_text": "Gi", }, ], } ] } form_class = generate_custom_form_class(form_config, TestModel) form = form_class(initial={"name": "test-instance", "disk_size": "25Gi"}) assert form.initial["disk_size"] == 25 form = form_class(data={"display_name": "test-instance", "disk_size": "25"}) form.fields["context"].required = False assert form.is_valid(), f"Form should be valid but has errors: {form.errors}" nested_data = form.get_nested_data() assert nested_data["disk_size"] == "25Gi"