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