Skip to content

Commit

Permalink
chore: deploy NUTs (#84)
Browse files Browse the repository at this point in the history
* chore: deploy NUTs

* chore: disable other NUTs

* chore: make test:nuts test all nuts

* chore: reduce number of redundant tests
  • Loading branch information
WillieRuemmele authored May 21, 2021
1 parent d27fb2a commit ac0bf69
Show file tree
Hide file tree
Showing 19 changed files with 154 additions and 96 deletions.
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

0 comments on commit ac0bf69

Please sign in to comment.