From d3213e9f1e16751a8f3fd1261895ec7c5a4cce84 Mon Sep 17 00:00:00 2001 From: Pepe Fagoaga Date: Tue, 10 Mar 2026 09:54:09 +0000 Subject: [PATCH] chore(providers): Return 409 on conflict (#10293) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- api/CHANGELOG.md | 8 ++++++++ api/src/backend/api/tests/test_views.py | 6 +++++- api/src/backend/api/v1/serializers.py | 21 +++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/api/CHANGELOG.md b/api/CHANGELOG.md index 22da548d71..cfaecfa2f2 100644 --- a/api/CHANGELOG.md +++ b/api/CHANGELOG.md @@ -2,6 +2,14 @@ All notable changes to the **Prowler API** are documented in this file. +## [1.21.0] (Prowler UNRELEASED) + +### 🔄 Changed + +- `POST /api/v1/providers` returns `409 Conflict` if already exists [(#10293)](https://github.com/prowler-cloud/prowler/pull/10293) + +--- + ## [1.20.0] (Prowler v5.19.0) ### 🚀 Added diff --git a/api/src/backend/api/tests/test_views.py b/api/src/backend/api/tests/test_views.py index 22e7ab091e..1631b4878d 100644 --- a/api/src/backend/api/tests/test_views.py +++ b/api/src/backend/api/tests/test_views.py @@ -1342,7 +1342,11 @@ class TestProviderViewSet: response = authenticated_client.post( reverse("provider-list"), data=provider_json_payload, format="json" ) - assert response.status_code == status.HTTP_400_BAD_REQUEST + assert response.status_code == status.HTTP_409_CONFLICT + error = response.json()["errors"][0] + assert error["detail"] == "Provider already exists." + assert error["code"] == "conflict" + assert error["source"]["pointer"] == "/data/attributes/uid" mock_delete_task.reset_mock() mock_delete_task.return_value = task_mock diff --git a/api/src/backend/api/v1/serializers.py b/api/src/backend/api/v1/serializers.py index 7c4bb41ca5..3ed623d365 100644 --- a/api/src/backend/api/v1/serializers.py +++ b/api/src/backend/api/v1/serializers.py @@ -6,6 +6,7 @@ from django.conf import settings from django.contrib.auth import authenticate from django.contrib.auth.models import update_last_login from django.contrib.auth.password_validation import validate_password +from django.core.exceptions import ValidationError as DjangoValidationError from django.db import IntegrityError from drf_spectacular.utils import extend_schema_field from jwt.exceptions import InvalidKeyError @@ -959,6 +960,26 @@ class ProviderCreateSerializer(RLSSerializer, BaseWriteSerializer): }, } + def create(self, validated_data): + try: + return super().create(validated_data) + except DjangoValidationError as e: + if "unique_provider_uids" in str(e): + raise ConflictException( + detail="Provider already exists.", + pointer="/data/attributes/uid", + ) + raise + except IntegrityError as e: + # Handle race conditions where the unique constraint is enforced at the DB level + # after validation has already passed. + if "unique_provider_uids" in str(e): + raise ConflictException( + detail="Provider already exists.", + pointer="/data/attributes/uid", + ) + raise + class ProviderUpdateSerializer(BaseWriteSerializer): """