fix(tests): strip ANSI escapes from --help output before substring asserts

Typer/rich emits ANSI styling in CI's --help output (e.g. `--metrics`
becomes `-\x1b[0m\x1b[1;36m-metrics`), so literal substring asserts
like `assert "--metrics" in result.output` fail. Locally the test runner
auto-detects no-TTY and produces plain text, masking the issue.

Add a small `_clean()` helper per test file that strips ANSI escape
codes (`\x1b\[[0-9;]*m`) before substring containment checks.
This commit is contained in:
ZdenekSrotyr 2026-05-04 19:43:47 +02:00
parent d311b07d5d
commit 5162c488bb
8 changed files with 63 additions and 23 deletions

View file

@ -2,6 +2,11 @@
from typer.testing import CliRunner from typer.testing import CliRunner
# CI-safety: Typer/rich emits ANSI escapes in --help output. Strip before asserts.
_ANSI_RE = __import__("re").compile(r"\x1b\[[0-9;]*m")
def _clean(s: str) -> str:
return _ANSI_RE.sub("", s)
from cli.commands.admin import admin_app from cli.commands.admin import admin_app
@ -9,6 +14,6 @@ def test_admin_metrics_subcommands_present():
runner = CliRunner() runner = CliRunner()
result = runner.invoke(admin_app, ["metrics", "--help"]) result = runner.invoke(admin_app, ["metrics", "--help"])
assert result.exit_code == 0 assert result.exit_code == 0
assert "import" in result.output assert "import" in _clean(result.output)
assert "export" in result.output assert "export" in _clean(result.output)
assert "validate" in result.output assert "validate" in _clean(result.output)

View file

@ -2,6 +2,11 @@
from typer.testing import CliRunner from typer.testing import CliRunner
# CI-safety: Typer/rich emits ANSI escapes in --help output. Strip before asserts.
_ANSI_RE = __import__("re").compile(r"\x1b\[[0-9;]*m")
def _clean(s: str) -> str:
return _ANSI_RE.sub("", s)
from cli.commands.catalog import catalog_app from cli.commands.catalog import catalog_app
@ -9,8 +14,8 @@ def test_catalog_metrics_help():
runner = CliRunner() runner = CliRunner()
result = runner.invoke(catalog_app, ["--help"]) result = runner.invoke(catalog_app, ["--help"])
assert result.exit_code == 0 assert result.exit_code == 0
assert "--metrics" in result.output assert "--metrics" in _clean(result.output)
assert "--show" in result.output assert "--show" in _clean(result.output)
def test_catalog_default_still_works(): def test_catalog_default_still_works():
@ -20,4 +25,4 @@ def test_catalog_default_still_works():
result = runner.invoke(catalog_app, ["--help"]) result = runner.invoke(catalog_app, ["--help"])
assert result.exit_code == 0 assert result.exit_code == 0
# No traceback # No traceback
assert "Traceback" not in result.output assert "Traceback" not in _clean(result.output)

View file

@ -3,6 +3,11 @@
from typer.testing import CliRunner from typer.testing import CliRunner
from cli.commands.diagnose import diagnose_app from cli.commands.diagnose import diagnose_app
# CI-safety: Typer/rich emits ANSI escapes in --help output. Strip before asserts.
_ANSI_RE = __import__("re").compile(r"\x1b\[[0-9;]*m")
def _clean(s: str) -> str:
return _ANSI_RE.sub("", s)
runner = CliRunner() runner = CliRunner()
@ -15,7 +20,7 @@ def test_diagnose_help_lists_system():
"""Top-level diagnose help should mention the `system` subcommand.""" """Top-level diagnose help should mention the `system` subcommand."""
result = runner.invoke(diagnose_app, ["--help"]) result = runner.invoke(diagnose_app, ["--help"])
assert result.exit_code == 0 assert result.exit_code == 0
assert "system" in result.output assert "system" in _clean(result.output)
def test_diagnose_default_still_works(): def test_diagnose_default_still_works():
@ -24,4 +29,4 @@ def test_diagnose_default_still_works():
result = runner.invoke(diagnose_app, []) result = runner.invoke(diagnose_app, [])
# Either runs successfully or fails for unrelated reasons (no server etc). # Either runs successfully or fails for unrelated reasons (no server etc).
# We just want to verify no traceback from the addition. # We just want to verify no traceback from the addition.
assert "Traceback" not in (result.output + (result.stderr or "")) assert "Traceback" not in (_clean(result.output) + _clean(result.stderr or ''))

View file

@ -2,6 +2,11 @@
from typer.testing import CliRunner from typer.testing import CliRunner
# CI-safety: Typer/rich emits ANSI escapes in --help output. Strip before asserts.
_ANSI_RE = __import__("re").compile(r"\x1b\[[0-9;]*m")
def _clean(s: str) -> str:
return _ANSI_RE.sub("", s)
from cli.commands.init import init_app from cli.commands.init import init_app
runner = CliRunner() runner = CliRunner()
@ -42,10 +47,10 @@ def _make_api_get():
def test_init_help(): def test_init_help():
result = runner.invoke(init_app, ["--help"]) result = runner.invoke(init_app, ["--help"])
assert result.exit_code == 0 assert result.exit_code == 0
assert "--server-url" in result.output assert "--server-url" in _clean(result.output)
assert "--token" in result.output assert "--token" in _clean(result.output)
assert "--force" in result.output assert "--force" in _clean(result.output)
assert "--workspace" in result.output assert "--workspace" in _clean(result.output)
def test_init_writes_expected_files(tmp_path, monkeypatch): def test_init_writes_expected_files(tmp_path, monkeypatch):
@ -130,7 +135,7 @@ def test_init_partial_state_friendly_exit(tmp_path, monkeypatch):
"--workspace", str(workspace), "--workspace", str(workspace),
]) ])
assert result.exit_code == 1 assert result.exit_code == 1
assert "Traceback" not in (result.output + (result.stderr or "")) assert "Traceback" not in (_clean(result.output) + _clean(result.stderr or ''))
def test_init_auth_failed_on_401(tmp_path, monkeypatch): def test_init_auth_failed_on_401(tmp_path, monkeypatch):

