From 203a2c010b05990ba29b2efbb1134424e288974e Mon Sep 17 00:00:00 2001 From: Rikki Schulte Date: Sun, 19 May 2024 23:39:59 +0200 Subject: [PATCH] startServer coverage --- .../src/__tests__/__utils__/MockProject.ts | 4 +- .../src/__tests__/startServer-test.ts | 109 ++++++++++++++++++ .../src/__tests__/startServer.spec.ts | 25 +++- .../src/startServer.ts | 87 +++++++------- 4 files changed, 177 insertions(+), 48 deletions(-) create mode 100644 packages/graphql-language-service-server/src/__tests__/startServer-test.ts diff --git a/packages/graphql-language-service-server/src/__tests__/__utils__/MockProject.ts b/packages/graphql-language-service-server/src/__tests__/__utils__/MockProject.ts index 38e936be2b0..f0cd000e329 100644 --- a/packages/graphql-language-service-server/src/__tests__/__utils__/MockProject.ts +++ b/packages/graphql-language-service-server/src/__tests__/__utils__/MockProject.ts @@ -74,7 +74,9 @@ export class MockProject { }, }, logger: new MockLogger(), - loadConfigOptions: { rootDir: root }, + loadConfigOptions: { + rootDir: root, + }, }); } diff --git a/packages/graphql-language-service-server/src/__tests__/startServer-test.ts b/packages/graphql-language-service-server/src/__tests__/startServer-test.ts new file mode 100644 index 00000000000..dea290df0fa --- /dev/null +++ b/packages/graphql-language-service-server/src/__tests__/startServer-test.ts @@ -0,0 +1,109 @@ +import { IPCMessageReader, IPCMessageWriter } from 'vscode-jsonrpc/node'; +import { addHandlers, buildOptions, initializeHandlers } from '../startServer'; + +describe('buildOptions', () => { + it('should build options', () => { + const options = buildOptions({}); + expect(options).toEqual({ + loadConfigOptions: { + extensions: [], + rootDir: process.cwd(), + }, + }); + }); + it('should build options with loadConfigOptions', () => { + const options = buildOptions({ loadConfigOptions: { rootDir: '/root' } }); + expect(options).toEqual({ + loadConfigOptions: { + rootDir: '/root', + }, + }); + }); + it('should build options with loadConfigOptions without rootDir', () => { + const options = buildOptions({ loadConfigOptions: { extensions: [] } }); + expect(options).toEqual({ + loadConfigOptions: { + rootDir: process.cwd(), + extensions: [], + }, + }); + }); + it('should build options with just extensions', () => { + const options = buildOptions({ extensions: [] }); + expect(options).toEqual({ + extensions: [], + loadConfigOptions: { + rootDir: process.cwd(), + extensions: [], + }, + }); + }); +}); + +describe('initializeHandlers', () => { + beforeEach(() => { + jest.resetModules(); + }); + it('should initialize handlers', async () => { + const reader = new IPCMessageReader(process); + const writer = new IPCMessageWriter(process); + const handlers = await initializeHandlers({ + reader, + writer, + options: { + loadConfigOptions: { rootDir: '/root' }, + }, + }); + expect(handlers).toBeDefined(); + }); +}); + +describe('addHandlers', () => { + it('should add handlers', async () => { + const connection = { + onInitialize: jest.fn(), + onInitialized: jest.fn(), + onShutdown: jest.fn(), + onExit: jest.fn(), + onNotification: jest.fn(), + onRequest: jest.fn(), + sendNotification: jest.fn(), + sendRequest: jest.fn(), + console: { + error: jest.fn(), + warn: jest.fn(), + info: jest.fn(), + log: jest.fn(), + }, + }; + + await addHandlers({ + connection, + options: { loadConfigOptions: { rootDir: '/root' } }, + }); + expect( + connection.onNotification.mock.calls.map(c => c[0].method ?? c[0]), + ).toEqual([ + 'textDocument/didOpen', + 'textDocument/didSave', + 'textDocument/didChange', + 'textDocument/didClose', + 'exit', + '$/cancelRequest', + 'workspace/didChangeWatchedFiles', + 'workspace/didChangeConfiguration', + ]); + expect( + connection.onRequest.mock.calls.map(c => c[0].method ?? c[0]), + ).toEqual([ + 'shutdown', + 'initialize', + 'textDocument/completion', + 'completionItem/resolve', + 'textDocument/definition', + 'textDocument/hover', + 'textDocument/documentSymbol', + 'workspace/symbol', + ]); + }); +}); diff --git a/packages/graphql-language-service-server/src/__tests__/startServer.spec.ts b/packages/graphql-language-service-server/src/__tests__/startServer.spec.ts index dbf4a496f7b..02a817258ba 100644 --- a/packages/graphql-language-service-server/src/__tests__/startServer.spec.ts +++ b/packages/graphql-language-service-server/src/__tests__/startServer.spec.ts @@ -2,7 +2,30 @@ import startServer from '../startServer'; describe('startServer', () => { it('should start the server', async () => { - await startServer({}); + await startServer(); + // if the server starts, we're good + expect(true).toBe(true); + }); + // this one fails to exit + it('should start the server with stream', async () => { + await startServer({ + method: 'stream', + }); + // if the server starts, we're good + expect(true).toBe(true); + }); + it('should start the server with ipc', async () => { + await startServer({ + method: 'node', + }); + // if the server starts, we're good + expect(true).toBe(true); + }); + it('should start the server with websockets', async () => { + await startServer({ + method: 'socket', + port: 4000, + }); // if the server starts, we're good expect(true).toBe(true); }); diff --git a/packages/graphql-language-service-server/src/startServer.ts b/packages/graphql-language-service-server/src/startServer.ts index e22c3a8df79..7ed7ad5aab0 100644 --- a/packages/graphql-language-service-server/src/startServer.ts +++ b/packages/graphql-language-service-server/src/startServer.ts @@ -6,7 +6,6 @@ * LICENSE file in the root directory of this source tree. * */ -import * as net from 'node:net'; import { MessageProcessor } from './MessageProcessor'; import { GraphQLConfig, GraphQLExtensionDeclaration } from 'graphql-config'; import { @@ -36,7 +35,7 @@ import { DocumentSymbolRequest, PublishDiagnosticsParams, WorkspaceSymbolRequest, - createConnection, + createConnection as createLanguageServerConnection, Connection, } from 'vscode-languageserver/node'; @@ -48,20 +47,28 @@ import { SupportedExtensionsEnum, } from './constants'; import { LoadConfigOptions } from './types'; +import { createConnection } from 'node:net'; export interface ServerOptions { /** - * port for the LSP server to run on. required if using method socket + * socket, streams, or node (ipc). + * @default 'node' + */ + method?: 'socket' | 'stream' | 'node'; + /** + * (socket only) port for the LSP server to run on. required if using method socket */ port?: number; /** - * hostname if using socker + * (socket only) hostname for the LSP server to run on. + * @default '127.0.0.1' */ hostname?: string; /** - * socket, streams, or node (ipc). `node` by default. + * (socket only) encoding for the LSP server to use. + * @default 'utf-8' */ - method?: 'socket' | 'stream' | 'node'; + encoding?: 'utf-8' | 'ascii'; /** * `LoadConfigOptions` from `graphql-config@3` to use when we `loadConfig()` * uses process.cwd() by default for `rootDir` option. @@ -69,22 +76,23 @@ export interface ServerOptions { */ loadConfigOptions?: LoadConfigOptions; /** - * (deprecated: use loadConfigOptions.rootDir now) the directory where graphql-config is found + * @deprecated use loadConfigOptions.rootDir now) the directory where graphql-config is found */ configDir?: string; /** - * (deprecated: use loadConfigOptions.extensions now) array of functions to transform the graphql-config and add extensions dynamically + * @deprecated use loadConfigOptions.extensions */ extensions?: GraphQLExtensionDeclaration[]; /** - * default: ['.js', '.jsx', '.tsx', '.ts', '.mjs'] * allowed file extensions for embedded graphql, used by the parser. * note that with vscode, this is also controlled by manifest and client configurations. * do not put full-file graphql extensions here! + * @default ['.js', '.jsx', '.tsx', '.ts', '.mjs'] */ fileExtensions?: ReadonlyArray; /** - * default: ['graphql'] - allowed file extensions for graphql, used by the parser + * allowed file extensions for full-file graphql, used by the parser. + * @default ['graphql', 'graphqls', 'gql' ] */ graphqlFileExtensions?: string[]; /** @@ -123,10 +131,14 @@ export type MappedServerOptions = Omit & { * Legacy mappings for < 2.5.0 * @param options {ServerOptions} */ -const buildOptions = (options: ServerOptions): MappedServerOptions => { +export const buildOptions = (options: ServerOptions): MappedServerOptions => { const serverOptions = { ...options } as MappedServerOptions; + if (serverOptions.loadConfigOptions) { const { extensions, rootDir } = serverOptions.loadConfigOptions; + if (extensions) { + serverOptions.loadConfigOptions.extensions = extensions; + } if (!rootDir) { if (serverOptions.configDir) { serverOptions.loadConfigOptions.rootDir = serverOptions.configDir; @@ -134,16 +146,10 @@ const buildOptions = (options: ServerOptions): MappedServerOptions => { serverOptions.loadConfigOptions.rootDir = process.cwd(); } } - if (serverOptions.extensions) { - serverOptions.loadConfigOptions.extensions = [ - ...serverOptions.extensions, - ...(extensions || []), - ]; - } } else { serverOptions.loadConfigOptions = { rootDir: options.configDir || process.cwd(), - extensions: [], + extensions: serverOptions.extensions || [], }; } return serverOptions; @@ -156,7 +162,7 @@ const buildOptions = (options: ServerOptions): MappedServerOptions => { * @returns {Promise} */ export default async function startServer( - options: ServerOptions, + options?: ServerOptions, ): Promise { if (!options?.method) { return; @@ -176,25 +182,13 @@ export default async function startServer( process.exit(1); } - const { port, hostname } = options; - const socket = net - .createServer(async client => { - client.setEncoding('utf8'); - reader = new SocketMessageReader(client); - writer = new SocketMessageWriter(client); - client.on('end', () => { - socket.close(); - process.exit(0); - }); - const s = await initializeHandlers({ - reader, - writer, - options: finalOptions, - }); - s.listen(); - }) - .listen(port, hostname); - return; + const { port, hostname, encoding } = options; + const socket = createConnection(port, hostname ?? '127.0.01'); + + reader = new SocketMessageReader(socket, encoding ?? 'utf-8'); + writer = new SocketMessageWriter(socket, encoding ?? 'utf-8'); + + break; case 'stream': reader = new StreamMessageReader(process.stdin); writer = new StreamMessageWriter(process.stdout); @@ -202,15 +196,15 @@ export default async function startServer( default: reader = new IPCMessageReader(process); writer = new IPCMessageWriter(process); + break; } - - const serverWithHandlers = await initializeHandlers({ + const streamServer = await initializeHandlers({ reader, writer, options: finalOptions, }); - serverWithHandlers.listen(); + streamServer.listen(); } type InitializerParams = { @@ -219,12 +213,12 @@ type InitializerParams = { options: MappedServerOptions; }; -async function initializeHandlers({ +export async function initializeHandlers({ reader, writer, options, }: InitializerParams): Promise { - const connection = createConnection(reader, writer); + const connection = createLanguageServerConnection(reader, writer); const logger = new Logger(connection, options.debug); try { @@ -266,7 +260,7 @@ type HandlerOptions = { * * @param options {HandlerOptions} */ -async function addHandlers({ +export async function addHandlers({ connection, logger, config, @@ -314,8 +308,9 @@ async function addHandlers({ }, ); - connection.onNotification(DidCloseTextDocumentNotification.type, params => - messageProcessor.handleDidCloseNotification(params), + connection.onNotification( + DidCloseTextDocumentNotification.type, + messageProcessor.handleDidCloseNotification, ); connection.onRequest(ShutdownRequest.type, () => messageProcessor.handleShutdownRequest(),