From 57e63722b6ddf724613da1734394a155e87051ac Mon Sep 17 00:00:00 2001 From: Tyagiquamar Date: Sun, 13 Sep 2026 23:34:43 +0530 Subject: [PATCH] fix(versioning): update segment override in place when patch omits feature_segment.id --- api/features/versioning/views.py | 20 +++++++ .../test_integration_v2_versioning.py | 55 +++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/api/features/versioning/views.py b/api/features/versioning/views.py index 7245bf1ac2ca..946ee895a7eb 100644 --- a/api/features/versioning/views.py +++ b/api/features/versioning/views.py @@ -288,6 +288,26 @@ def get_serializer_context(self): # type: ignore[no-untyped-def] context["environment_feature_version"] = self.environment_feature_version return context + def get_serializer(self, *args, **kwargs): # type: ignore[no-untyped-def] + instance = kwargs.get("instance", args[0] if args else None) + data = kwargs.get("data") + if ( + self.action in ("update", "partial_update") + and instance is not None + and getattr(instance, "feature_segment_id", None) + and isinstance(data, dict) + and isinstance(data.get("feature_segment"), dict) + and "id" not in data["feature_segment"] + ): + kwargs["data"] = { + **data, + "feature_segment": { + **data["feature_segment"], + "id": instance.feature_segment_id, + }, + } + return super().get_serializer(*args, **kwargs) + def perform_create( self, serializer: CustomCreateSegmentOverrideFeatureStateSerializer, # type: ignore[override] diff --git a/api/tests/integration/features/versioning/test_integration_v2_versioning.py b/api/tests/integration/features/versioning/test_integration_v2_versioning.py index 0548cfec50b1..f814da462ee5 100644 --- a/api/tests/integration/features/versioning/test_integration_v2_versioning.py +++ b/api/tests/integration/features/versioning/test_integration_v2_versioning.py @@ -651,3 +651,58 @@ def test_identities__segment_override_deleted__returns_default_environment_value environment_document_response_json["project"]["segments"][0]["feature_states"] == [] ) + + +def test_versioned_featurestate__patch_without_feature_segment_id__updates_in_place( # type: ignore[no-untyped-def] # noqa: FT004 + admin_client: "APIClient", + environment_v2_versioning: int, + feature: int, + segment: int, +) -> None: + # Given + environment = environment_v2_versioning + version_list_url = reverse( + "api-v1:versioning:environment-feature-versions-list", + args=[environment, feature], + ) + create_version_response = admin_client.post(version_list_url) + assert create_version_response.status_code == status.HTTP_201_CREATED + version_uuid = create_version_response.json()["uuid"] + featurestates_url = reverse( + "api-v1:versioning:environment-feature-version-featurestates-list", + args=[environment, feature, version_uuid], + ) + create_override_response = admin_client.post( + featurestates_url, + data=json.dumps( + { + "enabled": True, + "feature_state_value": { + "string_value": "v2-value", + "value_type": "unicode", + }, + "feature_segment": {"segment": segment}, + } + ), + content_type="application/json", + ) + assert create_override_response.status_code == status.HTTP_201_CREATED + override_id = create_override_response.json()["id"] + detail_url = reverse( + "api-v1:versioning:environment-feature-version-featurestates-detail", + args=[environment, feature, version_uuid, override_id], + ) + original_feature_segment_id = admin_client.get(detail_url).json()[ + "feature_segment" + ]["id"] + + # When + response = admin_client.patch( + detail_url, + data=json.dumps({"feature_segment": {"priority": 0, "segment": segment}}), + content_type="application/json", + ) + + # Then + assert response.status_code == status.HTTP_200_OK + assert response.json()["feature_segment"]["id"] == original_feature_segment_id