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 / telemetry.test.ts
164 lines · 3.9 KBRaw
1import type {
2 Args,
3 CommandRunInput,
4 HttpResponse,
5 On,
6 SessionAuthorization,
7} from 'claude-code'
8import type { Plugin } from 'claude-code/testing'
9import { expect, memoryEnv, seat, test } from 'claude-code/testing'
10
11seat('builtin')
12
13const BEARER: SessionAuthorization = { handle: 'the-handle', kind: 'bearer' }
14const ACCEPTED: HttpResponse = { status: 200, ok: true, headers: {}, text: '' }
15
16/**
17 * `/record <event>`, which the recording plugin answers.
18 */
19const record = (event: string): CommandRunInput => ({
20 command: 'record',
21 args: event,
22 origin: { kind: 'composer' },
23})
24
25/**
26 * A plugin whose `/record <event>` logs that event through `$.telemetry`,
27 * answering "sent", or why the row was refused.
28 */
29const recording: Plugin = {
30 name: 'recording',
31 register(on) {
32 on('command.run', { command: 'record' }, ($, e) =>
33 $.telemetry.log({ event: e.args, props: { answer: 2, seen: true } }).then(
34 () => ({ text: 'sent' }),
35 (error: unknown) => ({ text: String(error) }),
36 ),
37 )
38 },
39}
40
41/**
42 * A first-party session: its id and model, the credential it holds, and
43 * the ingest accepting each row, each post kept as it was made.
44 */
45function firstPartySession(
46 on: On,
47 authorization: SessionAuthorization = BEARER,
48) {
49 const posts: Args<'http.fetch'>[] = []
50 on('session.id', () => ({ value: 'the-session' }))
51 on('session.model', () => ({ value: 'the-model' }))
52 on('session.authorize', () => ({ value: authorization }))
53 on('http.fetch', ($, e) => {
54 posts.push(e)
55
56 return { value: ACCEPTED }
57 })
58
59 return posts
60}
61
62/**
63 * The batch a post to the ingest carries, as it was sent.
64 */
65function batchOf(post: Args<'http.fetch'>): unknown {
66 return JSON.parse(String(post.init?.body))
67}
68
69test(
70 'a plugin’s $.telemetry.log posts one first-party row',
71 { plugins: [recording] },
72 async ($, on) => {
73 memoryEnv(on, { USER_TYPE: 'ant' })
74 const posts = firstPartySession(on)
75
76 expect(await $.command.run(record('survey_answered'))).toEqual({
77 text: 'sent',
78 })
79 expect(posts).toHaveLength(1)
80
81 const [post] = posts
82
83 expect(post.init).toMatchObject({ method: 'POST', auth: 'the-handle' })
84 expect(batchOf(post)).toMatchObject({
85 events: [
86 {
87 event_data: {
88 event_name: 'tengu_plugin_survey_answered',
89 session_id: 'the-session',
90 model: 'the-model',
91 user_type: 'ant',
92 },
93 },
94 ],
95 })
96 },
97)
98
99test(
100 'a row already named tengu_ is sent under its own name',
101 { plugins: [recording] },
102 async ($, on) => {
103 memoryEnv(on, {})
104 const posts = firstPartySession(on)
105
106 await $.command.run(record('tengu_repl_diff_panel_shown'))
107
108 expect(posts.map(batchOf)).toMatchObject([
109 {
110 events: [
111 {
112 event_data: {
113 event_name: 'tengu_repl_diff_panel_shown',
114 user_type: 'external',
115 },
116 },
117 ],
118 },
119 ])
120 },
121)
122
123test(
124 'nothing is sent for a person who asked not to be tracked',
125 { plugins: [recording] },
126 async ($, on) => {
127 memoryEnv(on, { DO_NOT_TRACK: '1' })
128 const posts = firstPartySession(on)
129
130 expect(await $.command.run(record('survey_answered'))).toEqual({
131 text: 'sent',
132 })
133 expect(posts).toEqual([])
134 },
135)
136
137test(
138 'nothing is sent on a third-party provider',
139 { plugins: [recording] },
140 async ($, on) => {
141 memoryEnv(on, { CLAUDE_CODE_USE_BEDROCK: '1' })
142 const posts = firstPartySession(on)
143
144 await $.command.run(record('survey_answered'))
145
146 expect(posts).toEqual([])
147 },
148)
149
150test(
151 'a session with no first-party credential is refused, nothing sent',
152 { plugins: [recording] },
153 async ($, on) => {
154 memoryEnv(on, {})
155 const posts = firstPartySession(on, null)
156 const { text } = await $.command.run(record('survey_answered'))
157
158 expect(text).toEndWith(
159 '$.telemetry.log: this session has no first-party credential to ' +
160 'authorize',
161 )
162 expect(posts).toEqual([])
163 },
164)