diff --git a/src/specify_cli/workflows/steps/gate/__init__.py b/src/specify_cli/workflows/steps/gate/__init__.py index 8882af48a..e686a6a24 100644 --- a/src/specify_cli/workflows/steps/gate/__init__.py +++ b/src/specify_cli/workflows/steps/gate/__init__.py @@ -168,7 +168,11 @@ class GateStep(StepBase): except (EOFError, KeyboardInterrupt): print() return options[-1] # default to last (usually reject) - if raw.isdigit() and 1 <= int(raw) <= len(options): + # isdecimal() (not isdigit()): int() accepts exactly the decimal-digit + # set, whereas isdigit() also returns True for superscripts/subscripts + # (e.g. "²") that int() then rejects with ValueError — crashing + # this interactive loop. + if raw.isdecimal() and 1 <= int(raw) <= len(options): return options[int(raw) - 1] # Also accept the option name directly if raw.lower() in [o.lower() for o in options]: diff --git a/tests/test_workflows.py b/tests/test_workflows.py index d87e4a178..6ea7e9c31 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -2145,6 +2145,19 @@ class TestGateStep: assert result.status == StepStatus.COMPLETED assert result.output["choice"] == "approve" + def test_interactive_prompt_rejects_non_decimal_digit(self, monkeypatch, capsys): + """A Unicode digit int() can't parse — e.g. the superscript '²', which + str.isdigit() accepts but int() rejects — must be treated as an invalid + choice, not crash the prompt loop with an uncaught ValueError.""" + from specify_cli.workflows.steps.gate import GateStep + + _force_gate_stdin(monkeypatch, tty=True) + inputs = iter(["²", "1"]) # superscript-two, then a real "1" + monkeypatch.setattr("builtins.input", lambda _prompt="": next(inputs)) + + choice = GateStep._prompt("Review the spec.", ["approve", "reject"]) + assert choice == "approve" + def test_interactive_prompt_missing_show_file_does_not_crash( self, tmp_path, monkeypatch, capsys ):