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.forms import ServiceDefinitionAdminForm from servala.core.models import ControlPlaneCRD def test_custom_model_form_class_is_none_when_no_form_config(): crd = Mock(spec=ControlPlaneCRD) service_def = Mock() service_def.form_config = None crd.service_definition = service_def crd.django_model = Mock() if not ( crd.django_model and crd.service_definition and crd.service_definition.form_config and crd.service_definition.form_config.get("fieldsets") ): result = None else: result = generate_custom_form_class( crd.service_definition.form_config, crd.django_model ) assert result is None 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": "name", "required": True, } ], } ] } crd.service_definition = service_def class TestModel(models.Model): name = models.CharField(max_length=100) class Meta: app_label = "test" crd.django_model = TestModel if not ( crd.django_model and crd.service_definition and crd.service_definition.form_config and crd.service_definition.form_config.get("fieldsets") ): result = None else: 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): 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": "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): 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": "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): 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": "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={"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={"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={"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": "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": "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): 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": "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): 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": "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["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): 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": "name", "default_value": "default-name", }, { "type": "number", "label": "Port", "controlplane_field_mapping": "port", "default_value": "8080", }, ], } ] } instance = TestModel(name="existing-name", port=3000) form_class = generate_custom_form_class(form_config, TestModel) form = form_class(instance=instance) assert form.initial["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": "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": "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"]: 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_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"]: if field.get("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"]: if field.get("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])