Option C Tools
Loading...
Searching...
No Matches
test_quality_gate_workspace_secrets.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3# tests/tests_git/test_quality_gate_workspace_secrets.py
4
5"""
6Purpose
7-------
8Regression coverage for the workspace-mode secret-scanner fix
9(P2 of the drift resolution plan): when ``run_quality_gate_on_files``
10runs from a workspace root, per-subproject ``entropy_exclusions``
11configured in ``<sp>/.option_c/octrc.json`` must apply to files in
12that subproject. Path patterns are prefixed with the subproject's
13workspace-relative path. The new ``module_self_doc`` context value
14must be honoured alongside ``test_directory`` for ``path_pattern``
15exclusions and accepted by the octrc schema validator.
16
17Responsibilities
18----------------
19- Build a minimal workspace + subproject + manifest.
20- Plant a high-entropy string in a target Python file.
21- Verify the scanner finds it WITHOUT a config (baseline).
22- Verify the scanner skips it WITH a per-subproject ``module_self_doc``
23 exclusion when invoked from the workspace root.
24- Verify the schema validator accepts ``module_self_doc``.
25
26Diagnostics
27-----------
28Domain: GIT-TESTS
29Levels:
30 L2 — test lifecycle
31 L3 — per-assertion details
32
33Contracts
34---------
35- Tests run hermetically inside ``tmp_path``; no real workspace is touched.
36- Exclusion semantics are verified end-to-end through
37 :func:`oct.git.quality_gate.run_quality_gate_on_files`.
38
39Dependencies
40------------
41- oct.git.quality_gate (run_quality_gate_on_files entry point under test)
42- oct.linter.oct_lint (_validate_octrc_schema for context validation)
43"""
44
45from __future__ import annotations
46
47import json
48from pathlib import Path
49
50import pytest
51
52from oct.git.quality_gate import run_quality_gate_on_files
53from oct.linter.oct_lint import _validate_octrc_schema
54
55
56HIGH_ENTROPY_FILE = '''\
57#!/usr/bin/env python3
58# -*- coding: utf-8 -*-
59# subproj/help_text.py
60
61"""
62Purpose
63-------
64Help-text holder.
65
66Responsibilities
67----------------
68- Hold strings.
69
70Diagnostics
71-----------
72Domain: TEST
73Levels:
74 L2 -- lifecycle
75 L3 -- details
76 L4 -- tracing
77
78Contracts
79---------
80- Strings are constants.
81
82Dependencies
83------------
84"""
85
86# A long, high-entropy literal that would trip the entropy scanner
87# at the default code threshold of 4.5 bits/char. Mixed-case
88# alphanumerics with rare characters push entropy well above 4.5.
89HELP_BLURB = "aB3kQ9pZ2vMx7Lw5fT8r1NjY6sH4dGcPe0iVoUbXqMnRtKaCyEbWlIuFhDsAjVgZ"
90'''
91
92
93def _make_workspace(tmp_path: Path, subproj_octrc: dict | None) -> Path:
94 """Build a minimal workspace at ``tmp_path/ws`` with one subproject.
95
96 Returns the workspace root.
97 """
98 ws_root = tmp_path / "ws"
99 ws_root.mkdir()
100 (ws_root / ".option_c_workspace.json").write_text(
101 json.dumps({
102 "version": "1.0",
103 "name": "test-ws",
104 "subprojects": [
105 {"path": "sub", "name": "sub"},
106 ],
107 }),
108 encoding="utf-8",
109 )
110
111 sub_root = ws_root / "sub"
112 sub_root.mkdir()
113
114 if subproj_octrc is not None:
115 (sub_root / ".option_c").mkdir()
116 (sub_root / ".option_c" / "octrc.json").write_text(
117 json.dumps(subproj_octrc),
118 encoding="utf-8",
119 )
120
121 target = sub_root / "help_text.py"
122 target.write_text(HIGH_ENTROPY_FILE, encoding="utf-8")
123 return ws_root
124
125
127 """Without any subproject octrc, the high-entropy literal is flagged."""
128 ws_root = _make_workspace(tmp_path, subproj_octrc=None)
129 target = ws_root / "sub" / "help_text.py"
130
131 result = run_quality_gate_on_files(
132 files=[target],
133 project_root=ws_root,
134 profile="compact",
135 )
136
137 assert result.secrets_findings, "expected high-entropy literal to flag"
138 assert any(
139 "help_text.py" in finding[0] and "high-entropy" in finding[2].lower()
140 for finding in result.secrets_findings
141 ), result.secrets_findings
142
143
145 """Subproject's module_self_doc path_pattern is honoured at workspace scope.
146
147 The pattern ``help_text.py`` (subproject-relative) is prefixed with
148 ``sub/`` by the workspace merge, so the workspace-relative file
149 path ``sub/help_text.py`` matches and entropy is exempted.
150 """
151 octrc = {
152 "secret_scanner": {
153 "entropy_exclusions": [
154 {
155 "path_pattern": "help_text.py",
156 "context": "module_self_doc",
157 "reason": "Self-documenting help text",
158 "restrictions": {
159 "exempt_entropy_only": True,
160 "require_waiver_for_secret_patterns": True,
161 },
162 },
163 ],
164 },
165 }
166 ws_root = _make_workspace(tmp_path, subproj_octrc=octrc)
167 target = ws_root / "sub" / "help_text.py"
168
169 result = run_quality_gate_on_files(
170 files=[target],
171 project_root=ws_root,
172 profile="compact",
173 )
174
175 assert not result.secrets_findings, (
176 f"expected no findings; got {result.secrets_findings}"
177 )
178
179
181 """The schema validator accepts the new ``module_self_doc`` context."""
182 cfg = {
183 "secret_scanner": {
184 "entropy_exclusions": [
185 {
186 "path_pattern": "oct/cli.py",
187 "context": "module_self_doc",
188 "reason": "self-doc",
189 "restrictions": {"exempt_entropy_only": True},
190 },
191 ],
192 },
193 }
194 errors = _validate_octrc_schema(cfg)
195 assert errors == [], errors
196
197
199 """Unknown ``context`` values are still rejected (regression guard)."""
200 cfg = {
201 "secret_scanner": {
202 "entropy_exclusions": [
203 {
204 "path_pattern": "x/",
205 "context": "totally_made_up",
206 "reason": "nope",
207 },
208 ],
209 },
210 }
211 errors = _validate_octrc_schema(cfg)
212 assert any("context" in e for e in errors), errors
Path _make_workspace(Path tmp_path, dict|None subproj_octrc)