diff --git a/api/features/serializers.py b/api/features/serializers.py index c27ee5aa78e5..4b331ec2c757 100644 --- a/api/features/serializers.py +++ b/api/features/serializers.py @@ -597,14 +597,22 @@ def validate_feature(self, feature): # type: ignore[no-untyped-def] return feature def validate_environment(self, environment): # type: ignore[no-untyped-def] - if self.instance and self.instance.environment_id != environment.id: # type: ignore[union-attr] + if ( + environment is not None + and self.instance + and self.instance.environment_id != environment.id + ): # type: ignore[union-attr] raise serializers.ValidationError( "Cannot change the environment of a feature state" ) return environment def validate(self, attrs): # type: ignore[no-untyped-def] - environment = attrs.get("environment") or self.context["environment"] + environment = attrs.get("environment") or self.context.get("environment") + if environment is None: + raise serializers.ValidationError( + {"environment": ["This field may not be null."]} + ) identity = attrs.get("identity") feature_segment = attrs.get("feature_segment") identifier = attrs.pop("identifier", None) diff --git a/api/tests/unit/features/test_unit_features_serializers.py b/api/tests/unit/features/test_unit_features_serializers.py index 36e3eff19e79..ce76783b9d2c 100644 --- a/api/tests/unit/features/test_unit_features_serializers.py +++ b/api/tests/unit/features/test_unit_features_serializers.py @@ -14,6 +14,53 @@ from features.serializers import FeatureStateSerializerBasic +def test_feature_state_serializer_basic__null_environment_no_context__returns_validation_error( # type: ignore[no-untyped-def] + feature, environment +): + # Given - null environment in payload and no environment in context + feature_state = FeatureState.objects.get(feature=feature, environment=environment) + data = { + "id": feature_state.id, + "feature": feature.id, + "environment": None, + } + serializer = FeatureStateSerializerBasic( + instance=feature_state, + data=data, + context={}, + ) + + # When + is_valid = serializer.is_valid() + + # Then - should reject null environment, not raise AttributeError + assert not is_valid + assert "environment" in serializer.errors + + +def test_feature_state_serializer_basic__null_environment_with_context__falls_back_to_context( # type: ignore[no-untyped-def] + feature, environment +): + # Given - null environment in payload but valid environment in context + feature_state = FeatureState.objects.get(feature=feature, environment=environment) + data = { + "id": feature_state.id, + "feature": feature.id, + "environment": None, + } + serializer = FeatureStateSerializerBasic( + instance=feature_state, + data=data, + context={"environment": environment}, + ) + + # When + is_valid = serializer.is_valid() + + # Then - should fall back to context environment + assert is_valid + + @pytest.mark.parametrize( "percentage_value, expected_is_valid", ((90, True), (100, True), (110, False)) )