From 0018fe5a271b51d38f0e0baeb51c08071be0b1e4 Mon Sep 17 00:00:00 2001 From: wojcikmat Date: Fri, 10 May 2024 11:27:08 +0200 Subject: [PATCH] feat: multi tenancy delete tenant validation (#543) * Add delete tenant validation and subscription cancellation to mutation * Add test for tenant with free plan (nothing to cancel) --- packages/backend/apps/multitenancy/schema.py | 32 +++++++++++++- .../apps/multitenancy/tests/test_schema.py | 42 ++++++++++++++++++- 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/packages/backend/apps/multitenancy/schema.py b/packages/backend/apps/multitenancy/schema.py index 2e1f85fb4..ca44d71dc 100644 --- a/packages/backend/apps/multitenancy/schema.py +++ b/packages/backend/apps/multitenancy/schema.py @@ -10,10 +10,12 @@ from common.graphql import mutations, exceptions from common.graphql.acl.decorators import permission_classes from common.graphql.acl.wrappers import PERMISSION_DENIED_MESSAGE +from apps.finances.services import subscriptions +from apps.finances.serializers import CancelTenantActiveSubscriptionSerializer from . import models from . import serializers from .tokens import tenant_invitation_token -from .constants import TenantUserRole +from .constants import TenantUserRole, TenantType as ConstantsTenantType TenantUserRoleType = graphene.Enum.from_enum(TenantUserRole) @@ -128,9 +130,37 @@ def get_object(cls, model_class, root, info, **input): class DeleteTenantMutation(mutations.DeleteModelMutation): + """ + Mutation to delete a tenant from the system. + """ + class Meta: model = models.Tenant + @classmethod + def mutate_and_get_payload(cls, root, info, id, **kwargs): + """ + Perform deletion of a tenant and subscription cancellation. + + Returns: + DeleteTenantMutation: The mutation object with list of deleted_ids. + + Raises: + GraphQlValidationError: If deletion encounters validation errors. + """ + tenant = info.context.tenant + + if tenant.type == ConstantsTenantType.DEFAULT: + raise exceptions.GraphQlValidationError("Cannot delete default type tenant.") + + schedule = subscriptions.get_schedule(tenant) + cancel_subscription_serializer = CancelTenantActiveSubscriptionSerializer(instance=schedule, data={}) + if cancel_subscription_serializer.is_valid(): + cancel_subscription_serializer.save() + + tenant.delete() + return cls(deleted_ids=[id]) + class CreateTenantInvitationMutation(mutations.SerializerMutation): ok = graphene.Boolean() diff --git a/packages/backend/apps/multitenancy/tests/test_schema.py b/packages/backend/apps/multitenancy/tests/test_schema.py index d89638185..07b386bcb 100644 --- a/packages/backend/apps/multitenancy/tests/test_schema.py +++ b/packages/backend/apps/multitenancy/tests/test_schema.py @@ -135,14 +135,52 @@ class TestDeleteTenantMutation: } ''' - def test_delete_tenant(self, graphene_client, user, tenant_factory, tenant_membership_factory): + def test_delete_tenant( + self, + graphene_client, + user, + tenant_factory, + tenant_membership_factory, + subscription_schedule_factory, + monthly_plan_price, + ): + tenant = tenant_factory(name="Tenant 1", type=TenantType.ORGANIZATION) + tenant_id = tenant.id + tenant_membership_factory(tenant=tenant, user=user, role=TenantUserRole.OWNER) + subscription_schedule_factory( + phases=[{'items': [{'price': monthly_plan_price.id}], 'trialing': True}], customer__subscriber=tenant + ) + graphene_client.force_authenticate(user) + graphene_client.set_tenant_dependent_context(tenant, TenantUserRole.OWNER) + executed = self.mutate(graphene_client, {"id": to_global_id("TenantType", tenant.id)}) + response_data = executed["data"]["deleteTenant"]["deletedIds"] + assert response_data[0] == to_global_id("TenantType", tenant_id) + + def test_delete_tenant_with_free_plan( + self, + graphene_client, + user, + tenant_factory, + tenant_membership_factory, + subscription_schedule_factory, + ): tenant = tenant_factory(name="Tenant 1", type=TenantType.ORGANIZATION) + tenant_id = tenant.id tenant_membership_factory(tenant=tenant, user=user, role=TenantUserRole.OWNER) + subscription_schedule_factory(phases=None, customer__subscriber=tenant) graphene_client.force_authenticate(user) graphene_client.set_tenant_dependent_context(tenant, TenantUserRole.OWNER) executed = self.mutate(graphene_client, {"id": to_global_id("TenantType", tenant.id)}) response_data = executed["data"]["deleteTenant"]["deletedIds"] - assert response_data[0] == to_global_id("TenantType", tenant.id) + assert response_data[0] == to_global_id("TenantType", tenant_id) + + def test_delete_default_tenant(self, graphene_client, user, tenant_factory, tenant_membership_factory): + tenant = tenant_factory(name="Tenant 1", type=TenantType.DEFAULT) + tenant_membership_factory(tenant=tenant, user=user, role=TenantUserRole.OWNER) + graphene_client.force_authenticate(user) + graphene_client.set_tenant_dependent_context(tenant, TenantUserRole.OWNER) + executed = self.mutate(graphene_client, {"id": to_global_id("TenantType", tenant.id)}) + assert executed["errors"][0]["message"] == "GraphQlValidationError" def test_user_without_membership(self, graphene_client, user, tenant_factory): tenant = tenant_factory(name="Tenant 1", type=TenantType.ORGANIZATION)