| import { describe, it } from 'node:test'; |
| import assert from 'node:assert/strict'; |
| import http2 from 'http2'; |
| import { redactProxyUrl, buildLanguageServerEnv, probeLanguageServerPort } from '../src/langserver.js'; |
|
|
| async function withHttp2Server(handler, fn) { |
| const server = http2.createServer(); |
| server.on('stream', handler); |
| await new Promise(resolve => server.listen(0, '127.0.0.1', resolve)); |
| const port = server.address().port; |
| try { |
| return await fn(port); |
| } finally { |
| await new Promise(resolve => server.close(resolve)); |
| } |
| } |
|
|
| describe('redactProxyUrl', () => { |
| it('redacts credentials from proxy URLs', () => { |
| assert.equal(redactProxyUrl('http://user:secret@example.com:8080'), 'example.com:8080 (auth=true)'); |
| }); |
|
|
| it('shows host and port for unauthenticated proxies', () => { |
| assert.equal(redactProxyUrl({ host: 'proxy.example.com', port: 1080 }), 'proxy.example.com:1080'); |
| }); |
| }); |
|
|
| describe('buildLanguageServerEnv', () => { |
| it('keeps allowlisted vars and drops unrelated process env', () => { |
| const env = buildLanguageServerEnv({ |
| HOME: '/home/dev', |
| PATH: '/usr/bin', |
| LANG: 'en_US.UTF-8', |
| AWS_SECRET_ACCESS_KEY: 'leak-me', |
| GITHUB_TOKEN: 'leak-me-too', |
| }); |
| assert.equal(env.HOME, '/home/dev'); |
| assert.equal(env.PATH, '/usr/bin'); |
| assert.equal(env.LANG, 'en_US.UTF-8'); |
| assert.ok(!('AWS_SECRET_ACCESS_KEY' in env)); |
| assert.ok(!('GITHUB_TOKEN' in env)); |
| }); |
|
|
| it('applies proxy override across both HTTP and HTTPS forms', () => { |
| const env = buildLanguageServerEnv( |
| { HOME: '/home/dev', HTTP_PROXY: 'http://stale:8080' }, |
| { proxyUrl: 'http://fresh.example.com:9999' } |
| ); |
| assert.equal(env.HTTP_PROXY, 'http://fresh.example.com:9999'); |
| assert.equal(env.HTTPS_PROXY, 'http://fresh.example.com:9999'); |
| assert.equal(env.http_proxy, 'http://fresh.example.com:9999'); |
| assert.equal(env.https_proxy, 'http://fresh.example.com:9999'); |
| }); |
|
|
| it('falls back to /root for HOME only when source has none', () => { |
| const env = buildLanguageServerEnv({ PATH: '/usr/bin' }); |
| assert.equal(env.HOME, '/root'); |
| }); |
|
|
| it('preserves SSL trust env vars so hardened hosts still verify upstream', () => { |
| const env = buildLanguageServerEnv({ |
| HOME: '/home/dev', |
| SSL_CERT_FILE: '/etc/ssl/certs/ca-bundle.crt', |
| NODE_EXTRA_CA_CERTS: '/etc/ssl/extra.pem', |
| }); |
| assert.equal(env.SSL_CERT_FILE, '/etc/ssl/certs/ca-bundle.crt'); |
| assert.equal(env.NODE_EXTRA_CA_CERTS, '/etc/ssl/extra.pem'); |
| }); |
| }); |
|
|
| describe('probeLanguageServerPort', () => { |
| it('accepts a port only when the response has a gRPC-like LS signature', async () => { |
| await withHttp2Server((stream) => { |
| stream.respond({ ':status': 405, 'content-type': 'application/grpc', 'grpc-status': '12' }); |
| stream.end(); |
| }, async (port) => { |
| assert.equal(await probeLanguageServerPort(port), true); |
| }); |
| }); |
|
|
| it('rejects unrelated HTTP/2 services on the default LS port shape', async () => { |
| await withHttp2Server((stream) => { |
| stream.respond({ ':status': 200, 'content-type': 'text/plain', server: 'not-ls' }); |
| stream.end('ok'); |
| }, async (port) => { |
| assert.equal(await probeLanguageServerPort(port), false); |
| }); |
| }); |
| }); |
|
|