diff --git a/packages/core/src/ide/ide-client.test.ts b/packages/core/src/ide/ide-client.test.ts index 775c70afcc..25822a4161 100644 --- a/packages/core/src/ide/ide-client.test.ts +++ b/packages/core/src/ide/ide-client.test.ts @@ -12,6 +12,7 @@ import { beforeEach, afterEach, type Mocked, + type Mock, } from 'vitest'; import { IdeClient, IDEConnectionStatus } from './ide-client.js'; import * as fs from 'node:fs'; @@ -25,48 +26,32 @@ import * as path from 'node:path'; import { getIdeServerHost } from './ide-client.js'; import { pathToFileURL } from 'node:url'; -// Mock os.tmpdir to control the temp directory in tests -vi.mock('node:os', async (importOriginal) => { - const actualOs = await importOriginal(); - return { - ...actualOs, - tmpdir: vi.fn(), - }; -}); - -// Mock node:fs to allow spying on existsSync while keeping real implementation for others vi.mock('node:fs', async (importOriginal) => { const actual = await importOriginal(); return { - ...actual, - existsSync: vi.fn(actual.existsSync), + ...(actual as object), + promises: { + ...actual.promises, + readFile: vi.fn(), + readdir: vi.fn(), + }, + realpathSync: (p: string) => p, + existsSync: vi.fn(() => false), }; }); - vi.mock('./process-utils.js'); vi.mock('@modelcontextprotocol/sdk/client/index.js'); vi.mock('@modelcontextprotocol/sdk/client/streamableHttp.js'); vi.mock('@modelcontextprotocol/sdk/client/stdio.js'); vi.mock('./detect-ide.js'); +vi.mock('node:os'); describe('IdeClient', () => { let mockClient: Mocked; let mockHttpTransport: Mocked; let mockStdioTransport: Mocked; - let testTmpDir: string; - let ideConfigDir: string; beforeEach(async () => { - // Setup temporary directory for tests - testTmpDir = await fs.promises.mkdtemp( - path.join(os.homedir(), 'ide-client-test-'), - ); - ideConfigDir = path.join(testTmpDir, 'gemini', 'ide'); - await fs.promises.mkdir(ideConfigDir, { recursive: true }); - - // Mock os.tmpdir to return our test temp directory - vi.mocked(os.tmpdir).mockReturnValue(testTmpDir); - // Reset singleton instance for test isolation (IdeClient as unknown as { instance: IdeClient | undefined }).instance = undefined; @@ -85,6 +70,7 @@ describe('IdeClient', () => { pid: 12345, command: 'test-ide', }); + vi.mocked(os.tmpdir).mockReturnValue('/tmp'); // Mock MCP client and transports mockClient = { @@ -108,26 +94,27 @@ describe('IdeClient', () => { await IdeClient.getInstance(); }); - afterEach(async () => { - // Clean up temporary directory - if (testTmpDir) { - await fs.promises.rm(testTmpDir, { recursive: true, force: true }); - } + afterEach(() => { vi.restoreAllMocks(); }); describe('connect', () => { it('should connect using HTTP when port is provided in config file', async () => { const config = { port: '8080' }; - const configPath = path.join( - ideConfigDir, - 'gemini-ide-server-12345.json', - ); - await fs.promises.writeFile(configPath, JSON.stringify(config), 'utf8'); + vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config)); + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([]); const ideClient = await IdeClient.getInstance(); await ideClient.connect(); + expect(fs.promises.readFile).toHaveBeenCalledWith( + path.join('/tmp', 'gemini', 'ide', 'gemini-ide-server-12345.json'), + 'utf8', + ); expect(StreamableHTTPClientTransport).toHaveBeenCalledWith( new URL('http://127.0.0.1:8080/mcp'), expect.any(Object), @@ -140,11 +127,12 @@ describe('IdeClient', () => { it('should connect using stdio when stdio config is provided in file', async () => { const config = { stdio: { command: 'test-cmd', args: ['--foo'] } }; - const configPath = path.join( - ideConfigDir, - 'gemini-ide-server-12345.json', - ); - await fs.promises.writeFile(configPath, JSON.stringify(config), 'utf8'); + vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config)); + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([]); const ideClient = await IdeClient.getInstance(); await ideClient.connect(); @@ -164,11 +152,12 @@ describe('IdeClient', () => { port: '8080', stdio: { command: 'test-cmd', args: ['--foo'] }, }; - const configPath = path.join( - ideConfigDir, - 'gemini-ide-server-12345.json', - ); - await fs.promises.writeFile(configPath, JSON.stringify(config), 'utf8'); + vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config)); + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([]); const ideClient = await IdeClient.getInstance(); await ideClient.connect(); @@ -181,6 +170,14 @@ describe('IdeClient', () => { }); it('should connect using HTTP when port is provided in environment variables', async () => { + vi.mocked(fs.promises.readFile).mockRejectedValue( + new Error('File not found'), + ); + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([]); process.env['GEMINI_CLI_IDE_SERVER_PORT'] = '9090'; const ideClient = await IdeClient.getInstance(); @@ -197,6 +194,14 @@ describe('IdeClient', () => { }); it('should connect using stdio when stdio config is in environment variables', async () => { + vi.mocked(fs.promises.readFile).mockRejectedValue( + new Error('File not found'), + ); + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([]); process.env['GEMINI_CLI_IDE_SERVER_STDIO_COMMAND'] = 'env-cmd'; process.env['GEMINI_CLI_IDE_SERVER_STDIO_ARGS'] = '["--bar"]'; @@ -215,12 +220,12 @@ describe('IdeClient', () => { it('should prioritize file config over environment variables', async () => { const config = { port: '8080' }; - const configPath = path.join( - ideConfigDir, - 'gemini-ide-server-12345.json', - ); - await fs.promises.writeFile(configPath, JSON.stringify(config), 'utf8'); - + vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config)); + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([]); process.env['GEMINI_CLI_IDE_SERVER_PORT'] = '9090'; const ideClient = await IdeClient.getInstance(); @@ -236,6 +241,15 @@ describe('IdeClient', () => { }); it('should be disconnected if no config is found', async () => { + vi.mocked(fs.promises.readFile).mockRejectedValue( + new Error('File not found'), + ); + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([]); + const ideClient = await IdeClient.getInstance(); await ideClient.connect(); @@ -253,11 +267,7 @@ describe('IdeClient', () => { describe('getConnectionConfigFromFile', () => { it('should return config from the specific pid file if it exists', async () => { const config = { port: '1234', workspacePath: '/test/workspace' }; - const configPath = path.join( - ideConfigDir, - 'gemini-ide-server-12345.json', - ); - await fs.promises.writeFile(configPath, JSON.stringify(config), 'utf8'); + vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config)); const ideClient = await IdeClient.getInstance(); // In tests, the private method can be accessed like this. @@ -268,9 +278,20 @@ describe('IdeClient', () => { ).getConnectionConfigFromFile(); expect(result).toEqual(config); + expect(fs.promises.readFile).toHaveBeenCalledWith( + path.join('/tmp', 'gemini', 'ide', 'gemini-ide-server-12345.json'), + 'utf8', + ); }); it('should return undefined if no config files are found', async () => { + vi.mocked(fs.promises.readFile).mockRejectedValue(new Error('not found')); + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([]); + const ideClient = await IdeClient.getInstance(); const result = await ( ideClient as unknown as { @@ -283,12 +304,15 @@ describe('IdeClient', () => { it('should find and parse a single config file with the new naming scheme', async () => { const config = { port: '5678', workspacePath: '/test/workspace' }; - const configPath = path.join( - ideConfigDir, - 'gemini-ide-server-12345-123.json', - ); - await fs.promises.writeFile(configPath, JSON.stringify(config), 'utf8'); - + vi.mocked(fs.promises.readFile).mockRejectedValueOnce( + new Error('not found'), + ); // For old path + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue(['gemini-ide-server-12345-123.json']); + vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config)); vi.spyOn(IdeClient, 'validateWorkspacePath').mockReturnValue({ isValid: true, }); @@ -301,6 +325,10 @@ describe('IdeClient', () => { ).getConnectionConfigFromFile(); expect(result).toEqual(config); + expect(fs.promises.readFile).toHaveBeenCalledWith( + path.join('/tmp', 'gemini', 'ide', 'gemini-ide-server-12345-123.json'), + 'utf8', + ); }); it('should filter out configs with invalid workspace paths', async () => { @@ -312,17 +340,20 @@ describe('IdeClient', () => { port: '1111', workspacePath: '/invalid/workspace', }; - - await fs.promises.writeFile( - path.join(ideConfigDir, 'gemini-ide-server-12345-111.json'), - JSON.stringify(invalidConfig), - 'utf8', - ); - await fs.promises.writeFile( - path.join(ideConfigDir, 'gemini-ide-server-12345-222.json'), - JSON.stringify(validConfig), - 'utf8', + vi.mocked(fs.promises.readFile).mockRejectedValueOnce( + new Error('not found'), ); + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([ + 'gemini-ide-server-12345-111.json', + 'gemini-ide-server-12345-222.json', + ]); + vi.mocked(fs.promises.readFile) + .mockResolvedValueOnce(JSON.stringify(invalidConfig)) + .mockResolvedValueOnce(JSON.stringify(validConfig)); const validateSpy = vi .spyOn(IdeClient, 'validateWorkspacePath') @@ -350,18 +381,20 @@ describe('IdeClient', () => { it('should return the first valid config when multiple workspaces are valid', async () => { const config1 = { port: '1111', workspacePath: '/test/workspace' }; const config2 = { port: '2222', workspacePath: '/test/workspace2' }; - - await fs.promises.writeFile( - path.join(ideConfigDir, 'gemini-ide-server-12345-111.json'), - JSON.stringify(config1), - 'utf8', + vi.mocked(fs.promises.readFile).mockRejectedValueOnce( + new Error('not found'), ); - await fs.promises.writeFile( - path.join(ideConfigDir, 'gemini-ide-server-12345-222.json'), - JSON.stringify(config2), - 'utf8', - ); - + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([ + 'gemini-ide-server-12345-111.json', + 'gemini-ide-server-12345-222.json', + ]); + vi.mocked(fs.promises.readFile) + .mockResolvedValueOnce(JSON.stringify(config1)) + .mockResolvedValueOnce(JSON.stringify(config2)); vi.spyOn(IdeClient, 'validateWorkspacePath').mockReturnValue({ isValid: true, }); @@ -373,10 +406,6 @@ describe('IdeClient', () => { } ).getConnectionConfigFromFile(); - // readdir order is not guaranteed, but usually sorted by name or creation. - // The implementation of getConnectionConfigFromFile explicitly sorts the files: - // const matchingFiles = portFiles.filter(...).sort(); - // So '111' should come before '222'. expect(result).toEqual(config1); }); @@ -384,18 +413,20 @@ describe('IdeClient', () => { process.env['GEMINI_CLI_IDE_SERVER_PORT'] = '2222'; const config1 = { port: '1111', workspacePath: '/test/workspace' }; const config2 = { port: '2222', workspacePath: '/test/workspace2' }; - - await fs.promises.writeFile( - path.join(ideConfigDir, 'gemini-ide-server-12345-111.json'), - JSON.stringify(config1), - 'utf8', + vi.mocked(fs.promises.readFile).mockRejectedValueOnce( + new Error('not found'), ); - await fs.promises.writeFile( - path.join(ideConfigDir, 'gemini-ide-server-12345-222.json'), - JSON.stringify(config2), - 'utf8', - ); - + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([ + 'gemini-ide-server-12345-111.json', + 'gemini-ide-server-12345-222.json', + ]); + vi.mocked(fs.promises.readFile) + .mockResolvedValueOnce(JSON.stringify(config1)) + .mockResolvedValueOnce(JSON.stringify(config2)); vi.spyOn(IdeClient, 'validateWorkspacePath').mockReturnValue({ isValid: true, }); @@ -412,18 +443,20 @@ describe('IdeClient', () => { it('should handle invalid JSON in one of the config files', async () => { const validConfig = { port: '2222', workspacePath: '/test/workspace' }; - - await fs.promises.writeFile( - path.join(ideConfigDir, 'gemini-ide-server-12345-111.json'), - 'invalid json', - 'utf8', + vi.mocked(fs.promises.readFile).mockRejectedValueOnce( + new Error('not found'), ); - await fs.promises.writeFile( - path.join(ideConfigDir, 'gemini-ide-server-12345-222.json'), - JSON.stringify(validConfig), - 'utf8', - ); - + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([ + 'gemini-ide-server-12345-111.json', + 'gemini-ide-server-12345-222.json', + ]); + vi.mocked(fs.promises.readFile) + .mockResolvedValueOnce('invalid json') + .mockResolvedValueOnce(JSON.stringify(validConfig)); vi.spyOn(IdeClient, 'validateWorkspacePath').mockReturnValue({ isValid: true, }); @@ -438,25 +471,41 @@ describe('IdeClient', () => { expect(result).toEqual(validConfig); }); + it('should return undefined if readdir throws an error', async () => { + vi.mocked(fs.promises.readFile).mockRejectedValueOnce( + new Error('not found'), + ); + vi.mocked(fs.promises.readdir).mockRejectedValue( + new Error('readdir failed'), + ); + + const ideClient = await IdeClient.getInstance(); + const result = await ( + ideClient as unknown as { + getConnectionConfigFromFile: () => Promise; + } + ).getConnectionConfigFromFile(); + + expect(result).toBeUndefined(); + }); + it('should ignore files with invalid names', async () => { const validConfig = { port: '3333', workspacePath: '/test/workspace' }; - - await fs.promises.writeFile( - path.join(ideConfigDir, 'gemini-ide-server-12345-111.json'), // valid + vi.mocked(fs.promises.readFile).mockRejectedValueOnce( + new Error('not found'), + ); + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([ + 'gemini-ide-server-12345-111.json', // valid + 'not-a-config-file.txt', // invalid + 'gemini-ide-server-asdf.json', // invalid + ]); + vi.mocked(fs.promises.readFile).mockResolvedValueOnce( JSON.stringify(validConfig), - 'utf8', ); - await fs.promises.writeFile( - path.join(ideConfigDir, 'not-a-config-file.txt'), // invalid - 'some content', - 'utf8', - ); - await fs.promises.writeFile( - path.join(ideConfigDir, 'gemini-ide-server-asdf.json'), // invalid - 'some content', - 'utf8', - ); - vi.spyOn(IdeClient, 'validateWorkspacePath').mockReturnValue({ isValid: true, }); @@ -469,24 +518,34 @@ describe('IdeClient', () => { ).getConnectionConfigFromFile(); expect(result).toEqual(validConfig); + expect(fs.promises.readFile).toHaveBeenCalledWith( + path.join('/tmp', 'gemini', 'ide', 'gemini-ide-server-12345-111.json'), + 'utf8', + ); + expect(fs.promises.readFile).not.toHaveBeenCalledWith( + path.join('/tmp', 'gemini', 'ide', 'not-a-config-file.txt'), + 'utf8', + ); }); it('should match env port string to a number port in the config', async () => { process.env['GEMINI_CLI_IDE_SERVER_PORT'] = '3333'; const config1 = { port: 1111, workspacePath: '/test/workspace' }; const config2 = { port: 3333, workspacePath: '/test/workspace2' }; - - await fs.promises.writeFile( - path.join(ideConfigDir, 'gemini-ide-server-12345-111.json'), - JSON.stringify(config1), - 'utf8', + vi.mocked(fs.promises.readFile).mockRejectedValueOnce( + new Error('not found'), ); - await fs.promises.writeFile( - path.join(ideConfigDir, 'gemini-ide-server-12345-222.json'), - JSON.stringify(config2), - 'utf8', - ); - + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([ + 'gemini-ide-server-12345-111.json', + 'gemini-ide-server-12345-222.json', + ]); + vi.mocked(fs.promises.readFile) + .mockResolvedValueOnce(JSON.stringify(config1)) + .mockResolvedValueOnce(JSON.stringify(config2)); vi.spyOn(IdeClient, 'validateWorkspacePath').mockReturnValue({ isValid: true, }); @@ -510,12 +569,12 @@ describe('IdeClient', () => { it('should return false if tool discovery fails', async () => { const config = { port: '8080' }; - const configPath = path.join( - ideConfigDir, - 'gemini-ide-server-12345.json', - ); - await fs.promises.writeFile(configPath, JSON.stringify(config), 'utf8'); - + vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config)); + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([]); mockClient.request.mockRejectedValue(new Error('Method not found')); const ideClient = await IdeClient.getInstance(); @@ -529,12 +588,12 @@ describe('IdeClient', () => { it('should return false if diffing tools are not available', async () => { const config = { port: '8080' }; - const configPath = path.join( - ideConfigDir, - 'gemini-ide-server-12345.json', - ); - await fs.promises.writeFile(configPath, JSON.stringify(config), 'utf8'); - + vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config)); + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([]); mockClient.request.mockResolvedValue({ tools: [{ name: 'someOtherTool' }], }); @@ -550,12 +609,12 @@ describe('IdeClient', () => { it('should return false if only openDiff tool is available', async () => { const config = { port: '8080' }; - const configPath = path.join( - ideConfigDir, - 'gemini-ide-server-12345.json', - ); - await fs.promises.writeFile(configPath, JSON.stringify(config), 'utf8'); - + vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config)); + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([]); mockClient.request.mockResolvedValue({ tools: [{ name: 'openDiff' }], }); @@ -571,12 +630,12 @@ describe('IdeClient', () => { it('should return true if connected and diffing tools are available', async () => { const config = { port: '8080' }; - const configPath = path.join( - ideConfigDir, - 'gemini-ide-server-12345.json', - ); - await fs.promises.writeFile(configPath, JSON.stringify(config), 'utf8'); - + vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config)); + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([]); mockClient.request.mockResolvedValue({ tools: [{ name: 'openDiff' }, { name: 'closeDiff' }], }); @@ -843,11 +902,12 @@ describe('IdeClient', () => { it('should connect with an auth token if provided in the discovery file', async () => { const authToken = 'test-auth-token'; const config = { port: '8080', authToken }; - const configPath = path.join( - ideConfigDir, - 'gemini-ide-server-12345.json', - ); - await fs.promises.writeFile(configPath, JSON.stringify(config), 'utf8'); + vi.mocked(fs.promises.readFile).mockResolvedValue(JSON.stringify(config)); + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([]); const ideClient = await IdeClient.getInstance(); await ideClient.connect(); @@ -868,6 +928,14 @@ describe('IdeClient', () => { }); it('should connect with an auth token from environment variable if config file is missing', async () => { + vi.mocked(fs.promises.readFile).mockRejectedValue( + new Error('File not found'), + ); + ( + vi.mocked(fs.promises.readdir) as Mock< + (path: fs.PathLike) => Promise + > + ).mockResolvedValue([]); process.env['GEMINI_CLI_IDE_SERVER_PORT'] = '9090'; process.env['GEMINI_CLI_IDE_AUTH_TOKEN'] = 'env-auth-token'; @@ -892,12 +960,14 @@ describe('IdeClient', () => { }); describe('getIdeServerHost', () => { + let existsSyncMock: Mock; let originalSshConnection: string | undefined; let originalVscodeRemoteSession: string | undefined; let originalRemoteContainers: string | undefined; beforeEach(() => { - vi.mocked(fs.existsSync).mockClear(); + existsSyncMock = vi.mocked(fs.existsSync); + existsSyncMock.mockClear(); originalSshConnection = process.env['SSH_CONNECTION']; originalVscodeRemoteSession = process.env['VSCODE_REMOTE_CONTAINERS_SESSION']; @@ -909,7 +979,7 @@ describe('getIdeServerHost', () => { }); afterEach(() => { - vi.restoreAllMocks(); + vi.clearAllMocks(); if (originalSshConnection !== undefined) { process.env['SSH_CONNECTION'] = originalSshConnection; } else { @@ -933,18 +1003,15 @@ describe('getIdeServerHost', () => { dockerenvExists: boolean, containerenvExists: boolean, ) => { - vi.mocked(fs.existsSync).mockImplementation( - (path: string | fs.PathLike) => { - const p = path.toString(); - if (p === '/.dockerenv') { - return dockerenvExists; - } - if (p === '/run/.containerenv') { - return containerenvExists; - } - return false; - }, - ); + existsSyncMock.mockImplementation((path: string) => { + if (path === '/.dockerenv') { + return dockerenvExists; + } + if (path === '/run/.containerenv') { + return containerenvExists; + } + return false; + }); }; it('should return 127.0.0.1 when not in container and no SSH_CONNECTION or Dev Container env vars', () => { @@ -953,8 +1020,8 @@ describe('getIdeServerHost', () => { delete process.env['VSCODE_REMOTE_CONTAINERS_SESSION']; delete process.env['REMOTE_CONTAINERS']; expect(getIdeServerHost()).toBe('127.0.0.1'); - expect(fs.existsSync).toHaveBeenCalledWith('/.dockerenv'); - expect(fs.existsSync).toHaveBeenCalledWith('/run/.containerenv'); + expect(vi.mocked(fs.existsSync)).toHaveBeenCalledWith('/.dockerenv'); + expect(vi.mocked(fs.existsSync)).toHaveBeenCalledWith('/run/.containerenv'); }); it('should return 127.0.0.1 when not in container but SSH_CONNECTION is set', () => { @@ -963,8 +1030,8 @@ describe('getIdeServerHost', () => { delete process.env['VSCODE_REMOTE_CONTAINERS_SESSION']; delete process.env['REMOTE_CONTAINERS']; expect(getIdeServerHost()).toBe('127.0.0.1'); - expect(fs.existsSync).toHaveBeenCalledWith('/.dockerenv'); - expect(fs.existsSync).toHaveBeenCalledWith('/run/.containerenv'); + expect(vi.mocked(fs.existsSync)).toHaveBeenCalledWith('/.dockerenv'); + expect(vi.mocked(fs.existsSync)).toHaveBeenCalledWith('/run/.containerenv'); }); it('should return host.docker.internal when in .dockerenv container and no SSH_CONNECTION or Dev Container env vars', () => { @@ -973,8 +1040,10 @@ describe('getIdeServerHost', () => { delete process.env['VSCODE_REMOTE_CONTAINERS_SESSION']; delete process.env['REMOTE_CONTAINERS']; expect(getIdeServerHost()).toBe('host.docker.internal'); - expect(fs.existsSync).toHaveBeenCalledWith('/.dockerenv'); - expect(fs.existsSync).not.toHaveBeenCalledWith('/run/.containerenv'); // Short-circuiting + expect(vi.mocked(fs.existsSync)).toHaveBeenCalledWith('/.dockerenv'); + expect(vi.mocked(fs.existsSync)).not.toHaveBeenCalledWith( + '/run/.containerenv', + ); // Short-circuiting }); it('should return 127.0.0.1 when in .dockerenv container and SSH_CONNECTION is set', () => { @@ -983,8 +1052,10 @@ describe('getIdeServerHost', () => { delete process.env['VSCODE_REMOTE_CONTAINERS_SESSION']; delete process.env['REMOTE_CONTAINERS']; expect(getIdeServerHost()).toBe('127.0.0.1'); - expect(fs.existsSync).toHaveBeenCalledWith('/.dockerenv'); - expect(fs.existsSync).not.toHaveBeenCalledWith('/run/.containerenv'); // Short-circuiting + expect(vi.mocked(fs.existsSync)).toHaveBeenCalledWith('/.dockerenv'); + expect(vi.mocked(fs.existsSync)).not.toHaveBeenCalledWith( + '/run/.containerenv', + ); // Short-circuiting }); it('should return 127.0.0.1 when in .dockerenv container and VSCODE_REMOTE_CONTAINERS_SESSION is set', () => { @@ -992,8 +1063,10 @@ describe('getIdeServerHost', () => { delete process.env['SSH_CONNECTION']; process.env['VSCODE_REMOTE_CONTAINERS_SESSION'] = 'some_session_id'; expect(getIdeServerHost()).toBe('127.0.0.1'); - expect(fs.existsSync).toHaveBeenCalledWith('/.dockerenv'); - expect(fs.existsSync).not.toHaveBeenCalledWith('/run/.containerenv'); // Short-circuiting + expect(vi.mocked(fs.existsSync)).toHaveBeenCalledWith('/.dockerenv'); + expect(vi.mocked(fs.existsSync)).not.toHaveBeenCalledWith( + '/run/.containerenv', + ); // Short-circuiting }); it('should return host.docker.internal when in .containerenv container and no SSH_CONNECTION or Dev Container env vars', () => { @@ -1002,8 +1075,8 @@ describe('getIdeServerHost', () => { delete process.env['VSCODE_REMOTE_CONTAINERS_SESSION']; delete process.env['REMOTE_CONTAINERS']; expect(getIdeServerHost()).toBe('host.docker.internal'); - expect(fs.existsSync).toHaveBeenCalledWith('/.dockerenv'); - expect(fs.existsSync).toHaveBeenCalledWith('/run/.containerenv'); + expect(vi.mocked(fs.existsSync)).toHaveBeenCalledWith('/.dockerenv'); + expect(vi.mocked(fs.existsSync)).toHaveBeenCalledWith('/run/.containerenv'); }); it('should return 127.0.0.1 when in .containerenv container and SSH_CONNECTION is set', () => { @@ -1012,8 +1085,8 @@ describe('getIdeServerHost', () => { delete process.env['VSCODE_REMOTE_CONTAINERS_SESSION']; delete process.env['REMOTE_CONTAINERS']; expect(getIdeServerHost()).toBe('127.0.0.1'); - expect(fs.existsSync).toHaveBeenCalledWith('/.dockerenv'); - expect(fs.existsSync).toHaveBeenCalledWith('/run/.containerenv'); + expect(vi.mocked(fs.existsSync)).toHaveBeenCalledWith('/.dockerenv'); + expect(vi.mocked(fs.existsSync)).toHaveBeenCalledWith('/run/.containerenv'); }); it('should return 127.0.0.1 when in .containerenv container and REMOTE_CONTAINERS is set', () => { @@ -1021,8 +1094,8 @@ describe('getIdeServerHost', () => { delete process.env['SSH_CONNECTION']; process.env['REMOTE_CONTAINERS'] = 'true'; expect(getIdeServerHost()).toBe('127.0.0.1'); - expect(fs.existsSync).toHaveBeenCalledWith('/.dockerenv'); - expect(fs.existsSync).toHaveBeenCalledWith('/run/.containerenv'); + expect(vi.mocked(fs.existsSync)).toHaveBeenCalledWith('/.dockerenv'); + expect(vi.mocked(fs.existsSync)).toHaveBeenCalledWith('/run/.containerenv'); }); it('should return host.docker.internal when in both containers and no SSH_CONNECTION or Dev Container env vars', () => { @@ -1031,8 +1104,10 @@ describe('getIdeServerHost', () => { delete process.env['VSCODE_REMOTE_CONTAINERS_SESSION']; delete process.env['REMOTE_CONTAINERS']; expect(getIdeServerHost()).toBe('host.docker.internal'); - expect(fs.existsSync).toHaveBeenCalledWith('/.dockerenv'); - expect(fs.existsSync).not.toHaveBeenCalledWith('/run/.containerenv'); // Short-circuiting + expect(vi.mocked(fs.existsSync)).toHaveBeenCalledWith('/.dockerenv'); + expect(vi.mocked(fs.existsSync)).not.toHaveBeenCalledWith( + '/run/.containerenv', + ); // Short-circuiting }); it('should return 127.0.0.1 when in both containers and SSH_CONNECTION is set', () => { @@ -1041,8 +1116,10 @@ describe('getIdeServerHost', () => { delete process.env['VSCODE_REMOTE_CONTAINERS_SESSION']; delete process.env['REMOTE_CONTAINERS']; expect(getIdeServerHost()).toBe('127.0.0.1'); - expect(fs.existsSync).toHaveBeenCalledWith('/.dockerenv'); - expect(fs.existsSync).not.toHaveBeenCalledWith('/run/.containerenv'); // Short-circuiting + expect(vi.mocked(fs.existsSync)).toHaveBeenCalledWith('/.dockerenv'); + expect(vi.mocked(fs.existsSync)).not.toHaveBeenCalledWith( + '/run/.containerenv', + ); // Short-circuiting }); it('should return 127.0.0.1 when in both containers and VSCODE_REMOTE_CONTAINERS_SESSION is set', () => { @@ -1050,8 +1127,10 @@ describe('getIdeServerHost', () => { delete process.env['SSH_CONNECTION']; process.env['VSCODE_REMOTE_CONTAINERS_SESSION'] = 'some_session_id'; expect(getIdeServerHost()).toBe('127.0.0.1'); - expect(fs.existsSync).toHaveBeenCalledWith('/.dockerenv'); - expect(fs.existsSync).not.toHaveBeenCalledWith('/run/.containerenv'); // Short-circuiting + expect(vi.mocked(fs.existsSync)).toHaveBeenCalledWith('/.dockerenv'); + expect(vi.mocked(fs.existsSync)).not.toHaveBeenCalledWith( + '/run/.containerenv', + ); // Short-circuiting }); describe('validateWorkspacePath', () => {