Path: blob/main/src/vs/platform/agentHost/test/electron-browser/sshRelayTransport.test.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { Emitter, Event } from '../../../../base/common/event.js';7import { DisposableStore } from '../../../../base/common/lifecycle.js';8import { URI } from '../../../../base/common/uri.js';9import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';10import { SSHRelayTransport } from '../../electron-browser/sshRelayTransport.js';11import type { ISSHRelayMessage, ISSHRemoteAgentHostMainService, ISSHConnectProgress, ISSHConnectResult, ISSHAgentHostConfig, ISSHResolvedConfig } from '../../common/sshRemoteAgentHost.js';1213/**14* Minimal mock of ISSHRemoteAgentHostMainService for testing the relay transport.15*/16class MockSSHMainService {17private readonly _onDidRelayMessage = new Emitter<ISSHRelayMessage>();18readonly onDidRelayMessage = this._onDidRelayMessage.event;1920private readonly _onDidRelayClose = new Emitter<string>();21readonly onDidRelayClose = this._onDidRelayClose.event;2223readonly onDidChangeConnections = Event.None;24readonly onDidCloseConnection = Event.None;25readonly onDidReportConnectProgress = Event.None as Event<ISSHConnectProgress>;2627readonly sentMessages: { connectionId: string; message: string }[] = [];2829async relaySend(connectionId: string, message: string): Promise<void> {30this.sentMessages.push({ connectionId, message });31}3233async connect(_config: ISSHAgentHostConfig): Promise<ISSHConnectResult> {34throw new Error('Not implemented');35}36async disconnect(_host: string): Promise<void> { }37async listSSHConfigHosts(): Promise<string[]> { return []; }38async ensureUserSSHConfig(): Promise<URI> { return URI.file('/tmp/ssh-config'); }39async listSSHConfigFiles(): Promise<URI[]> { return [URI.file('/tmp/ssh-config')]; }40async resolveSSHConfig(_host: string): Promise<ISSHResolvedConfig> {41throw new Error('Not implemented');42}43async reconnect(_sshConfigHost: string, _name: string): Promise<ISSHConnectResult> {44throw new Error('Not implemented');45}4647// Test helpers48fireRelayMessage(msg: ISSHRelayMessage): void {49this._onDidRelayMessage.fire(msg);50}5152fireRelayClose(connectionId: string): void {53this._onDidRelayClose.fire(connectionId);54}5556dispose(): void {57this._onDidRelayMessage.dispose();58this._onDidRelayClose.dispose();59}60}6162suite('SSHRelayTransport', () => {6364const disposables = new DisposableStore();65let mockService: MockSSHMainService;6667setup(() => {68mockService = new MockSSHMainService();69disposables.add({ dispose: () => mockService.dispose() });70});7172teardown(() => disposables.clear());73ensureNoDisposablesAreLeakedInTestSuite();7475test('receives messages matching connectionId', () => {76const transport = disposables.add(new SSHRelayTransport('conn-1', mockService as unknown as ISSHRemoteAgentHostMainService));7778const received: unknown[] = [];79disposables.add(transport.onMessage(msg => received.push(msg)));8081mockService.fireRelayMessage({ connectionId: 'conn-1', data: '{"jsonrpc":"2.0","id":1}' });8283assert.strictEqual(received.length, 1);84assert.deepStrictEqual(received[0], { jsonrpc: '2.0', id: 1 });85});8687test('ignores messages for other connectionIds', () => {88const transport = disposables.add(new SSHRelayTransport('conn-1', mockService as unknown as ISSHRemoteAgentHostMainService));8990const received: unknown[] = [];91disposables.add(transport.onMessage(msg => received.push(msg)));9293mockService.fireRelayMessage({ connectionId: 'conn-2', data: '{"jsonrpc":"2.0","id":1}' });9495assert.strictEqual(received.length, 0);96});9798test('drops malformed JSON messages', () => {99const transport = disposables.add(new SSHRelayTransport('conn-1', mockService as unknown as ISSHRemoteAgentHostMainService));100101const received: unknown[] = [];102disposables.add(transport.onMessage(msg => received.push(msg)));103104// Should not throw105mockService.fireRelayMessage({ connectionId: 'conn-1', data: 'not-json{{{' });106107assert.strictEqual(received.length, 0);108});109110test('fires onClose when relay closes for matching connectionId', () => {111const transport = disposables.add(new SSHRelayTransport('conn-1', mockService as unknown as ISSHRemoteAgentHostMainService));112113let closed = false;114disposables.add(transport.onClose(() => { closed = true; }));115116mockService.fireRelayClose('conn-1');117118assert.strictEqual(closed, true);119});120121test('does not fire onClose for other connectionIds', () => {122const transport = disposables.add(new SSHRelayTransport('conn-1', mockService as unknown as ISSHRemoteAgentHostMainService));123124let closed = false;125disposables.add(transport.onClose(() => { closed = true; }));126127mockService.fireRelayClose('conn-2');128129assert.strictEqual(closed, false);130});131132test('send() calls relaySend with correct connectionId', async () => {133const transport = disposables.add(new SSHRelayTransport('conn-1', mockService as unknown as ISSHRemoteAgentHostMainService));134135const msg = { jsonrpc: '2.0' as const, method: 'test', id: 42 };136transport.send(msg as never);137138// Give the async relaySend a tick to register139await new Promise<void>(r => queueMicrotask(r));140141assert.strictEqual(mockService.sentMessages.length, 1);142assert.strictEqual(mockService.sentMessages[0].connectionId, 'conn-1');143assert.deepStrictEqual(JSON.parse(mockService.sentMessages[0].message), msg);144});145146test('receives multiple messages in order', () => {147const transport = disposables.add(new SSHRelayTransport('conn-1', mockService as unknown as ISSHRemoteAgentHostMainService));148149const received: unknown[] = [];150disposables.add(transport.onMessage(msg => received.push(msg)));151152mockService.fireRelayMessage({ connectionId: 'conn-1', data: '{"id":1}' });153mockService.fireRelayMessage({ connectionId: 'conn-1', data: '{"id":2}' });154mockService.fireRelayMessage({ connectionId: 'conn-1', data: '{"id":3}' });155156assert.strictEqual(received.length, 3);157assert.deepStrictEqual(received, [{ id: 1 }, { id: 2 }, { id: 3 }]);158});159160test('no events after dispose', () => {161const transport = disposables.add(new SSHRelayTransport('conn-1', mockService as unknown as ISSHRemoteAgentHostMainService));162163const received: unknown[] = [];164let closed = false;165disposables.add(transport.onMessage(msg => received.push(msg)));166disposables.add(transport.onClose(() => { closed = true; }));167168transport.dispose();169170mockService.fireRelayMessage({ connectionId: 'conn-1', data: '{"id":1}' });171mockService.fireRelayClose('conn-1');172173assert.strictEqual(received.length, 0);174assert.strictEqual(closed, false);175});176});177178179