diff --git a/api/experimentation/migrations/0003_unique_connection_per_environment.py b/api/experimentation/migrations/0003_unique_connection_per_environment.py new file mode 100644 index 000000000000..ec3a76a17a09 --- /dev/null +++ b/api/experimentation/migrations/0003_unique_connection_per_environment.py @@ -0,0 +1,23 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("experimentation", "0002_add_config_and_created_status"), + ] + + operations = [ + migrations.RemoveConstraint( + model_name="warehouseconnection", + name="unique_active_warehouse_per_type_and_env", + ), + migrations.AddConstraint( + model_name="warehouseconnection", + constraint=models.UniqueConstraint( + condition=models.Q(("deleted_at__isnull", True)), + fields=("environment",), + name="unique_active_warehouse_per_env", + ), + ), + ] diff --git a/api/experimentation/models.py b/api/experimentation/models.py index c1eca5e022e0..fd3fbf513782 100644 --- a/api/experimentation/models.py +++ b/api/experimentation/models.py @@ -42,8 +42,8 @@ class WarehouseConnection(LifecycleModelMixin, SoftDeleteExportableModel): # ty class Meta: constraints = [ models.UniqueConstraint( - fields=["warehouse_type", "environment"], + fields=["environment"], condition=models.Q(deleted_at__isnull=True), - name="unique_active_warehouse_per_type_and_env", + name="unique_active_warehouse_per_env", ), ] diff --git a/api/experimentation/serializers.py b/api/experimentation/serializers.py index bfece9cc5242..6a946232ef8b 100644 --- a/api/experimentation/serializers.py +++ b/api/experimentation/serializers.py @@ -55,13 +55,14 @@ def create( WarehouseConnection.objects.all_with_deleted() .filter( environment=environment, - warehouse_type=warehouse_type, deleted_at__isnull=False, ) + .order_by("-deleted_at") .first() ) if existing: existing.deleted_at = None + existing.warehouse_type = warehouse_type existing.status = WarehouseConnectionStatus.CREATED existing.name = validated_data["name"] existing.config = validated_data.get("config") diff --git a/api/experimentation/views.py b/api/experimentation/views.py index 4997b1d0b692..5f2968c25b02 100644 --- a/api/experimentation/views.py +++ b/api/experimentation/views.py @@ -52,13 +52,13 @@ def create(self, request: Request, *args: object, **kwargs: object) -> Response: serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) - warehouse_type = serializer.validated_data["warehouse_type"] if WarehouseConnection.objects.filter( environment=environment, - warehouse_type=warehouse_type, ).exists(): return Response( - {"detail": "Warehouse connection already exists."}, + { + "detail": "This environment already has an active warehouse connection." + }, status=409, ) diff --git a/api/tests/unit/experimentation/test_views.py b/api/tests/unit/experimentation/test_views.py index f0e70b9f4707..9ce9171eb19e 100644 --- a/api/tests/unit/experimentation/test_views.py +++ b/api/tests/unit/experimentation/test_views.py @@ -58,7 +58,39 @@ def test_post__already_exists__returns_409( # Then assert response.status_code == status.HTTP_409_CONFLICT - assert response.json()["detail"] == "Warehouse connection already exists." + assert ( + response.json()["detail"] + == "This environment already has an active warehouse connection." + ) + + +def test_post__different_type_already_exists__returns_409( + admin_client: APIClient, + environment: Environment, + enable_features: EnableFeaturesFixture, + warehouse_connection: WarehouseConnection, + warehouse_connection_url: str, +) -> None: + # Given + enable_features("experimentation_warehouse_connection") + + # When + response = admin_client.post( + warehouse_connection_url, + data={ + "warehouse_type": "snowflake", + "name": "My Snowflake", + "config": {"account_identifier": "xy12345.us-east-1"}, + }, + format="json", + ) + + # Then + assert response.status_code == status.HTTP_409_CONFLICT + assert ( + response.json()["detail"] + == "This environment already has an active warehouse connection." + ) def test_post__soft_deleted_exists__resurrects_and_returns_201(