audit-test 0.2.2__tar.gz → 0.2.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. {audit_test-0.2.2/src/audit_test.egg-info → audit_test-0.2.3}/PKG-INFO +9 -10
  2. {audit_test-0.2.2 → audit_test-0.2.3}/README.md +8 -9
  3. {audit_test-0.2.2 → audit_test-0.2.3}/pyproject.toml +1 -1
  4. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/__init__.py +1 -1
  5. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/quality.py +2 -3
  6. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/runner.py +2 -2
  7. {audit_test-0.2.2 → audit_test-0.2.3/src/audit_test.egg-info}/PKG-INFO +9 -10
  8. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_test.egg-info/SOURCES.txt +2 -0
  9. audit_test-0.2.3/tests/test_cli_focus_ignore.py +202 -0
  10. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_focus.py +17 -0
  11. audit_test-0.2.3/tests/test_quality_helpers.py +44 -0
  12. {audit_test-0.2.2 → audit_test-0.2.3}/LICENSE +0 -0
  13. {audit_test-0.2.2 → audit_test-0.2.3}/LICENSES.md +0 -0
  14. {audit_test-0.2.2 → audit_test-0.2.3}/setup.cfg +0 -0
  15. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/__main__.py +0 -0
  16. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/__init__.py +0 -0
  17. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/base.py +0 -0
  18. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/cpp/__init__.py +0 -0
  19. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/cpp/adapter.py +0 -0
  20. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/csharp/__init__.py +0 -0
  21. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/csharp/adapter.py +0 -0
  22. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/go/__init__.py +0 -0
  23. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/go/adapter.py +0 -0
  24. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/html/__init__.py +0 -0
  25. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/html/adapter.py +0 -0
  26. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/java/__init__.py +0 -0
  27. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/java/adapter.py +0 -0
  28. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/javascript/__init__.py +0 -0
  29. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/javascript/adapter.py +0 -0
  30. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/python/__init__.py +0 -0
  31. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/python/adapter.py +0 -0
  32. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/rust/__init__.py +0 -0
  33. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/rust/adapter.py +0 -0
  34. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/sql/__init__.py +0 -0
  35. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/adapters/sql/adapter.py +0 -0
  36. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/audit_config.py +0 -0
  37. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/audit_deps.py +0 -0
  38. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/audit_gate.py +0 -0
  39. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/audit_phd.py +0 -0
  40. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/audit_quality.py +0 -0
  41. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/audit_runtime.py +0 -0
  42. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/audit_shared.py +0 -0
  43. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/audit_suite.py +0 -0
  44. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/audit_wiring.py +0 -0
  45. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/cli.py +0 -0
  46. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/config.py +0 -0
  47. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/deps.py +0 -0
  48. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/gate.py +0 -0
  49. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/__init__.py +0 -0
  50. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/_tool_runner.py +0 -0
  51. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/bandit.py +0 -0
  52. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/checkstyle.py +0 -0
  53. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/clang_tidy.py +0 -0
  54. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/clippy.py +0 -0
  55. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/codeql.py +0 -0
  56. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/cppcheck.py +0 -0
  57. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/dependency_scan.py +0 -0
  58. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/dotnet_format.py +0 -0
  59. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/eslint.py +0 -0
  60. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/go_vet.py +0 -0
  61. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/golangci_lint.py +0 -0
  62. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/htmlhint.py +0 -0
  63. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/megalinter.py +0 -0
  64. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/pmd.py +0 -0
  65. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/prettier.py +0 -0
  66. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/rustfmt.py +0 -0
  67. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/secret_scan.py +0 -0
  68. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/semgrep.py +0 -0
  69. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/integrations/stylelint.py +0 -0
  70. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/models.py +0 -0
  71. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/phd.py +0 -0
  72. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/profiles/__init__.py +0 -0
  73. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/profiles/agent_engine/__init__.py +0 -0
  74. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/profiles/agent_engine/config_checks.py +0 -0
  75. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/profiles/agent_engine/profile.py +0 -0
  76. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/profiles/agent_engine/prompt_checks.py +0 -0
  77. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/profiles/agent_engine/stdout_checks.py +0 -0
  78. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/profiles/agent_engine/tool_registry_checks.py +0 -0
  79. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/project.py +0 -0
  80. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/reporting/__init__.py +0 -0
  81. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/reporting/json_report.py +0 -0
  82. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/reporting/junit.py +0 -0
  83. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/reporting/sarif.py +0 -0
  84. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/run_all_audits.py +0 -0
  85. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/runtime.py +0 -0
  86. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/suite.py +0 -0
  87. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_code/wiring.py +0 -0
  88. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_test.egg-info/dependency_links.txt +0 -0
  89. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_test.egg-info/entry_points.txt +0 -0
  90. {audit_test-0.2.2 → audit_test-0.2.3}/src/audit_test.egg-info/top_level.txt +0 -0
  91. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_adapters.py +0 -0
  92. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_audit_misc.py +0 -0
  93. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_audit_phd.py +0 -0
  94. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_audit_quality.py +0 -0
  95. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_audit_runtime.py +0 -0
  96. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_audit_wiring.py +0 -0
  97. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_base.py +0 -0
  98. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_cli.py +0 -0
  99. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_cli_combinatorics.py +0 -0
  100. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_config.py +0 -0
  101. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_coverage.py +0 -0
  102. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_gate.py +0 -0
  103. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_integrations.py +0 -0
  104. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_reporting.py +0 -0
  105. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_runner.py +0 -0
  106. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_smoke.py +0 -0
  107. {audit_test-0.2.2 → audit_test-0.2.3}/tests/test_wrappers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: audit-test
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Code and test verification orchestrator — interrogates code quality AND whether tests genuinely prove correctness
5
5
  License: CC BY-NC-ND 4.0
