Skip to content

Commit beb7fcb

Browse files
authored
fix: Merge pull request #656 from salesforcecli/wr/failToValidateDeployError
fix: warn when code coverage doesn't meet requirement
2 parents c1038a1 + 2750819 commit beb7fcb

File tree

5 files changed

+86
-9
lines changed

5 files changed

+86
-9
lines changed

messages/deploy.metadata.validate.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,5 @@ Run "%s project deploy quick --job-id %s" to execute this deploy
126126

127127
# error.FailedValidation
128128

129-
Failed to validate the deployment (%s).
129+
Failed to validate the deployment (%s). Due To:
130+
%s

src/commands/project/deploy/validate.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
* Licensed under the BSD 3-Clause license.
55
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
66
*/
7+
import * as os from 'os';
78
import { bold } from 'chalk';
89
import { EnvironmentVariable, Lifecycle, Messages, OrgConfigProperties, SfError } from '@salesforce/core';
9-
import { DeployVersionData, RequestStatus } from '@salesforce/source-deploy-retrieve';
10+
import { CodeCoverageWarnings, DeployVersionData, RequestStatus } from '@salesforce/source-deploy-retrieve';
1011
import { SfCommand, toHelpSection, Flags } from '@salesforce/sf-plugins-core';
12+
import { ensureArray } from '@salesforce/kit';
1113
import { AsyncDeployResultFormatter } from '../../../formatters/asyncDeployResultFormatter';
1214
import { DeployResultFormatter } from '../../../formatters/deployResultFormatter';
1315
import { DeployProgress } from '../../../utils/progressBar';
@@ -173,7 +175,20 @@ export default class DeployMetadataValidate extends SfCommand<DeployResultJson>
173175
this.logSuccess(messages.getMessage('info.SuccessfulValidation', [deploy.id]));
174176
this.log(messages.getMessage('info.suggestedQuickDeploy', [this.config.bin, deploy.id]));
175177
} else {
176-
throw messages.createError('error.FailedValidation', [deploy.id]);
178+
throw messages.createError('error.FailedValidation', [
179+
deploy.id,
180+
[
181+
// I think the type might be wrong in SDR
182+
...ensureArray(result.response.details.runTestResult?.codeCoverageWarnings).map(
183+
(warning: CodeCoverageWarnings & { name?: string }) =>
184+
`${warning.name ? `${warning.name} - ` : ''}${warning.message}`
185+
),
186+
result.response.errorMessage,
187+
result.response.numberComponentErrors ? `${result.response.numberComponentErrors} component error(s)` : '',
188+
]
189+
.join(os.EOL)
190+
.trim(),
191+
]);
177192
}
178193

179194
return formatter.getJson();

src/formatters/deployResultFormatter.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import * as fs from 'fs';
1010
import { ux } from '@oclif/core';
1111
import { dim, underline, bold } from 'chalk';
1212
import {
13+
CodeCoverageWarnings,
1314
DeployResult,
1415
Failures,
1516
FileResponse,
@@ -309,14 +310,16 @@ export class DeployResultFormatter implements Formatter<DeployResultJson> {
309310

310311
ux.log();
311312
ux.log(tableHeader('Test Results Summary'));
312-
const passing = this.result.response.numberTestsCompleted ?? 0;
313-
const failing = this.result.response.numberTestErrors ?? 0;
314-
const total = this.result.response.numberTestsTotal ?? 0;
313+
ux.log(`Passing: ${this.result.response.numberTestsCompleted ?? 0}`);
314+
ux.log(`Failing: ${this.result.response.numberTestErrors ?? 0}`);
315+
ux.log(`Total: ${this.result.response.numberTestsTotal ?? 0}`);
315316
const time = this.result.response.details.runTestResult?.totalTime ?? 0;
316-
ux.log(`Passing: ${passing}`);
317-
ux.log(`Failing: ${failing}`);
318-
ux.log(`Total: ${total}`);
319317
if (time) ux.log(`Time: ${time}`);
318+
// I think the type might be wrong in SDR
319+
ensureArray(this.result.response.details.runTestResult?.codeCoverageWarnings).map(
320+
(warning: CodeCoverageWarnings & { name?: string }) =>
321+
ux.warn(`${warning.name ? `${warning.name} - ` : ''}${warning.message}`)
322+
);
320323
}
321324

322325
private displayVerboseTestSuccesses(): void {

test/utils/deployResponses.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ export type DeployResponseType =
7878
| 'failedTest'
7979
| 'passedTest'
8080
| 'passedAndFailedTest'
81+
| 'codeCoverageWarning'
8182
| 'partialSuccessSync';
8283

8384
export const getDeployResponse = (
@@ -199,6 +200,52 @@ export const getDeployResponse = (
199200
response.runTestsEnabled = true;
200201
response.numberTestErrors = 0;
201202
}
203+
if (type === 'codeCoverageWarning') {
204+
response.status = RequestStatus.Failed;
205+
response.success = false;
206+
response.details.componentFailures = cloneJson(baseDeployResponse.details.componentSuccesses[1]) as DeployMessage;
207+
response.details.componentSuccesses = cloneJson(baseDeployResponse.details.componentSuccesses[0]) as DeployMessage;
208+
response.details.componentFailures.success = 'false';
209+
delete response.details.componentFailures.id;
210+
if (response.details.runTestResult) {
211+
response.details.runTestResult.successes = [
212+
{
213+
name: 'ChangePasswordController',
214+
methodName: 'testMethod',
215+
id: 'testId',
216+
time: 'testTime',
217+
},
218+
];
219+
response.details.runTestResult.failures = [];
220+
response.details.runTestResult.codeCoverageWarnings = [
221+
{
222+
id: response.id,
223+
namespace: '',
224+
message:
225+
'Average test coverage across all Apex Classes and Triggers is 25%, at least 75% test coverage is required.',
226+
},
227+
];
228+
response.details.runTestResult.codeCoverage = [
229+
{
230+
id: 'ChangePasswordController',
231+
type: 'ApexClass',
232+
name: 'ChangePasswordController',
233+
numLocations: '1',
234+
locationsNotCovered: {
235+
column: '54',
236+
line: '2',
237+
numExecutions: '1',
238+
time: '2',
239+
},
240+
numLocationsNotCovered: '5',
241+
},
242+
];
243+
response.details.runTestResult.numFailures = '0';
244+
response.numberTestsTotal = 1;
245+
}
246+
response.runTestsEnabled = true;
247+
response.numberTestErrors = 0;
248+
}
202249
if (type === 'passedAndFailedTest') {
203250
response.status = RequestStatus.Failed;
204251
response.success = false;

test/utils/output.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,17 @@ describe('deployResultFormatter', () => {
5858
});
5959
});
6060

61+
it('will warn when code coverage warning present from server', () => {
62+
const deployResult = getDeployResult('codeCoverageWarning');
63+
const formatter = new DeployResultFormatter(deployResult, {});
64+
const warnStub = sandbox.stub(ux, 'warn');
65+
formatter.display();
66+
expect(warnStub.callCount).to.equal(1);
67+
expect(warnStub.firstCall.args[0]).to.equal(
68+
'Average test coverage across all Apex Classes and Triggers is 25%, at least 75% test coverage is required.'
69+
);
70+
});
71+
6172
it('will write test output when in json mode', async () => {
6273
const deployResult = getDeployResult('passedTest');
6374
const formatter = new DeployResultFormatter(deployResult, {

0 commit comments

Comments
 (0)