Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/specify_cli/workflows/_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2376,7 +2376,15 @@ def workflow_info(
console.print(f"\n [bold]Steps ({len(definition.steps)}):[/bold]")
for step in definition.steps:
stype = step.get("type", "command")
console.print(f" → {step.get('id', '?')} [{stype}]")
# Escape the literal bracket (\[) so Rich renders `[<type>]`
# instead of parsing it as a style tag named after the step
# type (which it silently swallows); escape id/type too, as
# the sibling workflow_list does. Mirrors the `\[disabled]`
# precedent above.
console.print(
f" → {_escape_markup(str(step.get('id', '?')))} "
f"\\[{_escape_markup(str(stype))}]"
)
return

# Try catalog
Expand Down
31 changes: 31 additions & 0 deletions tests/test_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -7860,6 +7860,37 @@ def test_lowercase_extension_still_installs(self, temp_dir, monkeypatch, sample_
assert "installed" in result.output


class TestWorkflowInfoStepGraph:
"""`workflow info` must render each step as `→ <id> [<type>]` with LITERAL
brackets. Rich parses an unescaped `[<type>]` as a style tag and silently
swallows it, so the step type would vanish from the output."""

def test_step_type_rendered_in_literal_brackets(self, temp_dir, monkeypatch):
import types

from typer.testing import CliRunner
from specify_cli import app
from specify_cli.workflows.engine import WorkflowEngine

(temp_dir / ".specify" / "workflows").mkdir(parents=True)

fake = types.SimpleNamespace(
name="My WF", id="my-wf", version="1.0.0", author="", description="",
default_integration=None, inputs={},
steps=[{"id": "step-one", "type": "gate"}],
)
monkeypatch.setattr(WorkflowEngine, "load_workflow", lambda self, wid: fake)
monkeypatch.chdir(temp_dir)

result = CliRunner().invoke(app, ["workflow", "info", "my-wf"])

assert result.exit_code == 0, result.output
assert "step-one" in result.output
# The step type must survive as a literal bracketed token, not be eaten
# by Rich as an unknown style tag.
assert "[gate]" in result.output


class TestWorkflowAddSymlinkGuard:
def test_add_malformed_ipv6_url_exits_cleanly(self, temp_dir, monkeypatch):
"""A malformed IPv6 URL must produce a clean error, not a ValueError traceback."""
Expand Down