From d3f573f82d86eef14072d7abbafffb747e3306ad Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Mon, 20 Sep 2021 15:02:53 -0600 Subject: [PATCH 1/3] fix: find server errors and display them with FileResponse errors --- src/formatters/deployResultFormatter.ts | 22 +++++++-- test/formatters/deployResultFormatter.test.ts | 47 +++++++++++-------- 2 files changed, 46 insertions(+), 23 deletions(-) diff --git a/src/formatters/deployResultFormatter.ts b/src/formatters/deployResultFormatter.ts index 0053ce367..0fb99140b 100644 --- a/src/formatters/deployResultFormatter.ts +++ b/src/formatters/deployResultFormatter.ts @@ -15,6 +15,7 @@ import { FileResponse, MetadataApiDeployStatus, RequestStatus, + DeployMessage, } from '@salesforce/source-deploy-retrieve'; import { ResultFormatter, ResultFormatterOptions, toArray } from './resultFormatter'; @@ -124,10 +125,23 @@ export class DeployResultFormatter extends ResultFormatter { } protected displayFailures(): void { - if (this.hasStatus(RequestStatus.Failed) && this.fileResponses?.length) { - const failures = this.fileResponses.filter((f) => f.state === 'Failed'); - this.sortFileResponses(failures); - this.asRelativePaths(failures); + if (this.hasStatus(RequestStatus.Failed)) { + const failures: Array = []; + + if (this.fileResponses?.length) { + const fileResponses = this.fileResponses.filter((f) => f.state === 'Failed'); + this.sortFileResponses(fileResponses); + this.asRelativePaths(fileResponses); + failures.push(...fileResponses); + } + + if (this.result?.response?.details?.componentFailures) { + const deployMessages = toArray(this.result.response.details.componentFailures); + deployMessages.map((deployMessage) => { + // duplicate the problem message to the error property for displaying in the table + failures.push(Object.assign(deployMessage, { error: deployMessage.problem })); + }); + } this.ux.log(''); this.ux.styledHeader(chalk.red(`Component Failures [${failures.length}]`)); diff --git a/test/formatters/deployResultFormatter.test.ts b/test/formatters/deployResultFormatter.test.ts index 821d46b7d..1703ec8bb 100644 --- a/test/formatters/deployResultFormatter.test.ts +++ b/test/formatters/deployResultFormatter.test.ts @@ -10,7 +10,7 @@ import * as sinon from 'sinon'; import { expect } from 'chai'; import { Logger } from '@salesforce/core'; import { UX } from '@salesforce/command'; -import { FileResponse } from '@salesforce/source-deploy-retrieve'; +import { DeployMessage, FileResponse } from '@salesforce/source-deploy-retrieve'; import { stubInterface } from '@salesforce/ts-sinon'; import { getDeployResult } from '../commands/source/deployResponses'; import { DeployCommandResult, DeployResultFormatter } from '../../src/formatters/deployResultFormatter'; @@ -96,41 +96,50 @@ describe('DeployResultFormatter', () => { expect(styledHeaderStub.calledOnce).to.equal(true); expect(logStub.calledTwice).to.equal(true); expect(tableStub.called).to.equal(true); - expect(styledHeaderStub.firstCall.args[0]).to.contain('Component Failures [1]'); + expect(styledHeaderStub.args[0][0]).to.include('Component Failures [2]'); const fileResponses = deployResultFailure.getFileResponses(); resolveExpectedPaths(fileResponses); - expect(tableStub.firstCall.args[0]).to.deep.equal(fileResponses); + const mutatedObject = Object.assign(deployResultFailure.response.details.componentFailures, { + error: (deployResultFailure.response.details.componentFailures as DeployMessage).problem, + }); + + const tableData = [...fileResponses, mutatedObject]; + + expect(tableStub.firstCall.args[0]).to.deep.equal(tableData); }); it('should output as expected for a test failure with verbose', async () => { const formatter = new DeployResultFormatter(logger, ux, { verbose: true }, deployResultTestFailure); formatter.display(); - expect(styledHeaderStub.calledTwice).to.equal(true); - expect(logStub.callCount).to.equal(5); - expect(tableStub.calledTwice).to.equal(true); - expect(styledHeaderStub.firstCall.args[0]).to.contain('Test Failures [1]'); - expect(styledHeaderStub.secondCall.args[0]).to.contain('Apex Code Coverage'); + expect(styledHeaderStub.calledThrice).to.equal(true); + expect(logStub.callCount).to.equal(7); + expect(tableStub.calledThrice).to.equal(true); + expect(styledHeaderStub.args[0][0]).to.include('Component Failures [1]'); + expect(styledHeaderStub.args[1][0]).to.include('Test Failures [1]'); + expect(styledHeaderStub.args[2][0]).to.include('Apex Code Coverage'); }); it('should output as expected for passing tests with verbose', async () => { const formatter = new DeployResultFormatter(logger, ux, { verbose: true }, deployResultTestSuccess); formatter.display(); - expect(styledHeaderStub.calledTwice).to.equal(true); - expect(logStub.callCount).to.equal(5); - expect(tableStub.calledTwice).to.equal(true); - expect(styledHeaderStub.firstCall.args[0]).to.contain('Test Success [1]'); - expect(styledHeaderStub.secondCall.args[0]).to.contain('Apex Code Coverage'); + expect(styledHeaderStub.calledThrice).to.equal(true); + expect(logStub.callCount).to.equal(7); + expect(tableStub.calledThrice).to.equal(true); + expect(styledHeaderStub.args[0][0]).to.include('Component Failures [1]'); + expect(styledHeaderStub.args[1][0]).to.include('Test Success [1]'); + expect(styledHeaderStub.args[2][0]).to.include('Apex Code Coverage'); }); it('should output as expected for passing and failing tests with verbose', async () => { const formatter = new DeployResultFormatter(logger, ux, { verbose: true }, deployResultTestSuccessAndFailure); formatter.display(); - expect(styledHeaderStub.callCount).to.equal(3); - expect(logStub.callCount).to.equal(6); - expect(tableStub.callCount).to.equal(3); - expect(styledHeaderStub.firstCall.args[0]).to.contain('Test Failures [2]'); - expect(styledHeaderStub.secondCall.args[0]).to.contain('Test Success [1]'); - expect(styledHeaderStub.thirdCall.args[0]).to.contain('Apex Code Coverage'); + expect(styledHeaderStub.callCount).to.equal(4); + expect(logStub.callCount).to.equal(8); + expect(tableStub.callCount).to.equal(4); + expect(styledHeaderStub.args[0][0]).to.include('Component Failures [1]'); + expect(styledHeaderStub.args[1][0]).to.include('Test Failures [2]'); + expect(styledHeaderStub.args[2][0]).to.include('Test Success [1]'); + expect(styledHeaderStub.args[3][0]).to.include('Apex Code Coverage'); }); }); }); From 73398193431380bc8f8259a3da197e4a1f80144b Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Mon, 20 Sep 2021 17:06:54 -0600 Subject: [PATCH 2/3] chore: fix duplicate errors from being displayed --- src/formatters/deployResultFormatter.ts | 19 +++++++++++++++---- test/formatters/deployResultFormatter.test.ts | 14 ++++---------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/formatters/deployResultFormatter.ts b/src/formatters/deployResultFormatter.ts index 0fb99140b..552f9a2b3 100644 --- a/src/formatters/deployResultFormatter.ts +++ b/src/formatters/deployResultFormatter.ts @@ -135,11 +135,22 @@ export class DeployResultFormatter extends ResultFormatter { failures.push(...fileResponses); } - if (this.result?.response?.details?.componentFailures) { - const deployMessages = toArray(this.result.response.details.componentFailures); + const deployMessages = toArray(this.result?.response?.details?.componentFailures); + if (deployMessages.length > failures.length) { + // if there's additional failures in the API response, find the failure and add it to the output deployMessages.map((deployMessage) => { - // duplicate the problem message to the error property for displaying in the table - failures.push(Object.assign(deployMessage, { error: deployMessage.problem })); + if ( + !failures.find( + (fail: FileResponse & { error: string }) => + // if either of their error messages contains the other, and they're the same type and fullName, consider them the same error + (fail.error.includes(deployMessage.problem) || deployMessage.problem.includes(fail.error)) && + fail.type === deployMessage.componentType && + fail.fullName === deployMessage.fullName + ) + ) { + // duplicate the problem message to the error property for displaying in the table + failures.push(Object.assign(deployMessage, { error: deployMessage.problem })); + } }); } diff --git a/test/formatters/deployResultFormatter.test.ts b/test/formatters/deployResultFormatter.test.ts index 1703ec8bb..2e566c63e 100644 --- a/test/formatters/deployResultFormatter.test.ts +++ b/test/formatters/deployResultFormatter.test.ts @@ -10,7 +10,7 @@ import * as sinon from 'sinon'; import { expect } from 'chai'; import { Logger } from '@salesforce/core'; import { UX } from '@salesforce/command'; -import { DeployMessage, FileResponse } from '@salesforce/source-deploy-retrieve'; +import { FileResponse } from '@salesforce/source-deploy-retrieve'; import { stubInterface } from '@salesforce/ts-sinon'; import { getDeployResult } from '../commands/source/deployResponses'; import { DeployCommandResult, DeployResultFormatter } from '../../src/formatters/deployResultFormatter'; @@ -90,22 +90,16 @@ describe('DeployResultFormatter', () => { expect(tableStub.firstCall.args[0]).to.deep.equal(fileResponses); }); - it('should output as expected for a failure', async () => { + it('should output as expected for a failure and exclude duplicate information', async () => { const formatter = new DeployResultFormatter(logger, ux, {}, deployResultFailure); formatter.display(); expect(styledHeaderStub.calledOnce).to.equal(true); expect(logStub.calledTwice).to.equal(true); expect(tableStub.called).to.equal(true); - expect(styledHeaderStub.args[0][0]).to.include('Component Failures [2]'); + expect(styledHeaderStub.args[0][0]).to.include('Component Failures [1]'); const fileResponses = deployResultFailure.getFileResponses(); resolveExpectedPaths(fileResponses); - const mutatedObject = Object.assign(deployResultFailure.response.details.componentFailures, { - error: (deployResultFailure.response.details.componentFailures as DeployMessage).problem, - }); - - const tableData = [...fileResponses, mutatedObject]; - - expect(tableStub.firstCall.args[0]).to.deep.equal(tableData); + expect(tableStub.firstCall.args[0]).to.deep.equal(fileResponses); }); it('should output as expected for a test failure with verbose', async () => { From 1912122b64fb8e7a11ebe36d4547d2132d5bce22 Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Tue, 21 Sep 2021 09:38:12 -0600 Subject: [PATCH 3/3] chore: more efficient dupe checking --- src/formatters/deployResultFormatter.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/formatters/deployResultFormatter.ts b/src/formatters/deployResultFormatter.ts index 552f9a2b3..60ea4faa1 100644 --- a/src/formatters/deployResultFormatter.ts +++ b/src/formatters/deployResultFormatter.ts @@ -127,9 +127,16 @@ export class DeployResultFormatter extends ResultFormatter { protected displayFailures(): void { if (this.hasStatus(RequestStatus.Failed)) { const failures: Array = []; + const fileResponseFailures: Map = new Map(); if (this.fileResponses?.length) { - const fileResponses = this.fileResponses.filter((f) => f.state === 'Failed'); + const fileResponses: FileResponse[] = []; + this.fileResponses + .filter((f) => f.state === 'Failed') + .map((f: FileResponse & { error: string }) => { + fileResponses.push(f); + fileResponseFailures.set(`${f.type}#${f.fullName}`, f.error); + }); this.sortFileResponses(fileResponses); this.asRelativePaths(fileResponses); failures.push(...fileResponses); @@ -139,15 +146,7 @@ export class DeployResultFormatter extends ResultFormatter { if (deployMessages.length > failures.length) { // if there's additional failures in the API response, find the failure and add it to the output deployMessages.map((deployMessage) => { - if ( - !failures.find( - (fail: FileResponse & { error: string }) => - // if either of their error messages contains the other, and they're the same type and fullName, consider them the same error - (fail.error.includes(deployMessage.problem) || deployMessage.problem.includes(fail.error)) && - fail.type === deployMessage.componentType && - fail.fullName === deployMessage.fullName - ) - ) { + if (!fileResponseFailures.has(`${deployMessage.componentType}#${deployMessage.fullName}`)) { // duplicate the problem message to the error property for displaying in the table failures.push(Object.assign(deployMessage, { error: deployMessage.problem })); }