6
6
  Requires-Python: >=3.10
@@ -31,14 +31,6 @@ All in one shot. Zero config needed.
31
31
  pip install audit-test
32
32
  ```
33
33
 
34
- Three commands — all identical:
35
-
36
- ```powershell
37
- audit-test high
38
- audit-code high
39
- audit-tests high
40
- ```
41
-
42
34
  Or from source:
43
35
 
44
36
  ```powershell
@@ -49,10 +41,17 @@ pip install -e .
49
41
 
50
42
  ## Usage
51
43
 
44
+ Three commands — all identical:
45
+
46
+ ```powershell
47
+ audit-test
48
+ audit-code
49
+ audit-tests
50
+ ```
52
51
  Bare words or flags — both work:
53
52
 
54
53
  ```powershell
55
- audit-test # full audit (HIGH only)
54
+ audit-test # full audit
56
55
  audit-test v # full audit, verbose
57
56
  audit-test f # fix: auto-format (lint+black)
58
57
  audit-test min # min: fast wiring + phd + quality
@@ -20,14 +20,6 @@ All in one shot. Zero config needed.
20
20
  pip install audit-test
21
21
  ```
22
22
 
23
- Three commands — all identical:
24
-
25
- ```powershell
26
- audit-test high
27
- audit-code high
28
- audit-tests high
29
- ```
30
-
31
23
  Or from source:
32
24
 
33
25
  ```powershell
@@ -38,10 +30,17 @@ pip install -e .
38
30
 
39
31
  ## Usage
40
32
 
33
+ Three commands — all identical:
34
+
35
+ ```powershell
36
+ audit-test
37
+ audit-code
38
+ audit-tests
39
+ ```
41
40
  Bare words or flags — both work:
42
41
 
43
42
  ```powershell
44
- audit-test # full audit (HIGH only)
43
+ audit-test # full audit
45
44
  audit-test v # full audit, verbose
46
45
  audit-test f # fix: auto-format (lint+black)
47
46
  audit-test min # min: fast wiring + phd + quality
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "audit-test"
7
- version = "0.2.2"
7
+ version = "0.2.3"
8
8
  description = "Code and test verification orchestrator — interrogates code quality AND whether tests genuinely prove correctness"
9
9
  requires-python = ">=3.10"
10
10
  license = {text = "CC BY-NC-ND 4.0"}
@@ -1,3 +1,3 @@
1
1
  """audit-code: code and test verification orchestrator."""
