diff --git a/api/tests/unit/util/mappers/test_unit_mappers_engine.py b/api/tests/unit/util/mappers/test_unit_mappers_engine.py index e37f06fdbdc6..216b33b1c2b6 100644 --- a/api/tests/unit/util/mappers/test_unit_mappers_engine.py +++ b/api/tests/unit/util/mappers/test_unit_mappers_engine.py @@ -425,6 +425,65 @@ def test_map_environment_to_engine__multiple_segments_and_versions__returns_expe assert segment_featurestate.uuid not in segment_feature_state_uuids +def test_map_environment_to_engine__feature_specific_segment_not_in_env__excludes_segment( + environment: Environment, + feature: "Feature", + feature_specific_segment: Segment, +) -> None: + # Given + # `feature_specific_segment` has no `FeatureSegment` pointing at it + # in this environment, so it has no evaluation path here. + + # When + result = engine.map_environment_to_engine(environment) + + # Then + segment_ids = [s.id for s in result.project.segments] + assert feature_specific_segment.id not in segment_ids + + +def test_map_environment_to_engine__feature_specific_segment_in_env__includes_segment( + environment: Environment, + feature: "Feature", + feature_specific_segment: Segment, +) -> None: + # Given + feature_segment = FeatureSegment.objects.create( + feature=feature, + segment=feature_specific_segment, + environment=environment, + ) + FeatureState.objects.create( + feature_segment=feature_segment, + feature=feature, + environment=environment, + ) + + # When + result = engine.map_environment_to_engine(environment) + + # Then + segment_ids = [s.id for s in result.project.segments] + assert feature_specific_segment.id in segment_ids + + +def test_map_environment_to_engine__project_wide_segment_not_in_env__includes_segment( + environment: Environment, + segment: Segment, +) -> None: + # Given + # `segment` is a project-wide segment (Segment.feature_id IS NULL) + # with no `FeatureSegment` in this environment. Project-wide segments + # must remain in the environment document regardless. + + # When + result = engine.map_environment_to_engine(environment) + + # Then + segment_ids = [s.id for s in result.project.segments] + assert segment.id in segment_ids + + def test_map_environment_api_key_to_engine__valid_key__returns_expected_model( environment: Environment, environment_api_key: "EnvironmentAPIKey", diff --git a/api/util/mappers/engine.py b/api/util/mappers/engine.py index eac85292cc9b..8852187c9753 100644 --- a/api/util/mappers/engine.py +++ b/api/util/mappers/engine.py @@ -211,10 +211,6 @@ def map_environment_to_engine( ps for ps in project.segments.all() if ps.id == ps.version_of_id ] - project_segment_rules_by_segment_id: Dict[ - int, - Iterable["SegmentRule"], - ] = {segment.pk: segment.rules.all() for segment in project_segments} project_segment_feature_states_by_segment_id = _get_segment_feature_states( project_segments, environment.pk, @@ -229,6 +225,19 @@ def map_environment_to_engine( else [] ), ) + # Drop feature-specific segments that have no FeatureSegment in this + # environment — without one, they have no evaluation path here, and + # their rules only inflate the environment document. + project_segments = [ + ps + for ps in project_segments + if ps.feature_id is None + or project_segment_feature_states_by_segment_id.get(ps.pk) + ] + project_segment_rules_by_segment_id: Dict[ + int, + Iterable["SegmentRule"], + ] = {segment.pk: segment.rules.all() for segment in project_segments} environment_feature_states: List["FeatureState"] = _get_prioritised_feature_states( [ feature_state