generated from salesforcecli/plugin-template
-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: add remote components to report response
- Loading branch information
1 parent
7dbfd03
commit c7b2eb0
Showing
2 changed files
with
290 additions
and
4 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
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,265 @@ | ||
/* | ||
* 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 * as path from 'node:path'; | ||
import { assert, expect, config } from 'chai'; | ||
import sinon from 'sinon'; | ||
import { DeployMessage, DeployResult, FileResponse } from '@salesforce/source-deploy-retrieve'; | ||
import { ux } from '@oclif/core'; | ||
import { ensureArray } from '@salesforce/kit'; | ||
import { getCoverageFormattersOptions } from '../../src/utils/coverage.js'; | ||
import { DeployResultFormatter } from '../../src/formatters/deployResultFormatter.js'; | ||
import { getDeployResult } from './deployResponses.js'; | ||
|
||
config.truncateThreshold = 0; | ||
|
||
describe('deployResultFormatter', () => { | ||
const sandbox = sinon.createSandbox(); | ||
|
||
afterEach(() => { | ||
sandbox.restore(); | ||
}); | ||
|
||
describe('displaySuccesses', () => { | ||
const deployResultSuccess = getDeployResult('successSync'); | ||
let tableStub: sinon.SinonStub; | ||
|
||
beforeEach(() => { | ||
tableStub = sandbox.stub(ux, 'table'); | ||
}); | ||
|
||
it('finds components in server response, and adds them if not in fileResponses', () => { | ||
ensureArray(deployResultSuccess.response.details.componentSuccesses).push({ | ||
changed: 'false', | ||
created: 'true', | ||
createdDate: '123', | ||
fullName: 'remoteClass', | ||
componentType: 'ApexClass', | ||
success: 'true', | ||
deleted: 'false', | ||
fileName: '', | ||
}); | ||
const formatter = new DeployResultFormatter(deployResultSuccess, { verbose: true }); | ||
formatter.display(); | ||
expect(tableStub.callCount).to.equal(1); | ||
expect(tableStub.firstCall.args[0]).to.deep.equal([ | ||
{ | ||
filePath: 'classes/ProductController.cls', | ||
fullName: 'ProductController', | ||
state: 'Changed', | ||
type: 'ApexClass', | ||
}, | ||
{ | ||
filePath: 'Not found in project', | ||
fullName: 'remoteClass', | ||
state: 'Created', | ||
type: 'ApexClass', | ||
}, | ||
]); | ||
}); | ||
}); | ||
|
||
describe('displayFailures', () => { | ||
const deployResultFailure = getDeployResult('failed'); | ||
let tableStub: sinon.SinonStub; | ||
|
||
beforeEach(() => { | ||
tableStub = sandbox.stub(ux, 'table'); | ||
}); | ||
|
||
it('prints file responses, and messages from server', () => { | ||
const formatter = new DeployResultFormatter(deployResultFailure, { verbose: true }); | ||
formatter.display(); | ||
expect(tableStub.callCount).to.equal(1); | ||
expect(tableStub.firstCall.args[0]).to.deep.equal([ | ||
{ | ||
error: 'This component has some problems', | ||
fullName: 'ProductController', | ||
loc: '27:18', | ||
problemType: 'Error', | ||
}, | ||
]); | ||
}); | ||
|
||
it('displays errors from the server not in file responses', () => { | ||
const deployFailure = getDeployResult('failed'); | ||
const error1 = { | ||
changed: false, | ||
componentType: 'ApexClass', | ||
created: false, | ||
createdDate: '2021-04-27T22:18:07.000Z', | ||
deleted: false, | ||
fileName: 'classes/ProductController.cls', | ||
fullName: 'ProductController', | ||
success: false, | ||
problemType: 'Error', | ||
problem: 'This component has some problems', | ||
lineNumber: '27', | ||
columnNumber: '18', | ||
} as DeployMessage; | ||
|
||
// add package.xml error, which is different from a FileResponse error | ||
const error2 = { | ||
changed: false, | ||
componentType: '', | ||
created: false, | ||
createdDate: '2023-11-17T21:18:36.000Z', | ||
deleted: false, | ||
fileName: 'package.xml', | ||
fullName: 'Create_property', | ||
problem: | ||
"An object 'Create_property' of type Flow was named in package.xml, but was not found in zipped directory", | ||
problemType: 'Error', | ||
success: false, | ||
} as DeployMessage; | ||
|
||
deployFailure.response.details.componentFailures = [error1, error2]; | ||
sandbox.stub(deployFailure, 'getFileResponses').returns([ | ||
{ | ||
fullName: error1.fullName, | ||
filePath: error1.fileName, | ||
type: error1.componentType, | ||
state: 'Failed', | ||
lineNumber: error1.lineNumber, | ||
columnNumber: error1.columnNumber, | ||
error: error1.problem, | ||
problemType: error1.problemType, | ||
}, | ||
] as FileResponse[]); | ||
const formatter = new DeployResultFormatter(deployFailure, { verbose: true }); | ||
formatter.display(); | ||
expect(tableStub.callCount).to.equal(1); | ||
expect(tableStub.firstCall.args[0]).to.deep.equal([ | ||
{ | ||
error: error2.problem, | ||
fullName: error2.fullName, | ||
loc: '', | ||
problemType: error2.problemType, | ||
}, | ||
{ | ||
error: 'This component has some problems', | ||
fullName: 'ProductController', | ||
loc: '27:18', | ||
problemType: 'Error', | ||
}, | ||
]); | ||
}); | ||
}); | ||
|
||
describe('coverage functions', () => { | ||
describe('getCoverageFormattersOptions', () => { | ||
it('clover, json', () => { | ||
const result = getCoverageFormattersOptions(['clover', 'json']); | ||
expect(result).to.deep.equal({ | ||
reportFormats: ['clover', 'json'], | ||
reportOptions: { | ||
clover: { file: path.join('coverage', 'clover.xml'), projectRoot: '.' }, | ||
json: { file: path.join('coverage', 'coverage.json') }, | ||
}, | ||
}); | ||
}); | ||
|
||
it('will warn when code coverage warning present from server', () => { | ||
const deployResult = getDeployResult('codeCoverageWarning'); | ||
const formatter = new DeployResultFormatter(deployResult, {}); | ||
const warnStub = sandbox.stub(ux, 'warn'); | ||
formatter.display(); | ||
expect(warnStub.callCount).to.equal(1); | ||
expect(warnStub.firstCall.args[0]).to.equal( | ||
'Average test coverage across all Apex Classes and Triggers is 25%, at least 75% test coverage is required.' | ||
); | ||
}); | ||
|
||
it('will write test output when in json mode', async () => { | ||
const deployResult = getDeployResult('passedTest'); | ||
const formatter = new DeployResultFormatter(deployResult, { | ||
junit: true, | ||
'coverage-formatters': ['text', 'cobertura'], | ||
}); | ||
// private method stub | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
const coverageReportStub = sandbox.stub(formatter, 'createCoverageReport'); | ||
// private method stub | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
const junitStub = sandbox.stub(formatter, 'createJunitResults'); | ||
await formatter.getJson(); | ||
expect(coverageReportStub.calledOnce).to.equal(true); | ||
expect(junitStub.calledOnce).to.equal(true); | ||
}); | ||
|
||
it('teamcity', () => { | ||
const result = getCoverageFormattersOptions(['teamcity']); | ||
expect(result).to.deep.equal({ | ||
reportFormats: ['teamcity'], | ||
reportOptions: { | ||
teamcity: { file: path.join('coverage', 'teamcity.txt'), blockName: 'coverage' }, | ||
}, | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('replacements', () => { | ||
const deployResultSuccess = getDeployResult('successSync'); | ||
const deployResultSuccessWithReplacements = { | ||
...getDeployResult('successSync'), | ||
replacements: new Map<string, string[]>([['foo', ['bar', 'baz']]]), | ||
} as DeployResult; | ||
|
||
describe('json', () => { | ||
it('shows replacements when not concise', async () => { | ||
const formatter = new DeployResultFormatter(deployResultSuccessWithReplacements, { verbose: true }); | ||
const json = await formatter.getJson(); | ||
assert('replacements' in json && json.replacements); | ||
expect(json.replacements).to.deep.equal({ foo: ['bar', 'baz'] }); | ||
}); | ||
it('no replacements when concise', () => { | ||
const formatter = new DeployResultFormatter(deployResultSuccessWithReplacements, { concise: true }); | ||
const json = formatter.getJson(); | ||
expect(json).to.not.have.property('replacements'); | ||
}); | ||
}); | ||
describe('human', () => { | ||
let uxStub: sinon.SinonStub; | ||
beforeEach(() => { | ||
uxStub = sandbox.stub(process.stdout, 'write'); | ||
}); | ||
|
||
const getStdout = () => | ||
uxStub | ||
.getCalls() | ||
// args are typed as any[] | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-return | ||
.flatMap((call) => call.args) | ||
.join('\n'); | ||
|
||
it('shows replacements when verbose and replacements exist', () => { | ||
const formatter = new DeployResultFormatter(deployResultSuccessWithReplacements, { verbose: true }); | ||
formatter.display(); | ||
expect(getStdout()).to.include('Metadata Replacements'); | ||
expect(getStdout()).to.include('TEXT REPLACED'); | ||
}); | ||
|
||
it('no replacements when verbose but there are none', () => { | ||
const formatter = new DeployResultFormatter(deployResultSuccess, { verbose: true }); | ||
formatter.display(); | ||
expect(getStdout()).to.not.include('Metadata Replacements'); | ||
}); | ||
it('no replacements when not verbose', () => { | ||
const formatter = new DeployResultFormatter(deployResultSuccessWithReplacements, { verbose: false }); | ||
formatter.display(); | ||
expect(getStdout()).to.not.include('Metadata Replacements'); | ||
}); | ||
it('no replacements when concise', () => { | ||
const formatter = new DeployResultFormatter(deployResultSuccessWithReplacements, { concise: true }); | ||
formatter.display(); | ||
expect(getStdout()).to.not.include('Metadata Replacements'); | ||
}); | ||
}); | ||
}); | ||
}); |