Skip to content

Commit

Permalink
fix: display error message from response (#608)
Browse files Browse the repository at this point in the history
* fix: display error message from response

* fix: display error message from response for push
  • Loading branch information
shetzel authored Oct 17, 2022
1 parent 8d4fd0e commit 6ba254e
Show file tree
Hide file tree
Showing 10 changed files with 94 additions and 18 deletions.
2 changes: 1 addition & 1 deletion messages/deploy.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
"checkOnlySuccessVerbose": "Successfully validated the deployment.",
"deploySuccess": "Deploy Succeeded.",
"deployCanceled": "The deployment has been canceled by %s.",
"deployFailed": "Deploy failed.",
"deployFailed": "Deploy failed. %s",
"asyncDeployQueued": "Deploy has been queued.",
"asyncDeployCancel": "Run sfdx force:source:deploy:cancel -i %s -u %s to cancel the deploy.",
"asyncDeployReport": "Run sfdx force:source:deploy:report -i %s -u %s to get the latest status.",
Expand Down
2 changes: 1 addition & 1 deletion messages/md.deploy.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"soapDeploy": "Deploy metadata with SOAP API instead of the default REST API. Because SOAP API has a lower .ZIP file size limit (400 MB uncompressed, 39 MB compressed), Salesforce recommends REST API deployment. This flag provides backwards compatibility with API version 50.0 and earlier when deploy used SOAP API by default."
},
"noRestDeploy": "REST deploy is not available for this org. This feature is currently for internal Salesforce use only.",
"deployFailed": "The metadata deploy operation failed.",
"deployFailed": "The metadata deploy operation failed. %s",
"asyncDeployQueued": "Deploy has been queued.",
"asyncDeployCancel": "Run sfdx force:mdapi:deploy:cancel -i %s -u %s to cancel the deploy.",
"asyncDeployReport": "Run sfdx force:mdapi:deploy:report -i %s -u %s to get the latest status.",
Expand Down
2 changes: 1 addition & 1 deletion messages/push.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
"ignorewarningsLong": "Completes the deployment even if warnings are generated.",
"quiet": "minimize json and sdtout output on success"
},
"sourcepushFailed": "Push failed.",
"sourcepushFailed": "Push failed. %s",
"sequentialFail": "Check the order of your dependencies and ensure all metadata is included."
}
3 changes: 2 additions & 1 deletion src/commands/force/source/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,8 @@ export default class Push extends DeployCommand {
result.response.status === RequestStatus.Succeeded ||
// successful-ish (only warnings about deleted things that are already deleted)
(result.response.status === RequestStatus.Failed &&
result.getFileResponses().every((fr) => fr.state !== 'Failed'))
result.getFileResponses().every((fr) => fr.state !== 'Failed') &&
!result.response.errorMessage)
);
// all successes
if (this.deployResults.every((result) => isSuccessLike(result))) {
Expand Down
22 changes: 13 additions & 9 deletions src/formatters/deployResultFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ export class DeployResultFormatter extends ResultFormatter {

// Throw a DeployFailed error unless the deployment was successful.
if (!this.isSuccess()) {
throw new SfError(messages.getMessage('deployFailed'), 'DeployFailed');
// Add error message directly on the DeployResult (e.g., a GACK)
const errMsg = this.getResponse()?.errorMessage ?? '';
throw new SfError(messages.getMessage('deployFailed', [errMsg]), 'DeployFailed');
}

this.ux.log(messages.getMessage(this.isCheckOnly() ? 'checkOnlySuccessVerbose' : 'deploySuccess'));
Expand Down Expand Up @@ -180,14 +182,16 @@ export class DeployResultFormatter extends ResultFormatter {
});
}

this.ux.log('');
this.ux.styledHeader(chalk.red(`Component Failures [${failures.length}]`));
this.ux.table(failures, {
problemType: { header: 'Type' },
fullName: { header: 'Name' },
error: { header: 'Problem' },
});
this.ux.log('');
if (failures.length) {
this.ux.log('');
this.ux.styledHeader(chalk.red(`Component Failures [${failures.length}]`));
this.ux.table(failures, {
problemType: { header: 'Type' },
fullName: { header: 'Name' },
error: { header: 'Problem' },
});
this.ux.log('');
}
}
}

