Skip to content

Commit 8530e0c

Browse files
committed
tests: add tests for cli output using subprocess.run
1 parent a4a2186 commit 8530e0c

2 files changed

Lines changed: 254 additions & 12 deletions

File tree

.github/workflows/tests.yaml

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,6 @@ jobs:
1111
runs-on: ubuntu-latest
1212

1313
steps:
14-
- name: Install test dependencies
15-
run: |
16-
python -m pip install --upgrade pip
17-
pip install pytest ruff
18-
19-
- name: Run Ruff
20-
run: ruff check .
21-
22-
- name: Run tests
23-
run: pytest
24-
2514
- name: Check out repository
2615
uses: actions/checkout@v4
2716

@@ -33,7 +22,10 @@ jobs:
3322
- name: Install test dependencies
3423
run: |
3524
python -m pip install --upgrade pip
36-
pip install pytest
25+
pip install pytest ruff
26+
27+
- name: Run Ruff
28+
run: ruff check .
3729

3830
- name: Run tests
3931
run: pytest

tests/test_cli.py

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
import json
2+
import subprocess
3+
import sys
4+
5+
6+
def run_cli(*args):
7+
"""
8+
Run the SentinelScan CLI with the provided arguments.
9+
10+
Uses the current Python interpreter so tests work locally and in CI.
11+
"""
12+
return subprocess.run(
13+
[sys.executable, "main.py", *args],
14+
capture_output=True,
15+
text=True,
16+
)
17+
18+
19+
def parse_json_output(result):
20+
"""
21+
Parse CLI stdout as JSON.
22+
"""
23+
return json.loads(result.stdout)
24+
25+
26+
# Ensure JSON mode runs successfully and returns valid JSON
27+
def test_cli_json_output_is_valid_json():
28+
result = run_cli("test_dirs", "--json")
29+
30+
assert result.returncode == 0
31+
32+
data = parse_json_output(result)
33+
34+
assert isinstance(data, list)
35+
assert len(data) > 0
36+
37+
38+
# Ensure JSON findings use the expected schema
39+
def test_cli_json_schema():
40+
result = run_cli("test_dirs", "--json")
41+
42+
assert result.returncode == 0
43+
44+
data = parse_json_output(result)
45+
46+
assert isinstance(data, list)
47+
assert len(data) > 0
48+
49+
required_keys = {"line", "file", "rule", "severity", "value"}
50+
51+
for finding in data:
52+
assert required_keys.issubset(finding.keys())
53+
54+
55+
# Ensure JSON finding fields have the expected data types
56+
def test_cli_json_field_types():
57+
result = run_cli("test_dirs", "--json")
58+
59+
assert result.returncode == 0
60+
61+
data = parse_json_output(result)
62+
63+
assert len(data) > 0
64+
65+
for finding in data:
66+
assert isinstance(finding["line"], int)
67+
assert isinstance(finding["file"], str)
68+
assert isinstance(finding["rule"], str)
69+
assert isinstance(finding["severity"], str)
70+
assert isinstance(finding["value"], str)
71+
72+
73+
# Ensure JSON mode does not include human-readable CLI text
74+
def test_cli_json_output_is_pure_json():
75+
result = run_cli("test_dirs", "--json")
76+
77+
assert result.returncode == 0
78+
79+
assert "Scanning" not in result.stdout
80+
assert "--- Findings ---" not in result.stdout
81+
assert "Total findings" not in result.stdout
82+
83+
data = parse_json_output(result)
84+
assert isinstance(data, list)
85+
86+
87+
# Ensure normal CLI mode prints human-readable scan output
88+
def test_cli_text_output_contains_expected_sections():
89+
result = run_cli("test_dirs")
90+
91+
assert result.returncode == 0
92+
93+
assert "Scanning" in result.stdout
94+
assert "--- Findings ---" in result.stdout
95+
assert "Total findings" in result.stdout
96+
97+
98+
# Ensure normal CLI output contains finding details
99+
def test_cli_text_output_contains_finding_details():
100+
result = run_cli("test_dirs")
101+
102+
assert result.returncode == 0
103+
104+
assert "[HIGH]" in result.stdout
105+
assert "→" in result.stdout
106+
107+
108+
# Ensure HIGH severity filtering works in JSON mode
109+
def test_cli_json_severity_high_filter():
110+
result = run_cli("test_dirs", "--json", "--severity", "HIGH")
111+
112+
assert result.returncode == 0
113+
114+
data = parse_json_output(result)
115+
116+
assert len(data) > 0
117+
assert all(finding["severity"] == "HIGH" for finding in data)
118+
119+
120+
# Ensure MEDIUM severity filtering works in JSON mode
121+
def test_cli_json_severity_medium_filter():
122+
result = run_cli("test_dirs", "--json", "--severity", "MEDIUM")
123+
124+
assert result.returncode == 0
125+
126+
data = parse_json_output(result)
127+
128+
assert len(data) > 0
129+
assert all(finding["severity"] == "MEDIUM" for finding in data)
130+
131+
132+
# Ensure HIGH severity filtering works in text mode
133+
def test_cli_text_severity_high_filter():
134+
result = run_cli("test_dirs", "--severity", "HIGH")
135+
136+
assert result.returncode == 0
137+
138+
assert "[HIGH]" in result.stdout
139+
assert "[MEDIUM]" not in result.stdout
140+
141+
142+
# Ensure MEDIUM severity filtering works in text mode
143+
def test_cli_text_severity_medium_filter():
144+
result = run_cli("test_dirs", "--severity", "MEDIUM")
145+
146+
assert result.returncode == 0
147+
148+
assert "[MEDIUM]" in result.stdout
149+
assert "[HIGH]" not in result.stdout
150+
151+
152+
# Ensure JSON output and severity filtering work together
153+
def test_cli_json_and_severity_combined():
154+
result = run_cli("test_dirs", "--json", "--severity", "HIGH")
155+
156+
assert result.returncode == 0
157+
158+
data = parse_json_output(result)
159+
160+
assert len(data) > 0
161+
assert all(finding["severity"] == "HIGH" for finding in data)
162+
163+
164+
# Ensure invalid severity values are rejected by argparse
165+
def test_cli_invalid_severity_fails():
166+
result = run_cli("test_dirs", "--severity", "CRITICAL")
167+
168+
assert result.returncode != 0
169+
assert "invalid choice" in result.stderr
170+
171+
172+
# Ensure invalid paths are handled gracefully
173+
def test_cli_invalid_path_prints_error():
174+
result = run_cli("does_not_exist")
175+
176+
assert "[ERROR]" in result.stdout or "[ERROR]" in result.stderr
177+
178+
179+
# Ensure benign Python files produce no findings in text mode
180+
def test_cli_no_findings_text_output(tmp_path):
181+
benign_file = tmp_path / "safe.py"
182+
benign_file.write_text('username = "notsecret"\n', encoding="utf-8")
183+
184+
result = run_cli(str(tmp_path))
185+
186+
assert result.returncode == 0
187+
188+
assert "No vulnerabilities found." in result.stdout
189+
190+
191+
# Ensure benign Python files produce an empty JSON list
192+
def test_cli_no_findings_json_output(tmp_path):
193+
benign_file = tmp_path / "safe.py"
194+
benign_file.write_text('username = "notsecret"\n', encoding="utf-8")
195+
196+
result = run_cli(str(tmp_path), "--json")
197+
198+
assert result.returncode == 0
199+
200+
data = parse_json_output(result)
201+
202+
assert data == []
203+
204+
205+
# Ensure CLI can scan temporary directories, not just checked-in fixtures
206+
def test_cli_detects_secret_in_temp_directory(tmp_path):
207+
vulnerable_file = tmp_path / "vulnerable.py"
208+
vulnerable_file.write_text('password = "abcdef"\n', encoding="utf-8")
209+
210+
result = run_cli(str(tmp_path), "--json")
211+
212+
assert result.returncode == 0
213+
214+
data = parse_json_output(result)
215+
216+
assert data == [
217+
{
218+
"line": 1,
219+
"file": str(vulnerable_file),
220+
"rule": "Password",
221+
"severity": "HIGH",
222+
"value": "abcdef",
223+
}
224+
]
225+
226+
227+
# Ensure JSON output can be parsed even after severity filtering removes all findings
228+
def test_cli_json_filter_with_no_matching_findings(tmp_path):
229+
medium_file = tmp_path / "medium.py"
230+
medium_file.write_text('token = "abcdef"\n', encoding="utf-8")
231+
232+
result = run_cli(str(tmp_path), "--json", "--severity", "HIGH")
233+
234+
assert result.returncode == 0
235+
236+
data = parse_json_output(result)
237+
238+
assert data == []
239+
240+
241+
# Ensure text output handles severity filters with no matching findings
242+
def test_cli_text_filter_with_no_matching_findings(tmp_path):
243+
medium_file = tmp_path / "medium.py"
244+
medium_file.write_text('token = "abcdef"\n', encoding="utf-8")
245+
246+
result = run_cli(str(tmp_path), "--severity", "HIGH")
247+
248+
assert result.returncode == 0
249+
250+
assert "No vulnerabilities found." in result.stdout

0 commit comments

Comments
 (0)