From e256adfb3719ced95f2d3d65ecca40f2eafca1a4 Mon Sep 17 00:00:00 2001
From: Dmitry Gruzd <dgruzd@gitlab.com>
Date: Wed, 12 Mar 2025 14:06:43 +0100
Subject: [PATCH] Zoekt: Fix undefined feature flag handling in
 gitlab:zoekt:info

Changelog: fixed
EE: true
---
 ee/app/services/search/zoekt/info_service.rb  | 19 +++++--
 .../search/zoekt/info_service_spec.rb         | 49 +++++++++++++++++--
 2 files changed, 59 insertions(+), 9 deletions(-)

diff --git a/ee/app/services/search/zoekt/info_service.rb b/ee/app/services/search/zoekt/info_service.rb
index 155bd8397264e..e5648338810bf 100644
--- a/ee/app/services/search/zoekt/info_service.rb
+++ b/ee/app/services/search/zoekt/info_service.rb
@@ -48,10 +48,16 @@ def log_custom_feature_flags
         # Get the state of each persisted flag
         flag_states = {}
         persisted_flags.each do |flag|
-          enabled = Feature.enabled?(flag, Feature.current_request)
-          state_text = enabled ? 'enabled' : 'disabled'
-          state_color = enabled ? :green : :red
-          flag_states[flag] = Rainbow(state_text).color(state_color)
+          # Skip flags without YAML definitions to avoid InvalidFeatureFlagError
+          if Feature::Definition.has_definition?(flag.to_sym)
+            enabled = Feature.enabled?(flag, Feature.current_request)
+            state_text = enabled ? 'enabled' : 'disabled'
+            state_color = enabled ? :green : :red
+            flag_states[flag] = Rainbow(state_text).color(state_color)
+          else
+            # Mark flags without definitions
+            flag_states[flag] = Rainbow('no definition').yellow
+          end
         end
 
         # Sort flags alphabetically and log each flag and its state
@@ -62,7 +68,7 @@ def log_custom_feature_flags
 
       # Display default feature flags section - shows all flags using default values
       def log_default_feature_flags
-        # Get all zoekt-related feature flags
+        # Get all zoekt-related feature flags that have YAML definitions
         all_flags = Feature::Definition.definitions.keys
         zoekt_flags = all_flags.select { |name| name.to_s.start_with?('zoekt_') }
 
@@ -77,6 +83,9 @@ def log_default_feature_flags
         # Get the state of each default flag
         flag_states = {}
         default_flags.each do |flag|
+          # Skip flags without YAML definitions to avoid errors
+          next unless Feature::Definition.has_definition?(flag)
+
           enabled = Feature.enabled?(flag, Feature.current_request)
           state_text = enabled ? 'enabled' : 'disabled'
           state_color = enabled ? :green : :red
diff --git a/ee/spec/services/search/zoekt/info_service_spec.rb b/ee/spec/services/search/zoekt/info_service_spec.rb
index 86b5717e863ae..7a820f95d82e8 100644
--- a/ee/spec/services/search/zoekt/info_service_spec.rb
+++ b/ee/spec/services/search/zoekt/info_service_spec.rb
@@ -235,9 +235,18 @@
         allow(Feature).to receive(:persisted_names).and_return(['zoekt_custom_flag'])
 
         zoekt_flag = instance_double(Feature::Definition, to_s: 'zoekt_default_flag')
-        allow(Feature::Definition).to receive(:definitions).and_return({
-          'zoekt_default_flag' => zoekt_flag
-        })
+
+        # Combine all Feature::Definition stubs
+        allow(Feature::Definition).to receive_messages(
+          definitions: { 'zoekt_default_flag' => zoekt_flag },
+          has_definition?: false
+        )
+
+        # Set up specific has_definition? calls that override the default
+        allow(Feature::Definition).to receive(:has_definition?).with(:zoekt_custom_flag).and_return(true)
+        allow(Feature::Definition).to receive(:has_definition?).with('zoekt_custom_flag').and_return(true)
+        allow(Feature::Definition).to receive(:has_definition?).with(:zoekt_default_flag).and_return(true)
+        allow(Feature::Definition).to receive(:has_definition?).with('zoekt_default_flag').and_return(true)
 
         allow(Feature).to receive(:persisted_name?).with('zoekt_default_flag').and_return(false)
         allow(Feature).to receive(:enabled?).with('zoekt_custom_flag', nil).and_return(true)
@@ -265,7 +274,10 @@
             persisted_name?: false,
             enabled?: false
           )
-          allow(Feature::Definition).to receive(:definitions).and_return({})
+          allow(Feature::Definition).to receive_messages(
+            definitions: {},
+            has_definition?: false
+          )
         end
 
         it 'displays empty feature flags sections' do
@@ -283,6 +295,35 @@
           expect(logger).to have_received(:info).with(/Feature flags:.+#{Rainbow('none').yellow}/).at_least(:once)
         end
       end
+
+      context 'with feature flags without YAML definitions' do
+        before do
+          allow(Feature).to receive(:persisted_names).and_return(%w[zoekt_custom_flag zoekt_undefined_flag])
+
+          # Set up has_definition? to return appropriate values for both string and symbol keys
+          allow(Feature::Definition).to receive(:has_definition?).and_return(false)
+          allow(Feature::Definition).to receive(:has_definition?).with(:zoekt_custom_flag).and_return(true)
+          allow(Feature::Definition).to receive(:has_definition?).with('zoekt_custom_flag').and_return(true)
+          allow(Feature::Definition).to receive(:has_definition?).with(:zoekt_undefined_flag).and_return(false)
+          allow(Feature::Definition).to receive(:has_definition?).with('zoekt_undefined_flag').and_return(false)
+          allow(Feature::Definition).to receive(:has_definition?).with(:zoekt_default_flag).and_return(true)
+          allow(Feature::Definition).to receive(:has_definition?).with('zoekt_default_flag').and_return(true)
+
+          # We should never call enabled? on the undefined flag
+          allow(Feature).to receive(:enabled?).with('zoekt_undefined_flag', nil)
+            .and_raise('This should not be called')
+        end
+
+        it 'displays flags without definitions as "no definition"' do
+          service.execute
+
+          # The flag with no definition should show up in the output
+          expect(logger).to have_received(:info).with(/- zoekt_undefined_flag:.+#{Rainbow('no definition').yellow}/)
+
+          # The regular flag should still work
+          expect(logger).to have_received(:info).with(/- zoekt_custom_flag:.+#{Rainbow('enabled').green}/)
+        end
+      end
     end
   end
 end
-- 
GitLab