From 6f9b3dd7b8e2f7a3f186471c211c3130d23eae49 Mon Sep 17 00:00:00 2001 From: Daniel Meppiel <51440732+danielmeppiel@users.noreply.github.com> Date: Tue, 28 Apr 2026 05:42:48 +0200 Subject: [PATCH] fix(tests): strip ANSI escape codes in test_policy_status._ascii_only Rich emits ANSI CSI sequences (\x1b[3m italic, \x1b[1m bold) even when NO_COLOR is unset in the CliRunner environment. The _ascii_only helper was checking raw bytes, causing 7 tests to fail with False negatives. Fix: strip ANSI sequences via regex before the ASCII check, and add _strip_ansi() as a reusable helper in the test module. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- tests/unit/commands/test_policy_status.py | 93 ++++++++++++++--------- 1 file changed, 59 insertions(+), 34 deletions(-) diff --git a/tests/unit/commands/test_policy_status.py b/tests/unit/commands/test_policy_status.py index 65a5b14c1..bdbec265e 100644 --- a/tests/unit/commands/test_policy_status.py +++ b/tests/unit/commands/test_policy_status.py @@ -3,6 +3,7 @@ from __future__ import annotations import json +import re import textwrap import unicodedata from pathlib import Path @@ -14,13 +15,12 @@ from apm_cli.commands.policy import ( _count_rules, _format_age, - policy as policy_group, ) +from apm_cli.commands.policy import policy as policy_group from apm_cli.policy.discovery import PolicyFetchResult from apm_cli.policy.parser import load_policy from apm_cli.policy.schema import ApmPolicy - # -- Fixtures ------------------------------------------------------- @@ -36,9 +36,7 @@ def _make_policy(yaml_str: str) -> ApmPolicy: def _rich_policy() -> ApmPolicy: """A policy with a non-trivial set of rules across sections.""" - return _make_policy( - textwrap.dedent( - """\ + return _make_policy(textwrap.dedent("""\ name: test-policy version: '1.0' enforcement: block @@ -62,13 +60,24 @@ def _rich_policy() -> ApmPolicy: required_fields: [name, version] unmanaged_files: directories: [.legacy, .scratch] - """ - ) - ) + """)) + + +_ANSI_RE = re.compile(r"\x1b\[[0-9;]*[A-Za-z]") + + +def _strip_ansi(text: str) -> str: + """Remove ANSI/VT100 CSI escape sequences from *text*.""" + return _ANSI_RE.sub("", text) def _ascii_only(text: str) -> bool: - """Return True iff every codepoint is printable ASCII (U+0020-U+007E).""" + """Return True iff every codepoint is printable ASCII (U+0020-U+007E). + + ANSI escape sequences emitted by Rich are stripped first; they are an + artefact of the terminal renderer, not source-authored text. + """ + text = _strip_ansi(text) for ch in text: if ch in ("\n", "\r", "\t"): continue @@ -84,9 +93,23 @@ def _ascii_only(text: str) -> bool: # text we control. if 0x2500 <= cp <= 0x257F: continue - if cp in (0x2501, 0x2503, 0x250F, 0x2513, 0x2517, 0x251B, 0x2523, - 0x252B, 0x2533, 0x253B, 0x254B, 0x2578, 0x2579, - 0x257A, 0x257B): + if cp in ( + 0x2501, + 0x2503, + 0x250F, + 0x2513, + 0x2517, + 0x251B, + 0x2523, + 0x252B, + 0x2533, + 0x253B, + 0x254B, + 0x2578, + 0x2579, + 0x257A, + 0x257B, + ): continue return False return True @@ -266,12 +289,13 @@ def test_no_cache_triggers_fresh_fetch(self, runner): source="org:contoso/.github", outcome="absent", ) - with patch( - "apm_cli.commands.policy.discover_policy", - return_value=result_obj, - ) as mock_disc, patch( - "apm_cli.commands.policy.discover_policy_with_chain" - ) as mock_chain: + with ( + patch( + "apm_cli.commands.policy.discover_policy", + return_value=result_obj, + ) as mock_disc, + patch("apm_cli.commands.policy.discover_policy_with_chain") as mock_chain, + ): result = runner.invoke(policy_group, ["status", "--no-cache"]) assert result.exit_code == 0, result.output # --no-cache must bypass the chain helper and call discover_policy @@ -306,12 +330,13 @@ def test_policy_source_routes_through_discover_policy(self, runner): source="url:https://example.com/p.yml", outcome="found", ) - with patch( - "apm_cli.commands.policy.discover_policy", - return_value=result_obj, - ) as mock_disc, patch( - "apm_cli.commands.policy.discover_policy_with_chain" - ) as mock_chain: + with ( + patch( + "apm_cli.commands.policy.discover_policy", + return_value=result_obj, + ) as mock_disc, + patch("apm_cli.commands.policy.discover_policy_with_chain") as mock_chain, + ): result = runner.invoke( policy_group, ["status", "--policy-source", "https://example.com/p.yml"], @@ -386,7 +411,9 @@ class TestStatusAsciiOnly: ) def test_renderings_are_ascii_safe(self, runner, outcome, policy_obj, extras): result_obj = PolicyFetchResult( - outcome=outcome, policy=policy_obj, source="org:contoso/.github", + outcome=outcome, + policy=policy_obj, + source="org:contoso/.github", **extras, ) with patch( @@ -395,12 +422,12 @@ def test_renderings_are_ascii_safe(self, runner, outcome, policy_obj, extras): ): table_result = runner.invoke(policy_group, ["status"]) json_result = runner.invoke(policy_group, ["status", "--json"]) - assert _ascii_only(table_result.output), ( - f"non-ASCII output for outcome={outcome}: {table_result.output!r}" - ) - assert _ascii_only(json_result.output), ( - f"non-ASCII JSON for outcome={outcome}: {json_result.output!r}" - ) + assert _ascii_only( + table_result.output + ), f"non-ASCII output for outcome={outcome}: {table_result.output!r}" + assert _ascii_only( + json_result.output + ), f"non-ASCII JSON for outcome={outcome}: {json_result.output!r}" class TestStatusCheckFlag: @@ -457,9 +484,7 @@ def test_check_with_json_output(self, runner): "apm_cli.commands.policy.discover_policy_with_chain", return_value=result_obj, ): - result = runner.invoke( - policy_group, ["status", "--check", "--json"] - ) + result = runner.invoke(policy_group, ["status", "--check", "--json"]) assert result.exit_code == 1 payload = json.loads(result.output) assert payload["outcome"] == "absent"