Skip to content

Commit d8e4534

Browse files
committed
test: cover commit truncation, exit code remap, and Buildkite formatting
Adds and updates unit tests for the v2.3.0 behavior changes: tests/unit/test_cli_config.py - Commit message under 200 chars passes through unchanged - Commit message over 200 chars truncated to exactly 200 - Quote-strip runs BEFORE truncation (250-char inner -> 200) - --exit-code-on-api-error defaults to 3 - --exit-code-on-api-error accepts custom and zero values tests/unit/test_socketcli.py - APIFailure -> exit 3 by default - APIFailure -> exit 3 EVEN with --disable-blocking (breaking change for 2.3.0: --disable-blocking only affects security findings now) - RequestTimeoutExceeded -> exit 3 by default - --exit-code-on-api-error 100 remaps timeout to exit 100 - --exit-code-on-api-error 0 swallows infrastructure errors - Generic RuntimeError uses exit_code_on_api_error too - _emit_infrastructure_error: * BUILDKITE=true emits "^^^ +++" and "--- ⚠️" markers (stdout) plus a soft_fail hint (logger) * Without BUILDKITE, no markers and no soft_fail hint * Traceback only included when include_traceback=True Replaces the old test_cli_honors_disable_blocking_for_api_failures test which encoded the pre-2.3.0 coupling between --disable-blocking and infra errors -- that coupling is gone by design. Signed-off-by: lelia <2418071+lelia@users.noreply.github.com>
1 parent 6f3dc8a commit d8e4534

2 files changed

Lines changed: 174 additions & 6 deletions

File tree

tests/unit/test_cli_config.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,42 @@ def test_api_request_timeout_defaults_to_twenty_minutes(self):
9090
config = CliConfig.from_args(["--api-token", "test"])
9191
assert socketcli.get_api_request_timeout(config) == 1200
9292

93+
def test_commit_message_passes_through_under_limit(self):
94+
msg = "short commit message"
95+
config = CliConfig.from_args(["--api-token", "test", "--commit-message", msg])
96+
assert config.commit_message == msg
97+
98+
def test_commit_message_truncated_above_limit(self):
99+
# 250 chars -> must truncate to 200
100+
msg = "a" * 250
101+
config = CliConfig.from_args(["--api-token", "test", "--commit-message", msg])
102+
assert config.commit_message is not None
103+
assert len(config.commit_message) == 200
104+
assert config.commit_message == "a" * 200
105+
106+
def test_commit_message_quote_strip_runs_before_truncation(self):
107+
# Quoted message of 250 inner chars -> quote-stripped, then truncated to 200.
108+
inner = "b" * 250
109+
quoted = f'"{inner}"'
110+
config = CliConfig.from_args(["--api-token", "test", "--commit-message", quoted])
111+
assert config.commit_message == "b" * 200
112+
113+
def test_exit_code_on_api_error_default_is_3(self):
114+
config = CliConfig.from_args(["--api-token", "test"])
115+
assert config.exit_code_on_api_error == 3
116+
117+
def test_exit_code_on_api_error_accepts_custom_value(self):
118+
config = CliConfig.from_args(
119+
["--api-token", "test", "--exit-code-on-api-error", "100"]
120+
)
121+
assert config.exit_code_on_api_error == 100
122+
123+
def test_exit_code_on_api_error_accepts_zero(self):
124+
config = CliConfig.from_args(
125+
["--api-token", "test", "--exit-code-on-api-error", "0"]
126+
)
127+
assert config.exit_code_on_api_error == 0
128+
93129
def test_socket_sdk_receives_cli_timeout(self, monkeypatch):
94130
captured = {}
95131

tests/unit/test_socketcli.py

Lines changed: 138 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,84 @@
44
from socketdev.exceptions import APIFailure
55

66
from socketsecurity import socketcli
7+
from socketsecurity.core.exceptions import RequestTimeoutExceeded
78

89

9-
def test_cli_honors_disable_blocking_for_api_failures(monkeypatch):
10+
# ---------------------------------------------------------------------------
11+
# Exit code semantics (spec v2.3.0): infrastructure errors map to
12+
# config.exit_code_on_api_error (default 3), INDEPENDENT of --disable-blocking.
13+
# ---------------------------------------------------------------------------
14+
15+
16+
def test_api_failure_exits_with_default_exit_code_on_api_error(monkeypatch):
17+
def fail_main_code():
18+
raise APIFailure("upstream request timeout")
19+
20+
monkeypatch.setattr(socketcli, "main_code", fail_main_code)
21+
monkeypatch.setattr(sys, "argv", ["socketcli", "--api-token", "test"])
22+
23+
with pytest.raises(SystemExit) as exc_info:
24+
socketcli.cli()
25+
26+
assert exc_info.value.code == 3
27+
28+
29+
def test_api_failure_exits_3_even_with_disable_blocking(monkeypatch):
30+
# Breaking change for 2.3.0: --disable-blocking no longer zeroes out
31+
# infrastructure errors. Use --exit-code-on-api-error 0 for that.
1032
def fail_main_code():
1133
raise APIFailure("upstream request timeout")
1234

