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 / telemetry / tests / gate.test.ts
183 lines · 5.1 KBRaw
1import { describe, expect, mock, test, tier } from 'claude-code/testing'
2
3import Hooks from '../hooks'
4import Fixtures from './fixtures'
5
6tier('builtin')
7
8const refusal = `HooksError: reaching: $.telemetry.log: ${Hooks.REFUSED.deny}`
9
10describe('gate', () => {
11 test(
12 'the table an engine.create step is handed serves no one above either',
13 { plugins: [Fixtures.recording, Fixtures.reaching] },
14 async ($, on) => {
15 mock.env(on, Fixtures.SENDING_ENV)
16
17 const session = Fixtures.firstPartySession(on)
18
19 on('tool.call', () => ({ result: 'read' }))
20
21 await $.session.start(Fixtures.STARTED)
22 await session.clock.advance(1)
23 await $.tool.call(Fixtures.READ_TOOL)
24
25 const during = (await $.command.run(Fixtures.typed('reach', {}))).text
26
27 await $.command.run(Fixtures.record(Fixtures.surveyAnswer()))
28 await session.clock.advance(Hooks.BATCH_WINDOW_MS)
29
30 expect({
31 isServedDuringCreate: during === 'served',
32 later: session.lines.filter(line => !line.startsWith('telemetry: ')),
33 rows: Fixtures.rowsOf(session).map(Fixtures.eventNameOf),
34 }).toEqual({
35 isServedDuringCreate: false,
36 later: [`timer: ${refusal}`, `tool: ${refusal}`],
37 rows: ['tengu_plugin_survey_answered'],
38 })
39 },
40 )
41
42 test(
43 'a module that keeps the table to call later never loads',
44 { plugins: [Fixtures.holding] },
45 async ($, on) => {
46 mock.env(on, Fixtures.SENDING_ENV)
47
48 const session = Fixtures.firstPartySession(on)
49
50 const held = await $.command
51 .run(Fixtures.typed('hold', {}))
52 .then(result => result.text, String)
53
54 await session.clock.advance(Hooks.BATCH_WINDOW_MS)
55
56 expect({
57 isLoaded: !String(held).includes('did not load'),
58 posts: session.posts,
59 }).toEqual({ isLoaded: false, posts: [] })
60 },
61 )
62
63 test(
64 'a hook above may rename a row a built-in sends, never send its own',
65 { plugins: [Fixtures.recording, Fixtures.meddling] },
66 async ($, on) => {
67 mock.env(on, Fixtures.SENDING_ENV)
68
69 const session = Fixtures.firstPartySession(on)
70 const own = (await $.command.run(Fixtures.typed('meddle', {}))).text
71
72 await $.command.run(Fixtures.record(Fixtures.surveyAnswer()))
73 await session.clock.advance(Hooks.BATCH_WINDOW_MS)
74
75 expect({
76 own,
77 rows: Fixtures.rowsOf(session).map(Fixtures.eventNameOf),
78 }).toEqual({
79 own: `HooksError: meddling: $.telemetry.log: ${Hooks.REFUSED.deny}`,
80 rows: ['tengu_plugin_renamed'],
81 })
82 },
83 )
84
85 test(
86 'a hook above that answers without going on sends nothing at all',
87 { plugins: [Fixtures.recording, Fixtures.swallowing] },
88 async ($, on) => {
89 mock.env(on, Fixtures.SENDING_ENV)
90
91 const session = Fixtures.firstPartySession(on)
92
93 const answer = (
94 await $.command.run(Fixtures.record(Fixtures.surveyAnswer()))
95 ).text
96
97 await session.clock.advance(Hooks.BATCH_WINDOW_MS)
98
99 expect({ answer, posts: session.posts }).toEqual({
100 answer: 'queued',
101 posts: [],
102 })
103 },
104 )
105
106 test(
107 'a step above cannot hand up its own telemetry in place of this one',
108 { plugins: [Fixtures.recording, Fixtures.replacing] },
109 async ($, on) => {
110 mock.env(on, Fixtures.SENDING_ENV)
111
112 const session = Fixtures.firstPartySession(on)
113 const own = (await $.command.run(Fixtures.typed('replace', {}))).text
114
115 await $.command.run(Fixtures.record(Fixtures.surveyAnswer()))
116 await session.clock.advance(Hooks.BATCH_WINDOW_MS)
117
118 expect({
119 own,
120 rows: Fixtures.rowsOf(session).map(Fixtures.eventNameOf),
121 }).toEqual({
122 own: `HooksError: replacing: $.telemetry.log: ${Hooks.REFUSED.deny}`,
123 rows: ['tengu_plugin_survey_answered'],
124 })
125 },
126 )
127
128 test(
129 'a hook on every event cannot turn a row to the other destination',
130 { plugins: [Fixtures.recording, Fixtures.sweeping] },
131 async ($, on) => {
132 mock.env(on, Fixtures.SENDING_ENV)
133
134 const session = Fixtures.firstPartySession(on)
135
136 await $.session.start(Fixtures.STARTED)
137
138 const answer = (
139 await $.command.run(Fixtures.record(Fixtures.surveyAnswer()))
140 ).text
141
142 await session.clock.advance(Hooks.BATCH_WINDOW_MS)
143
144 expect({
145 answer,
146 posts: session.posts.length,
147 rows: Fixtures.rowsOf(session),
148 }).toEqual({
149 answer: 'queued',
150 posts: 1,
151 rows: [Fixtures.EXPECTED_ROW],
152 })
153 },
154 )
155
156 test(
157 'what a hook on every event adds beside a row never leaves in it',
158 { plugins: [Fixtures.recording, Fixtures.annotating] },
159 async ($, on) => {
160 mock.env(on, Fixtures.SENDING_ENV)
161
162 const session = Fixtures.firstPartySession(on)
163
164 await $.session.start(Fixtures.STARTED)
165
166 const answer = (
167 await $.command.run(Fixtures.record(Fixtures.surveyAnswer()))
168 ).text
169
170 await session.clock.advance(Hooks.BATCH_WINDOW_MS)
171
172 expect({
173 answer,
174 posts: session.posts.length,
175 rows: Fixtures.rowsOf(session),
176 }).toEqual({
177 answer: 'queued',
178 posts: 1,
179 rows: [Fixtures.EXPECTED_ROW],
180 })
181 },
182 )
183})