# Copyright 2026 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import annotations import click from click.testing import CliRunner from google.adk.cli.cli_tools_click import _apply_feature_overrides from google.adk.cli.cli_tools_click import feature_options from google.adk.features._feature_registry import _FEATURE_OVERRIDES from google.adk.features._feature_registry import _WARNED_FEATURES from google.adk.features._feature_registry import FeatureName from google.adk.features._feature_registry import is_feature_enabled from google.adk.features._feature_registry import temporary_feature_override import pytest @pytest.fixture(autouse=True) def reset_feature_overrides(): """Reset feature overrides and warnings before/after each test.""" _FEATURE_OVERRIDES.clear() _WARNED_FEATURES.clear() yield _FEATURE_OVERRIDES.clear() _WARNED_FEATURES.clear() class TestApplyFeatureOverrides: """Tests for _apply_feature_overrides helper function.""" def test_single_feature(self): """Single feature name is applied correctly.""" _apply_feature_overrides(enable_features=("JSON_SCHEMA_FOR_FUNC_DECL",)) assert is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL) def test_comma_separated_features(self): """Comma-separated feature names are applied correctly.""" _apply_feature_overrides( enable_features=("JSON_SCHEMA_FOR_FUNC_DECL,PROGRESSIVE_SSE_STREAMING",) ) assert is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL) assert is_feature_enabled(FeatureName.PROGRESSIVE_SSE_STREAMING) def test_multiple_flag_values(self): """Multiple --enable_features flags are applied correctly.""" _apply_feature_overrides( enable_features=( "JSON_SCHEMA_FOR_FUNC_DECL", "PROGRESSIVE_SSE_STREAMING", ) ) assert is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL) assert is_feature_enabled(FeatureName.PROGRESSIVE_SSE_STREAMING) def test_whitespace_handling(self): """Whitespace around feature names is stripped.""" _apply_feature_overrides( enable_features=(" JSON_SCHEMA_FOR_FUNC_DECL , COMPUTER_USE ",) ) assert is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL) assert is_feature_enabled(FeatureName.COMPUTER_USE) def test_empty_string_ignored(self): """Empty strings in the list are ignored.""" _apply_feature_overrides(enable_features=("",)) # No error should be raised def test_unknown_feature_warns(self, capsys): """Unknown feature names emit a warning.""" _apply_feature_overrides(enable_features=("UNKNOWN_FEATURE_XYZ",)) captured = capsys.readouterr() assert "WARNING" in captured.err assert "UNKNOWN_FEATURE_XYZ" in captured.err assert "Valid names are:" in captured.err def test_single_disable_feature(self): """Single feature name is disabled correctly.""" # First enable a feature _apply_feature_overrides(enable_features=("JSON_SCHEMA_FOR_FUNC_DECL",)) assert is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL) # Then disable it _apply_feature_overrides(disable_features=("JSON_SCHEMA_FOR_FUNC_DECL",)) assert not is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL) def test_comma_separated_disable_features(self): """Comma-separated feature names are disabled correctly.""" # First enable features _apply_feature_overrides( enable_features=("JSON_SCHEMA_FOR_FUNC_DECL,PROGRESSIVE_SSE_STREAMING",) ) # Then disable them _apply_feature_overrides( disable_features=( "JSON_SCHEMA_FOR_FUNC_DECL,PROGRESSIVE_SSE_STREAMING", ) ) assert not is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL) assert not is_feature_enabled(FeatureName.PROGRESSIVE_SSE_STREAMING) def test_disable_overrides_enable(self): """Disable is applied after enable, so disable wins for same feature.""" _apply_feature_overrides( enable_features=("JSON_SCHEMA_FOR_FUNC_DECL",), disable_features=("JSON_SCHEMA_FOR_FUNC_DECL",), ) # disable_features is processed after enable_features assert not is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL) def test_enable_and_disable_different_features(self): """Enable and disable can be used together for different features.""" # First enable a feature that we'll disable _apply_feature_overrides(enable_features=("PROGRESSIVE_SSE_STREAMING",)) _apply_feature_overrides( enable_features=("JSON_SCHEMA_FOR_FUNC_DECL",), disable_features=("PROGRESSIVE_SSE_STREAMING",), ) assert is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL) assert not is_feature_enabled(FeatureName.PROGRESSIVE_SSE_STREAMING) class TestFeatureOptionsDecorator: """Tests for feature_options decorator.""" def test_decorator_adds_enable_features_option(self): """Decorator adds --enable_features option to command.""" @click.command() @feature_options() def test_cmd(): pass runner = CliRunner() result = runner.invoke(test_cmd, ["--help"]) assert "--enable_features" in result.output def test_enable_features_applied_before_command(self): """Features are enabled before the command function runs.""" feature_was_enabled = [] @click.command() @feature_options() def test_cmd(): feature_was_enabled.append( is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL) ) runner = CliRunner() runner.invoke( test_cmd, ["--enable_features=JSON_SCHEMA_FOR_FUNC_DECL"], catch_exceptions=False, ) assert feature_was_enabled == [True] def test_multiple_enable_features_flags(self): """Multiple --enable_features flags work correctly.""" enabled_features = [] @click.command() @feature_options() def test_cmd(): enabled_features.append( is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL) ) enabled_features.append( is_feature_enabled(FeatureName.PROGRESSIVE_SSE_STREAMING) ) runner = CliRunner() runner.invoke( test_cmd, [ "--enable_features=JSON_SCHEMA_FOR_FUNC_DECL", "--enable_features=PROGRESSIVE_SSE_STREAMING", ], catch_exceptions=False, ) assert enabled_features == [True, True] def test_comma_separated_enable_features(self): """Comma-separated feature names work correctly.""" enabled_features = [] @click.command() @feature_options() def test_cmd(): enabled_features.append( is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL) ) enabled_features.append( is_feature_enabled(FeatureName.PROGRESSIVE_SSE_STREAMING) ) runner = CliRunner() runner.invoke( test_cmd, [ "--enable_features=JSON_SCHEMA_FOR_FUNC_DECL,PROGRESSIVE_SSE_STREAMING" ], catch_exceptions=False, ) assert enabled_features == [True, True] def test_no_enable_features_flag(self): """Command works without --enable_features flag.""" enabled_features = [] with temporary_feature_override( FeatureName.JSON_SCHEMA_FOR_FUNC_DECL, False ): @click.command() @feature_options() def test_cmd(): enabled_features.append( is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL) ) runner = CliRunner() result = runner.invoke(test_cmd, [], catch_exceptions=False) assert result.exit_code == 0 assert enabled_features == [False] def test_preserves_function_metadata(self): """Decorator preserves the wrapped function's metadata.""" @click.command() @feature_options() def my_test_command(): """My docstring.""" pass # The callback should have preserved metadata assert ( "my_test_command" in my_test_command.name or my_test_command.callback.__name__ == "my_test_command" ) def test_decorator_adds_disable_features_option(self): """Decorator adds --disable_features option to command.""" @click.command() @feature_options() def test_cmd(): pass runner = CliRunner() result = runner.invoke(test_cmd, ["--help"]) assert "--disable_features" in result.output def test_disable_features_applied_before_command(self): """Features are disabled before the command function runs.""" # First enable the feature via override _apply_feature_overrides(enable_features=("JSON_SCHEMA_FOR_FUNC_DECL",)) feature_was_disabled = [] @click.command() @feature_options() def test_cmd(): feature_was_disabled.append( not is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL) ) runner = CliRunner() runner.invoke( test_cmd, ["--disable_features=JSON_SCHEMA_FOR_FUNC_DECL"], catch_exceptions=False, ) assert feature_was_disabled == [True] def test_enable_and_disable_together(self): """Both --enable_features and --disable_features work together.""" feature_states = [] @click.command() @feature_options() def test_cmd(): feature_states.append( is_feature_enabled(FeatureName.JSON_SCHEMA_FOR_FUNC_DECL) ) feature_states.append( is_feature_enabled(FeatureName.PROGRESSIVE_SSE_STREAMING) ) runner = CliRunner() runner.invoke( test_cmd, [ "--enable_features=JSON_SCHEMA_FOR_FUNC_DECL", "--disable_features=PROGRESSIVE_SSE_STREAMING", ], catch_exceptions=False, ) # JSON_SCHEMA_FOR_FUNC_DECL should be enabled # PROGRESSIVE_SSE_STREAMING should be disabled assert feature_states == [True, False]