Skip to content

Commit

Permalink
Getting Org connection from Salesforce Extensions services
Browse files Browse the repository at this point in the history
  • Loading branch information
khawkins committed Nov 29, 2023
1 parent b7589f8 commit 19b6852
Show file tree
Hide file tree
Showing 16 changed files with 375 additions and 86 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
"activationEvents": [
"onStartupFinished"
],
"extensionDependencies": [
"salesforce.salesforcedx-vscode-core"
],
"main": "./out/extension.js",
"l10n": "./l10n",
"contributes": {
Expand Down
15 changes: 15 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,23 @@
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
import * as onboardingWizard from './commands/wizard/onboardingWizard';
import { CoreExtensionService } from './services/CoreExtensionService';

export function activate(context: vscode.ExtensionContext) {
// We need to do this first in case any other services need access to those provided by the core extension
try {
CoreExtensionService.loadDependencies();
} catch (err) {
console.error(err);
vscode.window.showErrorMessage(
vscode.l10n.t(
'Failed to activate the extension! Could not load required services from the Salesforce Extension Pack: {0}',
(err as Error).message
)
);
return;
}

onboardingWizard.registerCommand(context);
onboardingWizard.onActivate(context);
}
Expand Down
86 changes: 86 additions & 0 deletions src/services/CoreExtensionService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (c) 2023, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/

import { extensions } from 'vscode';
import { satisfies, valid } from 'semver';
import type {
CoreExtensionApi,
WorkspaceContext
} from '../types';
import {
CORE_EXTENSION_ID,
MINIMUM_REQUIRED_VERSION_CORE_EXTENSION
} from '../utils/constants';

const NOT_INITIALIZED_ERROR = 'CoreExtensionService not initialized';
const CORE_EXTENSION_NOT_FOUND = 'Core extension not found';
const WORKSPACE_CONTEXT_NOT_FOUND = 'Workspace Context not found';
const coreExtensionMinRequiredVersionError =
'You are running an older version of the Salesforce CLI Integration VSCode Extension. Please update the Salesforce Extension pack and try again.';

export class CoreExtensionService {
private static initialized = false;
private static workspaceContext: WorkspaceContext;

static loadDependencies() {
if (!CoreExtensionService.initialized) {
const coreExtension = extensions.getExtension(CORE_EXTENSION_ID);
if (!coreExtension) {
throw new Error(CORE_EXTENSION_NOT_FOUND);
}
const coreExtensionVersion = coreExtension.packageJSON.version;
if (
!CoreExtensionService.isAboveMinimumRequiredVersion(
MINIMUM_REQUIRED_VERSION_CORE_EXTENSION,
coreExtensionVersion
)
) {
throw new Error(coreExtensionMinRequiredVersionError);
}

const coreExtensionApi = coreExtension.exports as CoreExtensionApi;

CoreExtensionService.initializeWorkspaceContext(
coreExtensionApi?.services.WorkspaceContext
);

CoreExtensionService.initialized = true;
}
}

private static initializeWorkspaceContext(
workspaceContext: WorkspaceContext | undefined
) {
if (!workspaceContext) {
throw new Error(WORKSPACE_CONTEXT_NOT_FOUND);
}
CoreExtensionService.workspaceContext =
workspaceContext.getInstance(false);
}

private static isAboveMinimumRequiredVersion(
minRequiredVersion: string,
actualVersion: string
) {
// Check to see if version is in the expected MAJOR.MINOR.PATCH format
if (!valid(actualVersion)) {
console.debug(
'Invalid version format found for the Core Extension.' +
`\nActual version: ${actualVersion}` +
`\nMinimum required version: ${minRequiredVersion}`
);
}
return satisfies(actualVersion, '>=' + minRequiredVersion);
}

static getWorkspaceContext(): WorkspaceContext {
if (CoreExtensionService.initialized) {
return CoreExtensionService.workspaceContext;
}
throw new Error(NOT_INITIALIZED_ERROR);
}
}
7 changes: 7 additions & 0 deletions src/services/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* Copyright (c) 2023, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
export * from './CoreExtensionService';
12 changes: 12 additions & 0 deletions src/test/TestHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { mkdtemp, rm, stat } from 'fs/promises';
import * as os from 'os';
import * as path from 'path';
import * as process from 'process';
import sinon = require('sinon');
import { WorkspaceUtils } from '../utils/workspaceUtils';

export class TempProjectDirManager {
readonly projectDir: string;
Expand Down Expand Up @@ -65,3 +67,13 @@ export function createPlatformAbsolutePath(...pathArgs: string[]): string {
}
return absPath;
}

// Create a stub of WorkspaceUtis.getWorkspaceDir() that returns a path to
// a temporary directory.
export function setupTempWorkspaceDirectoryStub(
projectDirManager: TempProjectDirManager
): sinon.SinonStub<[], string> {
const getWorkspaceDirStub = sinon.stub(WorkspaceUtils, 'getWorkspaceDir');
getWorkspaceDirStub.returns(projectDirManager.projectDir);
return getWorkspaceDirStub;
}
50 changes: 47 additions & 3 deletions src/test/runTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@

import * as path from 'path';

import { runTests } from '@vscode/test-electron';
import {
downloadAndUnzipVSCode,
resolveCliArgsFromVSCodeExecutablePath,
runTests
} from '@vscode/test-electron';
import { spawnSync } from 'child_process';
import { CORE_EXTENSION_ID } from '../utils/constants';

async function main() {
try {
Expand All @@ -24,8 +30,46 @@ async function main() {
process.env['CODE_COVERAGE'] = '1';
}

// Download VS Code, unzip it and run the integration test
await runTests({ extensionDevelopmentPath, extensionTestsPath });
// Download VS Code, unzip it and run the integration tests.
// NB: We'll use the 'stable' version of VSCode for tests, to catch
// potential incompatibilities in newer versions than the minmum we
// support in the `engines` section of our package.
const vscodeExecutablePath = await downloadAndUnzipVSCode('stable');
const [cliPath, ...args] =
resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath);

// Install the Salesforce Extensions, which is a pre-req for our
// extension. Bail if there's an error.
const installExtensionDepsOuput = spawnSync(
cliPath,
[
...args,
'--install-extension',
CORE_EXTENSION_ID
],
{ stdio: 'inherit', encoding: 'utf-8' }
);
if (installExtensionDepsOuput.error) {
console.error(
`Error installing Salesforce Extensions in test: ${installExtensionDepsOuput.error.message}`
);
throw installExtensionDepsOuput.error;
}
if (
installExtensionDepsOuput.status &&
installExtensionDepsOuput.status !== 0
) {
const installNonZeroError = `Install of Salesforce Extensions finished with status ${installExtensionDepsOuput.status}. See console output for more information.`;
console.error(installNonZeroError);
throw new Error(installNonZeroError);
}

// All clear! Should be able to run the tests.
await runTests({
extensionDevelopmentPath,
extensionTestsPath,
vscodeExecutablePath
});
} catch (err) {
console.error('Failed to run tests', err);
process.exit(1);
Expand Down
70 changes: 43 additions & 27 deletions src/test/suite/commands/wizard/lwcGenerationCommand.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
import * as assert from 'assert';
import * as sinon from 'sinon';
import * as fs from 'fs';
import * as path from 'path';
import { afterEach, beforeEach } from 'mocha';
import {
LwcGenerationCommand,
SObjectQuickActionStatus
} from '../../../../commands/wizard/lwcGenerationCommand';
import { WorkspaceUtils } from '../../../../utils/workspaceUtils';
import { TempProjectDirManager } from '../../../TestHelper';

suite('LWC Generation Command Test Suite', () => {
beforeEach(function () {});
Expand Down Expand Up @@ -94,43 +96,57 @@ suite('LWC Generation Command Test Suite', () => {
});

test('Should return error status for landing page with invalid json', async () => {
const dirManager = await TempProjectDirManager.createTempProjectDir();
const getWorkspaceDirStub = sinon.stub(
WorkspaceUtils,
'getStaticResourcesDir'
);
getWorkspaceDirStub.returns(Promise.resolve('.'));
const fsAccess = sinon.stub(fs, 'access');
fsAccess.returns();
const invalidJsonFile = 'landing_page.json';
const invalidJsonContents = 'invalid_json_here';
fs.writeFileSync(invalidJsonFile, invalidJsonContents, 'utf8');

const status = await LwcGenerationCommand.getSObjectsFromLandingPage();

assert.ok(status.error && status.error.length > 0);

fs.unlinkSync(invalidJsonFile);
try {
getWorkspaceDirStub.returns(Promise.resolve(dirManager.projectDir));
const invalidJsonFile = 'landing_page.json';
const invalidJsonContents = 'invalid_json_here';
fs.writeFileSync(
path.join(dirManager.projectDir, invalidJsonFile),
invalidJsonContents,
'utf8'
);

const status =
await LwcGenerationCommand.getSObjectsFromLandingPage();

assert.ok(status.error && status.error.length > 0);
} finally {
getWorkspaceDirStub.restore();
await dirManager.removeDir();
}
});

test('Should return 2 sObjects', async () => {
const dirManager = await TempProjectDirManager.createTempProjectDir();
const getWorkspaceDirStub = sinon.stub(
WorkspaceUtils,
'getStaticResourcesDir'
);
getWorkspaceDirStub.returns(Promise.resolve('.'));
const fsAccess = sinon.stub(fs, 'access');
fsAccess.returns();
const validJsonFile = 'landing_page.json';
const jsonContents =
'{ "definition": "mcf/list", "properties": { "objectApiName": "Account" }, "nested": { "definition": "mcf/timedList", "properties": { "objectApiName": "Contact"} } }';
fs.writeFileSync(validJsonFile, jsonContents, 'utf8');

const status = await LwcGenerationCommand.getSObjectsFromLandingPage();

assert.equal(status.sobjects.length, 2);
assert.equal(status.sobjects[0], 'Account');
assert.equal(status.sobjects[1], 'Contact');

fs.unlinkSync(validJsonFile);
try {
getWorkspaceDirStub.returns(Promise.resolve(dirManager.projectDir));
const validJsonFile = 'landing_page.json';
const jsonContents =
'{ "definition": "mcf/list", "properties": { "objectApiName": "Account" }, "nested": { "definition": "mcf/timedList", "properties": { "objectApiName": "Contact"} } }';
fs.writeFileSync(
path.join(dirManager.projectDir, validJsonFile),
jsonContents,
'utf8'
);

const status =
await LwcGenerationCommand.getSObjectsFromLandingPage();

assert.equal(status.sobjects.length, 2);
assert.equal(status.sobjects[0], 'Account');
assert.equal(status.sobjects[1], 'Contact');
} finally {
getWorkspaceDirStub.restore();
await dirManager.removeDir();
}
});
});
39 changes: 22 additions & 17 deletions src/test/suite/utils/orgUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@ import { afterEach, beforeEach } from 'mocha';
import {
ConfigAggregator,
Connection,
Org,
OrgConfigProperties
} from '@salesforce/core';
import {
DescribeGlobalResult,
DescribeSObjectResult,
Field as FieldType
} from 'jsforce';
import { CoreExtensionService } from '../../../services';

suite('Org Utils Test Suite', () => {
const describeGlobalResult: DescribeGlobalResult = {
Expand Down Expand Up @@ -118,18 +118,11 @@ suite('Org Utils Test Suite', () => {
});

test('Returns list of sobjects', async () => {
const orgStub: SinonStub = sinon.stub(Org, 'create');
const stubConnection = sinon.createStubInstance(Connection);
const stubConnection = stubWorkspaceContextConnection();
stubConnection.describeGlobal.returns(
Promise.resolve(describeGlobalResult)
);

orgStub.returns({
getConnection: () => {
return stubConnection;
}
});

const sobjects = await OrgUtils.getSobjects();

assert.equal(sobjects.length, 1);
Expand Down Expand Up @@ -293,18 +286,11 @@ suite('Org Utils Test Suite', () => {
supportedScopes: null
};

const orgStub: SinonStub = sinon.stub(Org, 'create');
const stubConnection = sinon.createStubInstance(Connection);
const stubConnection = stubWorkspaceContextConnection();
stubConnection.describe
.withArgs('SomeObject')
.returns(Promise.resolve(describeSobjectResult));

orgStub.returns({
getConnection: () => {
return stubConnection;
}
});

const fields = await OrgUtils.getFieldsForSObject(
describeSobjectResult.name
);
Expand Down Expand Up @@ -380,4 +366,23 @@ suite('Org Utils Test Suite', () => {
writeRequiresMasterRead: true
};
}

function stubWorkspaceContextConnection(): sinon.SinonStubbedInstance<
Connection<any>
> {
const stubConnection = sinon.createStubInstance(Connection);
const getWorkspaceContextInstance = {
getConnection: () => {
return Promise.resolve(stubConnection);
},
onOrgChange: sinon.stub(),
getInstance: sinon.stub(),
username: sinon.stub(),
alias: sinon.stub()
};
sinon
.stub(CoreExtensionService, 'getWorkspaceContext')
.returns(getWorkspaceContextInstance);
return stubConnection;
}
});
Loading

0 comments on commit 19b6852

Please sign in to comment.