generated from salesforcecli/plugin-template
-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: add doctor diagnostic test from plugin-source (#901)
* fix: add doctor diagnostic test from plugin-source * fix: add test for the diagnostic
- Loading branch information
Showing
5 changed files
with
560 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# apiVersionMismatch | ||
|
||
The sourceApiVersion in sfdx-project.json doesn't match the apiVersion. The commands that deploy and retrieve source use the sourceApiVersion in this case. The version mismatch isn't a problem, as long as it's the behavior you actually want. | ||
|
||
# apiVersionUnset | ||
|
||
Neither sourceApiVersion nor apiVersion are defined. The commands that deploy and retrieve source use the max apiVersion of the target org in this case. The issue isn't a problem, as long as it's the behavior you actually want. | ||
|
||
# maxApiVersionMismatch | ||
|
||
The max apiVersion of the default DevHub org doesn't match the max apiVersion of the default target org. This mismatch means that the default target orgs are running different API versions. Be sure you explicitly set the apiVersion when you deploy or retrieve source, or you will likely run into problems. | ||
|
||
# sourceApiVersionMaxMismatch | ||
|
||
The sourceApiVersion in sfdx-project.json doesn't match the max apiVersion of the default target org. As a result, you're not using the latest features available in API version %s. The version mismatch isn't a problem, as long as it's the behavior you actually want. | ||
|
||
# apiVersionMaxMismatch | ||
|
||
The apiVersion doesn't match the max apiVersion of the default target org. As a result, you're not using the latest features available in API version %s. The version mismatch isn't a problem, as long as it's the behavior you actually want. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
/* | ||
* Copyright (c) 2022, salesforce.com, inc. | ||
* All rights reserved. | ||
* Licensed under the BSD 3-Clause license. | ||
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause | ||
*/ | ||
import { ConfigAggregator, Lifecycle, Logger, Messages, SfProject, OrgConfigProperties, Org } from '@salesforce/core'; | ||
import { SfDoctor } from '@salesforce/plugin-info'; | ||
|
||
type HookFunction = (options: { doctor: SfDoctor }) => Promise<[void]>; | ||
|
||
let logger: Logger; | ||
const getLogger = (): Logger => { | ||
if (!logger) { | ||
logger = Logger.childFromRoot('plugin-deploy-retrieve-diagnostics'); | ||
} | ||
return logger; | ||
}; | ||
|
||
const pluginName = '@salesforce/plugin-deploy-retrieve'; | ||
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); | ||
const messages = Messages.loadMessages(pluginName, 'diagnostics'); | ||
|
||
export const hook: HookFunction = async (options) => { | ||
getLogger().debug(`Running SfDoctor diagnostics for ${pluginName}`); | ||
return Promise.all([apiVersionTest(options.doctor)]); | ||
}; | ||
|
||
// ============================ | ||
// *** DIAGNOSTIC TESTS *** | ||
// ============================ | ||
|
||
// Gathers and compares the following API versions: | ||
// 1. apiVersion (if set) from the sfdx config, including environment variable | ||
// 2. sourceApiVersion (if set) from sfdx-project.json | ||
// 3. max apiVersion of the default target dev hub org (if set) | ||
// 4. max apiVersion of the default target org (if set) | ||
// | ||
// Warns if: | ||
// 1. apiVersion and sourceApiVersion are set but not equal | ||
// 2. apiVersion and sourceApiVersion are both not set | ||
// 3. default devhub target org and default target org have different max apiVersions | ||
// 4. sourceApiVersion is set and does not match max apiVersion of default target org | ||
// 5. apiVersion is set and does not match max apiVersion of default target org | ||
const apiVersionTest = async (doctor: SfDoctor): Promise<void> => { | ||
getLogger().debug('Running API Version tests'); | ||
|
||
// check org-api-version from ConfigAggregator | ||
const aggregator = await ConfigAggregator.create(); | ||
const apiVersion = aggregator.getPropertyValue<string>(OrgConfigProperties.ORG_API_VERSION); | ||
|
||
const sourceApiVersion = await getSourceApiVersion(); | ||
|
||
const targetDevHub = aggregator.getPropertyValue<string>(OrgConfigProperties.TARGET_DEV_HUB); | ||
const targetOrg = aggregator.getPropertyValue<string>(OrgConfigProperties.TARGET_ORG); | ||
const targetDevHubApiVersion = targetDevHub && (await getMaxApiVersion(aggregator, targetDevHub)); | ||
const targetOrgApiVersion = targetOrg && (await getMaxApiVersion(aggregator, targetOrg)); | ||
|
||
doctor.addPluginData(pluginName, { | ||
apiVersion, | ||
sourceApiVersion, | ||
targetDevHubApiVersion, | ||
targetOrgApiVersion, | ||
}); | ||
|
||
const testName1 = `[${pluginName}] sourceApiVersion matches apiVersion`; | ||
let status1 = 'pass'; | ||
if (diff(sourceApiVersion, apiVersion)) { | ||
status1 = 'warn'; | ||
doctor.addSuggestion(messages.getMessage('apiVersionMismatch')); | ||
} | ||
if (sourceApiVersion === undefined && apiVersion === undefined) { | ||
status1 = 'warn'; | ||
doctor.addSuggestion(messages.getMessage('apiVersionUnset')); | ||
} | ||
void Lifecycle.getInstance().emit('Doctor:diagnostic', { testName: testName1, status: status1 }); | ||
|
||
if (targetDevHubApiVersion && targetOrgApiVersion) { | ||
const testName2 = `[${pluginName}] default target DevHub max apiVersion matches default target org max apiVersion`; | ||
let status2 = 'pass'; | ||
if (diff(targetDevHubApiVersion, targetOrgApiVersion)) { | ||
status2 = 'warn'; | ||
doctor.addSuggestion(messages.getMessage('maxApiVersionMismatch')); | ||
} | ||
void Lifecycle.getInstance().emit('Doctor:diagnostic', { testName: testName2, status: status2 }); | ||
} | ||
|
||
// Only run this test if both sourceApiVersion and the default target org max version are set. | ||
if (sourceApiVersion?.length && targetOrgApiVersion?.length) { | ||
const testName3 = `[${pluginName}] sourceApiVersion matches default target org max apiVersion`; | ||
let status3 = 'pass'; | ||
if (diff(sourceApiVersion, targetOrgApiVersion)) { | ||
status3 = 'warn'; | ||
doctor.addSuggestion(messages.getMessage('sourceApiVersionMaxMismatch', [targetOrgApiVersion])); | ||
} | ||
void Lifecycle.getInstance().emit('Doctor:diagnostic', { testName: testName3, status: status3 }); | ||
} | ||
|
||
// Only run this test if both apiVersion and the default target org max version are set. | ||
if (apiVersion?.length && targetOrgApiVersion?.length) { | ||
const testName4 = `[${pluginName}] apiVersion matches default target org max apiVersion`; | ||
let status4 = 'pass'; | ||
if (diff(apiVersion, targetOrgApiVersion)) { | ||
status4 = 'warn'; | ||
doctor.addSuggestion(messages.getMessage('apiVersionMaxMismatch', [targetOrgApiVersion])); | ||
} | ||
void Lifecycle.getInstance().emit('Doctor:diagnostic', { testName: testName4, status: status4 }); | ||
} | ||
}; | ||
|
||
// check sfdx-project.json for sourceApiVersion | ||
const getSourceApiVersion = async (): Promise<string | undefined> => { | ||
try { | ||
const project = SfProject.getInstance(); | ||
const projectJson = await project.resolveProjectConfig(); | ||
return projectJson.sourceApiVersion as string | undefined; | ||
} catch (error) { | ||
const errMsg = (error as Error).message; | ||
getLogger().debug(`Cannot determine sourceApiVersion due to: ${errMsg}`); | ||
} | ||
}; | ||
|
||
// check max API version for default orgs | ||
const getMaxApiVersion = async (aggregator: ConfigAggregator, aliasOrUsername: string): Promise<string | undefined> => { | ||
try { | ||
const org = await Org.create({ aliasOrUsername, aggregator }); | ||
return await org.retrieveMaxApiVersion(); | ||
} catch (error) { | ||
const errMsg = (error as Error).message; | ||
getLogger().debug(`Cannot determine the max ApiVersion for org: [${aliasOrUsername}] due to: ${errMsg}`); | ||
} | ||
}; | ||
|
||
// Compare 2 API versions that have values and return if they are different. | ||
// E.g., | ||
// Comparing undefined with 56.0 would return false. | ||
// Comparing undefined with undefined would return false. | ||
// Comparing 55.0 with 55.0 would return false. | ||
// Comparing 55.0 with 56.0 would return true. | ||
const diff = (version1: string | undefined, version2: string | undefined): boolean => { | ||
getLogger().debug(`Comparing API versions: [${version1},${version2}]`); | ||
return !!version1?.length && !!version2?.length && version1 !== version2; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,214 @@ | ||
/* | ||
* Copyright (c) 2020, salesforce.com, inc. | ||
* All rights reserved. | ||
* Licensed under the BSD 3-Clause license. | ||
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause | ||
*/ | ||
|
||
import sinon from 'sinon'; | ||
import { expect } from 'chai'; | ||
import { fromStub, StubbedType, stubInterface, stubMethod } from '@salesforce/ts-sinon'; | ||
import { SfDoctor } from '@salesforce/plugin-info'; | ||
import { ConfigAggregator, Lifecycle, Messages, Org, SfProject } from '@salesforce/core'; | ||
import { TestContext } from '@salesforce/core/lib/testSetup.js'; | ||
import { hook } from '../../src/hooks/diagnostics.js'; | ||
const pluginName = '@salesforce/plugin-deploy-retrieve'; | ||
|
||
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url); | ||
const messages = Messages.loadMessages(pluginName, 'diagnostics'); | ||
|
||
describe('Doctor diagnostics', () => { | ||
const sandbox = new TestContext().SANDBOX; | ||
|
||
// Stubs for: | ||
// 1. the Doctor class needed by the hook | ||
// 2. ConfigAggregator for apiVersion in the config | ||
// 3. SfProject for sourceApiVersion in sfdx-project.json | ||
// 4. Org for maxApiVersion of default devhub and target orgs | ||
let doctorMock: SfDoctor; | ||
let doctorStubbedType: StubbedType<SfDoctor>; | ||
let configAggregatorMock: ConfigAggregator; | ||
let configAggregatorStubbedType: StubbedType<ConfigAggregator>; | ||
let sfProjectMock: SfProject; | ||
let sfProjectStubbedType: StubbedType<SfProject>; | ||
let orgMock: Org; | ||
let orgStubbedType: StubbedType<Org>; | ||
let addPluginDataStub: sinon.SinonStub; | ||
let getPropertyValueStub: sinon.SinonStub; | ||
let resolveProjectConfigStub: sinon.SinonStub; | ||
let addSuggestionStub: sinon.SinonStub; | ||
let lifecycleEmitStub: sinon.SinonStub; | ||
let maxApiVersionStub: sinon.SinonStub; | ||
|
||
beforeEach(() => { | ||
doctorStubbedType = stubInterface<SfDoctor>(sandbox); | ||
doctorMock = fromStub(doctorStubbedType); | ||
configAggregatorStubbedType = stubInterface<ConfigAggregator>(sandbox); | ||
configAggregatorMock = fromStub(configAggregatorStubbedType); | ||
stubMethod(sandbox, ConfigAggregator, 'create').resolves(configAggregatorMock); | ||
sfProjectStubbedType = stubInterface<SfProject>(sandbox); | ||
sfProjectMock = fromStub(sfProjectStubbedType); | ||
stubMethod(sandbox, SfProject, 'getInstance').returns(sfProjectMock); | ||
orgStubbedType = stubInterface<Org>(sandbox); | ||
orgMock = fromStub(orgStubbedType); | ||
stubMethod(sandbox, Org, 'create').resolves(orgMock); | ||
lifecycleEmitStub = stubMethod(sandbox, Lifecycle.prototype, 'emit'); | ||
|
||
// Shortening these for brevity in tests. | ||
addPluginDataStub = doctorStubbedType.addPluginData; | ||
addSuggestionStub = doctorStubbedType.addSuggestion; | ||
getPropertyValueStub = configAggregatorStubbedType.getPropertyValue; | ||
resolveProjectConfigStub = sfProjectStubbedType.resolveProjectConfig; | ||
maxApiVersionStub = orgStubbedType.retrieveMaxApiVersion; | ||
}); | ||
|
||
afterEach(() => { | ||
sandbox.restore(); | ||
}); | ||
|
||
it('should warn when apiVersion does not match sourceApiVersion', async () => { | ||
getPropertyValueStub.onFirstCall().returns('55.0'); | ||
resolveProjectConfigStub.onFirstCall().resolves({ sourceApiVersion: '52.0' }); | ||
|
||
await hook({ doctor: doctorMock }); | ||
|
||
expect(addPluginDataStub.callCount, 'Expected doctor.addPluginData() to be called once').to.equal(1); | ||
expect(addPluginDataStub.args[0][0]).to.equal(pluginName); | ||
expect(addPluginDataStub.args[0][1]).to.deep.equal({ | ||
apiVersion: '55.0', | ||
sourceApiVersion: '52.0', | ||
targetDevHubApiVersion: undefined, | ||
targetOrgApiVersion: undefined, | ||
}); | ||
expect(addSuggestionStub.callCount, 'Expected doctor.addSuggestion() to be called once').to.equal(1); | ||
expect(addSuggestionStub.args[0][0]).to.equal(messages.getMessage('apiVersionMismatch')); | ||
expect(lifecycleEmitStub.called).to.be.true; | ||
expect(lifecycleEmitStub.args[0][0]).to.equal('Doctor:diagnostic'); | ||
expect(lifecycleEmitStub.args[0][1]).to.deep.equal({ | ||
testName: `[${pluginName}] sourceApiVersion matches apiVersion`, | ||
status: 'warn', | ||
}); | ||
}); | ||
|
||
it('should pass when apiVersion matches sourceApiVersion', async () => { | ||
getPropertyValueStub.onFirstCall().returns('55.0'); | ||
resolveProjectConfigStub.onFirstCall().resolves({ sourceApiVersion: '55.0' }); | ||
|
||
await hook({ doctor: doctorMock }); | ||
|
||
expect(addPluginDataStub.callCount, 'Expected doctor.addPluginData() to be called once').to.equal(1); | ||
expect(addPluginDataStub.args[0][0]).to.equal(pluginName); | ||
expect(addPluginDataStub.args[0][1]).to.deep.equal({ | ||
apiVersion: '55.0', | ||
sourceApiVersion: '55.0', | ||
targetDevHubApiVersion: undefined, | ||
targetOrgApiVersion: undefined, | ||
}); | ||
expect(addSuggestionStub.callCount, 'Expected doctor.addSuggestion() NOT to be called').to.equal(0); | ||
expect(lifecycleEmitStub.called).to.be.true; | ||
expect(lifecycleEmitStub.args[0][0]).to.equal('Doctor:diagnostic'); | ||
expect(lifecycleEmitStub.args[0][1]).to.deep.equal({ | ||
testName: `[${pluginName}] sourceApiVersion matches apiVersion`, | ||
status: 'pass', | ||
}); | ||
}); | ||
|
||
it('should warn when both apiVersion and sourceApiVersion are not set', async () => { | ||
await hook({ doctor: doctorMock }); | ||
|
||
expect(addPluginDataStub.callCount, 'Expected doctor.addPluginData() to be called once').to.equal(1); | ||
expect(addPluginDataStub.args[0][0]).to.equal(pluginName); | ||
expect(addPluginDataStub.args[0][1]).to.deep.equal({ | ||
apiVersion: undefined, | ||
sourceApiVersion: undefined, | ||
targetDevHubApiVersion: undefined, | ||
targetOrgApiVersion: undefined, | ||
}); | ||
expect(addSuggestionStub.callCount, 'Expected doctor.addSuggestion() to be called once').to.equal(1); | ||
expect(addSuggestionStub.args[0][0]).to.equal(messages.getMessage('apiVersionUnset')); | ||
expect(lifecycleEmitStub.called).to.be.true; | ||
expect(lifecycleEmitStub.args[0][0]).to.equal('Doctor:diagnostic'); | ||
expect(lifecycleEmitStub.args[0][1]).to.deep.equal({ | ||
testName: `[${pluginName}] sourceApiVersion matches apiVersion`, | ||
status: 'warn', | ||
}); | ||
}); | ||
|
||
it('should warn when default devhub target org and default target org have different max apiVersions', async () => { | ||
getPropertyValueStub.onSecondCall().returns('devhubOrg').onThirdCall().returns('scratchOrg'); | ||
maxApiVersionStub.onFirstCall().resolves('55.0').onSecondCall().resolves('56.0'); | ||
|
||
await hook({ doctor: doctorMock }); | ||
|
||
expect(addPluginDataStub.callCount, 'Expected doctor.addPluginData() to be called once').to.equal(1); | ||
expect(addPluginDataStub.args[0][0]).to.equal(pluginName); | ||
expect(addPluginDataStub.args[0][1]).to.deep.equal({ | ||
apiVersion: undefined, | ||
sourceApiVersion: undefined, | ||
targetDevHubApiVersion: '55.0', | ||
targetOrgApiVersion: '56.0', | ||
}); | ||
expect(addSuggestionStub.callCount, 'Expected doctor.addSuggestion() to be called twice').to.equal(2); | ||
expect(addSuggestionStub.args[1][0]).to.equal(messages.getMessage('maxApiVersionMismatch')); | ||
expect(lifecycleEmitStub.called).to.be.true; | ||
expect(lifecycleEmitStub.args[1][0]).to.equal('Doctor:diagnostic'); | ||
expect(lifecycleEmitStub.args[1][1]).to.deep.equal({ | ||
testName: `[${pluginName}] default target DevHub max apiVersion matches default target org max apiVersion`, | ||
status: 'warn', | ||
}); | ||
}); | ||
|
||
it('should warn when sourceApiVersion and default target org max apiVersion does not match', async () => { | ||
const targetOrgApiVersion = '56.0'; | ||
resolveProjectConfigStub.resolves({ sourceApiVersion: '55.0' }); | ||
getPropertyValueStub.onThirdCall().returns('scratchOrg'); | ||
maxApiVersionStub.onFirstCall().resolves(targetOrgApiVersion); | ||
|
||
await hook({ doctor: doctorMock }); | ||
|
||
expect(addPluginDataStub.callCount, 'Expected doctor.addPluginData() to be called once').to.equal(1); | ||
expect(addPluginDataStub.args[0][0]).to.equal(pluginName); | ||
expect(addPluginDataStub.args[0][1]).to.deep.equal({ | ||
apiVersion: undefined, | ||
sourceApiVersion: '55.0', | ||
targetDevHubApiVersion: undefined, | ||
targetOrgApiVersion, | ||
}); | ||
expect(addSuggestionStub.callCount, 'Expected doctor.addSuggestion() to be called once').to.equal(1); | ||
expect(addSuggestionStub.args[0][0]).to.equal( | ||
messages.getMessage('sourceApiVersionMaxMismatch', [targetOrgApiVersion]) | ||
); | ||
expect(lifecycleEmitStub.called).to.be.true; | ||
expect(lifecycleEmitStub.args[1][0]).to.equal('Doctor:diagnostic'); | ||
expect(lifecycleEmitStub.args[1][1]).to.deep.equal({ | ||
testName: `[${pluginName}] sourceApiVersion matches default target org max apiVersion`, | ||
status: 'warn', | ||
}); | ||
}); | ||
|
||
it('should warn when apiVersion and default target org max apiVersion does not match', async () => { | ||
const targetOrgApiVersion = '56.0'; | ||
getPropertyValueStub.onFirstCall().returns('55.0'); | ||
getPropertyValueStub.onThirdCall().returns('scratchOrg'); | ||
maxApiVersionStub.onFirstCall().resolves(targetOrgApiVersion); | ||
|
||
await hook({ doctor: doctorMock }); | ||
|
||
expect(addPluginDataStub.callCount, 'Expected doctor.addPluginData() to be called once').to.equal(1); | ||
expect(addPluginDataStub.args[0][0]).to.equal(pluginName); | ||
expect(addPluginDataStub.args[0][1]).to.deep.equal({ | ||
apiVersion: '55.0', | ||
sourceApiVersion: undefined, | ||
targetDevHubApiVersion: undefined, | ||
targetOrgApiVersion, | ||
}); | ||
expect(addSuggestionStub.callCount, 'Expected doctor.addSuggestion() to be called once').to.equal(1); | ||
expect(addSuggestionStub.args[0][0]).to.equal(messages.getMessage('apiVersionMaxMismatch', [targetOrgApiVersion])); | ||
expect(lifecycleEmitStub.called).to.be.true; | ||
expect(lifecycleEmitStub.args[1][0]).to.equal('Doctor:diagnostic'); | ||
expect(lifecycleEmitStub.args[1][1]).to.deep.equal({ | ||
testName: `[${pluginName}] apiVersion matches default target org max apiVersion`, | ||
status: 'warn', | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.