2
2
 
3
- __version__ = "0.2.2"
3
+ __version__ = "0.2.3"
@@ -471,12 +471,11 @@ def run( # needs fix (god function 550+ lines — decompose into sub-audits)
471
471
  else:
472
472
  tmp = Path(tempfile.mkdtemp(prefix="audit_q5_"))
473
473
  json_file = tmp / "cov.json"
474
- reuse = (
474
+ if (
475
475
  shared_cov is not None
476
476
  and shared_cov.exists()
477
477
  and shared_cov.stat().st_size > 0
478
- )
479
- if reuse:
478
+ ):
480
479
  # The suite audit already ran the whole suite under coverage;
481
480
  # reuse its data instead of a second full test run.
482
481
  data_file = shared_cov
@@ -322,7 +322,7 @@ def _run_one_module(
322
322
  stderr=f"Module {module_name} has no run() function",
323
323
  )
324
324
 
325
- kwargs = {}
325
+ kwargs: dict[str, object] = {}
326
326
  if module_name == "quality" and (mode == "min" or fast):
327
327
  kwargs["fast"] = True # skip coverage
328
328
  if module_name == "quality" and fix:
@@ -332,7 +332,7 @@ def _run_one_module(
332
332
  if module_name == "suite" and shared_cov is not None:
333
333
  kwargs["cov_file"] = shared_cov # instrument the one shared run
334
334
  if module_name == "phd":
335
- kwargs["severity"] = severity # type: ignore[assignment]
335
+ kwargs["severity"] = severity
336
336
 
337
337
  return run_fn(target_root, **kwargs)
338
338
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: audit-test
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Code and test verification orchestrator — interrogates code quality AND whether tests genuinely prove correctness
5
5
  License: CC BY-NC-ND 4.0
6
6
  Requires-Python: >=3.10
@@ -31,14 +31,6 @@ All in one shot. Zero config needed.
31
31
  pip install audit-test
32
32
  ```
33
33
 
34
- Three commands — all identical:
35
-
36
- ```powershell
37
- audit-test high
38
- audit-code high
39
- audit-tests high
40
- ```
41
-
42
34
  Or from source:
43
35
 
44
36
  ```powershell
@@ -49,10 +41,17 @@ pip install -e .
49
41
 
50
42
  ## Usage
51
43
 
44
+ Three commands — all identical:
45
+
46
+ ```powershell
47
+ audit-test
48
+ audit-code
49
+ audit-tests
50
+ ```
52
51
  Bare words or flags — both work:
53
52
 
54
53
  ```powershell
55
- audit-test # full audit (HIGH only)
54
+ audit-test # full audit
56
55
  audit-test v # full audit, verbose
57
56
  audit-test f # fix: auto-format (lint+black)
58
57
  audit-test min # min: fast wiring + phd + quality
@@ -92,11 +92,13 @@ tests/test_audit_wiring.py
92
92
  tests/test_base.py
93
93
  tests/test_cli.py
94
94
  tests/test_cli_combinatorics.py
95
+ tests/test_cli_focus_ignore.py
95
96
  tests/test_config.py
96
97
  tests/test_coverage.py
97
98
  tests/test_focus.py
98
99
  tests/test_gate.py
99
100
  tests/test_integrations.py
101
+ tests/test_quality_helpers.py
100
102
  tests/test_reporting.py
101
103
  tests/test_runner.py
102
104
  tests/test_smoke.py
@@ -0,0 +1,202 @@
1
+ """Tests for the focus/ignore CLI surface and gate wiring in cli.py.
2
+
3
+ These drive the entry-point functions directly (sys.argv + a redirected
4
+ .audit-test-ignore) so the focus/ignore feature is actually exercised under the
5
+ suite rather than only in production subprocesses.
6
+ """
7
+
8
+ import pytest
9
+
10
+ from audit_code import cli
11
+
12
+
13
+ @pytest.fixture
14
+ def ig(tmp_path, monkeypatch):
15
+ """Redirect the module-level ignore-file path to a throwaway tmp file."""
16
+ path = tmp_path / ".audit-test-ignore"
17
+ monkeypatch.setattr(cli, "_IGNORE_PATH", path)
18
+ return path
19
+
20
+
21
+ def _run(monkeypatch, argv, inputs=None):
22
+ """Set argv, feed canned input(), run the matching handler, return exit code."""
23
+ monkeypatch.setattr(cli.sys, "argv", ["audit-code", *argv])
24
+ if inputs is not None:
25
+ it = iter(inputs)
26
+ monkeypatch.setattr("builtins.input", lambda *a, **k: next(it))
27
+ fn = cli._handle_focus if argv and argv[0] == "focus" else cli._handle_ignore
28
+ with pytest.raises(SystemExit) as exc:
29
+ fn()
30
+ code = exc.value.code
31
+ return code if code is not None else 0
32
+
33
+
34
+ # ── pure helpers ─────────────────────────────────────────────────────────────
35
+
36
+
37
+ def test_build_gate_parser_defaults_and_flags():
38
+ p = cli.build_gate_parser()
39
+ ns = p.parse_args(["--path", "x", "--fast", "--kill", "80", "--medium"])
40
+ assert ns.path == "x" and ns.fast is True and ns.kill == 80 and ns.medium is True
41
+ assert p.parse_args([]).kill == 60 # default
42
+
43
+
44
+ def test_ig_read_missing_returns_empty(ig):
45
+ assert cli._ig_read() == []
46
+
47
+
48
+ def test_ig_write_then_read_roundtrips(ig):
49
+ cli._ig_write(["node_modules", "dist"])
50
+ assert cli._ig_read() == ["node_modules", "dist"]
51
+
52
+
53
+ def test_parse_groups_reads_only_block():
54
+ lines = [
55
+ "#path /base",
56
+ "#only",
57
+ "fast=[main.py, cli.py] /proj | quick sweep",
58
+ "# a comment inside",
59
+ "#only",
60
+ ]
61
+ groups, default = cli._parse_groups(lines)
62
+ assert default == "/base"
63
+ assert set(groups) == {"fast"}
64
+ g = groups["fast"]
65
+ # NOTE: the " /" delimiter strips the leading slash — "/proj" is stored as
66
+ # "proj" (a known quirk; see the bug report accompanying these tests).
67
+ assert (
68
+ g.files == ("main.py", "cli.py")
69
+ and g.path == "proj"
70
+ and g.desc == "quick sweep"
71
+ )
72
+
73
+
74
+ def test_rebuild_file_replaces_only_block():
75
+ original = ["node_modules", "#only", "old=[a.py]", "#only", "trailing"]
76
+ groups = {"new": cli._Grp(("b.py",), "", "")}
77
+ out = cli._rebuild_file(original, groups, "")
78
+ assert "node_modules" in out and "trailing" in out
79
+ assert "#only" in out and any("new=[b.py]" in ln for ln in out)
80
+ assert not any("old=[a.py]" in ln for ln in out)
81
+
82
+
83
+ def test_confirm_yes_no_and_twice(monkeypatch):
84
+ monkeypatch.setattr("builtins.input", lambda *a, **k: "y")
85
+ assert cli._confirm("go?") is True
86
+ monkeypatch.setattr("builtins.input", lambda *a, **k: "n")
87
+ assert cli._confirm("go?") is False
88
+ answers = iter(["y", "q"])
89
+ monkeypatch.setattr("builtins.input", lambda *a, **k: next(answers))
90
+ assert cli._confirm("go?", twice=True) is True
91
+ answers = iter(["y", "nope"])
92
+ monkeypatch.setattr("builtins.input", lambda *a, **k: next(answers))
93
+ assert cli._confirm("go?", twice=True) is False
94
+
95
+
96
+ def test_focus_help_and_mode_flags(capsys, monkeypatch):
97
+ cli._focus_help()
98
+ assert "focus" in capsys.readouterr().out
99
+ monkeypatch.setattr(cli.sys, "argv", ["audit-code", "focus", "fast"])
100
+ assert cli._is_focus_mode() is True and cli._is_ignore_mode() is False
101
+ monkeypatch.setattr(cli.sys, "argv", ["audit-code", "ignore", "add", "x"])
102
+ assert cli._is_ignore_mode() is True and cli._is_focus_mode() is False
103
+
104
+
105
+ # ── focus command actions ────────────────────────────────────────────────────
106
+
107
+
108
+ def test_focus_add_del_path_desc(ig, monkeypatch, capsys):
109
+ assert _run(monkeypatch, ["focus", "add", "grp", "a.py", "b.py"]) == 0
110
+ assert "grp" in ig.read_text(encoding="utf-8")
111
+ assert _run(monkeypatch, ["focus", "del", "grp", "a.py"]) == 0
112
+ assert _run(monkeypatch, ["focus", "path", "grp", "/somewhere"]) == 0
113
+ assert _run(monkeypatch, ["focus", "desc", "grp", "my", "desc"]) == 0
114
+ text = ig.read_text(encoding="utf-8")
115
+ assert "b.py" in text and "/somewhere" in text and "my desc" in text
116
+
117
+
118
+ def test_focus_info_and_missing_group(ig, monkeypatch, capsys):
119
+ _run(monkeypatch, ["focus", "add", "grp", "a.py"])
120
+ assert _run(monkeypatch, ["focus", "info"]) == 0
121
+ assert "grp" in capsys.readouterr().out
122
+ assert _run(monkeypatch, ["focus", "info", "nope"]) == 2
123
+
124
+
125
+ def test_focus_clear_confirmed(ig, monkeypatch):
126
+ _run(monkeypatch, ["focus", "add", "grp", "a.py"])
127
+ assert _run(monkeypatch, ["focus", "clear", "grp"], inputs=["y"]) == 0
128
+ groups, _ = cli._parse_groups(cli._ig_read())
129
+ assert "grp" not in groups
130
+
131
+
132
+ def test_focus_help_action(monkeypatch):
133
+ assert _run(monkeypatch, ["focus", "help"]) == 0
134
+
135
+
136
+ def test_focus_run_missing_group_exits_2(ig, monkeypatch):
137
+ assert _run(monkeypatch, ["focus", "ghost"]) == 2
138
+
139
+
140
+ def test_focus_run_invokes_audit(ig, monkeypatch):
141
+ _run(monkeypatch, ["focus", "add", "grp", "a.py"])
142
+ called = {}
143
+
144
+ def fake_run_audit(ns):
145
+ called["path"] = ns.path
146
+ return 0
147
+
148
+ monkeypatch.setattr(cli, "run_audit", fake_run_audit)
149
+ assert _run(monkeypatch, ["focus", "grp", "high"]) == 0
150
+ assert called # run_audit was reached through _focus_run
151
+
152
+
153
+ # ── ignore command actions ───────────────────────────────────────────────────
154
+
155
+
156
+ def test_ignore_add_del_info_clear(ig, monkeypatch, capsys):
157
+ assert _run(monkeypatch, ["ignore", "add", "mydir"]) == 0
158
+ assert "mydir" in ig.read_text(encoding="utf-8")
159
+ assert _run(monkeypatch, ["ignore", "info"]) == 0
160
+ assert "mydir" in capsys.readouterr().out
161
+ assert _run(monkeypatch, ["ignore", "del", "mydir"]) == 0
162
+ assert _run(monkeypatch, ["ignore", "del", "ghost"]) == 2
163
+ _run(monkeypatch, ["ignore", "add", "again"])
164
+ assert _run(monkeypatch, ["ignore", "clear"], inputs=["y"]) == 0
165
+ assert "again" not in ig.read_text(encoding="utf-8")
166
+
167
+
168
+ def test_ignore_help_and_unknown(monkeypatch):
169
+ assert _run(monkeypatch, ["ignore", "help"]) == 0
170
+ assert _run(monkeypatch, ["ignore", "bogus"]) == 2
171
+
172
+
173
+ # ── main() dispatch branches ─────────────────────────────────────────────────
174
+
175
+
176
+ def test_main_routes_to_focus(monkeypatch):
177
+ monkeypatch.setattr(cli.sys, "argv", ["audit-code", "focus", "help"])
178
+ monkeypatch.setattr(cli, "_force_utf8_output", lambda: None)
179
+ with pytest.raises(SystemExit):
180
+ cli.main()
181
+
182
+
183
+ def test_main_routes_to_gate(monkeypatch):
184
+ monkeypatch.setattr(cli.sys, "argv", ["audit-code", "gate", "--fast"])
185
+ monkeypatch.setattr(cli, "_force_utf8_output", lambda: None)
186
+ monkeypatch.setattr(cli, "run_gate_cmd", lambda args: 0)
187
+ with pytest.raises(SystemExit) as exc:
188
+ cli.main()
189
+ assert (exc.value.code or 0) == 0
190
+
191
+
192
+ def test_run_gate_cmd_delegates(monkeypatch):
193
+ monkeypatch.setattr(cli, "find_target_root", lambda p: "root")
194
+ monkeypatch.setattr(cli, "gate_main", lambda root, **kw: 7)
195
+
196
+ class NS:
197
+ path = None
198
+ fast = True
199
+ no_static = False
200
+ kill = 60
201
+
202
+ assert cli.run_gate_cmd(NS()) == 7
@@ -53,6 +53,23 @@ def test_skip_parts_win_over_focus(monkeypatch):
53
53
  assert sh.should_audit(Path("__pycache__/quality.py")) is False
54
54
 
55
55
 
56
+ def test_parse_group_valid_forms():
57
+ name, g = sh._parse_group("fast=[a.py, b.py]", "def_path")
58
+ assert name == "fast" and g.files == ("a.py", "b.py") and g.path == "def_path"
59
+
60
+ name, g = sh._parse_group("slow=[x.py] /somewhere", "def_path")
61
+ # " /" delimiter strips the leading slash (known quirk)
62
+ assert name == "slow" and g.path == "somewhere"
63
+
64
+ name, g = sh._parse_group("d=[x.py] /p | full sweep", "")
65
+ assert g.description == "full sweep"
66
+
67
+
68
+ def test_parse_group_rejects_malformed():
69
+ assert sh._parse_group("not a group line", "") is None
70
+ assert sh._parse_group("bad=[x.py", "") is None # no closing bracket
71
+
72
+
56
73
  def test_quality_py_files_respects_focus(tmp_path, monkeypatch):
57
74
  root = tmp_path / "proj"
58
75
  (root / "tests").mkdir(parents=True)
@@ -0,0 +1,44 @@
1
+ """Tests for quality.py's low-level helpers (_run subprocess wrapper, _def_spans)."""
2
+
3
+ import sys
4
+
5
+ from audit_code import quality as q
6
+
7
+
8
+ def test_run_success_captures_output(tmp_path):
9
+ rc, out = q._run([sys.executable, "-c", "print('hello-run')"], tmp_path)
10
+ assert rc == 0 and "hello-run" in out
11
+
12
+
13
+ def test_run_missing_binary_returns_minus_two(tmp_path):
14
+ rc, out = q._run(["definitely-not-a-real-binary-xyz"], tmp_path)
15
+ assert rc == -2 and "failed to launch" in out
16
+
17
+
18
+ def test_run_timeout_returns_minus_one(tmp_path):
19
+ rc, out = q._run(
20
+ [sys.executable, "-c", "import time; time.sleep(5)"], tmp_path, timeout=1
21
+ )
22
+ assert rc == -1 and "timed out" in out
23
+
24
+
25
+ def test_def_spans_extracts_functions_and_methods(tmp_path):
26
+ src = tmp_path / "sample.py"
27
+ src.write_text(
28
+ "def top():\n"
29
+ " return 1\n"
30
+ "\n"
31
+ "class C:\n"
32
+ " def method(self):\n"
33
+ " pass\n",
34
+ encoding="utf-8",
35
+ )
36
+ spans = q._def_spans(src)
37
+ names = {s[0] for s in spans}
38
+ assert "top" in names and "C.method" in names
39
+
40
+
41
+ def test_def_spans_syntax_error_returns_empty(tmp_path):
42
+ bad = tmp_path / "broken.py"
43
+ bad.write_text("def broken(:\n", encoding="utf-8")
44
+ assert q._def_spans(bad) == []
File without changes
File without changes
File without changes
File without changes