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 / fixtures / first-party-session.ts
119 lines · 3.3 KBRaw
1import type { Args, On } from 'claude-code'
2import { mock } from 'claude-code/testing'
3
4import { ACCEPTED } from './accepted.js'
5import { BEARER } from './bearer.js'
6import { CONFIG_PATH } from './config-path.js'
7import { ENGINE_VERSION } from './engine-version.js'
8import { GLOBAL_CONFIG } from './global-config.js'
9import { LISTING } from './listing.js'
10import { PROBED } from './probed.js'
11import { REPO } from './repo.js'
12import type { SentSession } from './sent-session.js'
13import type { SessionOptions } from './session-options.js'
14
15/**
16 * Answers, beneath the plugins, everything the telemetry plugin reads of a
17 * session signed in first party on a Mac in a git checkout, on a mock clock.
18 *
19 * Keeps what the plugin did: each post, file read, program run and debug
20 * line, as it does it. The engine answers its version unless told not to.
21 *
22 * @param on the test's `on`
23 * @param options how this session differs from the plain one
24 * @returns what the plugin did, and the clock
25 */
26export function firstPartySession(
27 on: On,
28 options: SessionOptions = {},
29): SentSession {
30 const posts: Args<'http.fetch'>[] = []
31 const reads: string[] = []
32 const runs: Args<'process.run'>[] = []
33 const lines: string[] = []
34 const clock = mock.clock(on)
35
36 const files: Readonly<Record<string, string>> = {
37 [CONFIG_PATH]: GLOBAL_CONFIG,
38 ...options.files,
39 }
40
41 const answers = options.answers ?? [ACCEPTED]
42 const authorizations = options.authorizations ?? [BEARER]
43
44 let authorizeCalls = 0
45
46 const isPolicyUnreadable = options.policy?.kind === 'unreadable'
47
48 on('session.start', ($, e) => ({ cwd: e.cwd }))
49 on('session.end', ($, e) => ({ sessionId: e.sessionId }))
50 on('session.id', () => ({ value: 'the-session' }))
51 on('session.model', () => ({ value: 'the-model' }))
52 on('session.surfaces', () => ({ value: ['terminal'] }))
53 on('session.cwd', () => ({ value: '/work' }))
54 on('session.repo', () => ({ value: REPO }))
55 on('fs.list', () => ({ value: LISTING }))
56
57 const engineVersion = options.engineVersion ?? ENGINE_VERSION
58
59 if (engineVersion !== 'unanswered') {
60 on('session.version', () => ({ value: engineVersion }))
61 }
62
63 on('session.authorize', () => {
64 authorizeCalls += 1
65
66 const answer =
67 authorizations[Math.min(authorizeCalls, authorizations.length) - 1]
68
69 const isRefused = answer === 'refused'
70
71 return isRefused
72 ? { deny: 'the credential store is locked' }
73 : { value: answer === undefined ? BEARER : answer }
74 })
75
76 on('settings.read', () =>
77 isPolicyUnreadable
78 ? { deny: 'the managed settings file is unreadable' }
79 : { value: options.policy?.settings ?? {} },
80 )
81
82 on('fs.read', ($, e) => {
83 reads.push(e.path)
84
85 const text = files[e.path]
86
87 return text === undefined ? { deny: `ENOENT: ${e.path}` } : { value: text }
88 })
89
90 on('fs.exists', ($, e) => ({
91 value: (options.existing ?? []).includes(e.path),
92 }))
93
94 const probed = { ...PROBED, stdout: options.probeOutput ?? PROBED.stdout }
95
96 on('process.run', ($, e) => {
97 runs.push(e)
98
99 return { value: probed }
100 })
101
102 on('http.fetch', ($, e) => {
103 posts.push(e)
104
105 return {
106 value: answers[Math.min(posts.length, answers.length) - 1] ?? ACCEPTED,
107 }
108 })
109
110 on('ui.log', ($, e) => {
111 if (e.to === 'debug') {
112 lines.push(e.text)
113 }
114
115 return { value: undefined }
116 })
117
118 return { posts, reads, runs, lines, clock }
119}