View file

@ -2,6 +2,11 @@
from typer.testing import CliRunner from typer.testing import CliRunner
# CI-safety: Typer/rich emits ANSI escapes in --help output. Strip before asserts.
_ANSI_RE = __import__("re").compile(r"\x1b\[[0-9;]*m")
def _clean(s: str) -> str:
return _ANSI_RE.sub("", s)
from cli.commands.pull import pull_app from cli.commands.pull import pull_app
runner = CliRunner() runner = CliRunner()
@ -10,9 +15,9 @@ runner = CliRunner()
def test_pull_help(): def test_pull_help():
result = runner.invoke(pull_app, ["--help"]) result = runner.invoke(pull_app, ["--help"])
assert result.exit_code == 0 assert result.exit_code == 0
assert "--quiet" in result.output assert "--quiet" in _clean(result.output)
assert "--json" in result.output assert "--json" in _clean(result.output)
assert "--dry-run" in result.output assert "--dry-run" in _clean(result.output)
def test_pull_no_server_friendly_exit(tmp_path, monkeypatch): def test_pull_no_server_friendly_exit(tmp_path, monkeypatch):
@ -23,4 +28,4 @@ def test_pull_no_server_friendly_exit(tmp_path, monkeypatch):
result = runner.invoke(pull_app, []) result = runner.invoke(pull_app, [])
# Either exit 1 with hint, or exit 0 if a default server URL applies. # Either exit 1 with hint, or exit 0 if a default server URL applies.
# Either way, there must be no Python traceback in stderr/stdout. # Either way, there must be no Python traceback in stderr/stdout.
assert "Traceback" not in (result.output + (result.stderr or "")) assert "Traceback" not in (_clean(result.output) + _clean(result.stderr or ''))

View file

@ -2,6 +2,11 @@
from typer.testing import CliRunner from typer.testing import CliRunner
# CI-safety: Typer/rich emits ANSI escapes in --help output. Strip before asserts.
_ANSI_RE = __import__("re").compile(r"\x1b\[[0-9;]*m")
def _clean(s: str) -> str:
return _ANSI_RE.sub("", s)
from cli.commands.push import push_app from cli.commands.push import push_app
runner = CliRunner() runner = CliRunner()
@ -10,9 +15,9 @@ runner = CliRunner()
def test_push_help(): def test_push_help():
result = runner.invoke(push_app, ["--help"]) result = runner.invoke(push_app, ["--help"])
assert result.exit_code == 0 assert result.exit_code == 0
assert "--quiet" in result.output assert "--quiet" in _clean(result.output)
assert "--json" in result.output assert "--json" in _clean(result.output)
assert "--dry-run" in result.output assert "--dry-run" in _clean(result.output)
def test_push_no_sessions_no_mkdir(tmp_path, monkeypatch): def test_push_no_sessions_no_mkdir(tmp_path, monkeypatch):

View file

@ -2,6 +2,11 @@
from typer.testing import CliRunner from typer.testing import CliRunner
# CI-safety: Typer/rich emits ANSI escapes in --help output. Strip before asserts.
_ANSI_RE = __import__("re").compile(r"\x1b\[[0-9;]*m")
def _clean(s: str) -> str:
return _ANSI_RE.sub("", s)
from cli.commands.snapshot import snapshot_app from cli.commands.snapshot import snapshot_app
@ -19,7 +24,7 @@ def test_snapshot_create_help():
"--no-estimate", "--no-estimate",
"--force", "--force",
]: ]:
assert flag in result.output assert flag in _clean(result.output)
def test_snapshot_create_no_duckdb_friendly_exit(tmp_path, monkeypatch): def test_snapshot_create_no_duckdb_friendly_exit(tmp_path, monkeypatch):

View file

@ -3,6 +3,11 @@
import json import json
from typer.testing import CliRunner from typer.testing import CliRunner
# CI-safety: Typer/rich emits ANSI escapes in --help output. Strip before asserts.
_ANSI_RE = __import__("re").compile(r"\x1b\[[0-9;]*m")
def _clean(s: str) -> str:
return _ANSI_RE.sub("", s)
from cli.commands.status import status_app from cli.commands.status import status_app
runner = CliRunner() runner = CliRunner()
@ -15,7 +20,7 @@ def test_status_uninitialized_workspace(tmp_path, monkeypatch):
assert result.exit_code in (0, 1) assert result.exit_code in (0, 1)
out = result.output.lower() out = result.output.lower()
assert "no" in out # "Initialized: no" or similar assert "no" in out # "Initialized: no" or similar
assert "agnes init" in result.output # hint to initialize assert "agnes init" in _clean(result.output) # hint to initialize
def test_status_initialized_workspace(tmp_path, monkeypatch): def test_status_initialized_workspace(tmp_path, monkeypatch):
@ -31,7 +36,7 @@ def test_status_initialized_workspace(tmp_path, monkeypatch):
assert result.exit_code == 0 assert result.exit_code == 0
out = result.output.lower() out = result.output.lower()
assert "yes" in out # "Initialized: yes" assert "yes" in out # "Initialized: yes"
assert "1" in result.output # one parquet assert "1" in _clean(result.output) # one parquet
def test_status_json(tmp_path, monkeypatch): def test_status_json(tmp_path, monkeypatch):