diff --git a/src/specify_cli/workflows/engine.py b/src/specify_cli/workflows/engine.py index aff5e92e2..0e11a6b7d 100644 --- a/src/specify_cli/workflows/engine.py +++ b/src/specify_cli/workflows/engine.py @@ -1010,7 +1010,12 @@ class WorkflowEngine: value = float(value) if value == int(value): value = int(value) - except (ValueError, TypeError): + except (ValueError, TypeError, OverflowError): + # OverflowError: `int(value)` raises it for an infinite float + # (e.g. a `default: .inf` authoring mistake), which would + # otherwise escape validate_workflow's `except ValueError` and + # break its "return errors, never raise" contract. Surface it as + # the same clean "expected a number" error as NaN does. msg = f"Input {name!r} expected a number, got {value!r}." raise ValueError(msg) from None elif input_type == "boolean": diff --git a/tests/test_workflows.py b/tests/test_workflows.py index b239cb9a4..cee02c46b 100644 --- a/tests/test_workflows.py +++ b/tests/test_workflows.py @@ -2846,6 +2846,47 @@ steps: errors = validate_workflow(definition) assert any("invalid default" in e for e in errors), errors + def test_coerce_number_input_rejects_infinity_cleanly(self): + """An infinite float must surface as a clean ValueError (like NaN), not + let ``int(inf)``'s OverflowError escape: ``int()`` of an infinity raises + OverflowError, which is not ValueError/TypeError. + """ + from specify_cli.workflows.engine import WorkflowEngine + + for value in (float("inf"), float("-inf"), "inf", "Infinity", "-inf"): + with pytest.raises(ValueError, match="expected a number"): + WorkflowEngine._coerce_input("count", value, {"type": "number"}) + # Finite values still coerce (whole floats normalize to int). + assert WorkflowEngine._coerce_input("count", 5.0, {"type": "number"}) == 5 + assert WorkflowEngine._coerce_input("count", 3.5, {"type": "number"}) == 3.5 + + def test_validate_workflow_rejects_infinite_default_for_number_type(self): + """``type: number`` with an infinite default (YAML ``.inf``) must be + reported as an error, not raise. ``int(inf)`` raises OverflowError during + coercion, which previously escaped validate_workflow's ValueError handler + and broke its "return a list of errors" contract. + """ + from specify_cli.workflows.engine import WorkflowDefinition, validate_workflow + + definition = WorkflowDefinition.from_string(""" +schema_version: "1.0" +workflow: + id: "inf-as-number" + name: "Inf As Number" + version: "1.0.0" +inputs: + count: + type: number + default: .inf +steps: + - id: noop + type: gate + message: "noop" + options: [approve] +""") + errors = validate_workflow(definition) + assert any("invalid default" in e for e in errors), errors + def test_validate_workflow_rejects_non_string_default_for_string_type(self): """``type: string`` must require an actual string — a numeric YAML default like ``5`` would otherwise slip through unvalidated.