35+
monkeypatch.setattr(socketcli, "main_code", fail_main_code)
36+
monkeypatch.setattr(
37+
sys, "argv", ["socketcli", "--api-token", "test", "--disable-blocking"]
38+
)
39+
40+
with pytest.raises(SystemExit) as exc_info:
41+
socketcli.cli()
42+
43+
assert exc_info.value.code == 3
44+
45+
46+
def test_request_timeout_exceeded_exits_with_configured_code(monkeypatch):
47+
def fail_main_code():
48+
raise RequestTimeoutExceeded("scan diff timed out after 1200s")
49+
50+
monkeypatch.setattr(socketcli, "main_code", fail_main_code)
51+
monkeypatch.setattr(sys, "argv", ["socketcli", "--api-token", "test"])
52+
53+
with pytest.raises(SystemExit) as exc_info:
54+
socketcli.cli()
55+
56+
assert exc_info.value.code == 3
57+
58+
59+
def test_exit_code_on_api_error_remaps_timeout(monkeypatch):
60+
def fail_main_code():
61+
raise RequestTimeoutExceeded("scan diff timed out after 1200s")
62+
1363
monkeypatch.setattr(socketcli, "main_code", fail_main_code)
1464
monkeypatch.setattr(
1565
sys,
1666
"argv",
17-
["socketcli", "--api-token", "test", "--disable-blocking"],
67+
["socketcli", "--api-token", "test", "--exit-code-on-api-error", "100"],
68+
)
69+
70+
with pytest.raises(SystemExit) as exc_info:
71+
socketcli.cli()
72+
73+
assert exc_info.value.code == 100
74+
75+
76+
def test_exit_code_on_api_error_zero_swallows_infrastructure_errors(monkeypatch):
77+
def fail_main_code():
78+
raise RequestTimeoutExceeded("scan diff timed out after 1200s")
79+
80+
monkeypatch.setattr(socketcli, "main_code", fail_main_code)
81+
monkeypatch.setattr(
82+
sys,
83+
"argv",
84+
["socketcli", "--api-token", "test", "--exit-code-on-api-error", "0"],
1885
)
1986

2087
with pytest.raises(SystemExit) as exc_info:
@@ -23,14 +90,79 @@ def fail_main_code():
2390
assert exc_info.value.code == 0
2491

2592

26-
def test_cli_returns_error_for_api_failures_without_disable_blocking(monkeypatch):
93+
def test_generic_exception_uses_exit_code_on_api_error(monkeypatch):
2794
def fail_main_code():
28-
raise APIFailure("upstream request timeout")
95+
raise RuntimeError("unexpected boom")
2996

3097
monkeypatch.setattr(socketcli, "main_code", fail_main_code)
31-
monkeypatch.setattr(sys, "argv", ["socketcli", "--api-token", "test"])
98+
monkeypatch.setattr(
99+
sys,
100+
"argv",
101+
["socketcli", "--api-token", "test", "--exit-code-on-api-error", "7"],
102+
)
32103

33104
with pytest.raises(SystemExit) as exc_info:
34105
socketcli.cli()
35106

36-
assert exc_info.value.code == 3
107+
assert exc_info.value.code == 7
108+
109+
110+
# ---------------------------------------------------------------------------
111+
# Buildkite-aware log formatting (spec §3): ^^^ +++ / --- markers only
112+
# emitted when BUILDKITE=true; bare log.error otherwise.
113+
# ---------------------------------------------------------------------------
114+
115+
116+
def test_emit_infrastructure_error_no_buildkite_has_no_markers(
117+
monkeypatch, capsys, caplog
118+
):
119+
monkeypatch.setattr(socketcli, "IS_BUILDKITE", False)
120+
with caplog.at_level("ERROR", logger="socketcli"):
121+
socketcli._emit_infrastructure_error(
122+
"something failed", hint="just so you know"
123+
)
124+
captured = capsys.readouterr()
125+
assert "^^^ +++" not in captured.out
126+
assert "--- :warning:" not in captured.out
127+
log_text = "\n".join(r.getMessage() for r in caplog.records)
128+
assert "soft_fail" not in log_text
129+
130+
131+
def test_emit_infrastructure_error_buildkite_emits_markers(
132+
monkeypatch, capsys, caplog
133+
):
134+
monkeypatch.setattr(socketcli, "IS_BUILDKITE", True)
135+
with caplog.at_level("ERROR", logger="socketcli"):
136+
socketcli._emit_infrastructure_error(
137+
"something failed", hint="just so you know"
138+
)
139+
captured = capsys.readouterr()
140+
# Markers go to stdout via print() so pytest's capsys catches them cleanly.
141+
assert "^^^ +++" in captured.out
142+
assert "--- :warning: Socket Infrastructure Error" in captured.out
143+
# The soft_fail tip is appended via log.error() -- caplog captures it.
144+
log_text = "\n".join(r.getMessage() for r in caplog.records)
145+
assert "soft_fail" in log_text
146+
147+
148+
def test_emit_infrastructure_error_omits_traceback_by_default(monkeypatch, capsys):
149+
monkeypatch.setattr(socketcli, "IS_BUILDKITE", False)
150+
try:
151+
raise ValueError("boom")
152+
except ValueError:
153+
socketcli._emit_infrastructure_error("wrapped", include_traceback=False)
154+
captured = capsys.readouterr()
155+
assert "Traceback" not in captured.err
156+
assert "Traceback" not in captured.out
157+
158+
159+
def test_emit_infrastructure_error_includes_traceback_on_request(monkeypatch, capsys):
160+
monkeypatch.setattr(socketcli, "IS_BUILDKITE", False)
161+
try:
162+
raise ValueError("boom")
163+
except ValueError:
164+
socketcli._emit_infrastructure_error("wrapped", include_traceback=True)
165+
captured = capsys.readouterr()
166+
# traceback.print_exc() writes to sys.stderr by default.
167+
assert "Traceback" in captured.err
168+
assert "ValueError: boom" in captured.err

0 commit comments

Comments
 (0)