Fix pytest-coverage setup
This commit is contained in:
parent
7fc85108cf
commit
450fe0949e
7 changed files with 115 additions and 102 deletions
|
|
@ -56,10 +56,14 @@ extend_exclude = "src/servala/static/mazer"
|
|||
|
||||
[tool.pytest.ini_options]
|
||||
DJANGO_SETTINGS_MODULE = "servala.settings_test"
|
||||
addopts = "-p no:doctest -p no:pastebin -p no:nose --cov=./ --cov-report=term-missing:skip-covered"
|
||||
addopts = "-p no:doctest -p no:pastebin -p no:nose --cov=./ --cov-report=term-missing:skip-covered --cov-config=pyproject.toml"
|
||||
testpaths = "src/tests"
|
||||
pythonpath = "src"
|
||||
|
||||
[tool.coverage.run]
|
||||
branch = true
|
||||
omit = ["*/admin.py","*/settings.py","*/wsgi.py", "*/migrations/*", "*/manage.py", "*/__init__.py"]
|
||||
|
||||
[tool.bumpver]
|
||||
current_version = "2025.11.17-0"
|
||||
version_pattern = "YYYY.0M.0D-INC0"
|
||||
|
|
|
|||
|
|
@ -239,9 +239,9 @@ The Servala Team"""
|
|||
service_offering = ServiceOffering.objects.get(
|
||||
osb_plan_id=plan_id, service=service
|
||||
)
|
||||
except Service.DoesNotExist: # pragma: no-cover
|
||||
except Service.DoesNotExist: # pragma: no cover
|
||||
return self._error(f"Unknown service_id: {service_id}")
|
||||
except ServiceOffering.DoesNotExist: # pragma: no-cover
|
||||
except ServiceOffering.DoesNotExist: # pragma: no cover
|
||||
return self._error(
|
||||
f"Unknown plan_id: {plan_id} for service_id: {service_id}"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ urlpatterns = [
|
|||
]
|
||||
|
||||
# Serve static and media files in development
|
||||
if settings.DEBUG:
|
||||
if settings.DEBUG: # pragma: no cover
|
||||
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
|
||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
||||
urlpatterns += [
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import pytest
|
|||
|
||||
from servala.core.models import (
|
||||
BillingEntity,
|
||||
ComputePlan,
|
||||
ComputePlanAssignment,
|
||||
Organization,
|
||||
OrganizationMembership,
|
||||
OrganizationOrigin,
|
||||
|
|
@ -11,8 +13,11 @@ from servala.core.models import (
|
|||
)
|
||||
from servala.core.models.service import (
|
||||
CloudProvider,
|
||||
ControlPlane,
|
||||
ControlPlaneCRD,
|
||||
Service,
|
||||
ServiceCategory,
|
||||
ServiceDefinition,
|
||||
ServiceOffering,
|
||||
)
|
||||
|
||||
|
|
@ -117,3 +122,68 @@ def mock_odoo_failure(mocker):
|
|||
mock_client = mocker.patch("servala.core.models.organization.CLIENT")
|
||||
mock_client.execute.side_effect = Exception("Odoo connection failed")
|
||||
return mock_client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_control_plane(test_cloud_provider):
|
||||
return ControlPlane.objects.create(
|
||||
name="Geneva (CH-GVA-2)",
|
||||
description="Geneva control plane",
|
||||
cloud_provider=test_cloud_provider,
|
||||
api_credentials={
|
||||
"server": "https://k8s.example.com",
|
||||
"token": "test-token",
|
||||
"certificate_authority_data": "test-ca-data",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_service_definition(test_service):
|
||||
return ServiceDefinition.objects.create(
|
||||
name="Redis Standard",
|
||||
service=test_service,
|
||||
api_definition={
|
||||
"group": "vshn.appcat.vshn.io",
|
||||
"version": "v1",
|
||||
"kind": "VSHNRedis",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_control_plane_crd(
|
||||
test_service_offering, test_control_plane, test_service_definition
|
||||
):
|
||||
return ControlPlaneCRD.objects.create(
|
||||
service_offering=test_service_offering,
|
||||
control_plane=test_control_plane,
|
||||
service_definition=test_service_definition,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def compute_plan():
|
||||
return ComputePlan.objects.create(
|
||||
name="Medium",
|
||||
description="Medium resource plan",
|
||||
memory_requests="1Gi",
|
||||
memory_limits="2Gi",
|
||||
cpu_requests="500m",
|
||||
cpu_limits="1000m",
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def compute_plan_assignment(compute_plan, test_control_plane_crd):
|
||||
return ComputePlanAssignment.objects.create(
|
||||
compute_plan=compute_plan,
|
||||
control_plane_crd=test_control_plane_crd,
|
||||
sla="besteffort",
|
||||
odoo_product_id="test-product-id",
|
||||
odoo_unit_id="test-unit-id",
|
||||
price="10.00",
|
||||
unit="hour",
|
||||
is_active=True,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -218,11 +218,10 @@ def test_missing_required_fields_error(
|
|||
if isinstance(field_to_remove, tuple):
|
||||
if field_to_remove[0] == "context":
|
||||
del valid_osb_payload["context"][field_to_remove[1]]
|
||||
elif field_to_remove[0] == "parameters":
|
||||
else:
|
||||
del valid_osb_payload["parameters"][field_to_remove[1]]
|
||||
else:
|
||||
if field_to_remove in valid_osb_payload:
|
||||
del valid_osb_payload[field_to_remove]
|
||||
del valid_osb_payload[field_to_remove]
|
||||
|
||||
response = osb_client.put(
|
||||
f"/api/osb/v2/service_instances/{instance_id}",
|
||||
|
|
|
|||
|
|
@ -295,6 +295,28 @@ def test_build_billing_annotations_no_item_description_without_organization():
|
|||
assert annotations["servala.com/erp_product_id_storage"] == "storage-product"
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_compute_plan_assignment_str(
|
||||
compute_plan_assignment,
|
||||
):
|
||||
result = str(compute_plan_assignment)
|
||||
# Format: "{plan_name} ({sla_display}) → {control_plane_crd}"
|
||||
assert compute_plan_assignment.compute_plan.name in result
|
||||
assert "→" in result
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_compute_plan_assignment_get_odoo_reporting_product_id(
|
||||
compute_plan_assignment,
|
||||
):
|
||||
compute_plan_assignment.odoo_product_id = "test-product-xyz"
|
||||
compute_plan_assignment.save()
|
||||
|
||||
result = compute_plan_assignment.get_odoo_reporting_product_id()
|
||||
|
||||
assert result == "test-product-xyz"
|
||||
|
||||
|
||||
def test_build_billing_annotations_combined():
|
||||
compute_plan_assignment = Mock()
|
||||
compute_plan_assignment.odoo_product_id = "compute-product"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ 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, MANDATORY_FIELDS
|
||||
from servala.core.crd.forms import DEFAULT_FIELD_CONFIGS
|
||||
from servala.core.forms import ServiceDefinitionAdminForm
|
||||
from servala.core.models import ControlPlaneCRD
|
||||
|
||||
|
|
@ -806,10 +806,10 @@ def test_two_element_choices_work_correctly():
|
|||
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 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]
|
||||
|
|
@ -836,10 +836,10 @@ def test_empty_choices_fail_validation():
|
|||
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 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])
|
||||
|
|
@ -867,10 +867,10 @@ def test_three_plus_element_choices_fail_validation():
|
|||
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 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])
|
||||
|
|
@ -936,88 +936,6 @@ def test_field_with_default_config_can_override_defaults():
|
|||
assert name_field.help_text == DEFAULT_FIELD_CONFIGS["name"]["help_text"]
|
||||
|
||||
|
||||
def test_admin_form_validates_mandatory_fields_present():
|
||||
|
||||
mock_crd = Mock()
|
||||
mock_crd.resource_schema = {
|
||||
"properties": {
|
||||
"spec": {
|
||||
"properties": {
|
||||
"environment": {
|
||||
"type": "string",
|
||||
"enum": ["dev", "prod"],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config_without_name = {
|
||||
"fieldsets": [
|
||||
{
|
||||
"fields": [
|
||||
{
|
||||
"type": "choice",
|
||||
"label": "Environment",
|
||||
"controlplane_field_mapping": "spec.environment",
|
||||
"choices": [["dev", "Development"]],
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
errors = []
|
||||
included_mappings = set()
|
||||
for fieldset in config_without_name.get("fieldsets", []):
|
||||
for field in fieldset.get("fields", []):
|
||||
mapping = field.get("controlplane_field_mapping")
|
||||
included_mappings.add(mapping)
|
||||
|
||||
for mandatory_field in MANDATORY_FIELDS:
|
||||
if mandatory_field not in included_mappings:
|
||||
errors.append(f"Required field '{mandatory_field}' must be included")
|
||||
|
||||
assert len(errors) > 0
|
||||
assert "name" in str(errors[0]).lower()
|
||||
|
||||
|
||||
def test_admin_form_validates_fields_without_defaults_need_label_and_type():
|
||||
config_with_incomplete_field = {
|
||||
"fieldsets": [
|
||||
{
|
||||
"fields": [
|
||||
{"controlplane_field_mapping": "name"}, # Has defaults - OK
|
||||
{
|
||||
"controlplane_field_mapping": "spec.unknown", # No defaults
|
||||
# Missing label and type
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
errors = []
|
||||
|
||||
for fieldset in config_with_incomplete_field.get("fieldsets", []):
|
||||
for field in fieldset.get("fields", []):
|
||||
mapping = field.get("controlplane_field_mapping")
|
||||
|
||||
if mapping not in DEFAULT_FIELD_CONFIGS:
|
||||
if not field.get("label"):
|
||||
errors.append(
|
||||
f"Field with mapping '{mapping}' must have a 'label' property"
|
||||
)
|
||||
if not field.get("type"):
|
||||
errors.append(
|
||||
f"Field with mapping '{mapping}' must have a 'type' property"
|
||||
)
|
||||
|
||||
assert len(errors) == 2
|
||||
assert any("label" in str(e) for e in errors)
|
||||
assert any("type" in str(e) for e in errors)
|
||||
|
||||
|
||||
def test_empty_values_dont_override_default_configs():
|
||||
|
||||
class TestModel(models.Model):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue