spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / mods / sec-default / tests / held-verdict.test.ts
GitHub Actions (publish-mods)chore: Update mods/ bf2323b · 12 days ago History
234 lines · 7.4 KBRaw
1import { describe, expect, test, tier } from 'claude-code/testing'
2
3import Hooks from '../hooks'
4import Fixtures from './fixtures'
5
6tier('prepend')
7
8describe('held-verdict', () => {
9 test('over an allow: any deny, and an ask a rule or a hook decided', () => {
10 expect(
11 [
12 Fixtures.RULE_DENY,
13 Fixtures.RULE_ASK,
14 Fixtures.HOOK_DENY,
15 Fixtures.HOOK_ASK,
16 Fixtures.PLAIN_DENY,
17 Fixtures.ASKED,
18 Fixtures.ALLOWED,
19 ].map(held => Hooks.holdsOver(held, Fixtures.ALLOWED)),
20 ).toEqual([true, true, true, true, true, false, false])
21 })
22
23 test("a setting's ask and a host's hold over an allow and a rewording", () => {
24 expect(
25 [Fixtures.SETTING_ASK, Fixtures.HOST_ASK].map(held => [
26 Hooks.holdsOver(held, Fixtures.ALLOWED),
27 Hooks.holdsOver(held, Fixtures.ASKED),
28 Hooks.holdsOver(held, { ...held }),
29 ]),
30 ).toEqual([
31 [true, true, false],
32 [true, true, false],
33 ])
34 })
35
36 test('a setting or decider the answer itself names holds nothing', () => {
37 expect(
38 Hooks.holdsOver(Fixtures.ASKED, {
39 ...Fixtures.ALLOWED,
40 setting: 'permissions.blockReadsOutsideWorkingDirectories',
41 decider: 'host',
42 }),
43 ).toBe(false)
44 })
45
46 test('an ask is named for its hook, then its setting, then its host', () => {
47 expect(
48 [
49 { ...Fixtures.HOST_ASK, ...Fixtures.SETTING_ASK, ...Fixtures.HOOK_ASK },
50 { ...Fixtures.HOST_ASK, ...Fixtures.SETTING_ASK },
51 Fixtures.HOST_ASK,
52 ].map(held => Hooks.heldNotice('easy', 'Bash', held, 'lift')),
53 ).toEqual(
54 [
55 "a PreToolUse hook's ask from a Bash call; the PreToolUse hook's ask",
56 "a permissions.blockReadsOutsideWorkingDirectories setting's ask " +
57 'from a Bash call; the ' +
58 "permissions.blockReadsOutsideWorkingDirectories setting's ask",
59 "a host's ask from a Bash call; the host's ask",
60 ].map(
61 middle =>
62 `easy tried to lift ${middle} holds over the plugins you install ` +
63 '(allowModsToOverrideDenyRules)',
64 ),
65 )
66 })
67
68 test('over the same decision reworded; a stricter answer stands', () => {
69 expect(
70 [
71 Fixtures.RULE_DENY,
72 Fixtures.RULE_ASK,
73 Fixtures.HOOK_DENY,
74 Fixtures.HOOK_ASK,
75 ].map(held => [
76 Hooks.holdsOver(held, Fixtures.ASKED),
77 Hooks.holdsOver(held, Fixtures.PLAIN_DENY),
78 Hooks.holdsOver(held, { ...held }),
79 ]),
80 ).toEqual([
81 [true, true, false],
82 [true, false, false],
83 [true, true, false],
84 [true, false, false],
85 ])
86 })
87
88 test('an ask stripped of its rule, same sentence, is no same verdict', () => {
89 expect(
90 Hooks.holdsOver(Fixtures.RULE_ASK, {
91 decision: Fixtures.RULE_ASK.decision,
92 reason: Fixtures.RULE_ASK.reason,
93 }),
94 ).toBe(true)
95 })
96
97 test('a rule or hook the answer itself names makes nothing hold', () => {
98 expect(
99 Hooks.holdsOver(Fixtures.ASKED, {
100 ...Fixtures.ALLOWED,
101 rule: 'Bash(echo *)',
102 hook: 'PreToolUse',
103 }),
104 ).toBe(false)
105 })
106
107 test('the notice names the kind that held, and the rule when one did', () => {
108 expect(
109 [
110 Fixtures.RULE_DENY,
111 Fixtures.RULE_ASK,
112 { ...Fixtures.RULE_ASK, ...Fixtures.HOOK_ASK },
113 Fixtures.HOOK_ASK,
114 Fixtures.HOOK_DENY,
115 Fixtures.PLAIN_DENY,
116 ].map(held => Hooks.heldNotice('easy', 'Bash', held, 'lift')),
117 ).toEqual(
118 [
119 'a deny rule in your settings from a Bash call (Bash(echo *)); ' +
120 'the deny rule',
121 'an ask rule in your settings from a Bash call (Bash(echo *)); ' +
122 'the ask rule',
123 'an ask rule in your settings from a Bash call (Bash(echo *)); ' +
124 'the ask rule',
125 "a PreToolUse hook's ask from a Bash call; the PreToolUse hook's ask",
126 'a refusal from a Bash call; the refusal',
127 'a refusal from a Bash call; the refusal',
128 ].map(
129 middle =>
130 `easy tried to lift ${middle} holds over the plugins you install ` +
131 '(allowModsToOverrideDenyRules)',
132 ),
133 )
134 })
135
136 test('what no check vouches for is refused, and says why', () => {
137 expect(Hooks.UNCHECKED_DENY).toEqual({
138 decision: 'deny',
139 reason:
140 'the rules and hooks in your settings could not be checked for this ' +
141 'call, so it is refused',
142 })
143 })
144
145 test('an ask of theirs over a deny, caught, is no more vouched for', () => {
146 expect(
147 Hooks.caughtAnswer(Fixtures.ASKED, [
148 Fixtures.linkOf('easy', 'user', Fixtures.ASKED),
149 Fixtures.linkOf('engine', 'core', Fixtures.RULE_DENY),
150 ]),
151 ).toEqual(Hooks.UNCHECKED_DENY)
152 })
153
154 test('a link of theirs is named when it answered looser than handed', () => {
155 expect(
156 Hooks.rewrittenByUsers([
157 Fixtures.linkOf('listening', 'user', Fixtures.ALLOWED),
158 Fixtures.linkOf('easy', 'user', Fixtures.ALLOWED),
159 Fixtures.linkOf('engine', 'core', Fixtures.ASKED),
160 ]),
161 ).toEqual(['easy'])
162 })
163
164 test('one that never called next is measured against a deny', () => {
165 expect(
166 Hooks.rewrittenByUsers([
167 Fixtures.linkOf('listening', 'user', Fixtures.ALLOWED),
168 Fixtures.linkOf('blind', 'user', Fixtures.ALLOWED),
169 ]),
170 ).toEqual(['blind'])
171 })
172
173 test('one that reworded the same decision is named too', () => {
174 expect(
175 Hooks.rewrittenByUsers([
176 Fixtures.linkOf('reworder', 'user', Fixtures.ASKED),
177 Fixtures.linkOf('engine', 'core', Fixtures.RULE_ASK),
178 ]),
179 ).toEqual(['reworder'])
180 })
181
182 test('a tightening, a skipped link and links beneath theirs are not', () => {
183 expect(
184 Hooks.rewrittenByUsers([
185 Fixtures.linkOf('strict', 'user', Fixtures.PLAIN_DENY),
186 Fixtures.linkOf('passed-over', 'user'),
187 Fixtures.linkOf('suite', 'append', Fixtures.ALLOWED),
188 Fixtures.linkOf('bundled', 'builtin', Fixtures.ALLOWED),
189 Fixtures.linkOf('engine', 'core', Fixtures.RULE_DENY),
190 ]),
191 ).toEqual([])
192 })
193
194 test('a batch listed under prepend may hold a plugin of theirs', () => {
195 expect(
196 Hooks.rewrittenByUsers([
197 Fixtures.linkOf('audit+easy', 'prepend', Fixtures.ALLOWED),
198 Fixtures.linkOf('engine', 'core', Fixtures.RULE_DENY),
199 ]),
200 ).toEqual(['audit+easy'])
201 })
202
203 test('the handler keeps a verdict none of theirs made, or tightened', () => {
204 expect([
205 Hooks.caughtAnswer(Fixtures.PLAIN_DENY, [
206 Fixtures.linkOf('strict', 'user', Fixtures.PLAIN_DENY),
207 Fixtures.linkOf('engine', 'core', Fixtures.ASKED),
208 ]),
209 Hooks.caughtAnswer(Fixtures.ASKED, [
210 Fixtures.linkOf('easy', 'user'),
211 Fixtures.linkOf('engine', 'core', Fixtures.ASKED),
212 ]),
213 ]).toEqual([Fixtures.PLAIN_DENY, Fixtures.ASKED])
214 })
215
216 test('a deny of the rules they reworded is refused in our words', () => {
217 expect(
218 Hooks.caughtAnswer(Fixtures.PLAIN_DENY, [
219 Fixtures.linkOf('reworder', 'user', Fixtures.PLAIN_DENY),
220 Fixtures.linkOf('engine', 'core', Fixtures.RULE_DENY),
221 ]),
222 ).toEqual(Hooks.UNCHECKED_DENY)
223 })
224
225 test('the handler refuses a verdict they loosened, and no verdict', () => {
226 expect([
227 Hooks.caughtAnswer(Fixtures.ALLOWED, [
228 Fixtures.linkOf('easy', 'user', Fixtures.ALLOWED),
229 Fixtures.linkOf('engine', 'core', Fixtures.ASKED),
230 ]),
231 Hooks.caughtAnswer(undefined, []),
232 ]).toEqual([Hooks.UNCHECKED_DENY, Hooks.UNCHECKED_DENY])
233 })
234})