-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhandler.test.ts
More file actions
161 lines (147 loc) · 5.35 KB
/
Copy pathhandler.test.ts
File metadata and controls
161 lines (147 loc) · 5.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
import { describe, expect, it } from 'vitest';
import { __test, handleMessage, type LspMessage } from './handler.js';
describe('handleMessage — initialize', () => {
it('returns capabilities + serverInfo + supported commands', async () => {
const out: LspMessage[] = [];
await handleMessage(
{
jsonrpc: '2.0',
id: 1,
method: 'initialize',
params: { rootUri: 'file:///tmp/x' },
},
(m) => out.push(m),
);
expect(out).toHaveLength(1);
const r = out[0]!.result as {
capabilities: { executeCommandProvider: { commands: string[] } };
serverInfo: { name: string };
};
expect(r.serverInfo.name).toBe('deepcode-lsp');
expect(r.capabilities.executeCommandProvider.commands).toContain('deepcode.runAgent');
expect(r.capabilities.executeCommandProvider.commands).toContain('deepcode.abort');
expect(r.capabilities.executeCommandProvider.commands).toContain('deepcode.listSkills');
});
});
describe('handleMessage — executeCommand', () => {
it('returns a turnId for deepcode.runAgent and streams events', async () => {
const out: LspMessage[] = [];
// Resolve as soon as the real completion signal (turn_done) is emitted,
// rather than polling on a fixed timer — the agent run streams events
// asynchronously after lazily importing @deepcode/core, which can take
// arbitrarily long on a loaded CI runner.
let signalDone!: () => void;
const done = new Promise<void>((resolve) => {
signalDone = resolve;
});
const send = (m: LspMessage) => {
out.push(m);
if (
m.method === 'deepcode/agentEvent' &&
(m.params as { kind: string }).kind === 'turn_done'
) {
signalDone();
}
};
await handleMessage(
{
jsonrpc: '2.0',
id: 2,
method: 'workspace/executeCommand',
params: { command: 'deepcode.runAgent', arguments: [{ prompt: 'hi' }] },
},
send,
);
// Synchronous: started event + reply
expect(out.some((m) => m.method === 'deepcode/agentEvent')).toBe(true);
const reply = out.find((m) => m.id === 2);
expect(reply).toBeDefined();
expect((reply!.result as { turnId: string }).turnId).toMatch(/^lsp-/);
// Async: wait for the agent run to finish (will error in test env
// because no DEEPSEEK_API_KEY is set — that's the expected path, which
// still emits turn_done). Wait on the real signal, bounded only by the
// test timeout below.
await done;
const events = out.filter((m) => m.method === 'deepcode/agentEvent');
const kinds = events.map((e) => (e.params as { kind: string }).kind);
expect(kinds).toContain('started');
expect(kinds).toContain('turn_done');
}, 15000);
it('errors on missing prompt', async () => {
const out: LspMessage[] = [];
await handleMessage(
{
jsonrpc: '2.0',
id: 3,
method: 'workspace/executeCommand',
params: { command: 'deepcode.runAgent', arguments: [{}] },
},
(m) => out.push(m),
);
expect(out[0]!.error).toBeDefined();
expect(out[0]!.error!.message).toMatch(/prompt is required/);
});
it('deepcode.abort returns false for unknown turnId', async () => {
const out: LspMessage[] = [];
await handleMessage(
{
jsonrpc: '2.0',
id: 4,
method: 'workspace/executeCommand',
params: { command: 'deepcode.abort', arguments: [{ turnId: 'no-such' }] },
},
(m) => out.push(m),
);
expect((out[0]!.result as { aborted: boolean }).aborted).toBe(false);
});
it('deepcode.abort aborts the active turn controller', async () => {
const controller = new AbortController();
__test.state.activeTurns.set('active-turn', controller);
const out: LspMessage[] = [];
await handleMessage(
{
jsonrpc: '2.0',
id: 41,
method: 'workspace/executeCommand',
params: { command: 'deepcode.abort', arguments: [{ turnId: 'active-turn' }] },
},
(m) => out.push(m),
);
expect((out[0]!.result as { aborted: boolean }).aborted).toBe(true);
expect(controller.signal.aborted).toBe(true);
__test.state.activeTurns.delete('active-turn');
});
it('errors on unknown command', async () => {
const out: LspMessage[] = [];
await handleMessage(
{
jsonrpc: '2.0',
id: 5,
method: 'workspace/executeCommand',
params: { command: 'evil.command', arguments: [] },
},
(m) => out.push(m),
);
expect(out[0]!.error).toBeDefined();
expect(out[0]!.error!.message).toMatch(/Unknown command/);
});
});
describe('handleMessage — unknown method', () => {
it('returns -32603 internal error', async () => {
const out: LspMessage[] = [];
await handleMessage({ jsonrpc: '2.0', id: 6, method: 'unknown/method' }, (m) => out.push(m));
expect(out[0]!.error).toBeDefined();
});
});
describe('handleMessage — notifications', () => {
it('silently drops unknown notification', async () => {
const out: LspMessage[] = [];
await handleMessage({ jsonrpc: '2.0', method: 'unknown/notif' }, (m) => out.push(m));
expect(out).toHaveLength(0);
});
it('accepts initialized notification (no reply)', async () => {
const out: LspMessage[] = [];
await handleMessage({ jsonrpc: '2.0', method: 'initialized' }, (m) => out.push(m));
expect(out).toHaveLength(0);
});
});