Expand Down
4 changes: 3 additions & 1 deletion src/formatters/mdapi/mdDeployResultFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ export class MdDeployResultFormatter extends ResultFormatter {
}
// TODO: the toolbelt version of this is returning an SfError shape. This returns a status=1 and the result (mdapi response) but not the error name, etc
if (!this.isSuccess()) {
const error = new SfError(messages.getMessage('deployFailed'), 'mdapiDeployFailed');
// Add error message directly on the DeployResult (e.g., a GACK)
const errMsg = this.getResponse()?.errorMessage ?? '';
const error = new SfError(messages.getMessage('deployFailed', [errMsg]), 'mdapiDeployFailed');
error.setData(this.result);
throw error;
}
Expand Down
11 changes: 9 additions & 2 deletions src/formatters/source/pushResultFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export class PushResultFormatter extends ResultFormatter {
public getJson(): PushResponse {
// throws a particular json structure. commandName property will be appended by sfdxCommand when this throws
if (process.exitCode !== 0) {
const error = new SfError(messages.getMessage('sourcepushFailed'), 'DeployFailed', [], process.exitCode);
const error = new SfError(messages.getMessage('sourcepushFailed', ['']), 'DeployFailed', [], process.exitCode);
const errorData = this.fileResponses.filter((fileResponse) => fileResponse.state === ComponentStatus.Failed);
error.setData(errorData);
error['result'] = errorData;
Expand Down Expand Up @@ -85,7 +85,14 @@ export class PushResultFormatter extends ResultFormatter {

// Throw a DeployFailed error unless the deployment was successful.
if (!this.isSuccess()) {
throw new SfError(messages.getMessage('sourcepushFailed'), 'PushFailed');
// Add error message directly on the DeployResult (e.g., a GACK)
let errMsg = '';
this.results?.forEach((res) => {
if (res.response?.errorMessage) {
errMsg += `${res.response?.errorMessage}\n`;
}
});
throw new SfError(messages.getMessage('sourcepushFailed', [errMsg]), 'PushFailed');
}
}

Expand Down
21 changes: 21 additions & 0 deletions test/formatters/deployResultFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,27 @@ describe('DeployResultFormatter', () => {
expect(tableStub.firstCall.args[0]).to.deep.equal(fileResponses);
});

it('should output as expected for a deploy failure (GACK)', async () => {
const errorMessage =
'UNKNOWN_EXCEPTION: An unexpected error occurred. Please include this ErrorId if you contact support: 1730955361-49792 (-1117026034)';
const deployFailure = getDeployResult('failed', { errorMessage });
deployFailure.response.details.componentFailures = [];
deployFailure.response.details.componentSuccesses = [];
delete deployFailure.response.details.runTestResult;
const formatter = new DeployResultFormatter(logger, ux as UX, {}, deployFailure);
sandbox.stub(formatter, 'isSuccess').returns(false);

try {
formatter.display();
expect(false, 'should have thrown a DeployFailed error').to.be.true;
} catch (err) {
const error = err as Error;
expect(error.message).to.equal(`Deploy failed. ${errorMessage}`);
expect(styledHeaderStub.called).to.equal(false);
expect(tableStub.called).to.equal(false);
}
});

it('should output as expected for a test failure with verbose', async () => {
const formatter = new DeployResultFormatter(logger, ux as UX, { verbose: true }, deployResultTestFailure);
formatter.display();
Expand Down
21 changes: 21 additions & 0 deletions test/formatters/mdDeployResultFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,27 @@ describe('mdDeployResultFormatter', () => {
}
});

it('should output as expected for a deploy failure (GACK)', async () => {
const errorMessage =
'UNKNOWN_EXCEPTION: An unexpected error occurred. Please include this ErrorId if you contact support: 1730955361-49792 (-1117026034)';
const deployFailure = getDeployResult('failed', { errorMessage });
deployFailure.response.details.componentFailures = [];
deployFailure.response.details.componentSuccesses = [];
delete deployFailure.response.details.runTestResult;
const formatter = new MdDeployResultFormatter(logger, ux as UX, {}, deployFailure);
sandbox.stub(formatter, 'isSuccess').returns(false);

try {
formatter.display();
expect(false, 'should have thrown a DeployFailed error').to.be.true;
} catch (err) {
const error = err as Error;
expect(error.message).to.equal(`The metadata deploy operation failed. ${errorMessage}`);
expect(styledHeaderStub.called).to.equal(false);
expect(tableStub.called).to.equal(false);
}
});

it('should output as expected for a test failure with verbose', async () => {
process.exitCode = 1;
const formatter = new MdDeployResultFormatter(logger, ux as UX, { verbose: true }, deployResultTestFailure);
Expand Down
24 changes: 22 additions & 2 deletions test/formatters/pushResultFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ describe('PushResultFormatter', () => {
formatter.getJson();
throw new Error('should have thrown');
} catch (error) {
expect(error).to.have.property('message', 'Push failed.');
expect(error).to.have.property('message', 'Push failed. ');
expect(error).to.have.property('name', 'DeployFailed');
expect(error).to.have.property('stack').includes('DeployFailed:');
expect(error).to.have.property('actions').deep.equal([]);
Expand All @@ -87,7 +87,7 @@ describe('PushResultFormatter', () => {
formatter.getJson();
throw new Error('should have thrown');
} catch (error) {
expect(error).to.have.property('message', 'Push failed.');
expect(error).to.have.property('message', 'Push failed. ');
expect(error).to.have.property('result').deep.equal([expectedFail]);
}
});
Expand All @@ -102,6 +102,26 @@ describe('PushResultFormatter', () => {
expect(headerStub.callCount, JSON.stringify(headerStub.args)).to.equal(1);
expect(tableStub.callCount, JSON.stringify(tableStub.args)).to.equal(1);
});
it('should output as expected for a deploy failure (GACK)', async () => {
const errorMessage =
'UNKNOWN_EXCEPTION: An unexpected error occurred. Please include this ErrorId if you contact support: 1730955361-49792 (-1117026034)';
const deployFailure = getDeployResult('failed', { errorMessage });
deployFailure.response.details.componentFailures = [];
deployFailure.response.details.componentSuccesses = [];
delete deployFailure.response.details.runTestResult;
const formatter = new PushResultFormatter(logger, uxMock as UX, {}, [deployFailure]);
sandbox.stub(formatter, 'isSuccess').returns(false);

try {
formatter.display();
expect(false, 'should have thrown a PushFailed error').to.be.true;
} catch (err) {
const error = err as Error;
expect(error.message).to.equal(`Push failed. ${errorMessage}\n`);
expect(headerStub.called).to.equal(false);
expect(tableStub.called).to.equal(false);
}
});
describe('quiet', () => {
it('does not display successes for quiet', () => {
process.exitCode = 0;
Expand Down

0 comments on commit 6ba254e

Please sign in to comment.