Skip to content

Commit

Permalink
feat: check for command availability
Browse files Browse the repository at this point in the history
@W-16617918@
Add checks to see if target command is available before getting service.
  • Loading branch information
peternhale committed Sep 4, 2024
1 parent f87ddfd commit c41e17a
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 25 deletions.
4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: [
"/node_modules/",
"/lib/" // Add this line to ignore the lib directory
]
};
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
68 changes: 45 additions & 23 deletions src/services/serviceProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -35,7 +36,7 @@ export class ServiceProvider {
static async getService<T extends ServiceType>(
type: T,
instanceName?: string,
...rest: ServiceParams<T>[] // This does not make sense, so keep an eye on it
...rest: ServiceParams<T>[]
): Promise<ServiceReturnType<T>> {
let serviceInstance: ServiceReturnType<T> | undefined;

Expand All @@ -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
Expand All @@ -55,6 +55,8 @@ export class ServiceProvider {
}

if (!serviceInstance) {
const command = ServiceProvider.getCommandString(type);
await ServiceProvider.checkCommandAvailability(command);
serviceInstance = await ServiceProvider.materializeService<T>(
type,
correctedInstanceName,
Expand Down Expand Up @@ -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<void> {
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<string[]> {
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.
Expand All @@ -180,28 +219,11 @@ export class ServiceProvider {
const correctedParams = paramValidator.validateAndCorrect(
rest as ServiceParams<T>
);
let serviceInstance: ServiceReturnType<T> | undefined;
const command = ServiceProvider.getCommandString(type);

switch (type) {
case ServiceType.Logger:
// Call VSCode command to materialize service A
serviceInstance = await vscode.commands.executeCommand<
ServiceReturnType<T>
>(
'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<T>
>('sf.vscode.core.get.telemetry', instanceName, ...correctedParams);
break;
default:
throw new Error(`Unsupported service type: ${type}`);
}
const serviceInstance = await vscode.commands.executeCommand<
ServiceReturnType<T>
>(command, instanceName, ...correctedParams);

if (!serviceInstance) {
throw new Error(
Expand Down
52 changes: 50 additions & 2 deletions test/services/serviceProvider.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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.`
);
});
});
});

0 comments on commit c41e17a

Please sign in to comment.