Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/agentHost/test/electron-browser/sshRelayTransport.test.ts
13399 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { Emitter, Event } from '../../../../base/common/event.js';
8
import { DisposableStore } from '../../../../base/common/lifecycle.js';
9
import { URI } from '../../../../base/common/uri.js';
10
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
11
import { SSHRelayTransport } from '../../electron-browser/sshRelayTransport.js';
12
import type { ISSHRelayMessage, ISSHRemoteAgentHostMainService, ISSHConnectProgress, ISSHConnectResult, ISSHAgentHostConfig, ISSHResolvedConfig } from '../../common/sshRemoteAgentHost.js';
13
14
/**
15
* Minimal mock of ISSHRemoteAgentHostMainService for testing the relay transport.
16
*/
17
class MockSSHMainService {
18
private readonly _onDidRelayMessage = new Emitter<ISSHRelayMessage>();
19
readonly onDidRelayMessage = this._onDidRelayMessage.event;
20
21
private readonly _onDidRelayClose = new Emitter<string>();
22
readonly onDidRelayClose = this._onDidRelayClose.event;
23
24
readonly onDidChangeConnections = Event.None;
25
readonly onDidCloseConnection = Event.None;
26
readonly onDidReportConnectProgress = Event.None as Event<ISSHConnectProgress>;
27
28
readonly sentMessages: { connectionId: string; message: string }[] = [];
29
30
async relaySend(connectionId: string, message: string): Promise<void> {
31
this.sentMessages.push({ connectionId, message });
32
}
33
34
async connect(_config: ISSHAgentHostConfig): Promise<ISSHConnectResult> {
35
throw new Error('Not implemented');
36
}
37
async disconnect(_host: string): Promise<void> { }
38
async listSSHConfigHosts(): Promise<string[]> { return []; }
39
async ensureUserSSHConfig(): Promise<URI> { return URI.file('/tmp/ssh-config'); }
40
async listSSHConfigFiles(): Promise<URI[]> { return [URI.file('/tmp/ssh-config')]; }
41
async resolveSSHConfig(_host: string): Promise<ISSHResolvedConfig> {
42
throw new Error('Not implemented');
43
}
44
async reconnect(_sshConfigHost: string, _name: string): Promise<ISSHConnectResult> {
45
throw new Error('Not implemented');
46
}
47
48
// Test helpers
49
fireRelayMessage(msg: ISSHRelayMessage): void {
50
this._onDidRelayMessage.fire(msg);
51
}
52
53
fireRelayClose(connectionId: string): void {
54
this._onDidRelayClose.fire(connectionId);
55
}
56
57
dispose(): void {
58
this._onDidRelayMessage.dispose();
59
this._onDidRelayClose.dispose();
60
}
61
}
62
63
suite('SSHRelayTransport', () => {
64
65
const disposables = new DisposableStore();
66
let mockService: MockSSHMainService;
67
68
setup(() => {
69
mockService = new MockSSHMainService();
70
disposables.add({ dispose: () => mockService.dispose() });
71
});
72
73
teardown(() => disposables.clear());
74
ensureNoDisposablesAreLeakedInTestSuite();
75
76
test('receives messages matching connectionId', () => {
77
const transport = disposables.add(new SSHRelayTransport('conn-1', mockService as unknown as ISSHRemoteAgentHostMainService));
78
79
const received: unknown[] = [];
80
disposables.add(transport.onMessage(msg => received.push(msg)));
81
82
mockService.fireRelayMessage({ connectionId: 'conn-1', data: '{"jsonrpc":"2.0","id":1}' });
83
84
assert.strictEqual(received.length, 1);
85
assert.deepStrictEqual(received[0], { jsonrpc: '2.0', id: 1 });
86
});
87
88
test('ignores messages for other connectionIds', () => {
89
const transport = disposables.add(new SSHRelayTransport('conn-1', mockService as unknown as ISSHRemoteAgentHostMainService));
90
91
const received: unknown[] = [];
92
disposables.add(transport.onMessage(msg => received.push(msg)));
93
94
mockService.fireRelayMessage({ connectionId: 'conn-2', data: '{"jsonrpc":"2.0","id":1}' });
95
96
assert.strictEqual(received.length, 0);
97
});
98
99
test('drops malformed JSON messages', () => {
100
const transport = disposables.add(new SSHRelayTransport('conn-1', mockService as unknown as ISSHRemoteAgentHostMainService));
101
102
const received: unknown[] = [];
103
disposables.add(transport.onMessage(msg => received.push(msg)));
104
105
// Should not throw
106
mockService.fireRelayMessage({ connectionId: 'conn-1', data: 'not-json{{{' });
107
108
assert.strictEqual(received.length, 0);
109
});
110
111
test('fires onClose when relay closes for matching connectionId', () => {
112
const transport = disposables.add(new SSHRelayTransport('conn-1', mockService as unknown as ISSHRemoteAgentHostMainService));
113
114
let closed = false;
115
disposables.add(transport.onClose(() => { closed = true; }));
116
117
mockService.fireRelayClose('conn-1');
118
119
assert.strictEqual(closed, true);
120
});
121
122
test('does not fire onClose for other connectionIds', () => {
123
const transport = disposables.add(new SSHRelayTransport('conn-1', mockService as unknown as ISSHRemoteAgentHostMainService));
124
125
let closed = false;
126
disposables.add(transport.onClose(() => { closed = true; }));
127
128
mockService.fireRelayClose('conn-2');
129
130
assert.strictEqual(closed, false);
131
});
132
133
test('send() calls relaySend with correct connectionId', async () => {
134
const transport = disposables.add(new SSHRelayTransport('conn-1', mockService as unknown as ISSHRemoteAgentHostMainService));
135
136
const msg = { jsonrpc: '2.0' as const, method: 'test', id: 42 };
137
transport.send(msg as never);
138
139
// Give the async relaySend a tick to register
140
await new Promise<void>(r => queueMicrotask(r));
141
142
assert.strictEqual(mockService.sentMessages.length, 1);
143
assert.strictEqual(mockService.sentMessages[0].connectionId, 'conn-1');
144
assert.deepStrictEqual(JSON.parse(mockService.sentMessages[0].message), msg);
145
});
146
147
test('receives multiple messages in order', () => {
148
const transport = disposables.add(new SSHRelayTransport('conn-1', mockService as unknown as ISSHRemoteAgentHostMainService));
149
150
const received: unknown[] = [];
151
disposables.add(transport.onMessage(msg => received.push(msg)));
152
153
mockService.fireRelayMessage({ connectionId: 'conn-1', data: '{"id":1}' });
154
mockService.fireRelayMessage({ connectionId: 'conn-1', data: '{"id":2}' });
155
mockService.fireRelayMessage({ connectionId: 'conn-1', data: '{"id":3}' });
156
157
assert.strictEqual(received.length, 3);
158
assert.deepStrictEqual(received, [{ id: 1 }, { id: 2 }, { id: 3 }]);
159
});
160
161
test('no events after dispose', () => {
162
const transport = disposables.add(new SSHRelayTransport('conn-1', mockService as unknown as ISSHRemoteAgentHostMainService));
163
164
const received: unknown[] = [];
165
let closed = false;
166
disposables.add(transport.onMessage(msg => received.push(msg)));
167
disposables.add(transport.onClose(() => { closed = true; }));
168
169
transport.dispose();
170
171
mockService.fireRelayMessage({ connectionId: 'conn-1', data: '{"id":1}' });
172
mockService.fireRelayClose('conn-1');
173
174
assert.strictEqual(received.length, 0);
175
assert.strictEqual(closed, false);
176
});
177
});
178
179