From c7b2eb0c3d070aa9518a36978eecb044bd8cbe8d Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Wed, 3 Apr 2024 10:06:29 -0600 Subject: [PATCH 1/5] fix: add remote components to report response --- src/formatters/deployResultFormatter.ts | 29 ++- test/utils/deployResultFormatter.test.ts | 265 +++++++++++++++++++++++ 2 files changed, 290 insertions(+), 4 deletions(-) create mode 100644 test/utils/deployResultFormatter.test.ts diff --git a/src/formatters/deployResultFormatter.ts b/src/formatters/deployResultFormatter.ts index 8533cb2f..4690c720 100644 --- a/src/formatters/deployResultFormatter.ts +++ b/src/formatters/deployResultFormatter.ts @@ -25,6 +25,7 @@ import { JUnitReporter, TestResult, } from '@salesforce/apex-node'; +import { getState } from '@salesforce/source-deploy-retrieve/lib/src/client/deployMessages.js'; import { DeployResultJson, isSdrFailure, @@ -51,10 +52,10 @@ import { import { TestResultsFormatter } from '../formatters/testResultsFormatter.js'; export class DeployResultFormatter extends TestResultsFormatter implements Formatter { - private relativeFiles: FileResponse[]; - private absoluteFiles: FileResponse[]; - private coverageOptions: CoverageReporterOptions; - private resultsDir: string; + private readonly relativeFiles: FileResponse[]; + private readonly absoluteFiles: FileResponse[]; + private readonly coverageOptions: CoverageReporterOptions; + private readonly resultsDir: string; private readonly junit: boolean | undefined; public constructor( @@ -285,6 +286,26 @@ export class DeployResultFormatter extends TestResultsFormatter implements Forma private displaySuccesses(): void { const successes = this.relativeFiles.filter(isSdrSuccess); + ensureArray(this.result.response.details.componentSuccesses) + .filter( + (fromServer) => + // removes package.xml, other manifests + fromServer.componentType !== '' && + // if we don't find the file in the response, it's because it doesn't exist locally, yet + !successes.find( + (fromLocal) => fromServer.fullName === fromLocal.fullName && fromServer.componentType === fromLocal.type + ) + ) + .map((s) => + successes.push({ + fullName: s.fullName, + // @ts-expect-error getState can return 'failed' which isn't applicable to FileSuccess + state: getState(s), + type: s.componentType ?? '', + filePath: 'Not found in project', + }) + ); + if (!successes.length || this.result.response.status === RequestStatus.Failed) return; const columns = { diff --git a/test/utils/deployResultFormatter.test.ts b/test/utils/deployResultFormatter.test.ts new file mode 100644 index 00000000..7b9fb144 --- /dev/null +++ b/test/utils/deployResultFormatter.test.ts @@ -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([['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'); + }); + }); + }); +}); From dd224bcaa06e75fbfd5b626168c784a947d7fae9 Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Wed, 3 Apr 2024 10:08:36 -0600 Subject: [PATCH 2/5] refactor: move NUTs to NUT dir --- .../deploy => nuts}/metadata/cancel.nut.ts | 4 +- .../deploy => nuts/metadata}/metadata.nut.ts | 0 .../deploy => nuts}/metadata/quick.nut.ts | 2 +- .../metadata/report-mdapi.nut.ts | 2 +- .../deploy => nuts}/metadata/report.nut.ts | 2 +- .../deploy => nuts}/metadata/resume.nut.ts | 4 +- .../deploy => nuts}/metadata/validate.nut.ts | 2 +- test/utils/output.test.ts | 225 ------------------ 8 files changed, 8 insertions(+), 233 deletions(-) rename test/{commands/deploy => nuts}/metadata/cancel.nut.ts (97%) rename test/{commands/deploy => nuts/metadata}/metadata.nut.ts (100%) rename test/{commands/deploy => nuts}/metadata/quick.nut.ts (99%) rename test/{commands/deploy => nuts}/metadata/report-mdapi.nut.ts (98%) rename test/{commands/deploy => nuts}/metadata/report.nut.ts (98%) rename test/{commands/deploy => nuts}/metadata/resume.nut.ts (97%) rename test/{commands/deploy => nuts}/metadata/validate.nut.ts (98%) delete mode 100644 test/utils/output.test.ts diff --git a/test/commands/deploy/metadata/cancel.nut.ts b/test/nuts/metadata/cancel.nut.ts similarity index 97% rename from test/commands/deploy/metadata/cancel.nut.ts rename to test/nuts/metadata/cancel.nut.ts index 3f7b497f..4eef0f40 100644 --- a/test/commands/deploy/metadata/cancel.nut.ts +++ b/test/nuts/metadata/cancel.nut.ts @@ -11,8 +11,8 @@ import { strict as assert } from 'node:assert'; import { TestSession, execCmd } from '@salesforce/cli-plugins-testkit'; import { expect } from 'chai'; import { RequestStatus } from '@salesforce/source-deploy-retrieve'; -import { DeployResultJson } from '../../../../src/utils/types.js'; -import { CachedOptions } from '../../../../src/utils/deploy.js'; +import { DeployResultJson } from '../../../src/utils/types.js'; +import { CachedOptions } from '../../../src/utils/deploy.js'; function readDeployCache(sessionDir: string): Record { const contents = fs.readFileSync(path.join(sessionDir, '.sf', 'deploy-cache.json'), 'utf-8'); diff --git a/test/commands/deploy/metadata.nut.ts b/test/nuts/metadata/metadata.nut.ts similarity index 100% rename from test/commands/deploy/metadata.nut.ts rename to test/nuts/metadata/metadata.nut.ts diff --git a/test/commands/deploy/metadata/quick.nut.ts b/test/nuts/metadata/quick.nut.ts similarity index 99% rename from test/commands/deploy/metadata/quick.nut.ts rename to test/nuts/metadata/quick.nut.ts index aa748f04..caed2bf7 100644 --- a/test/commands/deploy/metadata/quick.nut.ts +++ b/test/nuts/metadata/quick.nut.ts @@ -11,7 +11,7 @@ import path from 'node:path'; import { SourceTestkit } from '@salesforce/source-testkit'; import { assert, config } from 'chai'; import { execCmd } from '@salesforce/cli-plugins-testkit'; -import { DeployResultJson } from '../../../../src/utils/types.js'; +import { DeployResultJson } from '../../../src/utils/types.js'; config.truncateThreshold = 0; diff --git a/test/commands/deploy/metadata/report-mdapi.nut.ts b/test/nuts/metadata/report-mdapi.nut.ts similarity index 98% rename from test/commands/deploy/metadata/report-mdapi.nut.ts rename to test/nuts/metadata/report-mdapi.nut.ts index d838828b..5c9bef5b 100644 --- a/test/commands/deploy/metadata/report-mdapi.nut.ts +++ b/test/nuts/metadata/report-mdapi.nut.ts @@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url'; import { SourceTestkit } from '@salesforce/source-testkit'; import { assert, expect } from 'chai'; import { RequestStatus } from '@salesforce/source-deploy-retrieve'; -import { DeployResultJson } from '../../../../src/utils/types.js'; +import { DeployResultJson } from '../../../src/utils/types.js'; describe('[project deploy report] NUTs with metadata-dir', () => { let testkit: SourceTestkit; diff --git a/test/commands/deploy/metadata/report.nut.ts b/test/nuts/metadata/report.nut.ts similarity index 98% rename from test/commands/deploy/metadata/report.nut.ts rename to test/nuts/metadata/report.nut.ts index a4ce3620..26314e43 100644 --- a/test/commands/deploy/metadata/report.nut.ts +++ b/test/nuts/metadata/report.nut.ts @@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url'; import { SourceTestkit } from '@salesforce/source-testkit'; import { assert, isObject } from '@salesforce/ts-types'; import { expect } from 'chai'; -import { DeployResultJson } from '../../../../src/utils/types.js'; +import { DeployResultJson } from '../../../src/utils/types.js'; describe('[project deploy report] NUTs with source-dir', () => { let testkit: SourceTestkit; diff --git a/test/commands/deploy/metadata/resume.nut.ts b/test/nuts/metadata/resume.nut.ts similarity index 97% rename from test/commands/deploy/metadata/resume.nut.ts rename to test/nuts/metadata/resume.nut.ts index 8afa3c6f..f4673dc4 100644 --- a/test/commands/deploy/metadata/resume.nut.ts +++ b/test/nuts/metadata/resume.nut.ts @@ -12,8 +12,8 @@ import { strict as assert } from 'node:assert'; import { SourceTestkit } from '@salesforce/source-testkit'; import { expect } from 'chai'; import { RequestStatus } from '@salesforce/source-deploy-retrieve'; -import { DeployResultJson } from '../../../../src/utils/types.js'; -import { CachedOptions } from '../../../../src/utils/deploy.js'; +import { DeployResultJson } from '../../../src/utils/types.js'; +import { CachedOptions } from '../../../src/utils/deploy.js'; function readDeployCache(projectDir: string): Record { // source-testkit doesn't expose the session, so we'll go up 1 level from the project to get to it diff --git a/test/commands/deploy/metadata/validate.nut.ts b/test/nuts/metadata/validate.nut.ts similarity index 98% rename from test/commands/deploy/metadata/validate.nut.ts rename to test/nuts/metadata/validate.nut.ts index 5fa12870..ea805524 100644 --- a/test/commands/deploy/metadata/validate.nut.ts +++ b/test/nuts/metadata/validate.nut.ts @@ -11,7 +11,7 @@ import { SourceTestkit } from '@salesforce/source-testkit'; import { isObject } from '@salesforce/ts-types'; import { assert, expect } from 'chai'; import { execCmd } from '@salesforce/cli-plugins-testkit'; -import { DeployResultJson } from '../../../../src/utils/types.js'; +import { DeployResultJson } from '../../../src/utils/types.js'; describe('deploy metadata validate NUTs', () => { let testkit: SourceTestkit; diff --git a/test/utils/output.test.ts b/test/utils/output.test.ts deleted file mode 100644 index 05796638..00000000 --- a/test/utils/output.test.ts +++ /dev/null @@ -1,225 +0,0 @@ -/* - * 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 { 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('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([['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'); - }); - }); - }); -}); From c54a6962eabf5bdc58006cfac751fb5653baa9c4 Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Wed, 3 Apr 2024 10:10:52 -0600 Subject: [PATCH 3/5] chore: filter when filename==='' to get server-only response --- test/utils/deployResponses.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/utils/deployResponses.ts b/test/utils/deployResponses.ts index ca96c705..c2c95360 100644 --- a/test/utils/deployResponses.ts +++ b/test/utils/deployResponses.ts @@ -389,7 +389,7 @@ export const getDeployResult = ( const successes = response.details.componentSuccesses; fileProps = ensureArray(successes); return fileProps - .filter((p) => p.fileName !== 'package.xml') + .filter((p) => p.fileName !== 'package.xml' && p.fileName !== '') .map((comp) => ({ fullName: comp.fullName, filePath: comp.fileName, From 0f7111636ca7e41f01eddebc4a04858a5355adbf Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Wed, 3 Apr 2024 10:35:13 -0600 Subject: [PATCH 4/5] chore: fix npm script for NUTs --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7174bbf6..6d95e958 100644 --- a/package.json +++ b/package.json @@ -125,7 +125,7 @@ "test:nuts:retrieve": "nyc mocha \"test/nuts/retrieve/*.nut.ts\" --slow 4500 --timeout 1200000 --parallel --retries 0 --jobs 20", "test:nuts:specialTypes": "nyc mocha \"test/nuts/specialTypes/*.nut.ts\" --slow 4500 --timeout 1200000 --parallel --retries 0 --jobs 20", "test:nuts:specialTypes:translations": "mocha \"test/nuts/specialTypes/translation.nut.ts\" --slow 4500 --timeout 1200000 --retries 0 --jobs 20", - "test:nuts:static": "nyc mocha \"test/commands/**/*.nut.ts\" \"test/nuts/*.nut.ts\" --slow 4500 --timeout 1200000 --parallel --retries 0 --jobs 20", + "test:nuts:static": "nyc mocha \"test/nuts/metadata/**/*.nut.ts\" \"test/nuts/*.nut.ts\" --slow 4500 --timeout 1200000 --parallel --retries 0 --jobs 20", "test:nuts:tracking": "nyc mocha \"test/nuts/tracking/*.nut.ts\" --slow 4500 --timeout 1200000 --parallel --retries 0 --jobs 20", "test:only": "wireit", "version": "oclif readme" From 88ab4410e86aee50348bf3892226a27b42857c1c Mon Sep 17 00:00:00 2001 From: Willie Ruemmele Date: Wed, 3 Apr 2024 11:16:28 -0600 Subject: [PATCH 5/5] chore: fix windows assertion path --- test/utils/deployResultFormatter.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/utils/deployResultFormatter.test.ts b/test/utils/deployResultFormatter.test.ts index 7b9fb144..16f0dc28 100644 --- a/test/utils/deployResultFormatter.test.ts +++ b/test/utils/deployResultFormatter.test.ts @@ -5,6 +5,7 @@ * 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 { join } from 'node:path'; import { assert, expect, config } from 'chai'; import sinon from 'sinon'; import { DeployMessage, DeployResult, FileResponse } from '@salesforce/source-deploy-retrieve'; @@ -47,7 +48,7 @@ describe('deployResultFormatter', () => { expect(tableStub.callCount).to.equal(1); expect(tableStub.firstCall.args[0]).to.deep.equal([ { - filePath: 'classes/ProductController.cls', + filePath: join('classes', 'ProductController.cls'), fullName: 'ProductController', state: 'Changed', type: 'ApexClass',