Better naming of test fixtures

This commit is contained in:
Tobias Kunze 2025-12-10 12:48:33 +01:00
parent 4dab8e4f92
commit 457bbaadc2
4 changed files with 151 additions and 128 deletions

View file

@ -18,6 +18,7 @@ from servala.core.models.service import (
Service,
ServiceCategory,
ServiceDefinition,
ServiceInstance,
ServiceOffering,
)
@ -75,7 +76,7 @@ def user():
@pytest.fixture
def test_service_category():
def service_category():
return ServiceCategory.objects.create(
name="Databases",
description="Database services",
@ -83,18 +84,18 @@ def test_service_category():
@pytest.fixture
def test_service(test_service_category):
def service(service_category):
return Service.objects.create(
name="Redis",
slug="redis",
category=test_service_category,
category=service_category,
description="Redis database service",
osb_service_id="test-service-123",
)
@pytest.fixture
def test_cloud_provider():
def cloud_provider():
return CloudProvider.objects.create(
name="Exoscale",
description="Exoscale cloud provider",
@ -102,10 +103,10 @@ def test_cloud_provider():
@pytest.fixture
def test_service_offering(test_service, test_cloud_provider):
def service_offering(service, cloud_provider):
return ServiceOffering.objects.create(
service=test_service,
provider=test_cloud_provider,
service=service,
provider=cloud_provider,
description="Redis on Exoscale",
osb_plan_id="test-plan-123",
)
@ -148,11 +149,11 @@ def mock_odoo_failure(mocker):
@pytest.fixture
def test_control_plane(test_cloud_provider):
def control_plane(cloud_provider):
return ControlPlane.objects.create(
name="Geneva (CH-GVA-2)",
description="Geneva control plane",
cloud_provider=test_cloud_provider,
cloud_provider=cloud_provider,
api_credentials={
"server": "https://k8s.example.com",
"token": "test-token",
@ -162,10 +163,10 @@ def test_control_plane(test_cloud_provider):
@pytest.fixture
def test_service_definition(test_service):
def service_definition(service):
return ServiceDefinition.objects.create(
name="Redis Standard",
service=test_service,
service=service,
api_definition={
"group": "vshn.appcat.vshn.io",
"version": "v1",
@ -175,13 +176,11 @@ def test_service_definition(test_service):
@pytest.fixture
def test_control_plane_crd(
test_service_offering, test_control_plane, test_service_definition
):
def control_plane_crd(service_offering, control_plane, service_definition):
return ControlPlaneCRD.objects.create(
service_offering=test_service_offering,
control_plane=test_control_plane,
service_definition=test_service_definition,
service_offering=service_offering,
control_plane=control_plane,
service_definition=service_definition,
)
@ -199,10 +198,10 @@ def compute_plan():
@pytest.fixture
def compute_plan_assignment(compute_plan, test_control_plane_crd):
def compute_plan_assignment(compute_plan, control_plane_crd):
return ComputePlanAssignment.objects.create(
compute_plan=compute_plan,
control_plane_crd=test_control_plane_crd,
control_plane_crd=control_plane_crd,
sla="besteffort",
odoo_product_id="test-product-id",
odoo_unit_id="test-unit-id",
@ -210,3 +209,30 @@ def compute_plan_assignment(compute_plan, test_control_plane_crd):
unit="hour",
is_active=True,
)
@pytest.fixture
def service_instance(organization, control_plane_crd):
return ServiceInstance.objects.create(
name="test-abc12345",
display_name="My Test Instance",
organization=organization,
context=control_plane_crd,
)
@pytest.fixture
def control_plane_with_storage(cloud_provider):
return ControlPlane.objects.create(
name="Storage Zone",
description="Zone with storage billing",
cloud_provider=cloud_provider,
api_credentials={
"server": "https://k8s.example.com",
"token": "test-token",
"certificate-authority-data": "test-ca-data",
},
storage_plan_odoo_product_id="storage-product-123",
storage_plan_odoo_unit_id="storage-unit-456",
storage_plan_price_per_gib="0.10",
)

View file

@ -55,14 +55,14 @@ def valid_osb_payload():
def test_successful_onboarding_new_organization(
mock_odoo_success,
osb_client,
test_service,
test_service_offering,
service,
service_offering,
valid_osb_payload,
exoscale_origin,
instance_id,
):
valid_osb_payload["service_id"] = test_service.osb_service_id
valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id
valid_osb_payload["service_id"] = service.osb_service_id
valid_osb_payload["plan_id"] = service_offering.osb_plan_id
response = osb_client.put(
f"/api/osb/v2/service_instances/{instance_id}",
@ -107,15 +107,15 @@ def test_successful_onboarding_new_organization(
@pytest.mark.django_db
def test_new_organization_inherits_origin(
osb_client,
test_service,
test_service_offering,
service,
service_offering,
valid_osb_payload,
exoscale_origin,
instance_id,
billing_entity,
):
valid_osb_payload["service_id"] = test_service.osb_service_id
valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id
valid_osb_payload["service_id"] = service.osb_service_id
valid_osb_payload["plan_id"] = service_offering.osb_plan_id
exoscale_origin.billing_entity = billing_entity
exoscale_origin.save()
@ -137,8 +137,8 @@ def test_new_organization_inherits_origin(
@pytest.mark.django_db
def test_duplicate_organization_returns_existing(
osb_client,
test_service,
test_service_offering,
service,
service_offering,
valid_osb_payload,
exoscale_origin,
instance_id,
@ -148,10 +148,10 @@ def test_duplicate_organization_returns_existing(
osb_guid="test-org-guid-123",
origin=exoscale_origin,
)
org.limit_osb_services.add(test_service)
org.limit_osb_services.add(service)
valid_osb_payload["service_id"] = test_service.osb_service_id
valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id
valid_osb_payload["service_id"] = service.osb_service_id
valid_osb_payload["plan_id"] = service_offering.osb_plan_id
response = osb_client.put(
f"/api/osb/v2/service_instances/{instance_id}",
@ -169,13 +169,13 @@ def test_duplicate_organization_returns_existing(
@pytest.mark.django_db
def test_unauthenticated_osb_api_request_fails(
client,
test_service,
test_service_offering,
service,
service_offering,
valid_osb_payload,
instance_id,
):
valid_osb_payload["service_id"] = test_service.osb_service_id
valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id
valid_osb_payload["service_id"] = service.osb_service_id
valid_osb_payload["plan_id"] = service_offering.osb_plan_id
response = client.put(
f"/api/osb/v2/service_instances/{instance_id}",
@ -205,15 +205,15 @@ def test_unauthenticated_osb_api_request_fails(
)
def test_missing_required_fields_error(
osb_client,
test_service,
test_service_offering,
service,
service_offering,
valid_osb_payload,
field_to_remove,
expected_error,
instance_id,
):
valid_osb_payload["service_id"] = test_service.osb_service_id
valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id
valid_osb_payload["service_id"] = service.osb_service_id
valid_osb_payload["plan_id"] = service_offering.osb_plan_id
if isinstance(field_to_remove, tuple):
if field_to_remove[0] == "context":
@ -251,10 +251,8 @@ def test_invalid_service_id_error(osb_client, valid_osb_payload, instance_id):
@pytest.mark.django_db
def test_invalid_plan_id_error(
osb_client, test_service, valid_osb_payload, instance_id
):
valid_osb_payload["service_id"] = test_service.osb_service_id
def test_invalid_plan_id_error(osb_client, service, valid_osb_payload, instance_id):
valid_osb_payload["service_id"] = service.osb_service_id
valid_osb_payload["plan_id"] = 99999
response = osb_client.put(
@ -266,17 +264,17 @@ def test_invalid_plan_id_error(
assert response.status_code == 400
response_data = json.loads(response.content)
assert (
f"Unknown plan_id: 99999 for service_id: {test_service.osb_service_id}"
f"Unknown plan_id: 99999 for service_id: {service.osb_service_id}"
in response_data["error"]
)
@pytest.mark.django_db
def test_empty_users_array_error(
osb_client, test_service, test_service_offering, valid_osb_payload, instance_id
osb_client, service, service_offering, valid_osb_payload, instance_id
):
valid_osb_payload["service_id"] = test_service.osb_service_id
valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id
valid_osb_payload["service_id"] = service.osb_service_id
valid_osb_payload["plan_id"] = service_offering.osb_plan_id
valid_osb_payload["parameters"]["users"] = []
response = osb_client.put(
@ -292,10 +290,10 @@ def test_empty_users_array_error(
@pytest.mark.django_db
def test_multiple_users_error(
osb_client, test_service, test_service_offering, valid_osb_payload, instance_id
osb_client, service, service_offering, valid_osb_payload, instance_id
):
valid_osb_payload["service_id"] = test_service.osb_service_id
valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id
valid_osb_payload["service_id"] = service.osb_service_id
valid_osb_payload["plan_id"] = service_offering.osb_plan_id
valid_osb_payload["parameters"]["users"] = [
{"email": "user1@example.com", "full_name": "User One"},
{"email": "user2@example.com", "full_name": "User Two"},
@ -314,10 +312,10 @@ def test_multiple_users_error(
@pytest.mark.django_db
def test_empty_email_address_error(
osb_client, test_service, test_service_offering, valid_osb_payload, instance_id
osb_client, service, service_offering, valid_osb_payload, instance_id
):
valid_osb_payload["service_id"] = test_service.osb_service_id
valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id
valid_osb_payload["service_id"] = service.osb_service_id
valid_osb_payload["plan_id"] = service_offering.osb_plan_id
valid_osb_payload["parameters"]["users"] = [
{"email": "", "full_name": "User With No Email"},
]
@ -350,14 +348,14 @@ def test_invalid_json_error(osb_client, instance_id):
def test_user_creation_with_name_parsing(
mock_odoo_success,
osb_client,
test_service,
test_service_offering,
service,
service_offering,
valid_osb_payload,
exoscale_origin,
instance_id,
):
valid_osb_payload["service_id"] = test_service.osb_service_id
valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id
valid_osb_payload["service_id"] = service.osb_service_id
valid_osb_payload["plan_id"] = service_offering.osb_plan_id
valid_osb_payload["parameters"]["users"][0]["full_name"] = "John Doe Smith"
response = osb_client.put(
@ -376,14 +374,14 @@ def test_user_creation_with_name_parsing(
def test_email_normalization(
mock_odoo_success,
osb_client,
test_service,
test_service_offering,
service,
service_offering,
valid_osb_payload,
exoscale_origin,
instance_id,
):
valid_osb_payload["service_id"] = test_service.osb_service_id
valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id
valid_osb_payload["service_id"] = service.osb_service_id
valid_osb_payload["plan_id"] = service_offering.osb_plan_id
valid_osb_payload["parameters"]["users"][0]["email"] = " TEST@EXAMPLE.COM "
response = osb_client.put(
@ -401,14 +399,14 @@ def test_email_normalization(
def test_odoo_integration_failure_handling(
mock_odoo_failure,
osb_client,
test_service,
test_service_offering,
service,
service_offering,
valid_osb_payload,
exoscale_origin,
instance_id,
):
valid_osb_payload["service_id"] = test_service.osb_service_id
valid_osb_payload["plan_id"] = test_service_offering.osb_plan_id
valid_osb_payload["service_id"] = service.osb_service_id
valid_osb_payload["plan_id"] = service_offering.osb_plan_id
response = osb_client.put(
f"/api/osb/v2/service_instances/{instance_id}",
@ -425,14 +423,14 @@ def test_odoo_integration_failure_handling(
def test_organization_creation_with_context_only(
mock_odoo_success,
osb_client,
test_service,
test_service_offering,
service,
service_offering,
exoscale_origin,
instance_id,
):
payload = {
"service_id": test_service.osb_service_id,
"plan_id": test_service_offering.osb_plan_id,
"service_id": service.osb_service_id,
"plan_id": service_offering.osb_plan_id,
"context": {
"organization_guid": "fallback-org-guid",
"organization_name": "Fallback Organization",
@ -462,13 +460,13 @@ def test_organization_creation_with_context_only(
def test_delete_offboarding_success(
mock_odoo_success,
osb_client,
test_service,
test_service_offering,
service,
service_offering,
instance_id,
):
response = osb_client.delete(
f"/api/osb/v2/service_instances/{instance_id}"
f"?service_id={test_service.osb_service_id}&plan_id={test_service_offering.osb_plan_id}"
f"?service_id={service.osb_service_id}&plan_id={service_offering.osb_plan_id}"
)
assert response.status_code == 200
@ -476,9 +474,9 @@ def test_delete_offboarding_success(
@pytest.mark.django_db
def test_delete_missing_service_id(osb_client, test_service_offering, instance_id):
def test_delete_missing_service_id(osb_client, service_offering, instance_id):
response = osb_client.delete(
f"/api/osb/v2/service_instances/{instance_id}?plan_id={test_service_offering.osb_plan_id}"
f"/api/osb/v2/service_instances/{instance_id}?plan_id={service_offering.osb_plan_id}"
)
assert response.status_code == 400
@ -487,9 +485,9 @@ def test_delete_missing_service_id(osb_client, test_service_offering, instance_i
@pytest.mark.django_db
def test_delete_missing_plan_id(osb_client, test_service, instance_id):
def test_delete_missing_plan_id(osb_client, service, instance_id):
response = osb_client.delete(
f"/api/osb/v2/service_instances/{instance_id}?service_id={test_service.osb_service_id}"
f"/api/osb/v2/service_instances/{instance_id}?service_id={service.osb_service_id}"
)
assert response.status_code == 400
@ -509,16 +507,16 @@ def test_delete_invalid_service_id(osb_client, instance_id):
@pytest.mark.django_db
def test_delete_invalid_plan_id(osb_client, test_service, instance_id):
def test_delete_invalid_plan_id(osb_client, service, instance_id):
response = osb_client.delete(
f"/api/osb/v2/service_instances/{instance_id}"
f"?service_id={test_service.osb_service_id}&plan_id=invalid"
f"?service_id={service.osb_service_id}&plan_id=invalid"
)
assert response.status_code == 400
response_data = json.loads(response.content)
assert (
f"Unknown plan_id: invalid for service_id: {test_service.osb_service_id}"
f"Unknown plan_id: invalid for service_id: {service.osb_service_id}"
in response_data["error"]
)
@ -527,13 +525,13 @@ def test_delete_invalid_plan_id(osb_client, test_service, instance_id):
def test_patch_suspension_success(
mock_odoo_success,
osb_client,
test_service,
test_service_offering,
service,
service_offering,
instance_id,
):
payload = {
"service_id": test_service.osb_service_id,
"plan_id": test_service_offering.osb_plan_id,
"service_id": service.osb_service_id,
"plan_id": service_offering.osb_plan_id,
"parameters": {
"users": [
{
@ -556,9 +554,9 @@ def test_patch_suspension_success(
@pytest.mark.django_db
def test_patch_missing_service_id(osb_client, test_service_offering, instance_id):
def test_patch_missing_service_id(osb_client, service_offering, instance_id):
payload = {
"plan_id": test_service_offering.osb_plan_id,
"plan_id": service_offering.osb_plan_id,
"parameters": {"users": []},
}
@ -574,9 +572,9 @@ def test_patch_missing_service_id(osb_client, test_service_offering, instance_id
@pytest.mark.django_db
def test_patch_missing_plan_id(osb_client, test_service, instance_id):
def test_patch_missing_plan_id(osb_client, service, instance_id):
payload = {
"service_id": test_service.osb_service_id,
"service_id": service.osb_service_id,
"parameters": {"users": []},
}
@ -609,8 +607,8 @@ def test_delete_creates_ticket_with_admin_links(
mocker,
mock_odoo_success,
osb_client,
test_service,
test_service_offering,
service,
service_offering,
instance_id,
):
# Mock the create_helpdesk_ticket function
@ -618,7 +616,7 @@ def test_delete_creates_ticket_with_admin_links(
response = osb_client.delete(
f"/api/osb/v2/service_instances/{instance_id}"
f"?service_id={test_service.osb_service_id}&plan_id={test_service_offering.osb_plan_id}"
f"?service_id={service.osb_service_id}&plan_id={service_offering.osb_plan_id}"
)
assert response.status_code == 200
@ -629,10 +627,10 @@ def test_delete_creates_ticket_with_admin_links(
# Check that the description contains an admin URL
assert "admin/core/serviceoffering" in call_kwargs["description"]
assert f"/{test_service_offering.pk}/" in call_kwargs["description"]
assert f"/{service_offering.pk}/" in call_kwargs["description"]
assert (
call_kwargs["title"]
== f"Exoscale OSB Offboard - {test_service.name} - {instance_id}"
== f"Exoscale OSB Offboard - {service.name} - {instance_id}"
)
@ -641,8 +639,8 @@ def test_patch_creates_ticket_with_user_admin_links(
mocker,
mock_odoo_success,
osb_client,
test_service,
test_service_offering,
service,
service_offering,
instance_id,
org_owner,
):
@ -650,8 +648,8 @@ def test_patch_creates_ticket_with_user_admin_links(
mock_create_ticket = mocker.patch("servala.api.views.create_helpdesk_ticket")
payload = {
"service_id": test_service.osb_service_id,
"plan_id": test_service_offering.osb_plan_id,
"service_id": service.osb_service_id,
"plan_id": service_offering.osb_plan_id,
"parameters": {
"users": [
{
@ -680,8 +678,7 @@ def test_patch_creates_ticket_with_user_admin_links(
assert "admin/core/user" in call_kwargs["description"]
assert f"/{org_owner.pk}/" in call_kwargs["description"]
assert (
call_kwargs["title"]
== f"Exoscale OSB Suspend - {test_service.name} - {instance_id}"
call_kwargs["title"] == f"Exoscale OSB Suspend - {service.name} - {instance_id}"
)
@ -690,8 +687,8 @@ def test_ticket_includes_organization_and_instance_when_found(
mocker,
mock_odoo_success,
osb_client,
test_service,
test_service_offering,
service,
service_offering,
organization,
):
# Mock the create_helpdesk_ticket function
@ -699,12 +696,12 @@ def test_ticket_includes_organization_and_instance_when_found(
service_definition = ServiceDefinition.objects.create(
name="Test Definition",
service=test_service,
service=service,
api_definition={"group": "test.example.com", "version": "v1", "kind": "Test"},
)
control_plane = ControlPlane.objects.create(
name="Test Control Plane",
cloud_provider=test_service_offering.provider,
cloud_provider=service_offering.provider,
api_credentials={
"certificate-authority-data": "test",
"server": "https://test",
@ -712,7 +709,7 @@ def test_ticket_includes_organization_and_instance_when_found(
},
)
crd = ControlPlaneCRD.objects.create(
service_offering=test_service_offering,
service_offering=service_offering,
control_plane=control_plane,
service_definition=service_definition,
)
@ -727,7 +724,7 @@ def test_ticket_includes_organization_and_instance_when_found(
response = osb_client.delete(
f"/api/osb/v2/service_instances/{instance_name}"
f"?service_id={test_service.osb_service_id}&plan_id={test_service_offering.osb_plan_id}"
f"?service_id={service.osb_service_id}&plan_id={service_offering.osb_plan_id}"
)
assert response.status_code == 200

View file

@ -109,7 +109,7 @@ class TestReencryptFieldsCommand:
assert "Starting re-encryption" in output
assert "Re-encrypted 0 ControlPlane objects" in output
def test_reencrypt_fields_with_control_plane(self, test_control_plane):
def test_reencrypt_fields_with_control_plane(self, control_plane):
out = StringIO()
call_command("reencrypt_fields", stdout=out)
@ -147,11 +147,11 @@ class TestSyncBillingMetadataCommand:
assert "No control planes found with the specified IDs" in out.getvalue()
def test_sync_billing_metadata_dry_run_with_control_plane(self, test_control_plane):
def test_sync_billing_metadata_dry_run_with_control_plane(self, control_plane):
out = StringIO()
call_command("sync_billing_metadata", "--dry-run", stdout=out)
output = out.getvalue()
assert "DRY RUN" in output
assert "Syncing billing metadata on 1 control plane(s)" in output
assert test_control_plane.name in output
assert control_plane.name in output

View file

@ -59,76 +59,76 @@ def test_organization_linked_in_sidebar(
@pytest.mark.django_db
def test_service_detail_redirects_with_single_offering(
client, org_owner, organization, test_service, test_service_offering
client, org_owner, organization, service, service_offering
):
client.force_login(org_owner)
url = f"/org/{organization.slug}/services/{test_service.slug}/"
url = f"/org/{organization.slug}/services/{service.slug}/"
response = client.get(url)
assert response.status_code == 302
expected_url = f"/org/{organization.slug}/services/{test_service.slug}/offering/{test_service_offering.pk}/"
expected_url = f"/org/{organization.slug}/services/{service.slug}/offering/{service_offering.pk}/"
assert response.url == expected_url
@pytest.mark.django_db
def test_service_detail_shows_multiple_offerings(
client, org_owner, organization, test_service, test_service_offering
client, org_owner, organization, service, service_offering
):
second_provider = CloudProvider.objects.create(
name="AWS", description="Amazon Web Services"
)
second_offering = ServiceOffering.objects.create(
service=test_service,
service=service,
provider=second_provider,
description="Redis on AWS",
osb_plan_id="test-plan-456",
)
client.force_login(org_owner)
url = f"/org/{organization.slug}/services/{test_service.slug}/"
url = f"/org/{organization.slug}/services/{service.slug}/"
response = client.get(url)
assert response.status_code == 200
content = response.content.decode()
assert test_service_offering.provider.name in content
assert service_offering.provider.name in content
assert second_offering.provider.name in content
assert "Create Instance" in content
@pytest.mark.django_db
def test_service_detail_respects_cloud_provider_restrictions(
client, org_owner, organization, test_service, test_service_offering
client, org_owner, organization, service, service_offering
):
second_provider = CloudProvider.objects.create(
name="AWS", description="Amazon Web Services"
)
ServiceOffering.objects.create(
service=test_service,
service=service,
provider=second_provider,
description="Redis on AWS",
osb_plan_id="test-plan-456",
)
organization.origin.limit_cloudproviders.add(test_service_offering.provider)
organization.origin.limit_cloudproviders.add(service_offering.provider)
client.force_login(org_owner)
url = f"/org/{organization.slug}/services/{test_service.slug}/"
url = f"/org/{organization.slug}/services/{service.slug}/"
response = client.get(url)
assert response.status_code == 302
expected_url = f"/org/{organization.slug}/services/{test_service.slug}/offering/{test_service_offering.pk}/"
expected_url = f"/org/{organization.slug}/services/{service.slug}/offering/{service_offering.pk}/"
assert response.url == expected_url
@pytest.mark.django_db
def test_service_detail_no_redirect_with_restricted_multiple_offerings(
client, org_owner, organization, test_service, test_service_offering
client, org_owner, organization, service, service_offering
):
second_provider = CloudProvider.objects.create(
name="AWS", description="Amazon Web Services"
)
second_offering = ServiceOffering.objects.create(
service=test_service,
service=service,
provider=second_provider,
description="Redis on AWS",
osb_plan_id="test-plan-456",
@ -137,35 +137,35 @@ def test_service_detail_no_redirect_with_restricted_multiple_offerings(
name="Azure", description="Microsoft Azure"
)
third_offering = ServiceOffering.objects.create(
service=test_service,
service=service,
provider=third_provider,
description="Redis on Azure",
osb_plan_id="test-plan-789",
)
organization.origin.limit_cloudproviders.add(
test_service_offering.provider, second_provider
service_offering.provider, second_provider
)
client.force_login(org_owner)
url = f"/org/{organization.slug}/services/{test_service.slug}/"
url = f"/org/{organization.slug}/services/{service.slug}/"
response = client.get(url)
assert response.status_code == 200
content = response.content.decode()
assert test_service_offering.provider.name in content
assert service_offering.provider.name in content
assert second_offering.provider.name in content
assert third_offering.provider.name not in content
@pytest.mark.django_db
def test_service_instance_update_spec_pushes_display_name_annotation(
organization, test_control_plane_crd, org_owner
organization, control_plane_crd, org_owner
):
instance = ServiceInstance.objects.create(
name="test-instance",
display_name="Original Name",
organization=organization,
context=test_control_plane_crd,
context=control_plane_crd,
)
mock_api = MagicMock()
with (