Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@salesforce/dev-scripts": "^0.9.11",
"@salesforce/plugin-command-reference": "^1.3.0",
"@salesforce/plugin-config": "^1.2.6",
"@salesforce/plugin-user": "^1.3.0",
"@salesforce/prettier-config": "^0.0.2",
"@salesforce/ts-sinon": "1.3.5",
"@types/debug": "^4.1.5",
Expand Down Expand Up @@ -84,7 +85,8 @@
"@oclif/plugin-help",
"@salesforce/plugin-command-reference",
"salesforcedx-templates",
"@salesforce/plugin-config"
"@salesforce/plugin-config",
"@salesforce/plugin-user"
],
"topics": {
"force": {
Expand Down Expand Up @@ -120,7 +122,7 @@
"test": "sf-test",
"test:command-reference": "./bin/run commandreference:generate --erroronwarnings",
"test:deprecation-policy": "./bin/run snapshot:compare",
"test:nuts": "ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/retrieve.*.nut.ts\" --slow 3000 --timeout 600000 --retries 0",
"test:nuts": "ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/*.nut.ts\" --slow 3000 --timeout 600000 --retries 0",
"test:nuts:convert": "PLUGIN_SOURCE_SEED_FILTER=\"convert\" ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/*.nut.ts\" --slow 3000 --timeout 600000 --parallel --retries 0",
"test:nuts:retrieve": "PLUGIN_SOURCE_SEED_FILTER=\"retrieve\" ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/*.nut.ts\" --slow 3000 --timeout 600000 --parallel --retries 0",
"test:nuts:deploy": "PLUGIN_SOURCE_SEED_FILTER=\"deploy\" ts-node ./test/nuts/generateNuts.ts && nyc mocha \"**/*.nut.ts\" --slow 3000 --timeout 600000 --parallel --retries 0",
Expand Down
2 changes: 1 addition & 1 deletion src/commands/force/source/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export class Deploy extends DeployCommand {
rollbackOnError: !this.getFlag<boolean>('ignoreerrors', false),
checkOnly: this.getFlag<boolean>('checkonly', false),
runTests: this.getFlag<string[]>('runtests'),
testLevel: this.getFlag<TestLevel>('testlevel'),
testLevel: this.getFlag<TestLevel>('testlevel', 'NoTestRun'),
},
});

