blob: 50302e6a978f34351a902811c578f1e48a9fea8f (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
"""Tests for hooks/destructive-bash-confirm.py — shlex-based rm -rf parsing."""
from conftest import load_hook
hook = load_hook("destructive-bash-confirm.py")
SENTINEL = "(unparsed — shell too complex to inspect safely)"
# --- detection of flag forms (combined / separate / reordered) -------------
def test_rf_combined_detected():
assert hook.detect_rm_rf("rm -rf build") == ["build"]
def test_r_f_separate_detected():
assert hook.detect_rm_rf("rm -r -f build") == ["build"]
def test_fr_reordered_detected():
assert hook.detect_rm_rf("rm -fr build") == ["build"]
def test_capital_R_detected():
assert hook.detect_rm_rf("rm -Rf build") == ["build"]
def test_long_flags_detected():
targets = hook.detect_rm_rf("rm --recursive --force build")
assert targets == ["build"]
# --- quoted / spaced paths now parse correctly -----------------------------
def test_quoted_path_with_space_parsed():
assert hook.detect_rm_rf('rm -rf "my dir"') == ["my dir"]
def test_multiple_targets():
assert hook.detect_rm_rf("rm -rf a b c") == ["a", "b", "c"]
def test_double_dash_separates_flags_from_paths():
assert hook.detect_rm_rf("rm -rf -- -weird-name") == ["-weird-name"]
# --- not-a-match cases: no modal -------------------------------------------
def test_no_r_returns_none():
assert hook.detect_rm_rf("rm -f file") is None
def test_no_f_returns_none():
assert hook.detect_rm_rf("rm -r dir") is None
def test_not_rm_returns_none():
assert hook.detect_rm_rf("rmdir foo") is None
def test_plain_rm_returns_none():
assert hook.detect_rm_rf("rm file.txt") is None
# --- dangerous path banner still fires on parsed targets -------------------
def test_home_var_target_flags_dangerous():
detection = hook.detect_destructive('rm -rf "$HOME/x"')
assert detection is not None
kind, ctx = detection
assert kind == "rm -rf"
assert "_banner" in ctx
assert "$HOME/x" in ctx["_banner"]
def test_root_path_flags_dangerous():
detection = hook.detect_destructive("rm -rf /etc/foo")
assert detection is not None
_, ctx = detection
assert "_banner" in ctx
def test_safe_relative_target_no_banner():
detection = hook.detect_destructive("rm -rf build/cache")
assert detection is not None
_, ctx = detection
assert "_banner" not in ctx
# --- fail-toward-asking on ambiguity ---------------------------------------
def test_compound_command_returns_sentinel():
# `ls && rm -rf foo` — the naive parser missed this; now we ask.
assert hook.detect_rm_rf("ls && rm -rf foo") == [SENTINEL]
def test_pipeline_returns_sentinel():
assert hook.detect_rm_rf("find . -type d | xargs rm -rf") == [SENTINEL]
def test_semicolon_returns_sentinel():
assert hook.detect_rm_rf("cd /tmp; rm -rf junk") == [SENTINEL]
def test_command_substitution_returns_sentinel():
assert hook.detect_rm_rf("rm -rf $(echo target)") == [SENTINEL]
def test_backtick_substitution_returns_sentinel():
assert hook.detect_rm_rf("rm -rf `echo target`") == [SENTINEL]
def test_redirect_returns_sentinel():
assert hook.detect_rm_rf("rm -rf foo > /dev/null") == [SENTINEL]
def test_unbalanced_quotes_returns_sentinel():
# shlex.split raises ValueError → ask anyway rather than silently pass.
assert hook.detect_rm_rf('rm -rf "unterminated') == [SENTINEL]
def test_compound_without_rm_rf_returns_none():
# Compound construct but no dangerous rm — should not fire.
assert hook.detect_rm_rf("ls && echo done") is None
def test_compound_with_rm_but_no_force_returns_none():
# `&&` present but the rm has no -f, so nothing to flag.
assert hook.detect_rm_rf("ls && rm -r dir") is None
def test_sentinel_fires_modal_via_detect_destructive():
detection = hook.detect_destructive("ls && rm -rf foo")
assert detection is not None
kind, ctx = detection
assert kind == "rm -rf"
assert ctx["targets"] == [SENTINEL]
|