Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: find server errors and display them with FileResponse errors #210

Merged
merged 3 commits into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions src/formatters/deployResultFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
FileResponse,
MetadataApiDeployStatus,
RequestStatus,
DeployMessage,
} from '@salesforce/source-deploy-retrieve';
import { ResultFormatter, ResultFormatterOptions, toArray } from './resultFormatter';

Expand Down Expand Up @@ -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<FileResponse | DeployMessage> = [];

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

aren't componentFailures and fileResponses going to include some of the same errors, and so this would show duplicates?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ugh. dupes 🤦

const deployMessages = toArray(this.result.response.details.componentFailures);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deployMessages.map((deployMessage) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could get really loopy on a big deployment fail (use of find within a loop where find is doing multiple string.includes)

perf suggestion: iterate failures once to build a keyed Map< type+fullName>, deployMessage>
then, you can iterate the deployMessages once and use deployMessageMap.has( type + fullName)
No would be a good sign to add it
has=true could still compare the messages (if that's actually a source of dupes)

At the very least, do the (do type and name match?) conditional first so that it mostly exits before having to do the string.includes ops

// 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}]`));
Expand Down
47 changes: 28 additions & 19 deletions test/formatters/deployResultFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these tests were giving false positives and needed to be updated

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');
});
});
});