mirror of
https://github.com/github/spec-kit.git
synced 2026-07-03 12:28:06 +08:00
fix: fail loudly on an unknown workflow expression filter (#3074)
* fix: fail loudly on an unknown workflow expression filter
The expression evaluator's filter dispatch fell through to `return value`
for any unregistered filter, so a typo'd or unsupported filter such as
`{{ items | length }}` rendered the value unchanged with no error and the
run completed — a silent wrong result.
Raise a clear ValueError instead, naming the offending filter and the valid
ones, mirroring the strict handling already used for `from_json`. The five
registered filters (default/join/map/contains/from_json) are unchanged; the
`name(arg)` form of an unknown filter is now caught too.
* fix: distinguish a misused registered filter from an unknown one; cover map
Address the review feedback on the unknown-filter fail-loud path:
- A *registered* filter used in an unsupported form (e.g. `| join` or
`| map` with no argument) raised the misleading "unknown filter
'<name>'" — the filter is registered, the syntax isn't. It now raises
a message naming it as a known filter misused. A new
`_REGISTERED_FILTERS` constant drives the distinction.
- `test_registered_filters_unaffected` now also exercises `map('attr')`,
which it previously claimed to cover but didn't. Add
`test_registered_filter_unsupported_form_raises` to pin the new path.
* fix: include the no-arg default form in the filter-error hint
Copilot review: the hint listed default('x') but omitted the valid
no-argument default form (| default), which this module supports.
This commit is contained in:
@@ -342,6 +342,73 @@ class TestExpressions:
|
||||
"{{ steps.emit.output.stdout | " + bad + " }}", ctx
|
||||
)
|
||||
|
||||
def test_filter_unknown_name_raises(self):
|
||||
# An unregistered filter name must fail loudly rather than silently
|
||||
# returning the unfiltered value (which hides a typo / unsupported
|
||||
# filter as a wrong result).
|
||||
import pytest
|
||||
from specify_cli.workflows.expressions import evaluate_expression
|
||||
from specify_cli.workflows.base import StepContext
|
||||
|
||||
ctx = StepContext(inputs={"items": [1, 2, 3]})
|
||||
with pytest.raises(ValueError, match="unknown filter 'length'"):
|
||||
evaluate_expression("{{ inputs.items | length }}", ctx)
|
||||
|
||||
def test_filter_unknown_name_with_args_raises(self):
|
||||
# The unknown-filter path must also catch the `name(arg)` form, which
|
||||
# otherwise falls through the recognized-args branch silently.
|
||||
import pytest
|
||||
from specify_cli.workflows.expressions import evaluate_expression
|
||||
from specify_cli.workflows.base import StepContext
|
||||
|
||||
ctx = StepContext(inputs={"text": "hello"})
|
||||
with pytest.raises(ValueError, match="unknown filter 'upper'"):
|
||||
evaluate_expression("{{ inputs.text | upper('x') }}", ctx)
|
||||
|
||||
def test_registered_filters_unaffected(self):
|
||||
# Regression: all five registered filters keep working unchanged.
|
||||
from specify_cli.workflows.expressions import evaluate_expression
|
||||
from specify_cli.workflows.base import StepContext
|
||||
|
||||
ctx = StepContext(
|
||||
inputs={
|
||||
"tags": ["a", "b", "c"],
|
||||
"text": "hello world",
|
||||
"missing": "",
|
||||
"rows": [{"id": "a"}, {"id": "b"}],
|
||||
},
|
||||
steps={"emit": {"output": {"stdout": '{"n": 1}'}}},
|
||||
)
|
||||
assert (
|
||||
evaluate_expression("{{ inputs.missing | default('fb') }}", ctx) == "fb"
|
||||
)
|
||||
assert evaluate_expression("{{ inputs.tags | join(', ') }}", ctx) == "a, b, c"
|
||||
assert evaluate_expression("{{ inputs.rows | map('id') }}", ctx) == ["a", "b"]
|
||||
assert (
|
||||
evaluate_expression("{{ inputs.text | contains('world') }}", ctx) is True
|
||||
)
|
||||
assert evaluate_expression(
|
||||
"{{ steps.emit.output.stdout | from_json }}", ctx
|
||||
) == {"n": 1}
|
||||
|
||||
def test_registered_filter_unsupported_form_raises(self):
|
||||
# A *registered* filter used in an unsupported form (e.g. `| join` with
|
||||
# no argument) must fail loudly with a message that names it as a known
|
||||
# filter misused, not as an "unknown filter".
|
||||
import pytest
|
||||
from specify_cli.workflows.expressions import evaluate_expression
|
||||
from specify_cli.workflows.base import StepContext
|
||||
|
||||
ctx = StepContext(inputs={"tags": ["a", "b", "c"]})
|
||||
with pytest.raises(
|
||||
ValueError, match="filter 'join' used in an unsupported form"
|
||||
):
|
||||
evaluate_expression("{{ inputs.tags | join }}", ctx)
|
||||
with pytest.raises(
|
||||
ValueError, match="filter 'map' used in an unsupported form"
|
||||
):
|
||||
evaluate_expression("{{ inputs.tags | map }}", ctx)
|
||||
|
||||
def test_condition_evaluation(self):
|
||||
from specify_cli.workflows.expressions import evaluate_condition
|
||||
from specify_cli.workflows.base import StepContext
|
||||
|
||||
Reference in New Issue
Block a user