Source code for tests.test_types

# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

from __future__ import annotations

import sys
from collections.abc import Callable
from enum import Enum, Flag, IntEnum, IntFlag, auto
from operator import attrgetter

import pytest

from click_extra import (
    UNSET,
    BadParameter,
    Choice,
    ChoiceSource,
    EnumChoice,
    echo,
)
from click_extra.pytest import command_decorators, option_decorators

if sys.version_info >= (3, 11):
    from enum import StrEnum
else:
    from backports.strenum import StrEnum  # type: ignore[import-not-found]


[docs] def test_click_choice_behavior() -> None: """Lockdown the behavior of method inherited from Click's Choice type.""" class SimpleEnum(Enum): FIRST_VALUE = auto() SECOND_VALUE = "second-value" enum_choice = Choice(SimpleEnum) assert repr(enum_choice) == ( "Choice([<SimpleEnum.FIRST_VALUE: 1>, " "<SimpleEnum.SECOND_VALUE: 'second-value'>])" ) assert enum_choice.choices == (SimpleEnum.FIRST_VALUE, SimpleEnum.SECOND_VALUE) assert enum_choice.case_sensitive is True # Choice strings or Eunum members are recognized as valid inputs. assert enum_choice.convert("FIRST_VALUE", None, None) == SimpleEnum.FIRST_VALUE assert enum_choice.convert("SECOND_VALUE", None, None) == SimpleEnum.SECOND_VALUE assert enum_choice.convert(SimpleEnum.FIRST_VALUE, None, None) == ( SimpleEnum.FIRST_VALUE ) assert enum_choice.convert(SimpleEnum.SECOND_VALUE, None, None) == ( SimpleEnum.SECOND_VALUE ) # Values are not recognized as valid inputs. with pytest.raises(BadParameter) as exc_info: enum_choice.convert("second-value", None, None) assert exc_info.value.args[0] == ( "'second-value' is not one of 'FIRST_VALUE', 'SECOND_VALUE'." ) # Normalization works for both choice strings and Enum members. assert enum_choice.normalize_choice("FIRST_VALUE", None) == "FIRST_VALUE" # type: ignore[arg-type] assert enum_choice.normalize_choice("SECOND_VALUE", None) == "SECOND_VALUE" # type: ignore[arg-type] assert enum_choice.normalize_choice(SimpleEnum.FIRST_VALUE, None) == "FIRST_VALUE" assert enum_choice.normalize_choice(SimpleEnum.SECOND_VALUE, None) == "SECOND_VALUE" # Normalization leave stings unchanged (case-sensitive). assert enum_choice.normalize_choice("first_value", None) == "first_value" # type: ignore[arg-type] assert enum_choice.normalize_choice("Second_Value", None) == "Second_Value" # type: ignore[arg-type] # Test case-insensitive behavior. enum_choice_ci = Choice(SimpleEnum, case_sensitive=False) assert enum_choice_ci.convert("first_value", None, None) == SimpleEnum.FIRST_VALUE assert enum_choice_ci.convert("SECOND_value", None, None) == ( SimpleEnum.SECOND_VALUE ) assert enum_choice_ci.normalize_choice("first_value", None) == "first_value" # type: ignore[arg-type] assert enum_choice_ci.normalize_choice("SECOND_value", None) == "second_value" # type: ignore[arg-type]
[docs] @pytest.mark.parametrize( ("enum_definition", "choice_source", "result"), ( # String-based Enum. ( Enum("Status", {"PENDING": "pending", "APPROVED": "approved"}), ChoiceSource.STR, ("Status.PENDING", "Status.APPROVED"), ), ( Enum("Status", {"PENDING": "pending", "APPROVED": "approved"}), ChoiceSource.NAME, ("PENDING", "APPROVED"), ), ( Enum("Status", {"PENDING": "pending", "APPROVED": "approved"}), ChoiceSource.VALUE, ("pending", "approved"), ), # Aliases in string-based Enum are hidden by default. ( Enum( "State", { "NEW": "new", "IN_PROGRESS": "in_progress", "ONGOING": "in_progress", # Alias for IN_PROGRESS "COMPLETED": "completed", }, ), ChoiceSource.STR, ("State.NEW", "State.IN_PROGRESS", "State.COMPLETED"), ), ( Enum( "State", { "NEW": "new", "IN_PROGRESS": "in_progress", "ONGOING": "in_progress", # Alias for IN_PROGRESS "COMPLETED": "completed", }, ), ChoiceSource.NAME, ("NEW", "IN_PROGRESS", "COMPLETED"), ), ( Enum( "State", { "NEW": "new", "IN_PROGRESS": "in_progress", "ONGOING": "in_progress", # Alias for IN_PROGRESS "COMPLETED": "completed", }, ), ChoiceSource.VALUE, ("new", "in_progress", "completed"), ), # Integer-based Enum. ( Enum("Color", {"RED": 1, "GREEN": 2, "BLUE": 3}), ChoiceSource.STR, ("Color.RED", "Color.GREEN", "Color.BLUE"), ), ( Enum("Color", {"RED": 1, "GREEN": 2, "BLUE": 3}), ChoiceSource.NAME, ("RED", "GREEN", "BLUE"), ), ( Enum("Color", {"RED": 1, "GREEN": 2, "BLUE": 3}), ChoiceSource.VALUE, "<Color.RED: 1> produced non-string choice 1", ), # Aliases in integer-based Enum are hidden by default. ( Enum( "Level", { "LOW": 1, "MEDIUM": 2, "NORMAL": 2, # Alias for MEDIUM "HIGH": 3, }, ), ChoiceSource.STR, ("Level.LOW", "Level.MEDIUM", "Level.HIGH"), ), ( Enum( "Level", { "LOW": 1, "MEDIUM": 2, "NORMAL": 2, # Alias for MEDIUM "HIGH": 3, }, ), ChoiceSource.NAME, ("LOW", "MEDIUM", "HIGH"), ), ( Enum( "Level", { "LOW": 1, "MEDIUM": 2, "NORMAL": 2, # Alias for MEDIUM "HIGH": 3, }, ), ChoiceSource.VALUE, "<Level.LOW: 1> produced non-string choice 1", ), # Auto-numbered Enum. ( Enum("Permission", {"READ": auto(), "WRITE": auto(), "EXECUTE": auto()}), ChoiceSource.STR, ("Permission.READ", "Permission.WRITE", "Permission.EXECUTE"), ), ( Enum("Permission", {"READ": auto(), "WRITE": auto(), "EXECUTE": auto()}), ChoiceSource.NAME, ("READ", "WRITE", "EXECUTE"), ), ( Enum("Permission", {"READ": auto(), "WRITE": auto(), "EXECUTE": auto()}), ChoiceSource.VALUE, "<Permission.READ: 1> produced non-string choice 1", ), # IntEnum. ( IntEnum("Priority", {"LOW": auto(), "MEDIUM": auto(), "HIGH": auto()}), ChoiceSource.STR, ("1", "2", "3") if sys.version_info >= (3, 11) else ("Priority.LOW", "Priority.MEDIUM", "Priority.HIGH"), ), ( IntEnum("Priority", {"LOW": auto(), "MEDIUM": auto(), "HIGH": auto()}), ChoiceSource.NAME, ("LOW", "MEDIUM", "HIGH"), ), ( IntEnum("Priority", {"LOW": auto(), "MEDIUM": auto(), "HIGH": auto()}), ChoiceSource.VALUE, "<Priority.LOW: 1> produced non-string choice 1", ), # Difference between Enum and StrEnum: StrEnum defines __str__() to return # the value. ( Enum( "MyEnum", {"FIRST_VALUE": "first_value", "SECOND_VALUE": "second-value"} ), ChoiceSource.STR, ("MyEnum.FIRST_VALUE", "MyEnum.SECOND_VALUE"), ), ( StrEnum( "MyEnum", {"FIRST_VALUE": "first_value", "SECOND_VALUE": "second-value"}, ), ChoiceSource.STR, ("first_value", "second-value"), ), ( StrEnum("MyEnum", {"FIRST_VALUE": auto(), "SECOND_VALUE": auto()}), ChoiceSource.STR, ("first_value", "second_value"), ), ( StrEnum("MyEnum", {"FIRST_VALUE": auto(), "SECOND_VALUE": auto()}), ChoiceSource.NAME, ("FIRST_VALUE", "SECOND_VALUE"), ), ( StrEnum("MyEnum", {"FIRST_VALUE": auto(), "SECOND_VALUE": auto()}), ChoiceSource.VALUE, ("first_value", "second_value"), ), # Flag enums. ( Flag("Features", {"FEATURE_A": auto(), "FEATURE_B": auto()}), ChoiceSource.STR, ("Features.FEATURE_A", "Features.FEATURE_B"), ), ( Flag("Features", {"FEATURE_A": auto(), "FEATURE_B": auto()}), ChoiceSource.NAME, ("FEATURE_A", "FEATURE_B"), ), ( Flag("Features", {"FEATURE_A": auto(), "FEATURE_B": auto()}), ChoiceSource.VALUE, "<Features.FEATURE_A: 1> produced non-string choice 1", ), # IntFlag enums. ( IntFlag( "Options", {"OPTION_X": auto(), "OPTION_Y": auto(), "OPTION_Z": auto()} ), ChoiceSource.STR, ("1", "2", "4") if sys.version_info >= (3, 11) else ("Options.OPTION_X", "Options.OPTION_Y", "Options.OPTION_Z"), ), ( IntFlag( "Options", {"OPTION_X": auto(), "OPTION_Y": auto(), "OPTION_Z": auto()} ), ChoiceSource.NAME, ("OPTION_X", "OPTION_Y", "OPTION_Z"), ), ( IntFlag( "Options", {"OPTION_X": auto(), "OPTION_Y": auto(), "OPTION_Z": auto()} ), ChoiceSource.VALUE, "<Options.OPTION_X: 1> produced non-string choice 1", ), ), ) def test_enum_string_choices( enum_definition: type[Enum], choice_source: ChoiceSource, result: tuple[str, ...] | str, ) -> None: # Expecting an error message. if isinstance(result, str): with pytest.raises(TypeError) as exc_info: EnumChoice(enum_definition, choice_source=choice_source) assert exc_info.value.args[0] == f"{result} when using {choice_source!r}." return # Normal case: valid string choices. enum_choice = EnumChoice(enum_definition, choice_source=choice_source) assert enum_choice.choices == result assert len(enum_choice.choices) == len(set(enum_choice.choices)) for choice_str, member in zip(enum_choice.choices, list(enum_definition)): # Conversion from choice strings to Enum members. assert enum_choice.convert(choice_str, None, None) == member # Conversion from Enum members should be idempotent. assert enum_choice.convert(member, None, None) == member
[docs] @pytest.mark.skipif( sys.version_info < (3, 11), reason="Enum aliasing not supported in Python < 3.11" ) @pytest.mark.parametrize( ("enum_definition", "choice_source", "show_aliases", "result"), ( # String-based Enum. ( Enum("Status", {"PENDING": "pending", "APPROVED": "approved"}), ChoiceSource.STR, False, ("Status.PENDING", "Status.APPROVED"), ), ( Enum("Status", {"PENDING": "pending", "APPROVED": "approved"}), ChoiceSource.STR, True, RuntimeError, ), ( Enum("Status", {"PENDING": "pending", "APPROVED": "approved"}), ChoiceSource.NAME, True, ("PENDING", "APPROVED", "aliased_pending"), ), ( Enum("Status", {"PENDING": "pending", "APPROVED": "approved"}), ChoiceSource.VALUE, True, ("pending", "approved", "aliased_approved"), ), # Integer-based Enum. ( Enum("Color", {"RED": 1, "GREEN": 2, "BLUE": 3}), ChoiceSource.NAME, True, ("RED", "GREEN", "BLUE", "aliased_pending"), ), ( Enum("Color", {"RED": 1, "GREEN": 2, "BLUE": 3}), ChoiceSource.VALUE, True, TypeError, ), ), ) def test_enum_choice_show_aliases( enum_definition: type[Enum], choice_source: ChoiceSource, show_aliases: bool, result: tuple[str, ...] | type[RuntimeError] | type[TypeError], ) -> None: """Test that EnumChoice correctly handles Enum with aliases.""" if result is RuntimeError: with pytest.raises(RuntimeError) as exc_info: EnumChoice( enum_definition, choice_source=choice_source, show_aliases=show_aliases ) assert exc_info.value.args[0] == ( f"Cannot use {choice_source!r} with show_aliases=True." ) return elif result is TypeError: with pytest.raises(TypeError) as type_exc_info: EnumChoice( enum_definition, choice_source=choice_source, show_aliases=show_aliases ) assert "produced non-string choice" in type_exc_info.value.args[0] assert f"when using {choice_source!r}." in type_exc_info.value.args[0] return # Augment the Enum with both key/name and value aliases. list(enum_definition)[0]._add_alias_("aliased_pending") # type: ignore[attr-defined] list(enum_definition)[1]._add_value_alias_("aliased_approved") # type: ignore[attr-defined] enum_choice = EnumChoice( enum_definition, choice_source=choice_source, show_aliases=show_aliases ) assert enum_choice.choices == result assert len(enum_choice.choices) == len(set(enum_choice.choices)) # Map choice strings to Enum members, including aliases. choice_to_member = list(zip(enum_choice.choices, list(enum_definition))) if isinstance(result, tuple) and "aliased_pending" in result: choice_to_member.append(("aliased_pending", list(enum_definition)[0])) if isinstance(result, tuple) and "aliased_approved" in result: choice_to_member.append(("aliased_approved", list(enum_definition)[1])) for choice_str, member in choice_to_member: # Conversion from choice strings to Enum members. assert enum_choice.convert(choice_str, None, None) == member # Conversion from Enum members should be idempotent. assert enum_choice.convert(member, None, None) == member
[docs] class MyEnum(Enum): """Produce different strings for keys/names, values and str().""" FIRST_VALUE = "first-value" SECOND_VALUE = "second-value" def __str__(self) -> str: return f"my-{self.value}"
[docs] @pytest.mark.parametrize( "source, expected_choices", ( # Exact ChoiceSource enum values. (ChoiceSource.KEY, ("FIRST_VALUE", "SECOND_VALUE")), (ChoiceSource.NAME, ("FIRST_VALUE", "SECOND_VALUE")), (ChoiceSource.VALUE, ("first-value", "second-value")), (ChoiceSource.STR, ("my-first-value", "my-second-value")), # String versions of the ChoiceSource values are supported too. ("key", ("FIRST_VALUE", "SECOND_VALUE")), ("name", ("FIRST_VALUE", "SECOND_VALUE")), ("value", ("first-value", "second-value")), ("str", ("my-first-value", "my-second-value")), # Random casing are supported too. ("kEy", ("FIRST_VALUE", "SECOND_VALUE")), ("Name", ("FIRST_VALUE", "SECOND_VALUE")), ("valuE", ("first-value", "second-value")), ("STR", ("my-first-value", "my-second-value")), # Callable choice_source. ( lambda member: f"custom-{member.value}", ("custom-first-value", "custom-second-value"), ), (attrgetter("name"), ("FIRST_VALUE", "SECOND_VALUE")), ), ) def test_enum_choice_internals( source: ChoiceSource | str | Callable[[Enum], str], expected_choices: tuple[str, ...], ) -> None: enum_choice = EnumChoice(MyEnum, choice_source=source) # Check the produced choice strings. assert enum_choice.choices == expected_choices assert len(enum_choice.choices) == 2 assert repr(enum_choice) == ( f"EnumChoice('{expected_choices[0]}', '{expected_choices[1]}')" ) # Check internal metadata. assert enum_choice.case_sensitive is False assert enum_choice._enum is MyEnum assert enum_choice._choice_source in ChoiceSource.__members__.values() or callable( enum_choice._choice_source ) assert len(enum_choice._enum_map) == 2 assert tuple(enum_choice._enum_map.keys()) == enum_choice.choices assert tuple(enum_choice._enum_map.values()) == tuple(MyEnum) # Choice strings and Enum members are normalized correctly (i.e. # lower-cased by default). assert ( enum_choice.normalize_choice(expected_choices[0], None) == expected_choices[0].casefold() ) assert ( enum_choice.normalize_choice(expected_choices[1], None) == expected_choices[1].casefold() ) assert ( enum_choice.normalize_choice(MyEnum.FIRST_VALUE, None) == expected_choices[0].casefold() ) assert ( enum_choice.normalize_choice(MyEnum.SECOND_VALUE, None) == expected_choices[1].casefold() ) # Conversion from choice strings to Enum members. assert enum_choice.convert(expected_choices[0], None, None) == MyEnum.FIRST_VALUE assert enum_choice.convert(expected_choices[1], None, None) == MyEnum.SECOND_VALUE # Conversion from Enum members should be idempotent. assert enum_choice.convert(MyEnum.FIRST_VALUE, None, None) == MyEnum.FIRST_VALUE assert enum_choice.convert(MyEnum.SECOND_VALUE, None, None) == MyEnum.SECOND_VALUE
[docs] @pytest.mark.parametrize("case_sensitive", (True, False, None)) def test_enum_choice_case_sensitivity(case_sensitive: bool) -> None: kwargs = {} if case_sensitive is not None: kwargs["case_sensitive"] = case_sensitive enum_choice = EnumChoice(MyEnum, choice_source=ChoiceSource.VALUE, **kwargs) assert enum_choice.choices == ("first-value", "second-value") # Provide the exact casing. assert enum_choice.convert("first-value", None, None) == MyEnum.FIRST_VALUE assert enum_choice.convert("second-value", None, None) == MyEnum.SECOND_VALUE # Different casing are accepted if case_sensitive is False or not set. if not case_sensitive: assert enum_choice.convert("FIRST-VALUE", None, None) == MyEnum.FIRST_VALUE assert enum_choice.convert("SeCoNd-VaLuE", None, None) == MyEnum.SECOND_VALUE # Strict casing is required if case_sensitive is True. else: with pytest.raises(BadParameter) as exc_info: enum_choice.convert("SeCoNd-VaLuE", None, None) assert exc_info.value.args[0] == ( "'SeCoNd-VaLuE' is not one of 'first-value', 'second-value'." )
[docs] def test_enum_choice_duplicate_string() -> None: class BadEnum(StrEnum): FIRST = auto() SECOND = auto() def __str__(self) -> str: return "constant-str" with pytest.raises(ValueError) as exc_info: EnumChoice(BadEnum, choice_source=ChoiceSource.STR) assert exc_info.value.args[0] == ( "<enum 'BadEnum'> has duplicated choice string 'constant-str' for members " "<BadEnum.FIRST: 'first'> and <BadEnum.SECOND: 'second'> when using " "<ChoiceSource.STR: 'str'>." )
[docs] @pytest.mark.parametrize( # XXX Got a strange issue with double <Option my_enum> in the cli() # with the click_extra.command(), hence the no_extra=True parameter here. "cmd_decorator", command_decorators(no_groups=True, no_extra=True), ) @pytest.mark.parametrize( "opt_decorator", option_decorators(no_arguments=True, with_parenthesis=False) ) @pytest.mark.parametrize( ("case_sensitive", "valid_args", "invalid_args"), ( ( # Case-insensitive mode. False, ( # Exact choice strings. (["--my-enum", "my-first-value"], MyEnum.FIRST_VALUE), (["--my-enum", "my-second-value"], MyEnum.SECOND_VALUE), (["--my-enum", str(MyEnum.FIRST_VALUE)], MyEnum.FIRST_VALUE), (["--my-enum", str(MyEnum.SECOND_VALUE)], MyEnum.SECOND_VALUE), # Case variations are accepted. (["--my-enum", "MY-FIRST-VALUE"], MyEnum.FIRST_VALUE), (["--my-enum", "My-Second-Value"], MyEnum.SECOND_VALUE), # Enum members. (["--my-enum", MyEnum.FIRST_VALUE], MyEnum.FIRST_VALUE), (["--my-enum", MyEnum.SECOND_VALUE], MyEnum.SECOND_VALUE), # Empty input defaults to None. ([], None), ), ( ["--my-enum", "FIRST_VALUE"], ["--my-enum", "first_value"], ["--my-enum", "my_second_value"], ["--my-enum", MyEnum.FIRST_VALUE.name], ["--my-enum", MyEnum.FIRST_VALUE.value], # Garbage types. ["--my-enum", 123], ["--my-enum", 45.67], ["--my-enum", True], ["--my-enum", False], # Missing and blank values. ["--my-enum"], ["--my-enum", None], ["--my-enum", ""], ["--my-enum", UNSET], ), ), ( # Case-sensitive mode. True, ( # Exact choice strings. (["--my-enum", "my-first-value"], MyEnum.FIRST_VALUE), (["--my-enum", "my-second-value"], MyEnum.SECOND_VALUE), (["--my-enum", str(MyEnum.FIRST_VALUE)], MyEnum.FIRST_VALUE), (["--my-enum", str(MyEnum.SECOND_VALUE)], MyEnum.SECOND_VALUE), # Enum members. (["--my-enum", MyEnum.FIRST_VALUE], MyEnum.FIRST_VALUE), (["--my-enum", MyEnum.SECOND_VALUE], MyEnum.SECOND_VALUE), # Empty input defaults to None. ([], None), ), ( ["--my-enum", "FIRST_VALUE"], ["--my-enum", "first_value"], ["--my-enum", "my_second_value"], ["--my-enum", MyEnum.FIRST_VALUE.name], ["--my-enum", MyEnum.FIRST_VALUE.value], # Case variations are rejected. ["--my-enum", "MY-FIRST-VALUE"], ["--my-enum", "My-Second-Value"], # Garbage types. ["--my-enum", 123], ["--my-enum", 45.67], ["--my-enum", True], ["--my-enum", False], # Missing and blank values. ["--my-enum"], ["--my-enum", None], ["--my-enum", ""], ["--my-enum", UNSET], ), ), ), ) def test_enum_choice_command( invoke, cmd_decorator, opt_decorator, case_sensitive, valid_args, invalid_args ) -> None: """Test EnumChoice used within an option.""" @cmd_decorator @opt_decorator("--my-enum", type=EnumChoice(MyEnum, case_sensitive=case_sensitive)) def cli(my_enum: MyEnum) -> None: echo(f"my_enum: {my_enum!r}") # Test valid input. for args, expected_member in valid_args: result = invoke(cli, args) assert result.stdout == f"my_enum: {expected_member!r}\n" assert not result.stderr assert result.exit_code == 0 # Test invalid inputs. for args in invalid_args: result = invoke(cli, args) assert not result.stdout if len(args) == 2 and args[1] is not None: # Invalid value provided. msg = ( "Error: Invalid value for '--my-enum': " f"'{args[1]}' is not one of 'my-first-value', 'my-second-value'." ) else: # Missing value. msg = "Error: Option '--my-enum' requires an argument." assert msg in result.stderr assert result.exit_code == 2 # Test help message. result = invoke(cli, ["--help"], color=False) assert "--my-enum [my-first-value|my-second-value]" in result.stdout assert not result.stderr assert result.exit_code == 0
[docs] @pytest.mark.parametrize("cmd_decorator", command_decorators(no_groups=True)) @pytest.mark.parametrize( ("opt_decorator", "opt_type"), option_decorators(no_arguments=True, with_parenthesis=False, with_types=True), ) @pytest.mark.parametrize( "default_value", (MyEnum.SECOND_VALUE, str(MyEnum.SECOND_VALUE), "my-second-value") ) def test_enum_choice_default_value( invoke, cmd_decorator, opt_decorator, opt_type, default_value ) -> None: """Test EnumChoice used within an option with a default value.""" @cmd_decorator @opt_decorator( "--my-enum", type=EnumChoice(MyEnum), default=default_value, show_default=True ) def cli(my_enum: MyEnum) -> None: echo(f"my_enum: {my_enum!r}") # Test default value is used when option is not provided. result = invoke(cli) assert result.stdout == "my_enum: <MyEnum.SECOND_VALUE: 'second-value'>\n" assert not result.stderr assert result.exit_code == 0 # Test help message showing the default. result = invoke(cli, ["--help"], color=False) assert "--my-enum [my-first-value|my-second-value]" in result.stdout # @click_extra.command fix the rendering of Enum default, but not the other # vanilla decorators. if "extra" not in opt_type and isinstance(default_value, MyEnum): default_rendering = "SECOND_VALUE" else: default_rendering = default_value assert f"[default: {default_rendering}]" in result.stdout assert not result.stderr assert result.exit_code == 0