Expand Down
36 changes: 23 additions & 13 deletions test/nuts/assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,23 @@ export class Assertions {
/**
* Finds all files in project based on the provided globs and expects them to be updated on the server
*/
public async filesToBeDeployed(globs: string[], deployCommand = 'force:source:deploy'): Promise<void> {
await this.filesToBeUpdated(globs, deployCommand);
public async filesToBeDeployed(
globs: string[],
ignore: string[] = [],
deployCommand = 'force:source:deploy'
): Promise<void> {
await this.filesToBeUpdated(globs, ignore, deployCommand);
}

/**
* Finds all files in project based on the provided globs and expects them to NOT be updated on the server
*/
public async filesToNotBeDeployed(globs: string[], deployCommand = 'force:source:deploy'): Promise<void> {
await this.filesToNotBeUpdated(globs, deployCommand);
public async filesToNotBeDeployed(
globs: string[],
ignore: string[] = [],
deployCommand = 'force:source:deploy'
): Promise<void> {
await this.filesToNotBeUpdated(globs, ignore, deployCommand);
}

/**
Expand Down Expand Up @@ -190,7 +198,7 @@ export class Assertions {
* Expect all given files to be be updated in the org
*/
public async filesToBePushed(globs: string[]): Promise<void> {
await this.filesToBeUpdated(globs, 'force:source:push');
await this.filesToBeUpdated(globs, [], 'force:source:push');
}

/**
Expand Down Expand Up @@ -310,9 +318,9 @@ export class Assertions {
expect(result[prop], `${prop} to have value that does not equal ${value.toString()}`).to.not.equal(value);
}

private async filesToBeUpdated(globs: string[], command: string): Promise<void> {
private async filesToBeUpdated(globs: string[], ignore: string[] = [], command: string): Promise<void> {
const { sourceMembers } = this.executionLog.getLatest(command);
const latestSourceMembers = await this.retrieveSourceMembers(globs);
const latestSourceMembers = await this.retrieveSourceMembers(globs, ignore);

for (const sourceMember of latestSourceMembers) {
const assertionMessage = `expect RevisionCounter for ${sourceMember.MemberName} (${sourceMember.MemberType}) to be incremented`;
Expand All @@ -323,9 +331,9 @@ export class Assertions {
}
}

private async filesToNotBeUpdated(globs: string[], command: string): Promise<void> {
private async filesToNotBeUpdated(globs: string[], ignore: string[] = [], command: string): Promise<void> {
const { sourceMembers } = this.executionLog.getLatest(command);
const latestSourceMembers = await this.retrieveSourceMembers(globs);
const latestSourceMembers = await this.retrieveSourceMembers(globs, ignore);
if (!latestSourceMembers.length) {
// Not finding any source members based on the globs means that there is no SourceMember for those files
// which we're assuming means that it hasn't been deployed to the org yet.
Expand Down Expand Up @@ -354,16 +362,18 @@ export class Assertions {
expect(someAreNotUpdated, 'expect some SourceMembers to not be updated').to.be.true;
}

private async retrieveSourceMembers(globs: string[]): Promise<SourceMember[]> {
private async retrieveSourceMembers(globs: string[], ignore: string[] = []): Promise<SourceMember[]> {
const query = 'SELECT Id,MemberName,MemberType,RevisionCounter FROM SourceMember';
const result = await this.connection.tooling.query<SourceMember>(query, {
autoFetch: true,
maxFetch: 50000,
});
const filesToExpect = await this.doGlob(globs);
const all = await this.doGlob(globs);
const ignoreFiles = await this.doGlob(ignore, false);
const toTrack = all.filter((file) => !ignoreFiles.includes(file));
const membersMap = new Map<string, Set<string>>();
for (const file of filesToExpect) {
const components = this.metadataResolver.getComponentsFromPath(file);
for (const file of toTrack) {
const components = this.metadataResolver.getComponentsFromPath(file.replace(/\//g, path.sep));
for (const component of components) {
const metadataType = component.type.name;
const metadataName = component.fullName;
Expand Down
9 changes: 9 additions & 0 deletions test/nuts/nutshell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,15 @@ export class Nutshell extends AsyncCreatable<Nutshell.Options> {
exec(`sfdx force:package:install --noprompt --package ${id} --wait 5 --json 2> /dev/null`, { silent: true });
}

/**
* assigns a permission set to the default user in the scratch org
*
* @param options
*/
public async assignPermissionSet(options: Partial<Nutshell.CommandOpts> = {}): Promise<void> {
await this.execute('force:user:permset:assign', options);
}

/**
* Adds given files to FileTracker for tracking
*/
Expand Down
2 changes: 1 addition & 1 deletion test/nuts/seeds/convert.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { TEST_REPOS_MAP } from '../testMatrix';
const REPO = TEST_REPOS_MAP.get('%REPO_URL%');
const EXECUTABLE = '%EXECUTABLE%';

context('Convert NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
context.skip('Convert NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
let nutshell: Nutshell;

before(async () => {
Expand Down
4 changes: 2 additions & 2 deletions test/nuts/seeds/deploy.async.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { TEST_REPOS_MAP } from '../testMatrix';
const REPO = TEST_REPOS_MAP.get('%REPO_URL%');
const EXECUTABLE = '%EXECUTABLE%';

context('Async Deploy NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
context.skip('Async Deploy NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
let nutshell: Nutshell;

before(async () => {
Expand Down Expand Up @@ -43,7 +43,7 @@ context('Async Deploy NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {

const report = await nutshell.deployReport({ args: `-i ${deploy.result.id}` });
nutshell.expect.toHavePropertyAndValue(report.result, 'status', 'Succeeded');
await nutshell.expect.filesToBeDeployed(nutshell.packageGlobs, 'force:source:deploy:report');
await nutshell.expect.filesToBeDeployed(nutshell.packageGlobs, [], 'force:source:deploy:report');
});

it('should return an id immediately when --wait is set to 0 and deploy:cancel should cancel the deploy', async () => {
Expand Down
16 changes: 12 additions & 4 deletions test/nuts/seeds/deploy.manifest.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ context('Deploy manifest NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
executable: EXECUTABLE,
nut: __filename,
});
// some deploys reference other metadata not included in the deploy, if it's not already in the org it will fail
await nutshell.deploy({ args: `--sourcepath ${nutshell.packageNames.join(',')}` });
await nutshell.assignPermissionSet({ args: '--permsetname dreamhouse' });
});

after(async () => {
Expand All @@ -30,9 +33,13 @@ context('Deploy manifest NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {

describe('--manifest flag', () => {
for (const testCase of REPO.deploy.manifest) {
it(`should deploy ${testCase.toDeploy}`, async () => {
await nutshell.convert({ args: `--sourcepath ${testCase.toDeploy} --outputdir out` });
const packageXml = path.join('out', 'package.xml');
const toDeploy = path.normalize(testCase.toDeploy);
it(`should deploy ${toDeploy}`, async () => {
// generate package.xml to use with the --manifest param
await nutshell.convert({ args: `--sourcepath ${toDeploy} --outputdir out` });
const outputDir = path.join(process.cwd(), 'out');
nutshell.findAndMoveManifest(outputDir);
const packageXml = path.join(process.cwd(), 'package.xml');

await nutshell.deploy({ args: `--manifest ${packageXml}` });
await nutshell.expect.filesToBeDeployed(testCase.toVerify);
Expand All @@ -41,7 +48,8 @@ context('Deploy manifest NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {

it('should throw an error if the package.xml is not valid', async () => {
const deploy = await nutshell.deploy({ args: '--manifest DOES_NOT_EXIST.xml', exitCode: 1 });
nutshell.expect.errorToHaveName(deploy, 'InvalidManifestError');
const expectedError = nutshell.isSourcePlugin() ? 'Error' : 'InvalidManifestError';
nutshell.expect.errorToHaveName(deploy, expectedError);
});
});
});
15 changes: 10 additions & 5 deletions test/nuts/seeds/deploy.metadata.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ context('Deploy metadata NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
executable: EXECUTABLE,
nut: __filename,
});
// some deploys reference other metadata not included in the deploy, if it's not already in the org it will fail
await nutshell.deploy({ args: `--sourcepath ${nutshell.packageNames.join(',')}` });
await nutshell.assignPermissionSet({ args: '--permsetname dreamhouse' });
});

after(async () => {
Expand All @@ -29,26 +32,28 @@ context('Deploy metadata NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {

it('should deploy the entire project', async () => {
await nutshell.deploy({ args: `--sourcepath ${nutshell.packageNames.join(',')}` });
await nutshell.expect.filesToBeDeployed(nutshell.packageGlobs);
await nutshell.expect.filesToBeDeployed(nutshell.packageGlobs, ['force-app/test/**/*']);
});

describe('--metadata flag', () => {
for (const testCase of REPO.deploy.metadata) {
it(`should deploy ${testCase.toDeploy}`, async () => {
await nutshell.deploy({ args: `--metadata ${testCase.toDeploy}` });
await nutshell.expect.filesToBeDeployed(testCase.toVerify);
await nutshell.expect.filesToBeDeployed(testCase.toVerify, testCase.toIgnore);
});
}

it('should throw an error if the metadata is not valid', async () => {
const deploy = await nutshell.deploy({ args: '--metadata DOES_NOT_EXIST', exitCode: 1 });
nutshell.expect.errorToHaveName(deploy, 'UnsupportedType');
const expectedError = nutshell.isSourcePlugin() ? 'RegistryError' : 'UnsupportedType';
nutshell.expect.errorToHaveName(deploy, expectedError);
});

it('should not deploy metadata outside of a package directory', async () => {
const apex = await nutshell.createApexClass({ args: '--outputdir NotAPackage --classname ShouldNotBeDeployed' });
await nutshell.createApexClass({ args: '--outputdir NotAPackage --classname ShouldNotBeDeployed' });
await nutshell.deploy({ args: '--metadata ApexClass' });
await nutshell.expect.filesToNotBeDeployed(apex.result.created);
// this is a glob, so no need for path.join
await nutshell.expect.filesToNotBeDeployed(['NotAPackage/**/*']);
});
});
});
2 changes: 1 addition & 1 deletion test/nuts/seeds/deploy.quick.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { TEST_REPOS_MAP } from '../testMatrix';
const REPO = TEST_REPOS_MAP.get('%REPO_URL%');
const EXECUTABLE = '%EXECUTABLE%';

context('Quick Deploy NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
context.skip('Quick Deploy NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
let nutshell: Nutshell;

before(async () => {
Expand Down
11 changes: 7 additions & 4 deletions test/nuts/seeds/deploy.sourcepath.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 'path';
import { Nutshell } from '../nutshell';
import { TEST_REPOS_MAP } from '../testMatrix';

Expand All @@ -29,15 +30,17 @@ context('Deploy sourcepath NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () =>

describe('--sourcepath flag', () => {
for (const testCase of REPO.deploy.sourcepath) {
it(`should deploy ${testCase.toDeploy}`, async () => {
await nutshell.deploy({ args: `--sourcepath ${testCase.toDeploy}` });
await nutshell.expect.filesToBeDeployed(testCase.toVerify);
const toDeploy = path.normalize(testCase.toDeploy);
it(`should deploy ${toDeploy}`, async () => {
await nutshell.deploy({ args: `--sourcepath ${toDeploy}` });
await nutshell.expect.filesToBeDeployed(testCase.toVerify, testCase.toIgnore);
});
}

it('should throw an error if the sourcepath is not valid', async () => {
const deploy = await nutshell.deploy({ args: '--sourcepath DOES_NOT_EXIST', exitCode: 1 });
nutshell.expect.errorToHaveName(deploy, 'SourcePathInvalid');
const expectedError = nutshell.isSourcePlugin() ? 'SfdxError' : 'SourcePathInvalid';
nutshell.expect.errorToHaveName(deploy, expectedError);
});
});
});
3 changes: 3 additions & 0 deletions test/nuts/seeds/deploy.testlevel.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ context('Deploy testlevel NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () =>
executable: EXECUTABLE,
nut: __filename,
});
// running tests requires a special permission in the 'dreamhouse' permission set
await nutshell.deploy({ args: `--sourcepath ${nutshell.packageNames.join(',')}` });
await nutshell.assignPermissionSet({ args: '--permsetname dreamhouse' });
});

after(async () => {
Expand Down
4 changes: 2 additions & 2 deletions test/nuts/seeds/mpd.pull.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { Nutshell } from '../nutshell';
// DO NOT TOUCH. generateNuts.ts will insert these values
const EXECUTABLE = '%EXECUTABLE%';

context('MPD Retrieve NUTs [exec: %EXECUTABLE%]', () => {
context.skip('MPD Retrieve NUTs [exec: %EXECUTABLE%]', () => {
let nutshell: Nutshell;

before(async () => {
Expand All @@ -20,7 +20,7 @@ context('MPD Retrieve NUTs [exec: %EXECUTABLE%]', () => {
nut: __filename,
});
await nutshell.trackGlobs(nutshell.packageGlobs);
await nutshell.push();
await nutshell.deploy({ args: `--sourcepath ${nutshell.packageNames.join(',')}` });
});

after(async () => {
Expand Down
2 changes: 1 addition & 1 deletion test/nuts/seeds/mpd.retrieve.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { Nutshell } from '../nutshell';
// DO NOT TOUCH. generateNuts.ts will insert these values
const EXECUTABLE = '%EXECUTABLE%';

context('MPD Retrieve NUTs [exec: %EXECUTABLE%]', () => {
context.skip('MPD Retrieve NUTs [exec: %EXECUTABLE%]', () => {
let nutshell: Nutshell;

before(async () => {
Expand Down
2 changes: 1 addition & 1 deletion test/nuts/seeds/retrieve.manifest.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { TEST_REPOS_MAP } from '../testMatrix';
const REPO = TEST_REPOS_MAP.get('%REPO_URL%');
const EXECUTABLE = '%EXECUTABLE%';

context('Retrieve manifest NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
context.skip('Retrieve manifest NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
let nutshell: Nutshell;

before(async () => {
Expand Down
2 changes: 1 addition & 1 deletion test/nuts/seeds/retrieve.metadata.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { TEST_REPOS_MAP } from '../testMatrix';
const REPO = TEST_REPOS_MAP.get('%REPO_URL%');
const EXECUTABLE = '%EXECUTABLE%';

context('Retrieve metadata NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
context.skip('Retrieve metadata NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
let nutshell: Nutshell;

before(async () => {
Expand Down
2 changes: 1 addition & 1 deletion test/nuts/seeds/retrieve.sourcepath.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { TEST_REPOS_MAP } from '../testMatrix';
const REPO = TEST_REPOS_MAP.get('%REPO_URL%');
const EXECUTABLE = '%EXECUTABLE%';

context('Retrieve Sourcepath NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
context.skip('Retrieve Sourcepath NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
let nutshell: Nutshell;

before(async () => {
Expand Down
2 changes: 1 addition & 1 deletion test/nuts/seeds/sourceTracking.seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { TEST_REPOS_MAP } from '../testMatrix';
const REPO = TEST_REPOS_MAP.get('%REPO_URL%');
const EXECUTABLE = '%EXECUTABLE%';

context('Source Tracking NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
context.skip('Source Tracking NUTs [name: %REPO_NAME%] [exec: %EXECUTABLE%]', () => {
let nutshell: Nutshell;

before(async () => {
Expand Down
Loading