Files
presidio/presidio-cli/tests/test_config.py
T
Dmitry Voropaev 645dfa1cc6 fix(cli): fix --no-warnings, exit code, github output and threshold config (#2266)
* fix(cli): make --no-warnings, the exit code and -f github work

PIIProblem never got the `level` attribute that show_problems() reads for
--no-warnings, so the flag crashed with AttributeError as soon as a file
had a finding. Findings now have level "error" (score 1.0) or "warning",
the same split the colored output already used.

show_problems() always returned 0 and run() overwrote its result for every
file, so the CLI exited with 0 even when it reported PII. show_problems()
now returns the number of findings it printed and run() adds them up, so
the exit code is 1 when any finding is reported.

-f github printed `::<score> file=...`, which is not a workflow command, so
GitHub Actions never created annotations. It now prints ::warning and
::error commands with escaped property values and message, and drops the
./ prefix that `presidio .` adds to file paths.

The problems test fixture used Mock objects, which create any attribute on
access and so hid the missing `level`; it now builds real PIIProblem
objects.

* fix(cli): validate the threshold read from the config file

PresidioCLIConfig.parse() range-checked the current threshold (the default
0) instead of the configured value, and only converted the configured
value afterwards. `threshold: 5` was accepted and filtered out every
finding, and `threshold: abc` raised an uncaught ValueError. The
configured value is now converted and range-checked before it is stored,
and invalid values raise PresidioCLIConfigError. This also covers YAML
booleans such as `true`, which float() would accept as 1.0, and integers
too large for a float.
2026-09-20 09:15:55 +00:00

91 lines
3.3 KiB
Python

import os
import pytest
from presidio_cli import config
def test_parse_config():
new = config.PresidioCLIConfig(
"entities:\n" " - PERSON\n" " - IP_ADDRESS\n" " - CREDIT_CARD\n" "threshold: 1.0\n" "locale: en_US.UTF-8\n"
)
assert new.entities == ["PERSON", "IP_ADDRESS", "CREDIT_CARD"]
def test_invalid_conf():
with pytest.raises(config.PresidioCLIConfigError):
config.PresidioCLIConfig("not: valid: yaml")
def test_invalid_extend_conf(temp_workspace):
with open(os.path.join(temp_workspace, "notvalid.yml"), "w") as f:
f.write("not: valid: yaml")
with pytest.raises(config.PresidioCLIConfigError):
config.PresidioCLIConfig(os.path.join(temp_workspace, "notvalid.yml"))
def test_not_dict():
with pytest.raises(config.PresidioCLIConfigError):
config.PresidioCLIConfig("example")
def test_unknown_entity():
with pytest.raises(config.PresidioCLIConfigError) as excinfo:
config.PresidioCLIConfig("entities:\n" " - NOTEXISTS\n")
assert "invalid config: no such entity NOTEXISTS" in str(excinfo.value)
def test_is_file(temp_workspace, config):
for f in [
os.path.join(temp_workspace, "empty.txt"),
os.path.join(temp_workspace, "sub", "directory.txt", "empty.txt"),
os.path.join(temp_workspace, "non-ascii", "éçäγλνπ¥", "utf-8"),
os.path.join(temp_workspace, "dos.yml"),
os.path.join(temp_workspace, *["s"] * 15, "file"),
]:
assert config.is_text_file(f)
assert not config.is_text_file(os.path.join(temp_workspace, "binary_file"))
def test_invalid_value(temp_workspace):
with pytest.raises(config.PresidioCLIConfigError):
config.PresidioCLIConfig("ignore: 1\n")
with pytest.raises(config.PresidioCLIConfigError):
config.PresidioCLIConfig("locale: 1\n")
def test_run_with_ignored_path(temp_workspace):
new = config.PresidioCLIConfig("ignore: |\n" " .git\n" " s/*\n" " dos.yml\n")
assert new.is_file_ignored("./dos.yml")
assert new.is_file_ignored("./.git/hooks/README.sample")
assert not new.is_file_ignored("notignored")
@pytest.mark.parametrize(
("value", "message"),
[
("5", "Invalid threshold value: 5. Threshold must be between 0 and 1"),
("-0.1", "Invalid threshold value: -0.1. Threshold must be between 0 and 1"),
(".nan", "Invalid threshold value: nan. Threshold must be between 0 and 1"),
("abc", "Invalid threshold value: abc. Threshold must be a number"),
("[]", "Invalid threshold value: []. Threshold must be a number"),
("true", "Invalid threshold value: True. Threshold must be a number"),
pytest.param(
"1" + "0" * 400,
f"Invalid threshold value: 1{'0' * 400}. Threshold must be a number",
id="int-too-large-for-float",
),
],
)
def test_invalid_threshold_raises_config_error(value, message):
with pytest.raises(config.PresidioCLIConfigError) as excinfo:
config.PresidioCLIConfig(f"threshold: {value}\n")
assert str(excinfo.value) == message
@pytest.mark.parametrize(("value", "expected"), [("0", 0.0), ("0.7", 0.7), ("1", 1.0)])
def test_threshold_is_read_from_config(value, expected):
assert config.PresidioCLIConfig(f"threshold: {value}\n").threshold == expected