From f75e4b39cc9eb82fd1cf1c520785f8bb56de46c0 Mon Sep 17 00:00:00 2001 From: Daniel Meppiel <51440732+danielmeppiel@users.noreply.github.com> Date: Wed, 29 Apr 2026 05:42:35 +0200 Subject: [PATCH] test: strip ANSI codes before ASCII check in test_policy_status _ascii_only() failed on Rich's ANSI escape sequences (bold/italic markers like \x1b[1m, \x1b[3m, \x1b[0m). These are control codes added by the Rich library, not content we author. Add _strip_ansi() helper using a compiled regex and apply it at the top of _ascii_only() so only visible characters are inspected. Fixes 7 pre-existing test failures: TestStatusAsciiOnly::test_renderings_are_ascii_safe[found-*] TestStatusAsciiOnly::test_renderings_are_ascii_safe[absent-*] TestStatusAsciiOnly::test_renderings_are_ascii_safe[cached_stale-*] TestStatusAsciiOnly::test_renderings_are_ascii_safe[disabled-*] Before: 6668 passed, 7 failed After: 6675 passed, 0 failed 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..d109897d1 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_ESCAPE = re.compile(r"\x1b\[[0-9;]*[A-Za-z]") + + +def _strip_ansi(text: str) -> str: + """Strip ANSI escape sequences emitted by Rich before content checks.""" + return _ANSI_ESCAPE.sub("", text) def _ascii_only(text: str) -> bool: - """Return True iff every codepoint is printable ASCII (U+0020-U+007E).""" + """Return True iff every visible codepoint is printable ASCII (U+0020-U+007E). + + ANSI escape sequences (from Rich) are stripped first since they are + terminal control codes, not content authored by APM source strings. + """ + 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"