From c41e17a3d9c407e0c9ee1b0440049f12732b60e5 Mon Sep 17 00:00:00 2001 From: peternhale Date: Wed, 4 Sep 2024 11:14:26 -0600 Subject: [PATCH] feat: check for command availability @W-16617918@ Add checks to see if target command is available before getting service. --- jest.config.js | 4 ++ src/index.ts | 3 ++ src/services/serviceProvider.ts | 68 ++++++++++++++++++--------- test/services/serviceProvider.test.ts | 52 +++++++++++++++++++- 4 files changed, 102 insertions(+), 25 deletions(-) diff --git a/jest.config.js b/jest.config.js index 6d6062b..c7e80c5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,8 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', + testPathIgnorePatterns: [ + "/node_modules/", + "/lib/" // Add this line to ignore the lib directory + ] }; diff --git a/src/index.ts b/src/index.ts index b63fcce..cf50a65 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,3 +7,6 @@ export { ServiceProvider } from './services'; export * from './types'; + +export const telemetryCommand = 'sf.vscode.core.get.telemetry'; +export const loggerCommand = 'sf.vscode.core.logger.get.instance'; diff --git a/src/services/serviceProvider.ts b/src/services/serviceProvider.ts index 3651d4b..9902156 100644 --- a/src/services/serviceProvider.ts +++ b/src/services/serviceProvider.ts @@ -12,6 +12,7 @@ import { ServiceValidators } from '../types'; import * as vscode from 'vscode'; +import { loggerCommand, telemetryCommand } from '../index'; /** * The ServiceProvider class is a utility class that provides services of different types. @@ -35,7 +36,7 @@ export class ServiceProvider { static async getService( type: T, instanceName?: string, - ...rest: ServiceParams[] // This does not make sense, so keep an eye on it + ...rest: ServiceParams[] ): Promise> { let serviceInstance: ServiceReturnType | undefined; @@ -46,7 +47,6 @@ export class ServiceProvider { if (ServiceProvider.serviceMap.has(type)) { const serviceInstances = ServiceProvider.serviceMap.get(type); - if (serviceInstances?.has(correctedInstanceName)) { serviceInstance = serviceInstances.get( correctedInstanceName @@ -55,6 +55,8 @@ export class ServiceProvider { } if (!serviceInstance) { + const command = ServiceProvider.getCommandString(type); + await ServiceProvider.checkCommandAvailability(command); serviceInstance = await ServiceProvider.materializeService( type, correctedInstanceName, @@ -161,6 +163,43 @@ export class ServiceProvider { ServiceProvider.serviceMap.clear(); } + /** + * Checks if a command is available in the list of VSCode commands. + * @param command - The command to check. + * @throws {Error} If the command is not available. + */ + private static async checkCommandAvailability( + command: string + ): Promise { + const availableCommands = await this.getCommands(); + if (!availableCommands.includes(command)) { + throw new Error( + `Command ${command} cannot be found in the current vscode session.` + ); + } + } + + private static getCommands(): Promise { + return Promise.resolve(vscode.commands.getCommands()); + } + + /** + * Returns the command string based on the service type. + * @param type - The type of the service. + * @returns The command string. + * @throws {Error} If the service type is unsupported. + */ + private static getCommandString(type: ServiceType): string { + switch (type) { + case ServiceType.Logger: + return loggerCommand; + case ServiceType.Telemetry: + return telemetryCommand; + default: + throw new Error(`Unsupported service type: ${type}`); + } + } + /** * Materializes a service instance of the specified type and instance name. * If the service instance does not exist, it will be created. @@ -180,28 +219,11 @@ export class ServiceProvider { const correctedParams = paramValidator.validateAndCorrect( rest as ServiceParams ); - let serviceInstance: ServiceReturnType | undefined; + const command = ServiceProvider.getCommandString(type); - switch (type) { - case ServiceType.Logger: - // Call VSCode command to materialize service A - serviceInstance = await vscode.commands.executeCommand< - ServiceReturnType - >( - 'sf.vscode.core.logger.get.instance', - instanceName, - ...correctedParams - ); - break; - case ServiceType.Telemetry: - // Call VSCode command to materialize service A - serviceInstance = await vscode.commands.executeCommand< - ServiceReturnType - >('sf.vscode.core.get.telemetry', instanceName, ...correctedParams); - break; - default: - throw new Error(`Unsupported service type: ${type}`); - } + const serviceInstance = await vscode.commands.executeCommand< + ServiceReturnType + >(command, instanceName, ...correctedParams); if (!serviceInstance) { throw new Error( diff --git a/test/services/serviceProvider.test.ts b/test/services/serviceProvider.test.ts index 6f0bea5..85eb0d4 100644 --- a/test/services/serviceProvider.test.ts +++ b/test/services/serviceProvider.test.ts @@ -14,7 +14,9 @@ import { Properties, Measurements, TelemetryData, - SFDX_CORE_EXTENSION_NAME + SFDX_CORE_EXTENSION_NAME, + telemetryCommand, + loggerCommand } from '../../src'; import { ExtensionContext, ExtensionMode } from 'vscode'; @@ -102,6 +104,10 @@ describe('ServiceProvider', () => { beforeEach(() => { ServiceProvider.clearAllServices(); (vscode.commands.executeCommand as jest.Mock).mockClear(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + jest.spyOn(ServiceProvider as any, 'getCommands').mockImplementation(() => { + return Promise.resolve([telemetryCommand, loggerCommand]); + }); }); it('should get a service', async () => { @@ -200,8 +206,50 @@ describe('ServiceProvider', () => { 'instance1', telemetryServiceInstance ); - }).toThrowError( + }).toThrow( new Error('Service instance instance1 of type Telemetry already exists') ); }); + describe('getCommandString', () => { + it('should return the correct command string for Logger service type', () => { + const command = ServiceProvider['getCommandString'](ServiceType.Logger); + expect(command).toBe(loggerCommand); + }); + + it('should return the correct command string for Telemetry service type', () => { + const command = ServiceProvider['getCommandString']( + ServiceType.Telemetry + ); + expect(command).toBe(telemetryCommand); + }); + + it('should throw an error for unsupported service type', () => { + expect(() => { + // @ts-ignore + ServiceProvider['getCommandString'](999 as ServiceType); + }).toThrow('Unsupported service type: 999'); + }); + }); + + describe('checkCommandAvailability', () => { + it('should not throw an error if the command is available', async () => { + await expect( + ServiceProvider['checkCommandAvailability'](loggerCommand) + ).resolves.not.toThrow(); + }); + + it('should throw an error if the command is not available', async () => { + jest + // eslint-disable-next-line @typescript-eslint/no-explicit-any + .spyOn(ServiceProvider as any, 'getCommands') + .mockImplementation(() => { + return Promise.resolve([telemetryCommand]); + }); + await expect( + ServiceProvider['checkCommandAvailability'](loggerCommand) + ).rejects.toThrow( + `Command ${loggerCommand} cannot be found in the current vscode session.` + ); + }); + }); });